Skip to content

fix(logs): keep the agent's pattern.hash, read Datadog span ids as decimal, throttle the freopen scan - #390

Merged
mayankpande88 merged 4 commits into
mainfrom
fix/log-attrs-review
Oct 10, 2026
Merged

mayankpande88 merged 4 commits into
mainfrom
fix/log-attrs-review

Conversation

@mayankpande88

@mayankpande88 mayankpande88 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #380 (from its review).

  • pattern.hash: a JSON log field with this name was exported next to the agent's own attribute, so a record had two pattern.hash keys and a backend keeping the last one lost the agent's. The agent's value is now the only one.
  • Datadog trace context: Datadog's log injection writes dd.span_id, and dd.trace_id for 64-bit ids, in decimal. A 16-digit span id was read as hex, giving a wrong span id, and a decimal trace id wasn't read at all. Both are now read as decimal; a decimal trace id becomes the lower half of the OpenTelemetry trace id (Datadog's mapping), and a 32-character hex dd.trace_id (128-bit) is read as before.
  • Span id without a trace id: it was still set on the record, where it means nothing; it now stays an attribute.
  • freopen fallback (--enable-dynamic-log-tailing, off by default): a process that opens its /var/log file for every write (PHP's error_log, a shell's >>) made the agent read all of its fds on every write, on the registry's event loop. The scan now runs at most every 10s per process.
  • Missing log file: a tail reader waiting for its file to appear now logs that once instead of waiting silently.
  • Flag text: --log-pattern-extraction-limit applies per log source (a file or a container's output), not per container.
Engineering detail

Tests: TestLogRecordAttrs covers the pattern.hash collision, hex trace context, a 16-digit decimal dd.span_id with a 128-bit hex dd.trace_id, a decimal 64-bit dd.trace_id, and a span id without a trace id.

CI: gofmt, goimports, vet, golangci-lint, go test (excluding /containers) and the build pass in a Linux container with Go 1.26.5.

Local e2e: I built agent binaries from this branch and from main and ran each as a systemd service on a local Debian 12 VM (kernel 6.1), with dynamic log tailing on, exporting logs to its own OpenTelemetry collector. A unit wrote JSON log lines to the journal.

  • pattern.hash: on main, a record carrying "pattern.hash":"spoofed" had two pattern.hash attributes, one of them spoofed. On this branch it had one, the agent's.
  • dd.span_id "1234567890123456": main exported span id 1234567890123456 (the digits read as hex); this branch exported 000462d53c8abac0, the decimal value.
  • Decimal dd.trace_id "1234567890123456" with dd.span_id "987654321": main exported no trace context and kept both as attributes; this branch exported trace id 0000000000000000000462d53c8abac0 and span id 000000003ade68b1.
  • span_id with no trace id: main set it as the record's span id; this branch kept it as a span_id attribute and left the span id empty.
  • freopen fallback: a process holding 1,000 open fds appended to a /var/log file, opening it for every write, for 30s. Agent CPU per write: main 37µs (33.2s for 897,666 writes), this branch 28µs (29.0s for 1,029,894 writes). The rest of that cost is handling each open event, which this change doesn't touch. With a process holding only a few fds the difference was within noise. The file was tailed by both.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces rate limiting for scanning log files per process, updates the log pattern extraction limit flag description, and adds support for parsing Datadog's decimal span IDs in OpenTelemetry log attributes. It also ensures that a span ID without a trace ID is kept as a regular attribute. The review feedback suggests extending this logic by adding a parseTraceId helper to correctly parse Datadog's decimal trace IDs as well, along with corresponding test updates to verify both decimal trace and span ID parsing.

Comment thread logs/otel.go
Comment thread logs/otel_test.go
mayankpande88 added a commit that referenced this pull request Oct 9, 2026
…e trace id

Datadog's log injection writes 64-bit trace ids in decimal. They were not
parsed, so with this branch's decimal span ids a record still got no trace
context: a span id without a trace id stays an attribute (#390 review).
…cimal, throttle the freopen scan

From the #380 review:
- A JSON field named pattern.hash no longer duplicates or replaces the
  agent's attribute.
- dd.span_id is decimal (Datadog's log injection); a 16-digit value was
  read as hex. A span id without a trace id stays an attribute.
- The freopen fallback reads a process's fds at most every 10s: a process
  that opens its /var/log file for every write triggered it on every write.
- A tail reader waiting for its file to appear logs that once.
- The pattern extraction limit is per log source, not per container: say so.
…e trace id

Datadog's log injection writes 64-bit trace ids in decimal. They were not
parsed, so with this branch's decimal span ids a record still got no trace
context: a span id without a trace id stays an attribute (#390 review).
@blue4209211

Copy link
Copy Markdown
Contributor

Thanks, this covers the #380 review. The pattern.hash skip, decimal dd.span_id, decimal dd.trace_id in the lower 8 bytes with the 128-bit hex form still parsed as hex, the span-id-without-trace fallback, the per-process freopen throttle, and the waiting-for-file log all look right. logFilesScanDue takes c.lock at a point where onFileOpen doesn't hold it, so there's no deadlock. LGTM.

Three small things for a later pass, none blocking:

  1. The throttle can skip a second freopen within 10s (logFilesScanDue). A process that redirects stdout to /var/log/a.log and then stderr to /var/log/b.log produces two open events. If the scan for the first runs before the second freopen, the second event lands inside the 10s window and is skipped. A one-off freopen never fires again, so b.log would never be tailed. Event handling usually lags enough that one scan sees both, so this is unlikely. Throttling only when the previous scan found no new log path would remove the case and still bound the PHP and >> pattern.
  2. Which trace key wins is random when a line has two (logRecordAttrs). With both trace_id and dd.trace_id (or span_id and dd.span_id), map iteration order picks the winner, and a trace id from one key can pair with a span id from another. This predates the PR. Preferring a consistent pair (both dd.*, or both plain) would make it deterministic.
  3. 64-bit hex trace ids are still rejected. 16-character hex trace ids (Jaeger, B3 and older Zipkin 64-bit ids) fail TraceIDFromHex and stay attributes. This predates the PR. They could be zero-padded into the lower half, the same way decimal Datadog ids now are.

@mayankpande88
mayankpande88 merged commit fd18747 into main Oct 10, 2026
@mayankpande88
mayankpande88 deleted the fix/log-attrs-review branch October 10, 2026 04:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants