MEDIUM: otel: added OpenTelemetry filter skeleton

The OpenTelemetry (OTel) filter enables distributed tracing of requests
across service boundaries, export of metrics such as request rates,
latencies and error counts, and structured logging tied to trace context,
giving operators a unified view of HAProxy traffic through any
OpenTelemetry-compatible backend.

The OTel filter is implemented using the standard HAProxy stream filter
API.  Stream filters attach to proxies and intercept traffic at each stage
of processing: they receive callbacks on stream creation and destruction,
channel analyzer events, HTTP header and payload processing, and TCP data
forwarding.  This allows the filter to collect telemetry data at every
stage of the request/response lifecycle without modifying the core proxy
logic.

This commit added the minimum set of files required for the filter to
compile: the addon Makefile with pkg-config-based detection of the
opentelemetry-c-wrapper library, header files with configuration
constants, utility macros and type definitions, and the source files
containing stub filter operation callbacks registered through
flt_otel_ops and the "opentelemetry" keyword parser entry point.

The filter uses the opentelemetry-c-wrapper library from HAProxy
Technologies, which provides a C interface to the OpenTelemetry C++ SDK.
This wrapper allows HAProxy, a C codebase, to leverage the full
OpenTelemetry observability pipeline without direct C++ dependencies
in the HAProxy source tree.

  https://github.com/haproxytech/opentelemetry-c-wrapper
  https://github.com/open-telemetry/opentelemetry-cpp

Build options:

  USE_OTEL     - enable the OpenTelemetry filter
  OTEL_DEBUG   - compile the filter in debug mode
  OTEL_INC     - force the include path to the C wrapper
  OTEL_LIB     - force the library path to the C wrapper
  OTEL_RUNPATH - add the C wrapper RUNPATH to the executable

Example build with OTel and debug enabled:

  make -j8 USE_OTEL=1 OTEL_DEBUG=1 TARGET=linux-glibc
This commit is contained in:
Miroslav Zagorac 2026-04-13 07:48:51 +02:00 committed by William Lallemand
parent b8145fa5d4
commit cd14abf9f3
13 changed files with 932 additions and 1 deletions

View file

@ -60,6 +60,7 @@
# USE_OBSOLETE_LINKER : use when the linker fails to emit __start_init/__stop_init
# USE_THREAD_DUMP : use the more advanced thread state dump system. Automatic.
# USE_OT : enable the OpenTracing filter
# USE_OTEL : enable the OpenTelemetry filter
# USE_MEMORY_PROFILING : enable the memory profiler. Linux-glibc only.
# USE_LIBATOMIC : force to link with/without libatomic. Automatic.
# USE_PTHREAD_EMULATION : replace pthread's rwlocks with ours
@ -128,6 +129,10 @@
# OT_LIB : force the lib path to libopentracing-c-wrapper
# OT_RUNPATH : add RUNPATH for libopentracing-c-wrapper to haproxy executable
# OT_USE_VARS : allows the use of variables for the OpenTracing context
# OTEL_DEBUG : compile the OpenTelemetry filter in debug mode
# OTEL_INC : force the include path to libopentelemetry-c-wrapper
# OTEL_LIB : force the lib path to libopentelemetry-c-wrapper
# OTEL_RUNPATH : add RUNPATH for libopentelemetry-c-wrapper to haproxy executable
# IGNOREGIT : ignore GIT commit versions if set.
# VERSION : force haproxy version reporting.
# SUBVERS : add a sub-version (eg: platform, model, ...).
@ -347,7 +352,7 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL \
USE_CPU_AFFINITY USE_TFO USE_NS USE_DL USE_RT USE_LIBATOMIC \
USE_MATH USE_DEVICEATLAS USE_51DEGREES \
USE_WURFL USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL \
USE_THREAD_DUMP USE_EVPORTS USE_OT USE_QUIC USE_PROMEX \
USE_THREAD_DUMP USE_EVPORTS USE_OT USE_OTEL USE_QUIC USE_PROMEX \
USE_MEMORY_PROFILING USE_SHM_OPEN \
USE_STATIC_PCRE USE_STATIC_PCRE2 \
USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT \
@ -862,6 +867,10 @@ ifneq ($(USE_OT:0=),)
include addons/ot/Makefile
endif
ifneq ($(USE_OTEL:0=),)
include addons/otel/Makefile
endif
# better keep this one close to the end, as several libs above may need it
ifneq ($(USE_DL:0=),)
DL_LDFLAGS = -ldl
@ -1170,6 +1179,7 @@ clean:
$(Q)rm -f addons/51degrees/*.[oas] addons/51degrees/dummy/*.[oas] addons/51degrees/dummy/*/*.[oas]
$(Q)rm -f addons/deviceatlas/*.[oas] addons/deviceatlas/dummy/*.[oas] addons/deviceatlas/dummy/*.o
$(Q)rm -f addons/deviceatlas/dummy/Os/*.o
$(Q)rm -f addons/otel/src/*.[oas]
$(Q)rm -f addons/ot/src/*.[oas]
$(Q)rm -f addons/wurfl/*.[oas] addons/wurfl/dummy/*.[oas]
$(Q)rm -f admin/*/*.[oas] admin/*/*/*.[oas]

