]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: otel: added log-record signal support
authorMiroslav Zagorac <mzagorac@haproxy.com>
Sat, 7 Mar 2026 14:28:10 +0000 (15:28 +0100)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
Added "log-record" as the third OpenTelemetry signal alongside traces
(span) and metrics (instrument).  This includes the
flt_otel_conf_log_record structure definition, parser keyword defines,
the otel-scope section parser with optional "id", "event", "span", and
"attr" keywords followed by sample fetch expressions or a log-format
string, init/free lifecycle, scope list wiring, log-format evaluation
in flt_otel_scope_run_instrument_record(), a test configuration example,
log-record span reference validation in flt_otel_check(), and logger
handle creation, startup, and teardown in the filter lifecycle.

addons/otel/include/conf.h
addons/otel/include/conf_funcs.h
addons/otel/include/parser.h
addons/otel/src/cli.c
addons/otel/src/conf.c
addons/otel/src/event.c
addons/otel/src/filter.c
addons/otel/src/parser.c
addons/otel/test/sa/otel.cfg

index 5441666ee99c2858dd19197b31b86908c8c3d10a..9549957f861c1046ab19c91e9617df22ce0d528f 100644 (file)
 #define FLT_OTEL_CONF_HDR_FMT         "%p:{ { '%.*s' %zu %d } "
 #define FLT_OTEL_CONF_HDR_ARGS(p,m)   (int)(p)->m##_len, (p)->m, (p)->m##_len, (p)->cfg_line
 
+/*
+ * Special two-byte prefix that triggers automatic id generation in
+ * FLT_OTEL_CONF_FUNC_INIT(): the text after the prefix is combined
+ * with the configuration line number to form a unique identifier.
+ */
+#define FLT_OTEL_CONF_HDR_SPECIAL     "\x1e\x1f"
+
 #define FLT_OTEL_CONF_STR_CMP(s,S)    ((s##_len == S##_len) && (memcmp(s, S, S##_len) == 0))
 
 #define FLT_OTEL_DBG_CONF_SAMPLE_EXPR(h,p) \
                         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 %s }", (p),           \
+       OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %d %u %s %p %s %s %s %s %s }", (p),        \
                         FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->flag_used, (p)->event, (p)->idle_timeout,      \
                         flt_otel_list_dump(&((p)->acls)), (p)->cond, flt_otel_list_dump(&((p)->contexts)), \
                         flt_otel_list_dump(&((p)->spans)), flt_otel_list_dump(&((p)->spans_to_finish)),    \
-                        flt_otel_list_dump(&((p)->instruments)))
+                        flt_otel_list_dump(&((p)->instruments)), flt_otel_list_dump(&((p)->log_records)))
 
 #define FLT_OTEL_DBG_CONF_GROUP(h,p)                                         \
        OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%hhu %s }", (p), \
 #define FLT_OTEL_DBG_CONF_PH(h,p) \
        OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%p }", (p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->ptr)
 
-#define FLT_OTEL_DBG_CONF_INSTR(h,p)                                                                                                 \
-       OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %p %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %u %s %s %s }", (p),       \
-                        FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->config, (p)->tracer, (p)->meter, (p)->rate_limit, (p)->flag_harderr,    \
-                        (p)->flag_disabled, (p)->logging, &((p)->proxy_log), flt_otel_list_dump(&((p)->proxy_log.loggers)),         \
-                        (p)->analyzers, (p)->idle_timeout, flt_otel_list_dump(&((p)->acls)), flt_otel_list_dump(&((p)->ph_groups)), \
+#define FLT_OTEL_DBG_CONF_INSTR(h,p)                                                                                         \
+       OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "'%s' %p %p %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %u %s %s %s }", \
+                        (p), FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->config, (p)->tracer, (p)->meter, (p)->logger,              \
+                        (p)->rate_limit, (p)->flag_harderr, (p)->flag_disabled, (p)->logging, &((p)->proxy_log),            \
+                        flt_otel_list_dump(&((p)->proxy_log.loggers)), (p)->analyzers, (p)->idle_timeout,                   \
+                        flt_otel_list_dump(&((p)->acls)), flt_otel_list_dump(&((p)->ph_groups)),                            \
                         flt_otel_list_dump(&((p)->ph_scopes)))
 
 #define FLT_OTEL_DBG_CONF_INSTRUMENT(h,p)                                                                                     \
                         OTELC_STR_ARG((p)->unit), flt_otel_list_dump(&((p)->samples)), (p)->attr, (p)->attr_len, (p)->ref,   \
                         (p)->bounds_num, (p)->bounds)
 
+#define FLT_OTEL_DBG_CONF_LOG_RECORD(h,p)                                                                             \
+       OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%d %" PRId64 " '%s' '%s' %p %zu %p }", (p),               \
+                        FLT_OTEL_CONF_HDR_ARGS(p, id), (p)->severity, (p)->event_id, OTELC_STR_ARG((p)->event_name), \
+                        OTELC_STR_ARG((p)->span), (p)->attr, (p)->attr_len, flt_otel_list_dump(&((p)->samples)))
+
 #define FLT_OTEL_DBG_CONF(h,p)                                    \
        OTELC_DBG(DEBUG, h "%p:{ %p '%s' '%s' %p %s %s }", (p),   \
                  (p)->proxy, (p)->id, (p)->cfg_file, (p)->instr, \
@@ -120,6 +133,7 @@ struct flt_otel_conf_sample_expr {
  * flt_otel_conf_span->baggages
  * flt_otel_conf_span->statuses (status_code -> extra.u.value_int32)
  * flt_otel_conf_instrument->samples
+ * flt_otel_conf_log_record->samples
  */
 struct flt_otel_conf_sample {
        FLT_OTEL_CONF_HDR(key);         /* The list containing sample names. */
@@ -188,6 +202,21 @@ struct flt_otel_conf_instrument {
        struct flt_otel_conf_instrument   *ref;         /* Resolved create-form instrument (update only). */
 };
 
+/*
+ * Log record configuration within a scope.
+ *   flt_otel_conf_scope->log_records
+ */
+struct flt_otel_conf_log_record {
+       FLT_OTEL_CONF_HDR(id);            /* Required by macro; member <id> is not used directly. */
+       otelc_log_severity_t  severity;   /* The severity level. */
+       int64_t               event_id;   /* Optional event identifier. */
+       char                 *event_name; /* Optional event name. */
+       char                 *span;       /* Optional span reference. */
+       struct otelc_kv      *attr;       /* Log record attributes. */
+       size_t                attr_len;   /* Number of log record attributes. */
+       struct list           samples;    /* Sample expressions for the body. */
+};
+
 /* Configuration for a single event scope. */
 struct flt_otel_conf_scope {
        FLT_OTEL_CONF_HDR(id);            /* The scope name. */
@@ -200,6 +229,7 @@ struct flt_otel_conf_scope {
        struct list      spans;           /* Declared spans. */
        struct list      spans_to_finish; /* The list of spans scheduled for finishing. */
        struct list      instruments;     /* The list of metric instruments. */
+       struct list      log_records;     /* The list of log records. */
 };
 
 /* Configuration for a named group of scopes. */
@@ -223,6 +253,7 @@ struct flt_otel_conf_instr {
        char                *config;        /* The OpenTelemetry configuration file name. */
        struct otelc_tracer *tracer;        /* The OpenTelemetry tracer handle. */
        struct otelc_meter  *meter;         /* The OpenTelemetry meter handle. */
+       struct otelc_logger *logger;        /* The OpenTelemetry logger handle. */
        uint32_t             rate_limit;    /* [0 2^32-1] <-> [0.0 100.0] */
        bool                 flag_harderr;  /* [0 1] */
        bool                 flag_disabled; /* [0 1] */
index 4459e02bb859c26b65abd8f5a66975fff9f5d735..6e11bac3555d6c312946e084ca4ef680c969e388 100644 (file)
@@ -14,6 +14,7 @@
        {                                                                                                                     \
                struct flt_otel_conf_##_type_ *retptr = NULL;                                                                 \
                struct flt_otel_conf_##_type_ *ptr;                                                                           \
+               char                           id_buffer[FLT_OTEL_ID_MAXLEN + 16];                                            \
                size_t                         _id_##_len;                                                                    \
                                                                                                                              \
                OTELC_FUNC("\"%s\", %d, %p, %p:%p", OTELC_STR_ARG(id), line, head, OTELC_DPTR_ARGS(err));                     \
                        FLT_OTEL_ERR("name not set");                                                                         \
                                                                                                                              \
                        OTELC_RETURN_PTR(retptr);                                                                             \
+               }                                                                                                             \
+               else if ((id[0] == FLT_OTEL_CONF_HDR_SPECIAL[0]) && (id[1] == FLT_OTEL_CONF_HDR_SPECIAL[1])) {                \
+                       (void)snprintf(id_buffer, sizeof(id_buffer), "%s:%d", id + 2, line);                                  \
+                                                                                                                             \
+                       id = id_buffer;                                                                                       \
                }                                                                                                             \
                                                                                                                              \
                _id_##_len = strlen(id);                                                                                      \
@@ -106,6 +112,7 @@ FLT_OTEL_CONF_FUNC_DECL(context)
 FLT_OTEL_CONF_FUNC_DECL(span)
 FLT_OTEL_CONF_FUNC_DECL(scope)
 FLT_OTEL_CONF_FUNC_DECL(instrument)
+FLT_OTEL_CONF_FUNC_DECL(log_record)
 FLT_OTEL_CONF_FUNC_DECL(group)
 FLT_OTEL_CONF_FUNC_DECL(instr)
 
index dde98aa472ac77b790efc48e4f8c1999fc8f2286..8e4a79a1c15e33118b348e7903e10618d646acba 100644 (file)
 #define FLT_OTEL_PARSE_INSTRUMENT_UNIT        "unit"
 #define FLT_OTEL_PARSE_INSTRUMENT_BOUNDS      "bounds"
 #define FLT_OTEL_PARSE_INSTRUMENT_AGGR        "aggr"
+#define FLT_OTEL_PARSE_LOG_RECORD_ID          "id"
+#define FLT_OTEL_PARSE_LOG_RECORD_EVENT       "event"
+#define FLT_OTEL_PARSE_LOG_RECORD_SPAN        "span"
+#define FLT_OTEL_PARSE_LOG_RECORD_ATTR        "attr"
 #define FLT_OTEL_PARSE_CTX_AUTONAME           "-"
 #define FLT_OTEL_PARSE_CTX_IGNORE_NAME        '-'
 #define FLT_OTEL_PARSE_CTX_USE_HEADERS        "use-headers"
        FLT_OTEL_PARSE_SCOPE_DEF(      STATUS, 1, NONE, 2, 0,   "status",     " <code> [<sample> ...]")                                                                        \
        FLT_OTEL_PARSE_SCOPE_DEF(      FINISH, 0, NONE, 2, 0,   "finish",     " <name> ...")                                                                                   \
        FLT_OTEL_PARSE_SCOPE_DEF(  INSTRUMENT, 0, NONE, 3, 0, "instrument",   " { update <name> [<attr> ...] | <type> <name> [<aggr>] [<desc>] [<unit>] <value> [<bounds>] }") \
+       FLT_OTEL_PARSE_SCOPE_DEF(  LOG_RECORD, 0, NONE, 3, 0, "log-record",   " <severity> [<id>] [<event>] [<span>] [<attr>] <sample>")                                       \
        FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", " <time>")                                                                                       \
        FLT_OTEL_PARSE_SCOPE_DEF(         ACL, 0, CHAR, 3, 0, "acl",          " <name> <criterion> [flags] [operator] <value> ...")                                            \
        FLT_OTEL_PARSE_SCOPE_DEF(    ON_EVENT, 0, NONE, 2, 0, "otel-event",   " <name> [{ if | unless } <condition>]")
index 6bbba2d5525bb16afad9a5d33bc852b64642471a..3528366dcc04712d7fffa973a73cc4e28f43abcf 100644 (file)
@@ -383,6 +383,7 @@ static int flt_otel_cli_parse_status(char **args, char *payload, struct appctx *
                (void)memprintf(&msg, "%s       configuration: %s\n", msg, conf->instr->config);
                (void)memprintf(&msg, "%s       tracer:        %s\n", msg, (conf->instr->tracer != NULL) ? "active" : "not initialized");
                (void)memprintf(&msg, "%s       meter:         %s\n", msg, (conf->instr->meter != NULL) ? "active" : "not initialized");
+               (void)memprintf(&msg, "%s       logger:        %s\n", msg, (conf->instr->logger != NULL) ? "active" : "not initialized");
                (void)memprintf(&msg, "%s       rate limit:    %.2f %%\n", msg, FLT_OTEL_U32_FLOAT(_HA_ATOMIC_LOAD(&(conf->instr->rate_limit))));
                (void)memprintf(&msg, "%s       hard errors:   %s\n", msg, FLT_OTEL_STR_FLAG_YN(_HA_ATOMIC_LOAD(&(conf->instr->flag_harderr))));
                (void)memprintf(&msg, "%s       disabled:      %s\n", msg, FLT_OTEL_STR_FLAG_YN(_HA_ATOMIC_LOAD(&(conf->instr->flag_disabled))));
index 72b9d26f1251907e85b79165be8c9b0fddc158c4..8a10df21944162711031238645d69321ff5ae42e 100644 (file)
@@ -544,6 +544,60 @@ FLT_OTEL_CONF_FUNC_FREE(instrument, id,
 )
 
 
+/***
+ * NAME
+ *   flt_otel_conf_log_record_init - conf_log_record structure allocation
+ *
+ * SYNOPSIS
+ *   struct flt_otel_conf_log_record *flt_otel_conf_log_record_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_log_record structure.  Initializes the
+ *   sample expressions list.  The <id> string is required by the macro but is
+ *   not used directly; the severity level is stored separately.  If <head> 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(log_record, id,
+       LIST_INIT(&(retptr->samples));
+)
+
+
+/***
+ * NAME
+ *   flt_otel_conf_log_record_free - conf_log_record structure deallocation
+ *
+ * SYNOPSIS
+ *   void flt_otel_conf_log_record_free(struct flt_otel_conf_log_record **ptr)
+ *
+ * ARGUMENTS
+ *   ptr - a pointer to the address of a structure
+ *
+ * DESCRIPTION
+ *   Deallocates memory used by the flt_otel_conf_log_record 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(log_record, id,
+       FLT_OTEL_DBG_CONF_LOG_RECORD("- conf_log_record free ", *ptr);
+
+       OTELC_SFREE((*ptr)->event_name);
+       OTELC_SFREE((*ptr)->span);
+       otelc_kv_destroy(&((*ptr)->attr), (*ptr)->attr_len);
+       FLT_OTEL_LIST_DESTROY(sample, &((*ptr)->samples));
+)
+
+
 /***
  * NAME
  *   flt_otel_conf_scope_init - conf_scope structure allocation
@@ -572,6 +626,7 @@ FLT_OTEL_CONF_FUNC_INIT(scope, id,
        LIST_INIT(&(retptr->spans));
        LIST_INIT(&(retptr->spans_to_finish));
        LIST_INIT(&(retptr->instruments));
+       LIST_INIT(&(retptr->log_records));
 )
 
 
@@ -608,6 +663,7 @@ FLT_OTEL_CONF_FUNC_FREE(scope, id,
        FLT_OTEL_LIST_DESTROY(span, &((*ptr)->spans));
        FLT_OTEL_LIST_DESTROY(str, &((*ptr)->spans_to_finish));
        FLT_OTEL_LIST_DESTROY(instrument, &((*ptr)->instruments));
+       FLT_OTEL_LIST_DESTROY(log_record, &((*ptr)->log_records));
 )
 
 
index 07683fcfa528c2c9b9e59dea99a9752221734cd1..4151eca61ded20ac2c9b9835de0a8460a4455275 100644 (file)
@@ -220,6 +220,137 @@ static int flt_otel_scope_run_instrument(struct stream *s, uint dir, struct flt_
 }
 
 
+/***
+ * NAME
+ *   flt_otel_scope_run_log_record - log record emitter
+ *
+ * SYNOPSIS
+ *   static int flt_otel_scope_run_log_record(struct stream *s, struct filter *f, uint dir, struct flt_otel_conf_scope *scope, struct otelc_logger *logger, const struct timespec *ts, char **err)
+ *
+ * ARGUMENTS
+ *   s      - the stream providing the sample context
+ *   f      - the filter instance
+ *   dir    - the sample fetch direction (SMP_OPT_DIR_REQ/RES)
+ *   scope  - the scope configuration containing the log record list
+ *   logger - the OTel logger instance
+ *   ts     - the wall-clock timestamp for the log record
+ *   err    - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Processes all log records configured in <scope>.  For each record, checks
+ *   whether the logger is enabled for the configured severity, evaluates the
+ *   sample expressions into a body string, resolves the optional span reference
+ *   against the runtime context, and emits the log record via the logger's
+ *   log_span operation.
+ *
+ * RETURN VALUE
+ *   Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
+ */
+static int flt_otel_scope_run_log_record(struct stream *s, struct filter *f, uint dir, struct flt_otel_conf_scope *scope, struct otelc_logger *logger, const struct timespec *ts, char **err)
+{
+       struct flt_otel_conf_log_record *conf_log;
+       int                              retval = FLT_OTEL_RET_OK;
+
+       OTELC_FUNC("%p, %p, %u, %p, %p, %p, %p:%p", s, f, dir, scope, logger, ts, OTELC_DPTR_ARGS(err));
+
+       list_for_each_entry(conf_log, &(scope->log_records), list) {
+               struct flt_otel_conf_sample      *sample;
+               struct flt_otel_conf_sample_expr *expr;
+               struct sample                     smp;
+               struct otelc_span                *otel_span = NULL;
+               struct buffer                     buffer;
+               int                               rc;
+
+               OTELC_DBG(DEBUG, "run log-record '%s' -> '%s'", scope->id, conf_log->id);
+
+               /* Skip if the logger is not enabled for this severity. */
+               if (OTELC_OPS(logger, enabled, conf_log->severity) == 0)
+                       continue;
+
+               /* The samples list has exactly one entry. */
+               sample = LIST_NEXT(&(conf_log->samples), typeof(sample), list);
+
+               (void)memset(&buffer, 0, sizeof(buffer));
+
+               if (sample->lf_used) {
+                       /*
+                        * Log-format path: evaluate the log-format expression
+                        * into a dynamically allocated buffer.
+                        */
+                       chunk_init(&buffer, OTELC_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
+                       if (buffer.area != NULL)
+                               buffer.data = build_logline(s, buffer.area, buffer.size, &(sample->lf_expr));
+               } else {
+                       /*
+                        * Bare sample expression path: evaluate each expression
+                        * and concatenate the results.
+                        */
+                       list_for_each_entry(expr, &(sample->exprs), list) {
+                               (void)memset(&smp, 0, sizeof(smp));
+
+                               if (sample_process(s->be, s->sess, s, dir | SMP_OPT_FINAL, expr->expr, &smp) == NULL) {
+                                       OTELC_DBG(NOTICE, "WARNING: failed to fetch '%s'", expr->fmt_expr);
+
+                                       retval = FLT_OTEL_RET_ERROR;
+
+                                       break;
+                               }
+
+                               if (buffer.area == NULL) {
+                                       chunk_init(&buffer, OTELC_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
+                                       if (buffer.area == NULL)
+                                               break;
+                               }
+
+                               rc = flt_otel_sample_to_str(&(smp.data), buffer.area + buffer.data, buffer.size - buffer.data, err);
+                               if (rc == FLT_OTEL_RET_ERROR) {
+                                       retval = FLT_OTEL_RET_ERROR;
+
+                                       break;
+                               }
+
+                               buffer.data += rc;
+                       }
+               }
+
+               if (buffer.area == NULL) {
+                       FLT_OTEL_ERR("out of memory");
+
+                       retval = FLT_OTEL_RET_ERROR;
+
+                       continue;
+               }
+
+               /*
+                * If the log record references a span, resolve it against the
+                * runtime context.  A missing span is not fatal -- the log
+                * record is emitted without span correlation.
+                */
+               if (conf_log->span != NULL) {
+                       struct flt_otel_runtime_context *rt_ctx = FLT_OTEL_RT_CTX(f->ctx);
+                       struct flt_otel_scope_span      *sc_span;
+
+                       list_for_each_entry(sc_span, &(rt_ctx->spans), list)
+                               if (strcmp(sc_span->id, conf_log->span) == 0) {
+                                       otel_span = sc_span->span;
+
+                                       break;
+                               }
+
+                       if (otel_span == NULL)
+                               OTELC_DBG(NOTICE, "WARNING: cannot find span '%s' for log-record", conf_log->span);
+               }
+
+               if (OTELC_OPS(logger, log_span, conf_log->severity, conf_log->event_id, conf_log->event_name, otel_span, ts, conf_log->attr, conf_log->attr_len, "%s", buffer.area) == OTELC_RET_ERROR)
+                       retval = FLT_OTEL_RET_ERROR;
+
+               OTELC_SFREE(buffer.area);
+       }
+
+       OTELC_RETURN_INT(retval);
+}
+
+
 /***
  * NAME
  *   flt_otel_scope_run_span - single span execution
@@ -357,7 +488,8 @@ static int flt_otel_scope_run_span(struct stream *s, struct filter *f, struct ch
  *   from HTTP headers or HAProxy variables, iterates over configured spans
  *   (resolving links, evaluating sample expressions for attributes, events,
  *   baggage and status), calls flt_otel_scope_run_span() for each, processes
- *   metric instruments, then marks and finishes completed spans.
+ *   metric instruments, emits log records, then marks and finishes completed
+ *   spans.
  *
  * RETURN VALUE
  *   Returns FLT_OTEL_RET_OK on success, FLT_OTEL_RET_ERROR on failure.
@@ -568,6 +700,11 @@ int flt_otel_scope_run(struct stream *s, struct filter *f, struct channel *chn,
                if (flt_otel_scope_run_instrument(s, dir, conf_scope, conf->instr->meter, err) == FLT_OTEL_RET_ERROR)
                        retval = FLT_OTEL_RET_ERROR;
 
+       /* Emit log records. */
+       if (!LIST_ISEMPTY(&(conf_scope->log_records)))
+               if (flt_otel_scope_run_log_record(s, f, dir, conf_scope, conf->instr->logger, ts_system, err) == FLT_OTEL_RET_ERROR)
+                       retval = FLT_OTEL_RET_ERROR;
+
        /* Mark the configured spans for finishing and clean up. */
        list_for_each_entry(span_to_finish, &(conf_scope->spans_to_finish), list)
                if (flt_otel_scope_finish_mark(f->ctx, span_to_finish->str, span_to_finish->str_len) == FLT_OTEL_RET_ERROR)
index 7cef5a5636f3773a46e91e57651a33c5b4e91e38..5bd80ae9aaad324786ebe59799c3b1b5103f556c 100644 (file)
@@ -200,6 +200,14 @@ static int flt_otel_lib_init(struct flt_otel_conf_instr *instr, char **err)
        if (instr->meter == NULL) {
                if (*err == NULL)
                        FLT_OTEL_ERR("%s", "failed to initialize OpenTelemetry meter");
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       instr->logger = otelc_logger_create(err);
+       if (instr->logger == NULL) {
+               if (*err == NULL)
+                       FLT_OTEL_ERR("%s", "failed to initialize OpenTelemetry logger");
        } else {
                otelc_ext_init(flt_otel_mem_malloc, flt_otel_mem_free, flt_otel_thread_id);
                otelc_log_set_handler(flt_otel_log_handler_cb, NULL, false);
@@ -415,6 +423,7 @@ static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
        struct flt_otel_conf **conf = (fconf == NULL) ? NULL : (typeof(conf))&(fconf->conf);
        struct otelc_tracer   *otel_tracer = NULL;
        struct otelc_meter    *otel_meter = NULL;
+       struct otelc_logger   *otel_logger = NULL;
 #ifdef DEBUG_OTEL
        char                   buffer[BUFSIZ];
        int                    i;
@@ -448,12 +457,13 @@ static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
        if ((*conf)->instr != NULL) {
                otel_tracer = (*conf)->instr->tracer;
                otel_meter  = (*conf)->instr->meter;
+               otel_logger = (*conf)->instr->logger;
        }
 
        flt_otel_conf_free(conf);
        OTELC_MEMINFO();
        flt_otel_pool_destroy();
-       otelc_deinit(&otel_tracer, &otel_meter, NULL);
+       otelc_deinit(&otel_tracer, &otel_meter, &otel_logger);
 
        OTELC_RETURN();
 }
@@ -817,6 +827,45 @@ static int flt_otel_ops_check(struct proxy *p, struct flt_conf *fconf)
                }
        }
 
+       OTELC_DBG(DEBUG, "- defined log records ----------");
+
+       /*
+        * Validate log-record span references: for each log-record that
+        * names a span, verify that a span with that name exists in one
+        * of the configured scopes.
+        */
+       list_for_each_entry(conf_scope, &(conf->scopes), list) {
+               struct flt_otel_conf_log_record *conf_log;
+
+               list_for_each_entry(conf_log, &(conf_scope->log_records), list) {
+                       FLT_OTEL_DBG_CONF_LOG_RECORD("  ", conf_log);
+
+                       if (conf_log->span != NULL) {
+                               struct flt_otel_conf_scope *find_scope;
+                               struct flt_otel_conf_span  *find_span;
+                               bool                        flag_found = false;
+
+                               list_for_each_entry(find_scope, &(conf->scopes), list) {
+                                       list_for_each_entry(find_span, &(find_scope->spans), list)
+                                               if (strcmp(find_span->id, conf_log->span) == 0) {
+                                                       flag_found = true;
+
+                                                       break;
+                                               }
+
+                                       if (flag_found)
+                                               break;
+                               }
+
+                               if (!flag_found) {
+                                       FLT_OTEL_ALERT("'" FLT_OTEL_PARSE_SECTION_SCOPE_ID " '%s' : log-record references undefined span '%s''", conf_scope->id, conf_log->span);
+
+                                       retval++;
+                               }
+                       }
+               }
+       }
+
        FLT_OTEL_DBG_LIST(conf, group, "", "defined", _group,
                          FLT_OTEL_DBG_CONF_GROUP("   ", _group);
                          FLT_OTEL_DBG_LIST(_group, ph_scope, "   ", "used", _scope, FLT_OTEL_DBG_CONF_PH("      ", _scope)));
@@ -876,6 +925,12 @@ static int flt_otel_ops_init_per_thread(struct proxy *p, struct flt_conf *fconf)
                                FLT_OTEL_ALERT("%s", conf->instr->meter->err);
                }
 
+               if (retval != OTELC_RET_ERROR) {
+                       retval = OTELC_OPS(conf->instr->logger, start);
+                       if (retval == OTELC_RET_ERROR)
+                               FLT_OTEL_ALERT("%s", conf->instr->logger->err);
+               }
+
                if (retval != FLT_OTEL_RET_ERROR)
                        fconf->flags |= FLT_CFG_FL_HTX;
        } else {
index 2b3bda7ec808fb570892945d6729de4f4035fca7..df4f05b8afe5cccf67808e14c57548a2beaa4a40 100644 (file)
@@ -1146,6 +1146,107 @@ static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args
 }
 
 
+/***
+ * NAME
+ *   flt_otel_parse_cfg_log_record - log-record keyword parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_log_record(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
+ *
+ * ARGUMENTS
+ *   file  - configuration file path
+ *   line  - configuration file line number
+ *   args  - configuration line arguments array
+ *   pdata - keyword metadata (name, usage, argument limits)
+ *   err   - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses the "log-record" keyword inside an otel-scope section.  The first
+ *   argument is a required severity level string.  Optional keywords "id",
+ *   "event", "span", and "attr" follow in any order.  The remaining arguments
+ *   at the end are parsed as fetch expressions or a log-format string.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_log_record(const char *file, int line, char **args, const struct flt_otel_parse_data *pdata, char **err)
+{
+       struct flt_otel_conf_log_record *log;
+       otelc_log_severity_t             severity;
+       int                              i, retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, pdata, OTELC_DPTR_ARGS(err));
+
+       /* Look up the severity level from args[1]. */
+       severity = otelc_logger_severity_parse(args[1]);
+       if (severity == OTELC_LOG_SEVERITY_INVALID) {
+               FLT_OTEL_PARSE_ERR(err, "'%s' : invalid log severity", args[1]);
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       log = flt_otel_conf_log_record_init(FLT_OTEL_CONF_HDR_SPECIAL "log-record", line, &(flt_otel_current_scope->log_records), err);
+       if (log == NULL) {
+               retval |= ERR_ABORT | ERR_ALERT;
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       log->severity = severity;
+
+       /* Parse optional keywords starting from args[2]. */
+       for (i = 2; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++) {
+               if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_ID)) {
+                       if (!FLT_OTEL_ARG_ISVALID(i + 1))
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       else if (log->event_id != 0)
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       else if (!flt_otel_strtoll(args[++i], &(log->event_id), 0, LLONG_MAX, err))
+                               retval |= ERR_ABORT | ERR_ALERT;
+               }
+               else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_EVENT)) {
+                       if (!FLT_OTEL_ARG_ISVALID(i + 1))
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       else if (log->event_name != NULL)
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       else
+                               retval = flt_otel_parse_strdup(&(log->event_name), NULL, args[++i], err, args[0]);
+               }
+               else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_SPAN)) {
+                       if (!FLT_OTEL_ARG_ISVALID(i + 1))
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       else if (log->span != NULL)
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       else
+                               retval = flt_otel_parse_strdup(&(log->span), NULL, args[++i], err, args[0]);
+               }
+               else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_LOG_RECORD_ATTR)) {
+                       if (!FLT_OTEL_ARG_ISVALID(i + 1) || !FLT_OTEL_ARG_ISVALID(i + 2))
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
+                       else if (otelc_kv_add(&(log->attr), &(log->attr_len), args[i + 1], args[i + 2], strlen(args[i + 2])) == OTELC_RET_ERROR)
+                               FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[0]);
+                       else
+                               i += 2;
+               }
+               else {
+                       /*
+                        * Not a recognized keyword -- the remaining arguments
+                        * are sample fetch expressions or a log-format string.
+                        */
+                       retval = flt_otel_parse_cfg_sample(file, line, args, i, 0, NULL, &(log->samples), err);
+
+                       break;
+               }
+       }
+
+       if (!(retval & ERR_CODE) && LIST_ISEMPTY(&(log->samples)))
+               FLT_OTEL_PARSE_ERR(err, "'%s' : missing body expression (use '%s%s')", args[0], pdata->name, pdata->usage);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
 /***
  * NAME
  *   flt_otel_parse_cfg_scope - otel-scope section parser
@@ -1162,8 +1263,8 @@ static int flt_otel_parse_cfg_instrument(const char *file, int line, char **args
  * DESCRIPTION
  *   Section parser for the otel-scope configuration block.  Handles keywords:
  *   scope ID, span (with optional root/parent/link modifiers), link, attribute,
- *   event, baggage, status, inject, extract, finish, instrument, acl, and
- *   otel-event (with optional if/unless conditions).
+ *   event, baggage, status, inject, extract, finish, instrument, log-record,
+ *   acl, and otel-event (with optional if/unless conditions).
  *
  * RETURN VALUE
  *   Returns ERR_NONE (== 0) in case of success,
@@ -1369,6 +1470,9 @@ static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int
        else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_INSTRUMENT) {
                retval = flt_otel_parse_cfg_instrument(file, line, args, pdata, &err);
        }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_LOG_RECORD) {
+               retval = flt_otel_parse_cfg_log_record(file, line, args, pdata, &err);
+       }
        else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ACL) {
                if (FLT_OTEL_PARSE_KEYWORD(1, "or"))
                        FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
index 245e1084c3983a577f7489d434663fa6fb768953..e7b25f790394d4938437ecf5550345e578328479 100644 (file)
@@ -65,6 +65,7 @@
             event "event_be" "be"  be_id str(" ") be_name
             event "event_ip" "dst" dst str(":") dst_port
             event "event_fe" "fe"  fe_id str(" ") fe_name
+        log-record trace id 1000 event "session-start" span "Client session" attr "attr_1_key" "attr_1_value" attr "attr_2_key" "attr_2_value" src str(":") src_port
         acl acl-test-src-ip src 127.0.0.1
         otel-event on-stream-start if acl-test-src-ip
 
@@ -78,6 +79,7 @@
             attribute "idle.elapsed" str("idle-check")
         instrument cnt_int "idle.count" value int(1)
         instrument update "idle.count"
+        log-record info str("heartbeat")
         otel-event on-idle-timeout
 
     otel-scope client_session_start
             attribute "http.url" url
             attribute "http.version" str("HTTP/") req.ver
         finish "HTTP body request"
+        log-record info id 1001 event "http-request" span "Frontend HTTP request" attr "http.method" "GET" method url
         otel-event on-frontend-http-request
 
     otel-scope switching_rules_request