Skip to content

[Laravel] Add opt-in context flattening for faceted log search#560

Open
nickmarden wants to merge 2 commits into
open-telemetry:mainfrom
nickmarden:feature/laravel-log-context-flatten
Open

[Laravel] Add opt-in context flattening for faceted log search#560
nickmarden wants to merge 2 commits into
open-telemetry:mainfrom
nickmarden:feature/laravel-log-context-flatten

Conversation

@nickmarden

Copy link
Copy Markdown
Contributor

Description

This PR restores the context-flattening feature from #491 (which was reverted in #559 due to a conflict with #511), now correctly layered on top of the structured-array default that #511 introduced.

Background

PR #491 identified a real observability problem: the Laravel LogWatcher was JSON-encoding the entire log context into a single context string attribute, making it impossible to filter by specific values in backends like SigNoz, Jaeger, or Grafana. For example, given context {"http": {"method": "GET", "path": "/users"}, "user_id": "123"}, there was no way to query http.method = "GET" as a facet.

PR #491 solved this with an opt-in OTEL_PHP_LARAVEL_LOG_ATTRIBUTES_FLATTEN flag that spreads context fields as individual OTLP attributes using dot notation for nested keys:

http.method = GET
http.path   = /users
user_id     = 123

What went wrong with #491

When #491 was merged, it conflicted with #511, which had already changed the default context handling from a JSON-encoded string to a structured array (->setAttribute('context', $context)). The #491 code regressed that improvement by reverting the non-flatten path back to json_encode().

PR #559 reverted #491 entirely to restore the #511 behaviour, but that also lost the flattening feature.

What this PR does

This PR re-introduces the flattening feature cleanly on top of #511:

  • Default behaviour (unchanged from feat: Update log builder for Laravel Instrumentation #511): context is stored as a structured array attribute, e.g. ->setAttribute('context', ['http' => ['method' => 'GET'], 'user_id' => '123']).
  • Opt-in flatten mode (OTEL_PHP_LARAVEL_LOG_ATTRIBUTES_FLATTEN=true): context fields are spread as individual top-level OTLP attributes using Illuminate\Support\Arr::dot(), enabling faceted search in observability backends.

The non-flatten default path is identical to what #511 shipped. The flatten path is additive and off by default.

Changes

  • src/Watchers/LogWatcher.php: adds OTEL_PHP_LARAVEL_LOG_ATTRIBUTES_FLATTEN constant, reads it via ConfigurationResolver in the constructor, and branches on it in recordLog(). A normalizeValue() helper ensures OTLP-compatible scalar types (casting Stringable objects to string, JSON-encoding anything else).
  • tests/Unit/Watchers/LogWatcherTest.php: 8 unit tests covering default structured-array mode, flatten mode, nested dot-notation expansion, exception extraction, Stringable normalisation, and null filtering.

Backward compatibility

Restores the OTEL_PHP_LARAVEL_LOG_ATTRIBUTES_FLATTEN feature from open-telemetry#491,
now correctly layered on top of open-telemetry#511's structured-array default.

- Default (no env var): context stored as structured array attribute,
  preserving the behaviour introduced in open-telemetry#511.
- Opt-in (OTEL_PHP_LARAVEL_LOG_ATTRIBUTES_FLATTEN=true): context fields
  spread as individual top-level OTLP attributes using dot notation for
  nested keys (e.g. http.method, http.path), enabling faceted search in
  backends such as SigNoz, Jaeger, and Grafana.

Adds unit tests covering both modes, exception extraction, Stringable
normalisation, null filtering, and nested dot-notation expansion.
@nickmarden
nickmarden requested a review from a team as a code owner April 30, 2026 09:38
@welcome

welcome Bot commented Apr 30, 2026

Copy link
Copy Markdown

Thanks for opening your first pull request! If you haven't yet signed our Contributor License Agreement (CLA), then please do so that we can accept your contribution. A link should appear shortly in this PR if you have not already signed one.

@codecov

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.09%. Comparing base (0309f76) to head (d90fb68).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #560      +/-   ##
============================================
+ Coverage     80.45%   81.09%   +0.63%     
+ Complexity     1669     1521     -148     
============================================
  Files           116       93      -23     
  Lines          6262     5431     -831     
============================================
- Hits           5038     4404     -634     
+ Misses         1224     1027     -197     
Flag Coverage Δ
Context/Swoole 0.00% <ø> (ø)
Exporter/Instana 49.80% <ø> (ø)
Instrumentation/AwsSdk 82.14% <ø> (ø)
Instrumentation/CakePHP 20.42% <ø> (ø)
Instrumentation/CodeIgniter 79.31% <ø> (ø)
Instrumentation/Curl 86.88% <ø> (ø)
Instrumentation/Doctrine 92.82% <ø> (ø)
Instrumentation/ExtAmqp 88.80% <ø> (ø)
Instrumentation/Guzzle 76.25% <ø> (ø)
Instrumentation/HttpAsyncClient 78.94% <ø> (ø)
Instrumentation/HttpConfig 28.76% <ø> (ø)
Instrumentation/IO 0.00% <ø> (ø)
Instrumentation/Laravel ?
Instrumentation/MongoDB 76.84% <ø> (ø)
Instrumentation/MySqli 93.39% <ø> (ø)
Instrumentation/OpenAIPHP 86.71% <ø> (ø)
Instrumentation/PostgreSql 91.36% <ø> (ø)
Instrumentation/Psr14 77.41% <ø> (ø)
Instrumentation/Psr15 89.74% <ø> (ø)
Instrumentation/Psr16 97.43% <ø> (ø)
Instrumentation/Psr18 79.41% <ø> (ø)
Instrumentation/Psr6 97.56% <ø> (ø)
Instrumentation/Session 94.28% <ø> (ø)
Instrumentation/Slim 84.21% <ø> (ø)
Propagation/CloudTrace 90.69% <ø> (ø)
Propagation/Instana 98.07% <ø> (ø)
Propagation/ServerTiming 94.73% <ø> (ø)
Propagation/TraceResponse 94.73% <ø> (ø)
ResourceDetectors/Azure 91.66% <ø> (ø)
ResourceDetectors/DigitalOcean 100.00% <ø> (ø)
Sampler/Xray 78.38% <ø> (ø)
Shims/OpenTracing 92.99% <ø> (ø)
SqlCommenter 95.58% <ø> (ø)
Utils/Test 87.79% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 23 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0309f76...d90fb68. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ChrisLightfootWild
ChrisLightfootWild self-requested a review April 30, 2026 16:40

@ChrisLightfootWild ChrisLightfootWild left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for re-creating this with a comprehensive write-up 💪

There's a failure in the pipeline, if you could take a look:
1) tests/Unit/Watchers/LogWatcherTest.php (class_definition)

