Log Metrics with OpenTelemetry
Overview
Section titled “Overview”Glouton can read application logs and generate metrics from the number of lines matching a regular expression (or a richer condition) per second. Log-to-metric counting is built directly into Glouton’s embedded OpenTelemetry Collector, alongside (and sharing configuration with) shipping logs.
A single receiver definition can:
- collect and ship logs to the Bleemeo Cloud Platform,
- select its logs by file path, by container name, by container labels/annotations, or by listening from the network (OTLP),
- feed one or more metrics, each with its own matching conditions, labels, and more.
Sharing configuration doesn’t mean the two features depend on each other: counting lines never requires
shipping them. Log-to-metric runs independently of shipping_enable,
so the metrics below keep being produced with log shipping switched off entirely.
Configuration
Section titled “Configuration”Everything lives under log.opentelemetry in glouton.conf, plus a top-level opentelemetry.listeners section for receivers that accept pushed logs from network.
log: opentelemetry: receivers: app: include: - /var/log/myapp/*.log metrics: - metric: app_errors_count regex: '\[error\]'Each metric definition accepts:
| Field | Type | Description |
|---|---|---|
metric |
string | Name of the generated metric. |
regex |
string | Shortcut for a single condition matching the raw log line body. See the official documentation for Go regular expressions. |
conditions |
list of strings | One or more OTTL conditions, OR’ed together, matched against the record body or a parsed attribute. |
attributes |
list of {key, default_value} objects |
Dynamic group-by-value: one series per distinct value seen for the named attribute key. Comes with real cardinality risk. |
labels |
map of string to string | Fixed key: value pairs stamped on every sample – zero cardinality risk. |
item |
string | Identifies which source a metric’s series came from. Defaults to the receiver’s name; set explicitly to override. |
For example:
log.opentelemetry.receivers.example: operators: - type: regex_parser regex: '^\S+ \S+ \S+ \[.*\] ".*" (?P<status_code>\d+) .*$' metrics: - metric: error_count regex: "ERROR" - metric: http_requests_by_status conditions: - 'IsMatch(attributes["status_code"], "5..")' - 'IsMatch(attributes["status_code"], "4..")' attributes: - key: status_code default_value: "unknown" labels: service: checkout env: production item: checkout-apiconditions here are OR’ed together: a line matching either one counts. If a metric sets both regex and conditions, they aren’t mutually exclusive: regex is just a simpler way to declare a IsMatch(body, ...) condition, added to the same OR’ed list as everything under conditions – so a line counts if it matches regex or any of the conditions. attributes splits the resulting metric into one series per distinct value of status_code seen in matching lines (falling back to default_value when the attribute is missing) – unlike labels, which stamps the same fixed value on every sample regardless of the line’s content.
The operators block above extracts status_code from the raw line – without it the attribute would never exist, and http_requests_by_status would silently never count anything.
See Matching Parsed Attributes below for more, including the http.response.status_code naming convention, built-in log formats that extract it for you, and why a second move step is sometimes needed.
Priority between item, labels, and attributes
Section titled “Priority between item, labels, and attributes”These three all end up as labels on the same series, but they’re not independent, for two unrelated reasons:
- When each is known.
itemandlabelscome straight from the receiver’s config, so they’re already set the moment the series is declared – before any log line has even matched.attributesvalues are only known once a line actually matches: they’re extracted from that line’s own content. - Who wins on a key collision. Nothing stops a
labelsentry, or a matched line’s attributes, from using a key that’s already spoken for – most notablyitemitself. Precedence isitem>labels>attributes:itemandlabelsare reserved-key-safe and always win; anattributesentry only fills in a label key nothing else has already claimed, and is dropped (never merged or promoted) on collision. One exception:labels: {item: ...}is treated as an equivalent, alternate spelling of the top-levelitem:field rather than a competing label – see the example below.
log.opentelemetry.receivers.example: metrics: - metric: requests_total labels: item: shipping-override # honored: same as writing item: shipping-override attributes: - key: item # ignored: attributes never override item - key: http.method # kept: no collisionHere the series reports item="shipping-override" (from labels.item, since no top-level item: was set – had both been set, the top-level item: would win), the attributes entry named item is dropped, and http.method’s per-line value is added as an extra label.
Select Logs by Path
Section titled “Select Logs by Path”If your application writes logs to a file on the host:
log: opentelemetry: receivers: app: include: - /var/log/myapp/*.log metrics: - metric: app_errors_count regex: '\[error\]'Select Logs by Container Name
Section titled “Select Logs by Container Name”If your application runs in a container with a stable name:
log: opentelemetry: receivers: redis: container_name: redis metrics: - metric: redis_errors_count regex: ERRORSelect Logs by Container Labels
Section titled “Select Logs by Container Labels”Select container logs by labels or annotations:
log: opentelemetry: receivers: web-workers: container_selectors: app: web-worker metrics: - metric: web_error_rate regex: '\[error\]'Select Logs by Container Labels (No Receiver Needed)
Section titled “Select Logs by Container Labels (No Receiver Needed)”A container doesn’t need a receiver entry at all: set the glouton.log_metrics label or annotation directly on it, naming a log.metrics_rules entry to count its logs against. The metric’s item is then that container’s own runtime name, so different containers sharing the same label value don’t merge into one series.
log: metrics_rules: known_web_errors: - metric: web_error_rate conditions: - 'IsMatch(body, "\\[error\\]")'glouton.log_metrics=known_web_errorsOther glouton.* labels/annotations affect log shipping, not metrics, and are covered on the log shipping page: glouton.log_enable, glouton.send_logs, glouton.log_format, glouton.log_filter.
Whether this container’s logs are also shipped is decided by those labels — glouton.send_logs, else glouton.log_enable, else whether container auto-discovery is on. There is no receiver here, so receivers_default_send_logs does not apply: to count without shipping, set glouton.send_logs=false on the container.
A container already matched by an explicit receiver’s container_name/container_selectors ignores glouton.log_metrics – it’s counted exactly once, under that receiver’s own item, not twice.
Select Logs Pushed Over the Network
Section titled “Select Logs Pushed Over the Network”A receiver can also accept logs pushed to it (e.g. from another OpenTelemetry Collector) instead of reading files or watching containers. Declare a listener under the top-level opentelemetry.listeners, then reference it by name from one or more receivers with from_listeners.
Several receivers can share one listener by referencing a named entry under the top-level opentelemetry.listeners:
opentelemetry: listeners: otlp: protocols: grpc: endpoint: "localhost:4317" http: endpoint: "localhost:4318"
log: opentelemetry: receivers: billing_from_grpc: from_listeners: [otlp] metrics: - metric: billing_error_rate conditions: - 'resource["service.name"] == "billing"'You can also declare a network listener without endpoints to use the default OpenTelemetry endpoints (localhost:4317 for grpc protocol, localhost:4318 http protocol):
opentelemetry: listeners: otlp: protocols: grpc: http:Matching Parsed Attributes
Section titled “Matching Parsed Attributes”By default, regex match against the raw log line (body when using conditions). To match against a structured field instead (e.g. an HTTP status code), the line must first be parsed into attributes with log_format.
If a built-in format already extracts the field you need (e.g. apache_access and nginx_access both produce http.response.status_code), just reference it by name:
log: opentelemetry: receivers: app: include: - /var/log/myapp/*.log log_format: apache_access metrics: - metric: apache_server_error conditions: - 'IsMatch(attributes["http.response.status_code"], "5..")'Otherwise, define your own format under log.opentelemetry.known_log_formats and reference it from the receiver’s own log_format:
log: opentelemetry: known_log_formats: my_custom_format: - type: regex_parser regex: '^\S+ \S+ \S+ \[.*\] ".*" (?P<status_code>\d+) .*$' - type: move # named capture groups can't contain dots, so rename into the dotted key used below from: attributes.status_code to: attributes['http.response.status_code']
receivers: app: include: - /var/log/myapp/*.log log_format: my_custom_format metrics: - metric: app_server_error conditions: - 'IsMatch(attributes["http.response.status_code"], "5..")'For a one-off receiver, inline parsing stanzas can be used instead of a named format, with operators:
log: opentelemetry: receivers: app: include: - /var/log/myapp/*.log operators: - type: regex_parser regex: '^\S+ \S+ \S+ \[.*\] ".*" (?P<status_code>\d+) .*$' - type: move from: attributes.status_code to: attributes['http.response.status_code'] metrics: - metric: app_server_error conditions: - 'IsMatch(attributes["http.response.status_code"], "5..")'See more about log formats and operators here.
Metric Rules
Section titled “Metric Rules”Reusable metric definitions can be declared once under log.metrics_rules and pulled into any receiver with {include: <name>}:
log: metrics_rules: known_web_errors: - metric: web_error_rate conditions: - 'IsMatch(body, "\\[error\\]")'
opentelemetry: receivers: web-workers: container_selectors: app: web-worker metrics: - include: known_web_errorsMigrating from Fluent Bit
Section titled “Migrating from Fluent Bit”Existing log.inputs configurations (the legacy Fluent Bit-era format) keeps working: Glouton automatically translates them into the equivalent log.opentelemetry.receivers / log.metrics_rules shape at startup with a deprecation warning. There’s nothing to migrate by hand, but new configurations should use log.opentelemetry.receivers / metrics directly.