From eaa05d2af30a3d92cb8f59899b06e480106d409f Mon Sep 17 00:00:00 2001 From: Miroslav Zagorac Date: Thu, 26 Feb 2026 11:09:33 +0100 Subject: [PATCH] MINOR: otel: added span link support Added span link support, allowing a span to reference other spans or extracted contexts without establishing a parent relationship. Introduced the flt_otel_conf_link structure and added a links list to flt_otel_conf_span. The parser accepted both an inline syntax on the span declaration line ("span link ") and a standalone multi- argument form ("link ..."), each creating a conf_link entry appended to the span's link list. At runtime, each configured link name was resolved against the active spans and extracted contexts in the runtime context. Resolved references were collected into flt_otel_scope_data_link entries and passed to the C wrapper add_link API during span creation. Initialization, cleanup, and debug dump routines were added for the link data structures at both configuration and runtime levels. --- addons/otel/include/conf.h | 19 ++++++--- addons/otel/include/conf_funcs.h | 1 + addons/otel/include/parser.h | 4 +- addons/otel/include/scope.h | 18 +++++++-- addons/otel/src/conf.c | 49 +++++++++++++++++++++++ addons/otel/src/event.c | 67 ++++++++++++++++++++++++++++++++ addons/otel/src/parser.c | 13 +++++++ addons/otel/src/scope.c | 16 ++++++++ addons/otel/test/sa/otel.cfg | 3 +- 9 files changed, 178 insertions(+), 12 deletions(-) diff --git a/addons/otel/include/conf.h b/addons/otel/include/conf.h index 99c37cad4..08e82b845 100644 --- a/addons/otel/include/conf.h +++ b/addons/otel/include/conf.h @@ -47,12 +47,13 @@ #define FLT_OTEL_DBG_CONF_CONTEXT(h,p) \ OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "0x%02hhx }", (p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->flags) -#define FLT_OTEL_DBG_CONF_SPAN(h,p) \ - OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %zu %s' %zu %hhu 0x%02hhx %s %s %s %s }", \ - (p), FLT_OTEL_CONF_HDR_ARGS(p, id), FLT_OTEL_STR_HDR_ARGS(p, ref_id), \ - FLT_OTEL_STR_HDR_ARGS(p, ctx_id), (p)->flag_root, (p)->ctx_flags, \ - flt_otel_list_dump(&((p)->attributes)), flt_otel_list_dump(&((p)->events)), \ - flt_otel_list_dump(&((p)->baggages)), flt_otel_list_dump(&((p)->statuses))) +#define FLT_OTEL_DBG_CONF_SPAN(h,p) \ + OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %zu %s' %zu %hhu 0x%02hhx %s %s %s %s %s }", \ + (p), FLT_OTEL_CONF_HDR_ARGS(p, id), FLT_OTEL_STR_HDR_ARGS(p, ref_id), \ + FLT_OTEL_STR_HDR_ARGS(p, ctx_id), (p)->flag_root, (p)->ctx_flags, \ + flt_otel_list_dump(&((p)->links)), flt_otel_list_dump(&((p)->attributes)), \ + flt_otel_list_dump(&((p)->events)), flt_otel_list_dump(&((p)->baggages)), \ + flt_otel_list_dump(&((p)->statuses))) #define FLT_OTEL_DBG_CONF_SCOPE(h,p) \ OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %d %u %s %p %s %s %s }", (p), \ @@ -138,6 +139,11 @@ struct flt_otel_conf_context { uint8_t flags; /* The type of storage from which the span context is extracted. */ }; +/* flt_otel_conf_span->links */ +struct flt_otel_conf_link { + FLT_OTEL_CONF_HDR(span); /* The list containing link names. */ +}; + /* * Span configuration within a scope. * flt_otel_conf_scope->spans @@ -148,6 +154,7 @@ struct flt_otel_conf_span { FLT_OTEL_CONF_STR(ctx_id); /* The span context name, if used. */ uint8_t ctx_flags; /* The type of storage used for the span context. */ bool flag_root; /* Whether this is a root span. */ + struct list links; /* The set of linked span names. */ struct list attributes; /* The set of key:value attributes. */ struct list events; /* The set of events with key-value attributes. */ struct list baggages; /* The set of key:value baggage items. */ diff --git a/addons/otel/include/conf_funcs.h b/addons/otel/include/conf_funcs.h index af19aa9e1..4ca6da943 100644 --- a/addons/otel/include/conf_funcs.h +++ b/addons/otel/include/conf_funcs.h @@ -101,6 +101,7 @@ FLT_OTEL_CONF_FUNC_DECL(str) FLT_OTEL_CONF_FUNC_DECL(ph) FLT_OTEL_CONF_FUNC_DECL(sample_expr) FLT_OTEL_CONF_FUNC_DECL(sample) +FLT_OTEL_CONF_FUNC_DECL(link) FLT_OTEL_CONF_FUNC_DECL(context) FLT_OTEL_CONF_FUNC_DECL(span) FLT_OTEL_CONF_FUNC_DECL(scope) diff --git a/addons/otel/include/parser.h b/addons/otel/include/parser.h index 9eb45e612..282ffd8f6 100644 --- a/addons/otel/include/parser.h +++ b/addons/otel/include/parser.h @@ -19,6 +19,7 @@ #define FLT_OTEL_PARSE_SPAN_ROOT "root" #define FLT_OTEL_PARSE_SPAN_PARENT "parent" +#define FLT_OTEL_PARSE_SPAN_LINK "link" #define FLT_OTEL_PARSE_CTX_AUTONAME "-" #define FLT_OTEL_PARSE_CTX_IGNORE_NAME '-' #define FLT_OTEL_PARSE_CTX_USE_HEADERS "use-headers" @@ -73,7 +74,8 @@ */ #define FLT_OTEL_PARSE_SCOPE_DEFINES \ FLT_OTEL_PARSE_SCOPE_DEF( ID, 0, CHAR, 2, 2, "otel-scope", " ") \ - FLT_OTEL_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 7, "span", " [] [root]") \ + FLT_OTEL_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 7, "span", " [] [] [root]") \ + FLT_OTEL_PARSE_SCOPE_DEF( LINK, 1, NONE, 2, 0, "link", " ...") \ FLT_OTEL_PARSE_SCOPE_DEF( ATTRIBUTE, 1, NONE, 3, 0, "attribute", " ...") \ FLT_OTEL_PARSE_SCOPE_DEF( EVENT, 1, NONE, 4, 0, "event", " ...") \ FLT_OTEL_PARSE_SCOPE_DEF( BAGGAGE, 1, VAR, 3, 0, "baggage", " ...") \ diff --git a/addons/otel/include/scope.h b/addons/otel/include/scope.h index 7570e6222..669a8ad67 100644 --- a/addons/otel/include/scope.h +++ b/addons/otel/include/scope.h @@ -29,10 +29,10 @@ #define FLT_OTEL_DBG_SCOPE_DATA_KV_FMT "%p:{ %p %zu %zu }" #define FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS(p) &(p), (p).attr, (p).cnt, (p).size -#define FLT_OTEL_DBG_SCOPE_DATA(h,p) \ - OTELC_DBG(DEBUG, h "%p:{ " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " %s }", (p), \ - FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->baggage), FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->attributes), \ - flt_otel_list_dump(&((p)->events))) +#define FLT_OTEL_DBG_SCOPE_DATA(h,p) \ + OTELC_DBG(DEBUG, h "%p:{ " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " " FLT_OTEL_DBG_SCOPE_DATA_KV_FMT " %s %s }", (p), \ + FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->baggage), FLT_OTEL_DBG_SCOPE_DATA_KV_ARGS((p)->attributes), \ + flt_otel_list_dump(&((p)->events)), flt_otel_list_dump(&((p)->links))) #define FLT_OTEL_DBG_RUNTIME_CONTEXT(h,p) \ OTELC_DBG(DEBUG, h "%p:{ %p %p '%s' %hhu %hhu 0x%02hhx 0x%08x %u %d %s %s }", (p), \ @@ -64,15 +64,25 @@ struct flt_otel_scope_data_event { struct list list; /* Used to chain this structure. */ }; +/* Span link referencing another span or span context. */ +struct flt_otel_scope_data_link { + struct otelc_span *span; /* Linked span, or NULL. */ + struct otelc_span_context *context; /* Linked span context, or NULL. */ + struct list list; /* Used to chain this structure. */ +}; + +/* Span status code and description. */ struct flt_otel_scope_data_status { int code; /* OTELC_SPAN_STATUS_* value. */ char *description; /* Span status description string. */ }; +/* Aggregated runtime data collected during scope execution. */ struct flt_otel_scope_data { struct flt_otel_scope_data_kv baggage; /* Defined scope baggage. */ struct flt_otel_scope_data_kv attributes; /* Defined scope attributes. */ struct list events; /* Defined scope events. */ + struct list links; /* Defined scope links. */ struct flt_otel_scope_data_status status; /* Defined scope status. */ }; diff --git a/addons/otel/src/conf.c b/addons/otel/src/conf.c index f2f46b1d3..0914a962d 100644 --- a/addons/otel/src/conf.c +++ b/addons/otel/src/conf.c @@ -95,6 +95,53 @@ FLT_OTEL_CONF_FUNC_FREE(str, str, ) +/*** + * NAME + * flt_otel_conf_link_init - conf_link structure allocation + * + * SYNOPSIS + * struct flt_otel_conf_link *flt_otel_conf_link_init(const char *id, int line, struct list *head, char **err) + * + * ARGUMENTS + * id - identifier string to duplicate + * line - configuration file line number + * head - list to append to (or NULL) + * err - indirect pointer to error message string + * + * DESCRIPTION + * Allocates and initializes a conf_link structure for a span link + * reference. The string is duplicated and stored as the linked + * span name. If is non-NULL, the structure is appended to + * the list. + * + * RETURN VALUE + * Returns a pointer to the initialized structure, or NULL on failure. + */ +FLT_OTEL_CONF_FUNC_INIT(link, span, ) + + +/*** + * NAME + * flt_otel_conf_link_free - conf_link structure deallocation + * + * SYNOPSIS + * void flt_otel_conf_link_free(struct flt_otel_conf_link **ptr) + * + * ARGUMENTS + * ptr - a pointer to the address of a structure + * + * DESCRIPTION + * Deallocates memory used by the flt_otel_conf_link structure and its + * contents, then removes it from the list of structures of that type. + * + * RETURN VALUE + * This function does not return a value. + */ +FLT_OTEL_CONF_FUNC_FREE(link, span, + FLT_OTEL_DBG_CONF_HDR("- conf_link free ", *ptr, span); +) + + /*** * NAME * flt_otel_conf_ph_init - conf_ph placeholder structure allocation @@ -401,6 +448,7 @@ FLT_OTEL_CONF_FUNC_FREE(context, id, * Returns a pointer to the initialized structure, or NULL on failure. */ FLT_OTEL_CONF_FUNC_INIT(span, id, + LIST_INIT(&(retptr->links)); LIST_INIT(&(retptr->attributes)); LIST_INIT(&(retptr->events)); LIST_INIT(&(retptr->baggages)); @@ -430,6 +478,7 @@ FLT_OTEL_CONF_FUNC_FREE(span, id, OTELC_SFREE((*ptr)->ref_id); OTELC_SFREE((*ptr)->ctx_id); + FLT_OTEL_LIST_DESTROY(link, &((*ptr)->links)); FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->attributes)); FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->events)); FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->baggages)); diff --git a/addons/otel/src/event.c b/addons/otel/src/event.c index 38a064458..e2c6ddffa 100644 --- a/addons/otel/src/event.c +++ b/addons/otel/src/event.c @@ -54,6 +54,18 @@ static int flt_otel_scope_run_span(struct stream *s, struct filter *f, struct ch OTELC_RETURN_INT(FLT_OTEL_RET_ERROR); } + /* Add all resolved span links to the current span. */ + if (!LIST_ISEMPTY(&(data->links))) { + struct flt_otel_scope_data_link *link; + + list_for_each_entry(link, &(data->links), list) { + OTELC_DBG(DEBUG, "adding link %p %p", link->span, link->context); + + if (OTELC_OPS(span->span, add_link, link->span, link->context, NULL, 0) == -1) + retval = FLT_OTEL_RET_ERROR; + } + } + /* Set baggage key-value pairs on the span. */ if (data->baggage.attr != NULL) if (OTELC_OPS(span->span, set_baggage_kv_n, data->baggage.attr, data->baggage.cnt) == -1) @@ -245,6 +257,61 @@ int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn, if (span == NULL) retval = FLT_OTEL_RET_ERROR; + /* + * Resolve configured span links against the runtime context. + * Each link name is looked up first in the active spans, then + * in the extracted contexts. + */ + if (!LIST_ISEMPTY(&(conf_span->links))) { + struct flt_otel_runtime_context *rt_ctx = FLT_OTEL_RT_CTX(f->ctx); + struct flt_otel_conf_link *conf_link; + + list_for_each_entry(conf_link, &(conf_span->links), list) { + struct flt_otel_scope_data_link *data_link; + struct otelc_span *link_span = NULL; + struct otelc_span_context *link_ctx = NULL; + struct flt_otel_scope_span *sc_span; + struct flt_otel_scope_context *sc_ctx; + + /* Try to find a matching span first. */ + list_for_each_entry(sc_span, &(rt_ctx->spans), list) + if (FLT_OTEL_CONF_STR_CMP(sc_span->id, conf_link->span)) { + link_span = sc_span->span; + + break; + } + + /* If no span found, try to find a matching context. */ + if (link_span == NULL) { + list_for_each_entry(sc_ctx, &(rt_ctx->contexts), list) + if (FLT_OTEL_CONF_STR_CMP(sc_ctx->id, conf_link->span)) { + link_ctx = sc_ctx->context; + + break; + } + } + + if ((link_span == NULL) && (link_ctx == NULL)) { + OTELC_DBG(NOTICE, "WARNING: cannot find linked span/context '%s'", conf_link->span); + + continue; + } + + data_link = OTELC_CALLOC(1, sizeof(*data_link)); + if (data_link == NULL) { + retval = FLT_OTEL_RET_ERROR; + + break; + } + + data_link->span = link_span; + data_link->context = link_ctx; + LIST_APPEND(&(data.links), &(data_link->list)); + + OTELC_DBG(DEBUG, "resolved link '%s' -> %p %p", conf_link->span, link_span, link_ctx); + } + } + list_for_each_entry(sample, &(conf_span->attributes), list) { OTELC_DBG(DEBUG, "adding attribute '%s' -> '%s'", sample->key, sample->fmt_string); diff --git a/addons/otel/src/parser.c b/addons/otel/src/parser.c index 18627b62b..bcb3b71eb 100644 --- a/addons/otel/src/parser.c +++ b/addons/otel/src/parser.c @@ -955,6 +955,14 @@ static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int else FLT_OTEL_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage); } + else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_SPAN_LINK)) { + if (FLT_OTEL_ARG_ISVALID(i + 1)) { + if (flt_otel_conf_link_init(args[++i], line, &(flt_otel_current_span->links), &err) == NULL) + retval |= ERR_ABORT | ERR_ALERT; + } else { + FLT_OTEL_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage); + } + } else { FLT_OTEL_PARSE_ERR(&err, "'%s' : invalid argument (use '%s%s')", args[i], pdata->name, pdata->usage); } @@ -968,6 +976,11 @@ static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int OTELC_DBG(DEBUG, "new span '%s' without reference", flt_otel_current_span->id); } } + else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_LINK) { + for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) + if (flt_otel_conf_link_init(args[i], line, &(flt_otel_current_span->links), &err) == NULL) + retval |= ERR_ABORT | ERR_ALERT; + } else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ATTRIBUTE) { retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, NULL, &(flt_otel_current_span->attributes), &err); } diff --git a/addons/otel/src/scope.c b/addons/otel/src/scope.c index cdef4c19f..eca2741ad 100644 --- a/addons/otel/src/scope.c +++ b/addons/otel/src/scope.c @@ -418,6 +418,17 @@ void flt_otel_scope_data_dump(const struct flt_otel_scope_data *data) OTELC_DBG(WORKER, "}"); } + if (LIST_ISEMPTY(&(data->links))) { + OTELC_DBG(WORKER, "links %p:{ }", &(data->links)); + } else { + struct flt_otel_scope_data_link *link; + + OTELC_DBG(WORKER, "links %p:{", &(data->links)); + list_for_each_entry(link, &(data->links), list) + OTELC_DBG(WORKER, " %p %p", link->span, link->context); + OTELC_DBG(WORKER, "}"); + } + if ((data->status.code == 0) && (data->status.description == NULL)) OTELC_DBG(WORKER, "status %p:{ }", &(data->status)); else @@ -453,6 +464,7 @@ void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr) (void)memset(ptr, 0, sizeof(*ptr)); LIST_INIT(&(ptr->events)); + LIST_INIT(&(ptr->links)); OTELC_RETURN(); } @@ -479,6 +491,7 @@ void flt_otel_scope_data_init(struct flt_otel_scope_data *ptr) void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr) { struct flt_otel_scope_data_event *event, *event_back; + struct flt_otel_scope_data_link *link, *link_back; OTELC_FUNC("%p", ptr); @@ -495,6 +508,9 @@ void flt_otel_scope_data_free(struct flt_otel_scope_data *ptr) OTELC_SFREE(event->name); OTELC_SFREE(event); } + /* Free all resolved link entries. */ + list_for_each_entry_safe(link, link_back, &(ptr->links), list) + OTELC_SFREE(link); OTELC_SFREE(ptr->status.description); (void)memset(ptr, 0, sizeof(*ptr)); diff --git a/addons/otel/test/sa/otel.cfg b/addons/otel/test/sa/otel.cfg index 147222b5f..69f2f32ef 100644 --- a/addons/otel/test/sa/otel.cfg +++ b/addons/otel/test/sa/otel.cfg @@ -95,7 +95,7 @@ otel-event on-http-body-request otel-scope frontend_http_request - span "Frontend HTTP request" parent "HTTP body request" + span "Frontend HTTP request" parent "HTTP body request" link "HAProxy session" attribute "http.method" method attribute "http.url" url attribute "http.version" str("HTTP/") req.ver @@ -147,6 +147,7 @@ otel-scope server_session_start span "Server session" parent "HAProxy session" + link "HAProxy session" "Client session" finish "Process sticking rules request" otel-event on-server-session-start