OpenTelemetry

Export logs using the HAProxy OpenTelemetry filter

This page applies to:

  • HAProxy 3.4 and newer

The HAProxy OpenTelemetry filter supports forwarding HAProxy logs to an OpenTelemetry compatible logging system, such as OpenTelemetry Collector. The benefit of this is that your logs will gain metadata that links them to your traces, allowing you to correlate a client’s request across both types of signals.

OpenTelemetry approaches logging with the intent of not reinventing the wheel. Logging protocols and frameworks are often well established within an organization, helping operations, security, and engineering teams to understand events happening within their infrastructure and applications. Instead of trying to change how organizations capture and format logs, OpenTelemetry wants only to correlate logs with traces so that you can easily go from one to the other for a given request. To achieve that, you need to add metadata to your logs. Then, while still relaying logs to the servers you’ve used before, you can begin to send your logs to software that understands how to interpret the OpenTelemetry protocol and link your logs to your traces.

Enable OpenTelemetry logs Jump to heading

To enable OpenTelemetry logging:

  1. The advantage of adding OpenTelemetry metadata to your logs is to correlate logs with traces. Begin by configuring traces, which will set up the instrumentation file in which you’ll set logging.

  2. In your instrumentation file, add a log-record directive to an otel-scope section. The syntax is:

    text
    log-record <severity> [id <integer> event <name>] [time [<unit>] <sample>] [span <ref>] [attr <key> <sample>] ... <sample> ... [{ if | unless } <condition>]
    text
    log-record <severity> [id <integer> event <name>] [time [<unit>] <sample>] [span <ref>] [attr <key> <sample>] ... <sample> ... [{ if | unless } <condition>]
  3. Configure the client library to send your log data to software that can process it.

Example: HTTP log format Jump to heading

In the example below, we add a log-record to the otel-scope section that subscribes to the on-server-session-end event, which runs at the end of the session. By emitting the log record at the end of the session, we ensure that all log variables are available. We’re using the HTTP log format. You can, however, place log-record directives into any of your otel-scope sections. Having more than one log-record in your instrumentation file lets you emit messages for various events.

instrumentation.cfg
ini
otel-scope server_session_end
finish *
log-record info id 100 event "http-request" span "HTTP request" "%[%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r]"
otel-event on-server-session-end
instrumentation.cfg
ini
otel-scope server_session_end
finish *
log-record info id 100 event "http-request" span "HTTP request" "%[%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r]"
otel-event on-server-session-end
  • We set the severity to the level we’d like to assign to this log message, in this case info. Supported severity levels are trace, trace2, trace3, trace4, debug, debug2, debug3, debug4, info, info2, info3, info4, warn, warn2, warn3, warn4, error, error2, error3, error4, fatal, fatal2, fatal3, and fatal4. For more information, see the entry on SeverityNumber in the OpenTelemetry Logs Data Model documentation.
  • The id and event fields are up to you. Choose any number and string, respectively, that helps identify the origin of this log record.
  • With the span argument, we set the name of the span with which we want to correlate this log record. Notice how we’re defining log-record in an otel-scope section that doesn’t define the HTTP request span. That’s fine; it’s defined in a different otel-scope in our instrumentation file.
  • The rest of the line sets the body of the log record. In this case, we store the details of the current HTTP request, using the HTTP log format.

The generated LogRecord stores the log message in its Body field. Other fields are metadata that estabish the correlation with trace data and help to categorize the message, including the timestamp, severity, event name, trace ID, and span ID. For example:

text
LogRecord #0
ObservedTimestamp: 2026-06-18 21:14:58.237200767 +0000 UTC
Timestamp: 2026-06-18 21:14:58.237200767 +0000 UTC
SeverityText: INFO
SeverityNumber: Info(9)
EventName: http-request
Body: Str(192.168.56.1:62946 [18/Jun/2026:21:14:58.223] mysite webservers/web1 0/0/4/5/0 200 0 ---- 1/1/1/0/0 0/0 "GET / HTTP/1.1")
Trace ID: 191dac51890f3a5a641fb404760b7311
Span ID: 023f1c2616b48127
Flags: 1
text
LogRecord #0
ObservedTimestamp: 2026-06-18 21:14:58.237200767 +0000 UTC
Timestamp: 2026-06-18 21:14:58.237200767 +0000 UTC
SeverityText: INFO
SeverityNumber: Info(9)
EventName: http-request
Body: Str(192.168.56.1:62946 [18/Jun/2026:21:14:58.223] mysite webservers/web1 0/0/4/5/0 200 0 ---- 1/1/1/0/0 0/0 "GET / HTTP/1.1")
Trace ID: 191dac51890f3a5a641fb404760b7311
Span ID: 023f1c2616b48127
Flags: 1

Example: JSON log format Jump to heading

The log-record directive can set a JSON log format. In the example below, we add a log-record to the otel-scope section that subscribes to the on-server-session-end event, which runs at the end of the session when all log variables are available.