1
addons/otel/AUTHORS Normal file
View file

@ -0,0 +1 @@
Miroslav Zagorac <mzagorac@haproxy.com>

1
addons/otel/MAINTAINERS Normal file
View file

@ -0,0 +1 @@
Miroslav Zagorac <mzagorac@haproxy.com>

57
addons/otel/Makefile Normal file
View file

@ -0,0 +1,57 @@
# USE_OTEL : enable the OpenTelemetry filter
# OTEL_DEBUG : compile the OpenTelemetry filter in debug mode
# OTEL_INC : force the include path to libopentelemetry-c-wrapper
# OTEL_LIB : force the lib path to libopentelemetry-c-wrapper
# OTEL_RUNPATH : add libopentelemetry-c-wrapper RUNPATH to haproxy executable
OTEL_DEFINE =
OTEL_CFLAGS =
OTEL_LDFLAGS =
OTEL_DEBUG_EXT =
OTEL_PKGSTAT =
OTELC_WRAPPER = opentelemetry-c-wrapper
ifneq ($(OTEL_DEBUG:0=),)
OTEL_DEBUG_EXT = _dbg
OTEL_DEFINE = -DDEBUG_OTEL
endif
ifeq ($(OTEL_INC),)
OTEL_PKGSTAT = $(shell pkg-config --exists $(OTELC_WRAPPER)$(OTEL_DEBUG_EXT); echo $$?)
OTEL_CFLAGS = $(shell pkg-config --silence-errors --cflags $(OTELC_WRAPPER)$(OTEL_DEBUG_EXT))
else
ifneq ($(wildcard $(OTEL_INC)/$(OTELC_WRAPPER)/.*),)
OTEL_CFLAGS = -I$(OTEL_INC) $(if $(OTEL_DEBUG),-DOTELC_DBG_MEM)
endif
endif
ifeq ($(OTEL_PKGSTAT),)
ifeq ($(OTEL_CFLAGS),)
$(error OpenTelemetry C wrapper : can't find headers)
endif
else
ifneq ($(OTEL_PKGSTAT),0)
$(error OpenTelemetry C wrapper : can't find package)
endif
endif
ifeq ($(OTEL_LIB),)
OTEL_LDFLAGS = $(shell pkg-config --silence-errors --libs $(OTELC_WRAPPER)$(OTEL_DEBUG_EXT))
else
ifneq ($(wildcard $(OTEL_LIB)/lib$(OTELC_WRAPPER).*),)
OTEL_LDFLAGS = -L$(OTEL_LIB) -l$(OTELC_WRAPPER)$(OTEL_DEBUG_EXT)
ifneq ($(OTEL_RUNPATH),)
OTEL_LDFLAGS += -Wl,--rpath,$(OTEL_LIB)
endif
endif
endif
ifeq ($(OTEL_LDFLAGS),)
$(error OpenTelemetry C wrapper : can't find library)
endif
OPTIONS_OBJS += \
addons/otel/src/filter.o \
addons/otel/src/parser.o
OTEL_CFLAGS := $(OTEL_CFLAGS) -Iaddons/otel/include $(OTEL_DEFINE)

View file

@ -0,0 +1,17 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_CONFIG_H_
#define _OTEL_CONFIG_H_
#define FLT_OTEL_DEBUG_LEVEL 0b11101111111 /* Default debug bitmask. */
#endif /* _OTEL_CONFIG_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_DEFINE_H_
#define _OTEL_DEFINE_H_
/* Execute a statement exactly once across all invocations. */
#define FLT_OTEL_RUN_ONCE(f) do { static bool _f = 1; if (_f) { _f = 0; { f; } } } while (0)
#endif /* _OTEL_DEFINE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_FILTER_H_
#define _OTEL_FILTER_H_
/* Return codes for OTel filter operations. */
enum FLT_OTEL_RET_enum {
FLT_OTEL_RET_ERROR = -1,
FLT_OTEL_RET_WAIT = 0,
FLT_OTEL_RET_IGNORE = 0,
FLT_OTEL_RET_OK = 1,
};
extern const char *otel_flt_id;
#endif /* _OTEL_FILTER_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -0,0 +1,42 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_INCLUDE_H_
#define _OTEL_INCLUDE_H_
#include <errno.h>
#include <stdbool.h>
#include <math.h>
#include <values.h>
#include <haproxy/api.h>
#include <haproxy/cfgparse.h>
#include <haproxy/acl.h>
#include <haproxy/cli.h>
#include <haproxy/clock.h>
#include <haproxy/filters.h>
#include <haproxy/http_htx.h>
#include <haproxy/http_rules.h>
#include <haproxy/log.h>
#include <haproxy/proxy.h>
#include <haproxy/sample.h>
#include <haproxy/tcp_rules.h>
#include <haproxy/tools.h>
#include <haproxy/vars.h>
#include <opentelemetry-c-wrapper/include.h>
#include "config.h"
#include "define.h"
#include "filter.h"
#include "parser.h"
#endif /* _OTEL_INCLUDE_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef _OTEL_PARSER_H_
#define _OTEL_PARSER_H_
#define FLT_OTEL_SCOPE "OTEL"
#define FLT_OTEL_OPT_NAME "opentelemetry"
#endif /* _OTEL_PARSER_H_ */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

666
addons/otel/src/filter.c Normal file
View file

@ -0,0 +1,666 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
/*
* OpenTelemetry filter id, used to identify OpenTelemetry filters. The name
* of this variable is consistent with the other filter names declared in
* include/haproxy/filters.h .
*/
const char *otel_flt_id = "the OpenTelemetry filter";
/***
* NAME
* flt_otel_ops_init - filter init callback (flt_ops.init)
*
* SYNOPSIS
* static int flt_otel_ops_init(struct proxy *p, struct flt_conf *fconf)
*
* ARGUMENTS
* p - the proxy to which the filter is attached
* fconf - the filter configuration
*
* DESCRIPTION
* It initializes the filter for a proxy. You may define this callback if you
* need to complete your filter configuration.
*
* RETURN VALUE
* Returns a negative value if an error occurs, any other value otherwise.
*/
static int flt_otel_ops_init(struct proxy *p, struct flt_conf *fconf)
{
OTELC_FUNC("%p, %p", p, fconf);
OTELC_RETURN_INT(0);
}
/***
* NAME
* flt_otel_ops_deinit - filter deinit callback (flt_ops.deinit)
*
* SYNOPSIS
* static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
*
* ARGUMENTS
* p - the proxy to which the filter is attached
* fconf - the filter configuration
*
* DESCRIPTION
* It cleans up what the parsing function and the init callback have done.
* This callback is useful to release memory allocated for the filter
* configuration.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_ops_deinit(struct proxy *p, struct flt_conf *fconf)
{
OTELC_FUNC("%p, %p", p, fconf);
OTELC_RETURN();
}
/***
* NAME
* flt_otel_ops_check - filter check callback (flt_ops.check)
*
* SYNOPSIS
* static int flt_otel_ops_check(struct proxy *p, struct flt_conf *fconf)
*
* ARGUMENTS
* p - the proxy to which the filter is attached
* fconf - the filter configuration
*
* DESCRIPTION
* Validates the internal configuration of the OTel filter after the parsing
* phase, when the HAProxy configuration is fully defined. The following
* checks are performed: duplicate filter IDs across all proxies, presence of
* an instrumentation section and its configuration file, duplicate group and
* scope names, empty groups, group-to-scope and instrumentation-to-group/scope
* cross-references, unused scopes, root span count, analyzer bits, and
* create-form instrument name uniqueness and update-form instrument
* resolution.
*
* RETURN VALUE
* Returns the number of encountered errors.
*/
static int flt_otel_ops_check(struct proxy *p, struct flt_conf *fconf)
{
OTELC_FUNC("%p, %p", p, fconf);
OTELC_RETURN_INT(0);
}
/***
* NAME
* flt_otel_ops_init_per_thread - per-thread init callback (flt_ops.init_per_thread)
*
* SYNOPSIS
* static int flt_otel_ops_init_per_thread(struct proxy *p, struct flt_conf *fconf)
*
* ARGUMENTS
* p - the proxy to which the filter is attached
* fconf - the filter configuration
*
* DESCRIPTION
* Per-thread filter initialization called after thread creation. Starts
* the OTel tracer and meter threads via their start operations and enables
* HTX stream filtering. Subsequent calls on the same filter are no-ops.
*
* RETURN VALUE
* Returns a negative value if an error occurs, any other value otherwise.
*/
static int flt_otel_ops_init_per_thread(struct proxy *p, struct flt_conf *fconf)
{
OTELC_FUNC("%p, %p", p, fconf);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
#ifdef DEBUG_OTEL
/***
* NAME
* flt_otel_ops_deinit_per_thread - per-thread deinit callback (flt_ops.deinit_per_thread)
*
* SYNOPSIS
* static void flt_otel_ops_deinit_per_thread(struct proxy *p, struct flt_conf *fconf)
*
* ARGUMENTS
* p - the proxy to which the filter is attached
* fconf - the filter configuration
*
* DESCRIPTION
* It cleans up what the init_per_thread callback have done. It is called
* in the context of a thread, before exiting it.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_ops_deinit_per_thread(struct proxy *p, struct flt_conf *fconf)
{
OTELC_FUNC("%p, %p", p, fconf);
OTELC_RETURN();
}
#endif /* DEBUG_OTEL */
/***
* NAME
* flt_otel_ops_attach - filter attach callback (flt_ops.attach)
*
* SYNOPSIS
* static int flt_otel_ops_attach(struct stream *s, struct filter *f)
*
* ARGUMENTS
* s - the stream to which the filter is being attached
* f - the filter instance
*
* DESCRIPTION
* It is called after a filter instance creation, when it is attached to a
* stream. This happens when the stream is started for filters defined on
* the stream's frontend and when the backend is set for filters declared
* on the stream's backend. It is possible to ignore the filter, if needed,
* by returning 0. This could be useful to have conditional filtering.
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 to ignore the filter,
* any other value otherwise.
*/
static int flt_otel_ops_attach(struct stream *s, struct filter *f)
{
OTELC_FUNC("%p, %p", s, f);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_stream_start - stream start callback (flt_ops.stream_start)
*
* SYNOPSIS
* static int flt_otel_ops_stream_start(struct stream *s, struct filter *f)
*
* ARGUMENTS
* s - the stream that is being started
* f - the filter instance
*
* DESCRIPTION
* It is called when a stream is started. This callback can fail by returning
* a negative value. It will be considered as a critical error by HAProxy
* which disabled the listener for a short time. After the stream-start
* event, it initializes the idle timer in the runtime context from the
* precomputed minimum idle_timeout in the instrumentation configuration.
*
* RETURN VALUE
* Returns a negative value if an error occurs, any other value otherwise.
*/
static int flt_otel_ops_stream_start(struct stream *s, struct filter *f)
{
OTELC_FUNC("%p, %p", s, f);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_stream_set_backend - stream set-backend callback (flt_ops.stream_set_backend)
*
* SYNOPSIS
* static int flt_otel_ops_stream_set_backend(struct stream *s, struct filter *f, struct proxy *be)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* be - the backend proxy being assigned
*
* DESCRIPTION
* It is called when a backend is set for a stream. This callback will be
* called for all filters attached to a stream (frontend and backend). Note
* this callback is not called if the frontend and the backend are the same.
* It fires the on-backend-set event.
*
* RETURN VALUE
* Returns a negative value if an error occurs, any other value otherwise.
*/
static int flt_otel_ops_stream_set_backend(struct stream *s, struct filter *f, struct proxy *be)
{
OTELC_FUNC("%p, %p, %p", s, f, be);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_stream_stop - stream stop callback (flt_ops.stream_stop)
*
* SYNOPSIS
* static void flt_otel_ops_stream_stop(struct stream *s, struct filter *f)
*
* ARGUMENTS
* s - the stream being stopped
* f - the filter instance
*
* DESCRIPTION
* It is called when a stream is stopped. This callback always succeed.
* Anyway, it is too late to return an error.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_ops_stream_stop(struct stream *s, struct filter *f)
{
OTELC_FUNC("%p, %p", s, f);
OTELC_RETURN();
}
/***
* NAME
* flt_otel_ops_detach - filter detach callback (flt_ops.detach)
*
* SYNOPSIS
* static void flt_otel_ops_detach(struct stream *s, struct filter *f)
*
* ARGUMENTS
* s - the stream from which the filter is being detached
* f - the filter instance
*
* DESCRIPTION
* It is called when a filter instance is detached from a stream, before its
* destruction. This happens when the stream is stopped for filters defined
* on the stream's frontend and when the analyze ends for filters defined on
* the stream's backend.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_ops_detach(struct stream *s, struct filter *f)
{
OTELC_FUNC("%p, %p", s, f);
OTELC_RETURN();
}
/***
* NAME
* flt_otel_ops_check_timeouts - timeout callback (flt_ops.check_timeouts)
*
* SYNOPSIS
* static void flt_otel_ops_check_timeouts(struct stream *s, struct filter *f)
*
* ARGUMENTS
* s - the stream whose timer has expired
* f - the filter instance
*
* DESCRIPTION
* Timeout callback for the filter. When the idle-timeout timer has expired,
* it fires the on-idle-timeout event via flt_otel_event_run() and reschedules
* the timer for the next interval. It also sets the STRM_EVT_MSG pending
* event flag on the <s> stream so that the stream processing loop
* re-evaluates the message state after the timeout.
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_ops_check_timeouts(struct stream *s, struct filter *f)
{
OTELC_FUNC("%p, %p", s, f);
OTELC_RETURN();
}
/***
* NAME
* flt_otel_ops_channel_start_analyze - channel start-analyze callback
*
* SYNOPSIS
* static int flt_otel_ops_channel_start_analyze(struct stream *s, struct filter *f, struct channel *chn)
*
* ARGUMENTS
* s - the stream being analyzed
* f - the filter instance
* chn - the channel on which the analyzing starts
*
* DESCRIPTION
* Channel start-analyze callback. It registers the configured analyzers
* on the <chn> channel and runs the client or server session-start event
* depending on the channel direction.
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
static int flt_otel_ops_channel_start_analyze(struct stream *s, struct filter *f, struct channel *chn)
{
OTELC_FUNC("%p, %p, %p", s, f, chn);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_channel_pre_analyze - channel pre-analyze callback
*
* SYNOPSIS
* static int flt_otel_ops_channel_pre_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit)
*
* ARGUMENTS
* s - the stream being analyzed
* f - the filter instance
* chn - the channel on which the analyzing is done
* an_bit - the analyzer identifier bit
*
* DESCRIPTION
* Channel pre-analyze callback. It maps the <an_bit> analyzer bit to an
* event index and runs the corresponding event via flt_otel_event_run().
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
static int flt_otel_ops_channel_pre_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit)
{
OTELC_FUNC("%p, %p, %p, 0x%08x", s, f, chn, an_bit);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_channel_post_analyze - channel post-analyze callback
*
* SYNOPSIS
* static int flt_otel_ops_channel_post_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit)
*
* ARGUMENTS
* s - the stream being analyzed
* f - the filter instance
* chn - the channel on which the analyzing is done
* an_bit - the analyzer identifier bit
*
* DESCRIPTION
* This function, for its part, is not resumable. It is called when a
* filterable analyzer finishes its processing. So it is called once for
* the same analyzer.
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
static int flt_otel_ops_channel_post_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit)
{
OTELC_FUNC("%p, %p, %p, 0x%08x", s, f, chn, an_bit);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_channel_end_analyze - channel end-analyze callback
*
* SYNOPSIS
* static int flt_otel_ops_channel_end_analyze(struct stream *s, struct filter *f, struct channel *chn)
*
* ARGUMENTS
* s - the stream being analyzed
* f - the filter instance
* chn - the channel on which the analyzing ends
*
* DESCRIPTION
* Channel end-analyze callback. It runs the client or server session-end
* event depending on the <chn> channel direction. For the request channel,
* it also fires the server-unavailable event if response analyzers were
* configured but never executed.
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
static int flt_otel_ops_channel_end_analyze(struct stream *s, struct filter *f, struct channel *chn)
{
OTELC_FUNC("%p, %p, %p", s, f, chn);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_http_headers - HTTP headers callback (flt_ops.http_headers)
*
* SYNOPSIS
* static int flt_otel_ops_http_headers(struct stream *s, struct filter *f, struct http_msg *msg)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* msg - the HTTP message whose headers are ready
*
* DESCRIPTION
* HTTP headers callback. It fires the on-http-headers-request or
* on-http-headers-response event depending on the channel direction.
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
static int flt_otel_ops_http_headers(struct stream *s, struct filter *f, struct http_msg *msg)
{
OTELC_FUNC("%p, %p, %p", s, f, msg);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
#ifdef DEBUG_OTEL
/***
* NAME
* flt_otel_ops_http_payload - HTTP payload callback (flt_ops.http_payload)
*
* SYNOPSIS
* static int flt_otel_ops_http_payload(struct stream *s, struct filter *f, struct http_msg *msg, uint offset, uint len)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* msg - the HTTP message containing the payload
* offset - the offset in the HTX message where data starts
* len - the maximum number of bytes to forward
*
* DESCRIPTION
* Debug-only HTTP payload callback. It logs the channel direction, proxy
* mode, offset and data length. No actual data processing is performed.
*
* RETURN VALUE
* Returns the number of bytes to forward, or a negative value on error.
*/
static int flt_otel_ops_http_payload(struct stream *s, struct filter *f, struct http_msg *msg, uint offset, uint len)
{
OTELC_FUNC("%p, %p, %p, %u, %u", s, f, msg, offset, len);
OTELC_RETURN_INT(len);
}
#endif /* DEBUG_OTEL */
/***
* NAME
* flt_otel_ops_http_end - HTTP end callback (flt_ops.http_end)
*
* SYNOPSIS
* static int flt_otel_ops_http_end(struct stream *s, struct filter *f, struct http_msg *msg)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* msg - the HTTP message that has ended
*
* DESCRIPTION
* HTTP end callback. It fires the on-http-end-request or
* on-http-end-response event depending on the channel direction.
*
* RETURN VALUE
* Returns a negative value if an error occurs, 0 if it needs to wait,
* any other value otherwise.
*/
static int flt_otel_ops_http_end(struct stream *s, struct filter *f, struct http_msg *msg)
{
OTELC_FUNC("%p, %p, %p", s, f, msg);
OTELC_RETURN_INT(FLT_OTEL_RET_OK);
}
/***
* NAME
* flt_otel_ops_http_reply - HTTP reply callback (flt_ops.http_reply)
*
* SYNOPSIS
* static void flt_otel_ops_http_reply(struct stream *s, struct filter *f, short status, const struct buffer *msg)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* status - the HTTP status code of the reply
* msg - the reply message buffer, or NULL
*
* DESCRIPTION
* HTTP reply callback. It fires the on-http-reply event when HAProxy
* generates an internal reply (e.g. error page or deny response).
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_ops_http_reply(struct stream *s, struct filter *f, short status, const struct buffer *msg)
{
OTELC_FUNC("%p, %p, %hd, %p", s, f, status, msg);
OTELC_RETURN();
}
#ifdef DEBUG_OTEL
/***
* NAME
* flt_otel_ops_http_reset - HTTP reset callback (flt_ops.http_reset)
*
* SYNOPSIS
* static void flt_otel_ops_http_reset(struct stream *s, struct filter *f, struct http_msg *msg)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* msg - the HTTP message being reset
*
* DESCRIPTION
* Debug-only HTTP reset callback. It logs the channel direction and proxy
* mode when an HTTP message is reset (e.g. due to a redirect or retry).
*
* RETURN VALUE
* This function does not return a value.
*/
static void flt_otel_ops_http_reset(struct stream *s, struct filter *f, struct http_msg *msg)
{
OTELC_FUNC("%p, %p, %p", s, f, msg);
OTELC_RETURN();
}
/***
* NAME
* flt_otel_ops_tcp_payload - TCP payload callback (flt_ops.tcp_payload)
*
* SYNOPSIS
* static int flt_otel_ops_tcp_payload(struct stream *s, struct filter *f, struct channel *chn, uint offset, uint len)
*
* ARGUMENTS
* s - the stream being processed
* f - the filter instance
* chn - the channel containing the payload data
* offset - the offset in the buffer where data starts
* len - the maximum number of bytes to forward
*
* DESCRIPTION
* Debug-only TCP payload callback. It logs the channel direction, proxy
* mode, offset and data length. No actual data processing is performed.
*
* RETURN VALUE
* Returns the number of bytes to forward, or a negative value on error.
*/
static int flt_otel_ops_tcp_payload(struct stream *s, struct filter *f, struct channel *chn, uint offset, uint len)
{
OTELC_FUNC("%p, %p, %p, %u, %u", s, f, chn, offset, len);
OTELC_RETURN_INT(len);
}
#endif /* DEBUG_OTEL */
struct flt_ops flt_otel_ops = {
/* Callbacks to manage the filter lifecycle. */
.init = flt_otel_ops_init,
.deinit = flt_otel_ops_deinit,
.check = flt_otel_ops_check,
.init_per_thread = flt_otel_ops_init_per_thread,
.deinit_per_thread = OTELC_DBG_IFDEF(flt_otel_ops_deinit_per_thread, NULL),
/* Stream callbacks. */
.attach = flt_otel_ops_attach,
.stream_start = flt_otel_ops_stream_start,
.stream_set_backend = flt_otel_ops_stream_set_backend,
.stream_stop = flt_otel_ops_stream_stop,
.detach = flt_otel_ops_detach,
.check_timeouts = flt_otel_ops_check_timeouts,
/* Channel callbacks. */
.channel_start_analyze = flt_otel_ops_channel_start_analyze,
.channel_pre_analyze = flt_otel_ops_channel_pre_analyze,
.channel_post_analyze = flt_otel_ops_channel_post_analyze,
.channel_end_analyze = flt_otel_ops_channel_end_analyze,
/* HTTP callbacks. */
.http_headers = flt_otel_ops_http_headers,
.http_payload = OTELC_DBG_IFDEF(flt_otel_ops_http_payload, NULL),
.http_end = flt_otel_ops_http_end,
.http_reset = OTELC_DBG_IFDEF(flt_otel_ops_http_reset, NULL),
.http_reply = flt_otel_ops_http_reply,
/* TCP callbacks. */
.tcp_payload = OTELC_DBG_IFDEF(flt_otel_ops_tcp_payload, NULL)
};
/* Advertise OTel support in haproxy -vv output. */
REGISTER_BUILD_OPTS("Built with OpenTelemetry support (C++ version " OTELCPP_VERSION ", C Wrapper version " OTELC_VERSION ").");
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

73
addons/otel/src/parser.c Normal file
View file

@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "../include/include.h"
#ifdef OTELC_DBG_MEM
static struct otelc_dbg_mem_data dbg_mem_data[1000000];
static struct otelc_dbg_mem dbg_mem;
#endif
/***
* NAME
* flt_otel_parse - main filter parser entry point
*
* SYNOPSIS
* static int flt_otel_parse(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf, char **err, void *private)
*
* ARGUMENTS
* args - configuration line arguments array
* cur_arg - pointer to the current argument index
* px - proxy instance owning the filter
* fconf - filter configuration structure to populate
* err - indirect pointer to error message string
* private - unused private data pointer
*
* DESCRIPTION
* Main filter parser entry point, registered for the "otel" filter keyword.
* Verifies that insecure-fork-wanted is enabled, then parses the filter ID
* and configuration file path from the HAProxy configuration line. If no
* filter ID is specified, the default ID is used.
*
* 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(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf, char **err, void *private)
{
int retval = ERR_NONE;
OTELC_FUNC("%p, %p, %p, %p, %p:%p, %p", args, cur_arg, px, fconf, OTELC_DPTR_ARGS(err), private);
OTELC_DBG_IFDEF(otelc_dbg_level = FLT_OTEL_DEBUG_LEVEL, );
#ifdef OTELC_DBG_MEM
/* Initialize the debug memory tracker before the first allocation. */
FLT_OTEL_RUN_ONCE(
if (otelc_dbg_mem_init(&dbg_mem, dbg_mem_data, OTELC_TABLESIZE(dbg_mem_data)) == -1)
OTELC_RETURN_INT(retval);
);
#endif
OTELC_RETURN_INT(retval);
}
/* Declare the filter parser for FLT_OTEL_OPT_NAME keyword. */
static struct flt_kw_list flt_kws = { FLT_OTEL_SCOPE, { }, {
{ FLT_OTEL_OPT_NAME, flt_otel_parse, NULL },
{ NULL, NULL, NULL },
}
};
INITCALL1(STG_REGISTER, flt_register_keywords, &flt_kws);
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*
* vi: noexpandtab shiftwidth=8 tabstop=8
*/

View file

@ -92,6 +92,7 @@ enum {
ARGC_TCK, /* tcp-check expression */
ARGC_CFG, /* configuration expression */
ARGC_CLI, /* CLI expression*/
ARGC_OTEL, /* opentelemetry scope args */
};
/* flags used when compiling and executing regex */

View file

@ -1471,6 +1471,7 @@ int smp_resolve_args(struct proxy *p, char **err)
case ARGC_TCK: where = "in tcp-check expression in"; break;
case ARGC_CFG: where = "in configuration expression in"; break;
case ARGC_CLI: where = "in CLI expression in"; break;
case ARGC_OTEL: where = "in otel-scope directive in"; break;
}
/* set a few default settings */