15#define _POSIX_C_SOURCE 200809L
20#include <cjson/cJSON.h>
29#define OTEL_DEFAULT_ENDPOINT "http://localhost:4318/v1/traces"
30#define OTEL_DEFAULT_SERVICE "cwist"
31#define OTEL_DEFAULT_QUEUE 512U
32#define OTEL_DEFAULT_BATCH 64U
33#define OTEL_EXPORT_TIMEOUT_MS 2000
34#define OTEL_TRACE_ID_HEX 32U
35#define OTEL_SPAN_ID_HEX 16U
75static uint64_t otel_unix_nanos(
void) {
81static bool otel_is_hex(
const char *s,
size_t n) {
82 for (
size_t i = 0;
i <
n;
i++) {
84 if (!((
c >=
'0' &&
c <=
'9') || (
c >=
'a' &&
c <=
'f')))
return false;
89static bool otel_is_zero(
const char *s,
size_t n) {
90 for (
size_t i = 0;
i <
n;
i++) {
91 if (s[
i] !=
'0')
return false;
97static void otel_random_id(
char *out,
size_t nibbles) {
98 static const char hex[] =
"0123456789abcdef";
103 out[2 *
i] =
hex[bytes[
i] >> 4];
104 out[2 *
i + 1] =
hex[bytes[
i] & 0xF];
107 }
while (otel_is_zero(out,
nibbles));
112static bool otel_parse_traceparent(
const char *h,
char *trace_id,
char *
parent_id) {
113 if (!h ||
strlen(h) < 55)
return false;
114 if (!otel_is_hex(h, 2) ||
strncmp(h,
"ff", 2) == 0 || h[2] !=
'-')
return false;
121 if (!otel_is_hex(h + 53, 2) || (h[55] !=
'\0' && (
strncmp(h,
"00", 2) == 0 || h[55] !=
'-')))
130static cJSON *otel_attr(
const char *key,
cJSON *value,
const char *type) {
139static char *otel_encode_spans(
const otel_span_t *
spans,
size_t n) {
151 for (
size_t i = 0;
i <
n;
i++) {
152 const otel_span_t *s = &
spans[
i];
161 snprintf(
num,
sizeof(
num),
"%llu", (
unsigned long long)s->start_ns);
163 snprintf(
num,
sizeof(
num),
"%llu", (
unsigned long long)s->end_ns);
173 if (s->status >= 500)
182static void otel_export_batch(cwist_http_client *client,
const otel_span_t *
spans,
size_t n) {
183 char *body = otel_encode_spans(
spans,
n);
195static void *otel_exporter_main(
void *arg) {
204 size_t n = g_otel.count < g_otel.batch ? g_otel.count : g_otel.batch;
205 for (
size_t i = 0;
i <
n;
i++) {
207 g_otel.head = (g_otel.head + 1) % g_otel.cap;
211 if (client &&
batch) otel_export_batch(client,
batch,
n);
217static void otel_atfork_prepare(
void) {
221static void otel_atfork_parent(
void) {
227static void otel_atfork_child(
void) {
228 g_otel.head = g_otel.count = 0;
229 g_otel.exporter_alive =
false;
235static void otel_enqueue(
const otel_span_t *
span) {
237 if (!g_otel.exporter_alive)
238 g_otel.exporter_alive =
240 if (g_otel.exporter_alive) {
241 if (g_otel.count == g_otel.cap) {
242 g_otel.head = (g_otel.head + 1) % g_otel.cap;
245 g_otel.ring[(g_otel.head + g_otel.count) % g_otel.cap] = *
span;
254 otel_span_t
span = {.method = req->
method, .start_ns = otel_unix_nanos()};
256 span.parent_span_id))
260 otel_ctx ctx = {.req = req, .trace_id =
span.trace_id, .outer = t_otel_ctx};
263 t_otel_ctx = ctx.outer;
265 span.end_ns = otel_unix_nanos();
274 if (!g_otel.configured) {
282 if (g_otel.endpoint && g_otel.service_name && g_otel.ring &&
283 pthread_atfork(otel_atfork_prepare, otel_atfork_parent, otel_atfork_child) == 0) {
286 g_otel.configured =
true;
291 g_otel.endpoint = g_otel.service_name =
NULL;
295 bool ok = g_otel.configured;
297 return ok ? otel_middleware :
NULL;
301 if (!req)
return NULL;
302 for (
const otel_ctx *ctx = t_otel_ctx; ctx; ctx = ctx->outer) {
303 if (ctx->req == req)
return ctx->trace_id;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value by key.
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
void cwist_http_response_destroy(cwist_http_response *res)
Destroy an HTTP response object.
void cwist_http_header_free_all(cwist_http_header_node *head)
Free all headers.
const char * cwist_http_method_to_string(cwist_http_method_t method)
Convert method enum to string.
Unified HTTP/1.1 and HTTP/2 client using libcurl.
cwist_http_client * cwist_http_client_create(void)
Create a new HTTP client.
struct cwist_http_client cwist_http_client
Opaque HTTP client handle.
cwist_error_t cwist_http_client_request(cwist_http_client *client, const char *url, cwist_http_method_t method, cwist_http_header_node *headers, const char *body, size_t body_len, cwist_http_response **out_response)
Perform a synchronous HTTP request.
void cwist_http_client_set_timeout_ms(cwist_http_client *client, int timeout_ms)
Set request timeout in milliseconds.
HTTP Protocol Definitions and Helpers.
#define OTEL_DEFAULT_SERVICE
const char * cwist_mw_otel_get_trace_id(const cwist_http_request *req)
Trace id of the span cwist_mw_otel_trace() opened for req.
#define OTEL_DEFAULT_BATCH
#define OTEL_DEFAULT_ENDPOINT
#define OTEL_DEFAULT_QUEUE
#define OTEL_EXPORT_TIMEOUT_MS
#define OTEL_TRACE_ID_HEX
cwist_middleware_func cwist_mw_otel_trace(const cwist_otel_cfg_t *cfg)
OpenTelemetry tracing middleware.
mbedTLS TLS backend: RAND_bytes over mbedTLS CTR-DRBG.
cwist_sstring * path
e.g., "/users/1"
cwist_http_header_node * headers
cwist_http_method_t method
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
cwist_http_status_t status_code
Configuration for cwist_mw_otel_trace().
const char * service_name
char * data
Access directly only when raw handling is necessary.
void(* cwist_handler_func)(cwist_http_request *req, cwist_http_response *res)
Function pointer type for HTTP route handlers.
void(* cwist_middleware_func)(cwist_http_request *req, cwist_http_response *res, cwist_handler_func next)
Middleware type that receives req/res pair and the next stage in the chain.
Built-in middleware factories.