Remove the space between `class` and `()` in the anonymous class instantiation,
which the class_definition fixer rule requires.
@nickmarden

Copy link
Copy Markdown
Contributor Author

@ChrisLightfootWild I think I fixed the issue that you highlighted in your previous comment, but CI is generally red for reasons unrelated to this PR AFAICT.

Do you want me to take a stab at getting CI green (which would be a lot of scope creep), or is this PR OK as-is?

@andrii-androshchuk

Copy link
Copy Markdown
Contributor

@nickmarden, why do we need the flattening in the first place?

I understand the original problem with the entire context being stored as a single string. However, after #511, the attributes are exposed as properties under the root context attribute (as it should), and everything inside it should already be available for faceted search.

Because of that, I am wondering what additional benefit flattening provides here, if the faceted search is working now.

@ChrisLightfootWild

Copy link
Copy Markdown
Contributor

@ChrisLightfootWild I think I fixed the issue that you highlighted in your previous comment, but CI is generally red for reasons unrelated to this PR AFAICT.

Do you want me to take a stab at getting CI green (which would be a lot of scope creep), or is this PR OK as-is?

Apologies for my delayed response, I'm just back from vacation. Generally happy to delegate unrelated CI failures to separate PRs.

@nickmarden

Copy link
Copy Markdown
Contributor Author

Because of that, I am wondering what additional benefit flattening provides here, if the faceted search is working now.

@andrii-androshchuk I wasn't under the impression that ALL components of the context would be surfaced as attributes in dotted notation such as {"user_info", {"id": 12345}} -> user_info.id but we're going to test it to see if that is true.

@nickmarden

nickmarden commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Because of that, I am wondering what additional benefit flattening provides here, if the faceted search is working now.

@andrii-androshchuk Thanks for the question — @arthurmartelli and I looked into this carefully.

After #511, setAttribute('context', ['user' => ['id' => 1234]]) is exported via OTLP as a single context attribute whose value is a nested kvlist AnyValue. In SigNoz's ClickHouse schema, log attributes are stored in flat typed maps (attributes_string, attributes_number, etc.). A kvlist value gets serialized as a JSON blob under the key context — there is no separate user.id or context.user.id entry in those maps.

So user.id = 1234 is not available as a facet in SigNoz after #511. The context attribute is structurally better than the old JSON-encoded string, but SigNoz's faceted search still treats it as a single opaque value.

The flattening this PR adds explicitly emits user.id = 1234 as a primitive-valued top-level attribute, which SigNoz does index as a searchable facet. That's the gap #511 doesn't close.

Attached are SigNoz screenshots showing

The surfaced top-level attribute http.ip in SigNoz (with OTEL_PHP_LARAVEL_LOG_ATTRIBUTES_FLATTEN):

image

And the current main branch behavior (no flattening) in which http.ip is just part of the context attribute JSON ball (no flattening):

image (1)

My conclusion: I think we should still merge this to support the originally-intended use case.

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