diff --git a/addons/otel/README b/addons/otel/README index 5f083b29d..c3d8ac17a 100644 --- a/addons/otel/README +++ b/addons/otel/README @@ -669,15 +669,15 @@ instrument { update [] | [] [] [] < attr - attribute key-value pairs (update form only) -log-record [id ] [event ] [span ] [attr ] ... ... +log-record [id ] [event ] [span ] [attr ] ... ... This keyword emits an OpenTelemetry log record within the scope. The first argument is a required severity level. Optional keywords follow in any order before the trailing sample expressions that form the log record body: - id - numeric event identifier - event - event name string - span - associate the log record with an open span - attr - add a key-value attribute (repeatable) + id - numeric event identifier + event - event name string + span - associate the log record with an open span + attr - add an attribute evaluated at runtime (repeatable) The remaining arguments at the end are sample fetch expressions. A single sample preserves its native type; multiple samples are concatenated as a @@ -694,8 +694,8 @@ log-record [id ] [event ] [span ] [attr [id ] [event ] [span ] [attr ] ... ... + log-record [id ] [event ] [span ] [attr ] ... ... Emit an OpenTelemetry log record. The first argument is a required severity level. Optional keywords follow in any order: - id - numeric event identifier - event - event name string - span - associate the log record with an open span - attr - add a key-value attribute (repeatable) + id - numeric event identifier + event - event name string + span - associate the log record with an open span + attr - add an attribute evaluated at runtime (repeatable) + + The 'attr' keyword takes an attribute name and a single HAProxy sample + expression. The expression is evaluated at runtime, following the same + rules as span attributes: a bare sample fetch (e.g. src) or a log-format + string (e.g. "%[src]:%[src_port]"). The remaining arguments at the end are sample fetch expressions that form the log record body. A single sample preserves its native type; multiple @@ -389,8 +394,8 @@ Supported keywords: Examples: log-record info str("heartbeat") - log-record info id 1001 event "http-request" span "Frontend HTTP request" attr "http.method" "GET" method url - 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 + log-record info id 1001 event "http-request" span "Frontend HTTP request" attr "http.method" method method url + log-record trace id 1000 event "session-start" span "Client session" attr "src_ip" src attr "src_port" src_port src str(":") src_port log-record warn event "server-unavailable" str("503 Service Unavailable") log-record info event "session-stop" str("stream stopped") diff --git a/addons/otel/README-implementation b/addons/otel/README-implementation index 3dc6934f7..4ce66db3f 100644 --- a/addons/otel/README-implementation +++ b/addons/otel/README-implementation @@ -155,7 +155,7 @@ executes all referenced scopes when the rule fires. inject [use-headers] [use-vars] finish ... instrument ... / instrument update ... - log-record [id ] [event ] [span ] [attr ] ... ... + log-record [id ] [event ] [span ] [attr ] ... ... acl ... Each scope ties to a single HAProxy analyzer event (or none, if used only @@ -1157,10 +1157,14 @@ The flt_otel_conf_log_record structure (conf.h) holds: event_id Optional numeric event identifier (int64). event_name Optional event name string. span Optional span reference name (resolved at runtime). - attr Key-value attribute array (from "attr" keywords). - attr_len Number of attributes. + attributes List of flt_otel_conf_sample entries for attributes. samples List of sample expressions for the body. +The attributes list contains flt_otel_conf_sample entries, one per "attr" +keyword. Each entry's key field holds the attribute name and its sample +expressions are evaluated at runtime, following the same two-path model +(bare sample or log-format) as span attributes. + The samples list contains exactly one flt_otel_conf_sample entry, which in turn holds either a list of bare sample expressions or a single log-format expression (when the value contains "%["). @@ -1178,7 +1182,12 @@ For each configured log record the function performs: skipped. The threshold is controlled by the "min_severity" option in the YAML logs signal configuration. - 2. Body evaluation: the single sample entry is evaluated using one of + 2. Attribute evaluation: each entry in the attributes list is evaluated via + flt_otel_sample_add() into a temporary flt_otel_scope_data structure. + The evaluated key-value array is passed to logger->log_span() and freed + after emission. + + 3. Body evaluation: the single sample entry is evaluated using one of two paths: Log-format path (sample->lf_used is true): @@ -1191,14 +1200,14 @@ For each configured log record the function performs: flt_otel_sample_to_str(). Results are concatenated into a single buffer. - 3. Span resolution: if conf_log->span is non-NULL, the runtime + 4. Span resolution: if conf_log->span is non-NULL, the runtime context's spans list is searched for a scope_span with a matching name. If found, the OTel span pointer is captured for correlation. A missing span is non-fatal -- a NOTICE warning is logged and the record is emitted without span correlation. - 4. Emission: logger->log_span() is called with the severity, event_id, - event_name, resolved span (or NULL), wall-clock timestamp, + 5. Emission: logger->log_span() is called with the severity, event_id, + event_name, resolved span (or NULL), wall-clock timestamp, the evaluated attributes and the evaluated body string. 18.6.3 Logger Lifecycle Summary diff --git a/addons/otel/include/conf.h b/addons/otel/include/conf.h index 9549957f8..9bd4c7a49 100644 --- a/addons/otel/include/conf.h +++ b/addons/otel/include/conf.h @@ -91,9 +91,9 @@ (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), \ + OTELC_DBG_STRUCT(DEBUG, h, h FLT_OTEL_CONF_HDR_FMT "%d %" PRId64 " '%s' '%s' %s %s }", (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))) + OTELC_STR_ARG((p)->span), flt_otel_list_dump(&((p)->attributes)), 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), \ @@ -212,8 +212,7 @@ struct flt_otel_conf_log_record { 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 attributes; /* Log record attributes (flt_otel_conf_sample). */ struct list samples; /* Sample expressions for the body. */ }; diff --git a/addons/otel/include/parser.h b/addons/otel/include/parser.h index 8e4a79a1c..8412b2066 100644 --- a/addons/otel/include/parser.h +++ b/addons/otel/include/parser.h @@ -121,7 +121,7 @@ FLT_OTEL_PARSE_SCOPE_DEF( STATUS, 1, NONE, 2, 0, "status", " [ ...]") \ FLT_OTEL_PARSE_SCOPE_DEF( FINISH, 0, NONE, 2, 0, "finish", " ...") \ FLT_OTEL_PARSE_SCOPE_DEF( INSTRUMENT, 0, NONE, 3, 0, "instrument", " { update [ ...] | [] [] [] [] }") \ - FLT_OTEL_PARSE_SCOPE_DEF( LOG_RECORD, 0, NONE, 3, 0, "log-record", " [] [] [] [] ") \ + FLT_OTEL_PARSE_SCOPE_DEF( LOG_RECORD, 0, NONE, 3, 0, "log-record", " [] [] [] [] ...") \ FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", "