instrumentation.cfg
ini
otel-scope server_session_end
finish *
log-record info id 100 event "http-request" span "HTTP request" "%[%{+json}o %(client_ip)ci %(client_port)cp %(request_date)tr %(frontend_name)ft %(backend_name)b %(server_name)s %(time_to_receive)TR %(time_waiting)Tw %(time_to_connect)Tc %(time_server_response)Tr %(time_active)Ta %(status_code)ST %(bytes_read)B %(request_cookies)CC %(response_cookies)CS %(termination_state)tsc %(process_active_connections)ac %(frontend_active_connections)fc %(backend_active_connections)bc %(server_active_connections)sc %(retries)rc %(server_queue)sq %(backend_queue)bq %(request_headers)hr %(response_headers)hs %(request_line)r]"
otel-event on-server-session-end
instrumentation.cfg
ini
otel-scope server_session_end
finish *
log-record info id 100 event "http-request" span "HTTP request" "%[%{+json}o %(client_ip)ci %(client_port)cp %(request_date)tr %(frontend_name)ft %(backend_name)b %(server_name)s %(time_to_receive)TR %(time_waiting)Tw %(time_to_connect)Tc %(time_server_response)Tr %(time_active)Ta %(status_code)ST %(bytes_read)B %(request_cookies)CC %(response_cookies)CS %(termination_state)tsc %(process_active_connections)ac %(frontend_active_connections)fc %(backend_active_connections)bc %(server_active_connections)sc %(retries)rc %(server_queue)sq %(backend_queue)bq %(request_headers)hr %(response_headers)hs %(request_line)r]"
otel-event on-server-session-end
  • We set the severity to the level we’d like to assign to this log message, in this case info. Supported severity levels are trace, trace2, trace3, trace4, debug, debug2, debug3, debug4, info, info2, info3, info4, warn, warn2, warn3, warn4, error, error2, error3, error4, fatal, fatal2, fatal3, and fatal4. For more information, see the entry on SeverityNumber in the OpenTelemetry Logs Data Model documentation.
  • The id and event fields are up to you. Choose any number and string, respectively, that helps identify the origin of this log record.
  • With the span argument, we set the name of the span with which we want to correlate this log record. Notice how we’re defining log-record in an otel-scope section that doesn’t define the HTTP request span. That’s fine; it’s defined in a different otel-scope in our instrumentation file.
  • The rest of the line sets the body of the log record. In this case, we store the details of the current HTTP request, using a JSON format.

The log record will look like this:

text
LogRecord #0
ObservedTimestamp: 2026-06-18 21:32:23.568490124 +0000 UTC
Timestamp: 2026-06-18 21:32:23.568490124 +0000 UTC
SeverityText: INFO
SeverityNumber: Info(9)
EventName: http-request
Body: Str({"client_ip": "192.168.56.1", "client_port": 63000, "request_date": "18/Jun/2026:21:32:23.558", "frontend_name": "mysite", "backend_name": "webservers", "server_name": "web1", "time_to_receive": 0, "time_waiting": 0, "time_to_connect": 4, "time_server_response": 3, "time_active": 0, "status_code": 200, "bytes_read": 0, "request_cookies": "", "response_cookies": "", "termination_state": "----", "process_active_connections": 1, "frontend_active_connections": 1, "backend_active_connections": 1, "server_active_connections": 0, "retries": 0, "server_queue": 0, "backend_queue": 0, "request_headers": null, "response_headers": null, "request_line": "GET / HTTP/1.1"})
Trace ID: bb00ff28f89c29a6801d588b2223409a
Span ID: 2e8faeeb8d117864
Flags: 1
text
LogRecord #0
ObservedTimestamp: 2026-06-18 21:32:23.568490124 +0000 UTC
Timestamp: 2026-06-18 21:32:23.568490124 +0000 UTC
SeverityText: INFO
SeverityNumber: Info(9)
EventName: http-request
Body: Str({"client_ip": "192.168.56.1", "client_port": 63000, "request_date": "18/Jun/2026:21:32:23.558", "frontend_name": "mysite", "backend_name": "webservers", "server_name": "web1", "time_to_receive": 0, "time_waiting": 0, "time_to_connect": 4, "time_server_response": 3, "time_active": 0, "status_code": 200, "bytes_read": 0, "request_cookies": "", "response_cookies": "", "termination_state": "----", "process_active_connections": 1, "frontend_active_connections": 1, "backend_active_connections": 1, "server_active_connections": 0, "retries": 0, "server_queue": 0, "backend_queue": 0, "request_headers": null, "response_headers": null, "request_line": "GET / HTTP/1.1"})
Trace ID: bb00ff28f89c29a6801d588b2223409a
Span ID: 2e8faeeb8d117864
Flags: 1

Add attributes to log records Jump to heading

You can add attributes to your log records to serve as extra metadata. Use the log-record directive’s attr argument to add an attribute. In this example, we add one attribute named app.comment with a string literal value and another named app.version set to a captured response header.

instrumentation.cfg
ini
otel-scope server_session_end
finish *
log-record info id 100 event "http-request" span "HTTP request" attr "app.comment" str("Testing OpenTelemetry") attr "app.version" capture.res.hdr(0) "%[%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r]"
otel-event on-server-session-end
instrumentation.cfg
ini
otel-scope server_session_end
finish *
log-record info id 100 event "http-request" span "HTTP request" attr "app.comment" str("Testing OpenTelemetry") attr "app.version" capture.res.hdr(0) "%[%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r]"
otel-event on-server-session-end

The LogRecord will have attributes:

text
Attributes:
-> app.comment: Str(Testing OpenTelemetry)
-> app.version: Str(1.0.0)
text
Attributes:
-> app.comment: Str(Testing OpenTelemetry)
-> app.version: Str(1.0.0)

See also Jump to heading

Do you have any suggestions on how we can improve the content of this page?