OpenTelemetry¶
The C runtime instruments client calls and server dispatch with W3C trace context, OTLP/HTTP JSON spans, request counters, duration histograms, and in-band log correlation. It uses the existing C json-c/libcurl dependencies. It does not link the OpenTelemetry C++ SDK.
#include <vgi_rpc/telemetry.h>
#include <vgi_rpc/rpc.h>
struct VgiTelemetryOptions options;
VgiTelemetryOptionsInit(&options);
options.endpoint = "http://127.0.0.1:4318";
options.service_name = "my-c-service";
struct VgiTelemetry *telemetry = NULL;
struct ArrowError error = {{0}};
int rc = VgiTelemetryNew(&options, &telemetry, &error);
struct VgiRpcServer *server = VgiRpcServerNew();
if (!rc) rc = VgiRpcServerSetTelemetry(server, telemetry);
/* Register protocols, serve requests, then stop/join all serving threads. */
VgiRpcServerFree(server);
if (!rc) rc = VgiTelemetryFlush(telemetry, 5000);
VgiTelemetryFree(telemetry);
For clients, assign the same or another instance to VgiClientOptions.telemetry.
Telemetry objects are thread-safe and borrowed: they must outlive all attached
servers, clients, pools, and outstanding leases. VgiWorkerSetTelemetry exposes
the same option in the separate vgi-c SDK. Its example worker enables telemetry
when VGI_RPC_OTEL_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT is set.
OptionsInit reads OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_SERVICE_NAME.
Defaults are http://localhost:4318, service vgi-rpc-c, a 2-second export
timeout, and a 1,024-record queue. /v1/traces and /v1/metrics are appended to
the base URL. HTTPS verifies certificate chains and hostnames; tls_ca_file
selects a trust bundle. A custom export_json callback can instead hand the
OTLP payloads to an application's configured exporter. Callback payloads are
borrowed and callbacks run on the export thread.
Context and span lifetime¶
Incoming traceparent and tracestate are extracted from Arrow metadata or HTTP
headers. HTTP headers take precedence. Invalid parents start a new trace, and
invalid tracestate is discarded. A valid unsampled parent stays unsampled;
metrics are still recorded. Root spans are sampled. Trace/span IDs use libsodium
randomness. All scopes restore the preceding thread-local context.
Client spans cover complete unary calls or logical streams. Byte-stream server
spans cover complete dispatch, including streaming. HTTP server spans cover each
request/continuation, allowing stateless streams to move across worker processes.
Server callbacks run inside the server context: downstream C client calls inherit
it automatically. Client requests inject their context into IPC metadata and
HTTP headers. VgiTraceContextParse, Current, and Swap let an application
bridge an existing tracer's context; restore the previous context after a scope.
RPC errors and client cancellation set error status; logs emitted during dispatch include
trace_id and span_id in their JSON extras. Spans carry RPC system, service,
method, method type, transport, and status. Payload values, credentials, arbitrary
claims, and error-message text are not exported as attributes.
Metrics and delivery¶
rpc.server.requests / rpc.client.requests are monotonic delta sums.
rpc.server.duration / rpc.client.duration are delta histograms in seconds,
with explicit bounds from 5 ms through 10 seconds. Measurements are aggregated
by method and status within an export batch. Identifiers and caller identity are
excluded from metric dimensions.
RPC threads enqueue completed records without waiting for the collector.
The export thread sends batches of up to 64 records. A full queue drops telemetry
and increments dropped, preserving RPC availability. Transient HTTP/network
failures receive two bounded retries; permanent and partial-rejection responses
increment export_failures. Flush waits up to its deadline and returns EIO
if any export has failed. Free drains the queue and joins the exporter, so stop
RPC producers first; a slow/unavailable collector can extend shutdown by the
bounded export attempts for queued batches.
The collector tests decode actual HTTP bodies with the official OpenTelemetry protobuf schemas, applying the OTLP JSON hex-ID rule. They check parentage, root isolation, unsampled calls, errors, metrics, and log correlation on stdio and HTTP. Native tests also check malformed context, bounded queues, flush timeouts, and exporter failures that must not affect RPC results. Collector response tests cover transient retries, permanent failures, and partial rejections without retrying rejected data.
The wire encodings follow W3C Trace Context and OTLP JSON.