C++ Client API
All types described here are defined in chronolog_client.h under the chronolog namespace.
#include <chronolog_client.h>
Wire-protocol version
static constexpr uint32_t chronolog::CLIENT_PROTOCOL_VERSION;
The wire-protocol version exchanged on Connect(). The Visor returns CL_ERR_PROTOCOL_VERSION_MISMATCH if a connecting client's version doesn't match the server's expected version. The constant is bumped whenever the wire format between the client and any ChronoLog server component changes in an incompatible way.
Client Class
The Client class manages the connection to ChronoVisor and provides all chronicle, story, and event operations.
Constructors
// WRITER mode — event logging only
Client::Client(ClientPortalServiceConf const& portalConf);
// WRITER + READER mode — event logging and story replay
Client::Client(ClientPortalServiceConf const& portalConf,
ClientQueryServiceConf const& queryConf);
- The first constructor creates a write-only client connected to the ChronoVisor portal service.
- The second constructor additionally connects to the ChronoPlayer query service, enabling
ReplayStory. CallingReplayStoryon a write-only client returnsCL_ERR_NOT_READER_MODE.
Connection
int Client::Connect();
- Establish a session with ChronoVisor. The client advertises
CLIENT_PROTOCOL_VERSIONas part of the handshake. - Returns
CL_SUCCESSon success,CL_ERR_PROTOCOL_VERSION_MISMATCHif the server rejects the client's protocol version, or another error code on failure.
int Client::Disconnect();
- Close the active session. Any stories still acquired by this client are auto-released by the Visor as part of disconnect, so callers don't need to walk their own acquisitions to clean up.
- The destructor calls
Disconnect()as best-effort; theClientis always safe to delete. - Returns
CL_SUCCESSon success, error code otherwise.
ClientId Client::client_id() const;
- Returns this client's packed
ClientId— the same value that appears inEvent::client_id()andEventSequence::clientIdfor every event produced by this client and surfaced at retrieval. - Valid only after a successful
Connect(); returns0before that.
Chronicle Management
int Client::CreateChronicle(std::string const& chronicle_name);
- Create a new chronicle named
chronicle_name. - Returns
CL_SUCCESSon success,CL_ERR_CHRONICLE_EXISTSif a chronicle with that name already exists, or another error code on failure.
int Client::DestroyChronicle(std::string const& chronicle_name);
- Permanently delete the chronicle, including all of its stories and any persisted archive data.
- Returns
CL_SUCCESSon success,CL_ERR_ACQUIREDif any story within the chronicle is currently acquired (by any client), or another error code on failure.
std::pair<int, std::vector<std::string>> Client::ShowChronicles();
- Returns a pair
{return_code, names}. On success,return_codeisCL_SUCCESSandnamescontains the names of all chronicles visible to this client. On failure,return_codeis an error code andnamesis empty.
Story Management
std::pair<int, StoryHandle*> Client::AcquireStory(
std::string const& chronicle_name,
std::string const& story_name);
- Open the story
story_nameinsidechronicle_namefor data access. If the story does not exist it is created. - Returns a pair
{return_code, handle}. On success,return_codeisCL_SUCCESSandhandleis a valid pointer; on failure,handleisnullptr. - The
StoryHandleis owned by theClientand must not be deleted by the caller. - Returns
CL_ERR_NOT_EXISTif the chronicle does not exist.
int Client::ReleaseStory(std::string const& chronicle_name,
std::string const& story_name);
- Release the story handle obtained from
AcquireStory. Pending events buffered by this client are flushed before the call returns, so a successfulReleaseStoryguarantees that everything written vialog_eventis visible to subsequent readers. - Returns
CL_SUCCESSon success,CL_ERR_NOT_ACQUIREDif the story was not acquired by this client.
int Client::DestroyStory(std::string const& chronicle_name,
std::string const& story_name);
- Permanently delete the story, including any persisted archive data.
- Returns
CL_SUCCESSon success,CL_ERR_ACQUIREDif the story is currently acquired (by any client), or another error code on failure.
std::pair<int, std::vector<std::string>> Client::ShowStories(
std::string const& chronicle_name);
- Returns a pair
{return_code, names}. On success,return_codeisCL_SUCCESSandnamescontains the names of all stories inchronicle_name. On failure (e.g.CL_ERR_NOT_EXISTif the chronicle does not exist),namesis empty.
Event Replay
int Client::ReplayStory(std::string const& chronicle_name,
std::string const& story_name,
uint64_t start,
uint64_t end,
std::vector<Event>& event_series);
- Retrieve all events in the story whose timestamps fall within the half-open range
[start, end). - Events are appended to
event_seriesin chronological order. - Returns
CL_SUCCESSon success,CL_ERR_NOT_READER_MODEif the client was constructed without aClientQueryServiceConf,CL_ERR_QUERY_TIMED_OUTif the query did not complete in time, or another error code on failure. - Returns
CL_ERR_PARTIAL_RESULT(-18) when ChronoPlayer answered but knows the series is short: a keeper of the story did not answer within its 5-second fetch deadline, a keeper's answer hit its per-query event cap, or an archive file in the range could not be read.event_seriesholds the events it did gather; retry the range to pick up the rest.
using EventCallback = std::function<void(Event const&)>;
int Client::ReplayStory(std::string const& chronicle_name,
std::string const& story_name,
uint64_t start,
uint64_t end,
EventCallback callback);
Streaming overload: invoke callback once per event in [start, end), with no client-side std::vector<Event> materialization.
- The callback runs on the query service receive thread; the caller is responsible for any locking it needs.
- The callback must not throw. If it does, the exception is caught and logged, that event is skipped, and the remaining events in the response are still delivered.
- Scope note: this overload removes the client-side vector materialization, but the wire-level response is unchanged — the Player still ships one
PlaybackQueryResponseper query and events are fully deserialized into a server-supplied buffer before the callback loop runs. Player-side chunked delivery is tracked as a follow-up. - Returns the same error codes as the vector overload.
StoryHandle Class
StoryHandle is obtained from Client::AcquireStory and provides write access as well as low-latency on-demand tail reading for a story. It is owned by the Client and must not be deleted by the caller.
uint64_t StoryHandle::log_event(std::string const& event_record);
- Append a new event with the log record
event_record. - Returns the
uint64_ttimestamp assigned to the event by the client library. - The keeper's answer is discarded. A keeper crash before ChronoGrapher writes the event loses it
without the caller being told. Use the overload below with
Durability::Durablewhen the call must not return until the event is on the keeper's disk.
enum class Durability : uint8_t
{
Accepted = 1, // in keeper memory; what log_event(record) gives
Durable = 2 // fsync'd in the keeper's WAL
};
struct LogEventOptions
{
Durability durability = Durability::Accepted;
// durable writes only: deadline per RPC attempt, and how many attempts
// to send before giving up; an attempt that gets no answer is retried
uint32_t timeout_ms = 10000;
uint32_t attempts = 3;
};
int StoryHandle::log_event(std::string const& event_record,
LogEventOptions const& options,
uint64_t& event_time);
Append a new event and return CL_SUCCESS once it has reached options.durability. On success
event_time receives the event's timestamp. It also receives it with CL_ERR_DURABILITY_UNKNOWN,
so the caller can check later whether the event was stored; on any other return the client
library's handle sets it to 0. The default StoryHandle implementation, for subclasses, returns
CL_ERR_DURABILITY_UNAVAILABLE to Durable without touching event_time.
Durability::Accepted(the default) takes the same path aslog_event(event_record), and returns the keeper's status instead of discarding it.CL_SUCCESSmeans the keeper holds the event in memory; a keeper that is shutting down returnsCL_ERR_UNKNOWNand stores nothing. There is no deadline and no retry, as withlog_event(event_record).Durability::DurablereturnsCL_SUCCESSonly after the keeper wrote the event to its write-ahead log (WAL) andfdatasyncreturned. What that survives depends on where the WAL is: on a local disk, a keeper crash and a node reboot; on tmpfs, a keeper crash only; it never survives losing the disk. See Keeper WAL. A keeper that cannot make the event durable answers with an error and does not store it, except in one case, when the keeper cannot erase a group whose write orfdatasyncfailed and answersCL_ERR_DURABILITY_UNKNOWN; see Durability error codes. It never downgrades the write to Accepted.- The default stays Accepted, so existing callers keep their cost: a durable write pays one RPC per event and waits for a sync. Concurrent durable writes to one keeper share a sync, but a single writer waits for one per event.
- The WAL is off by default on the keepers. A deployment must turn it on (
--walin the deploy scripts) before a durable write can succeed; with it off every durable write getsCL_ERR_DURABILITY_UNAVAILABLE.
Retries. A durable write builds the event once and sends it to the keeper chosen by its
timestamp, with a deadline of timeout_ms. When an attempt gets no answer (a timeout or a transport
error), the client sends the identical event again, up to attempts times, with no backoff besides
the wait below. A timed-out attempt has already used its timeout_ms; one that failed sooner, such
as a forward to a dead keeper, waits until timeout_ms has passed, so a keeper restarted at the
same address can answer a later attempt. The retry budget is therefore attempts × timeout_ms, 30 s
with the defaults, and the call can block that long. Every attempt carries the same identity,
(time, client id, index). The keeper choice depends only on the timestamp, so a retry reaches the
same keeper while the story's keeper list is unchanged, and the keeper answers a retry of an event
it already holds with CL_SUCCESS without storing it again, also after a crash and restart. The
keeper holds the event until the grapher confirms it written, which is at least about a minute after
the event's timestamp, so the default retry budget always finds it. A budget longer than that can
send a retry after the confirmation; the keeper then stores the event again, and the copies collapse
on read as below.
Storage is at-least-once; reads see each event once. If a keeper left the story's keeper list
between attempts, the retry lands on another keeper and both may store the event. Copies with the
same identity collapse in the keeper's chunk, in playback and in ReplayStory, so a reader sees
the event once. A crash can also leave a second copy of some events in the HDF5 archive; reads drop
it the same way.
Return values for Durability::Durable:
CL_SUCCESS(0): the event is in the keeper's WAL and synced.CL_ERR_INVALID_ARG(-2): nothing was sent or nothing was stored. The client refuses an empty record,attemptsortimeout_msof 0, and adurabilityvalue other than the two above. Only the last check applies to Accepted writes, which ignoreattemptsandtimeout_ms. The keeper refuses an event larger than its WAL record limit (256 MiB minus 36 bytes).CL_ERR_NO_KEEPERS(-7): the story has no keepers. Nothing was sent.CL_ERR_DURABILITY_UNAVAILABLE(-19),CL_ERR_DURABILITY_FULL(-20),CL_ERR_DURABILITY_UNKNOWN(-21): see Durability error codes.
Subclasses and ABI. log_event(record, options, event_time) is a new virtual of StoryHandle.
Its default implementation sends Accepted through log_event(record) and answers
CL_ERR_DURABILITY_UNAVAILABLE to Durable, sending nothing. A subclass that overrides only one of
the two log_event overloads adds using StoryHandle::log_event; so it does not hide the other.
The new virtual changes the client library's ABI: code built against an earlier libchronolog_client
that calls log_event, including the plugins, needs a rebuild but no source change.
CLIENT_PROTOCOL_VERSION is unchanged; a durable write uses a new keeper RPC, which an older
keeper does not register. The client returns CL_ERR_DURABILITY_UNAVAILABLE for the resulting
HG_NO_MATCH only while that keeper has never answered a durable call from this client and still
answers a tail read of no story; otherwise it counts as an unanswered attempt (see Durability error codes).
chronolog::LogEventOptions options;
options.durability = chronolog::Durability::Durable;
uint64_t event_time = 0;
int ret = story->log_event("sensor reading 42", options, event_time);
if(ret == chronolog::CL_SUCCESS)
{
// on the keeper's disk; event_time is its timestamp
}
else if(ret == chronolog::CL_ERR_DURABILITY_FULL)
{
// nothing stored; back off and call log_event again
}
else if(ret == chronolog::CL_ERR_DURABILITY_UNKNOWN)
{
// may be stored; calling log_event again creates a second event.
// event_time is the event's timestamp: replay the story around it to
// see whether the event is there
}
else
{
// nothing stored (-19, -2, -7): retrying will not help
std::cerr << chronolog::to_string_client(ret) << "\n";
}
int StoryHandle::playback(size_t n, std::vector<Event>& events);
- On-Demand Tail Read: Retrieve the most recent
nevents of this story directly from ChronoKeeper in-memory buffers (KeeperChunkRetentionStore), bypassing the ChronoPlayer/HDF5 archive path. - Two-Phase Scatter/Gather: Scatters sequence queries across all assigned keepers, selects the global last-$n$, and fetches only winning payloads over the wire.
- Ordering: Events in
eventsare populated in ascendingEventSequenceorder ((chrono_time, clientId, index)). - Writer-Mode Supported: Works with clients instantiated with
ClientPortalServiceConfonly (noClientQueryServiceConfneeded). - Return Values:
CL_SUCCESS(0): Tail read succeeded. If the story has fewer thannevents in keeper memory, returns all available tail events.CL_ERR_NO_KEEPERS(-7): No keepers are assigned to this story.CL_ERR_QUERY_TIMED_OUT(-12): Keeper RPC timed out during phase 1 or phase 2.CL_ERR_PARTIAL_RESULT(-18): Promised events were evicted from keeper memory between phase 1 and phase 2 due to capacity limits.eventscontains the events that were successfully retrieved.
Event Class
Event is the smallest data unit in ChronoLog. Events are returned by Client::ReplayStory.
Constructor
Event::Event(chrono_time event_time = 0,
ClientId client_id = 0,
chrono_index index = 0,
std::string const& record = std::string());
Getters
uint64_t Event::time() const; // event timestamp
ClientId const& Event::client_id() const; // originating client ID
uint32_t Event::index() const; // per-client sequence index
std::string const& Event::log_record() const; // the log record string
EventSequence Event::sequence() const; // {time, client_id, index}
Operators
bool Event::operator==(Event const& other) const; // equal if time, client_id, and index match
bool Event::operator!=(Event const& other) const;
bool Event::operator< (Event const& other) const; // ordered by time, then client_id, then index
Utilities
std::string Event::to_string() const;
- Returns a string in the format:
{Event:<eventTime>:<clientId>:<eventIndex>:<logRecord>}
ClientIdentity
Helper for packing and unpacking the 64-bit ClientId surfaced on every Event.
struct ClientIdentity
{
uint32_t ip = 0; // IPv4 address in host byte order (0 if undetermined)
uint16_t port = 0; // query service port for reader-mode clients; 0 for writer-only
uint16_t instance = 0; // instance discriminator (pid & 0xFFFF)
ClientId pack() const;
static ClientIdentity unpack(ClientId id);
};
ClientId layout: bits [63:32] ip, bits [31:16] port, bits [15:0] instance. The high 48 bits identify the client's network endpoint so a downstream consumer reading back events can identify the producer; the low 16 bits disambiguate multiple writer-only processes running on the same host. For a reader-mode client the ip is the address its query service listens on, unless that is a loopback address. Otherwise, and for a writer-only client, it is this host's address on the route to the visor when the visor is on another host, and the address the host name resolves to when it is not.
Error Codes
See Error Codes for the full list of ClientErrorCode values returned by the methods above.