Skip to content

fix: conform feature_flag span event to the OTEL spec - #54

Open
Vadman97 wants to merge 1 commit into
mainfrom
fix/otel-spec-conformance-types-set-id
Open

fix: conform feature_flag span event to the OTEL spec#54
Vadman97 wants to merge 1 commit into
mainfrom
fix/otel-spec-conformance-types-set-id

Conversation

@Vadman97

@Vadman97 Vadman97 commented Aug 11, 2026

Copy link
Copy Markdown

Summary

Brings the feature_flag span event into conformance with the OTEL spec (OTEL v1.0.0, ACCEPTED).

Three requirements were unmet:

Req Spec says This hook did
1.2.2.11 feature_flag.result.variationIndex is an int emitted str(...)
1.2.2.10 feature_flag.result.reason.inExperiment is a boolean emitted the string 'true'
1.2.2.9 / 1.2.4 support feature_flag.set.id and a configurable environmentId not implemented

The Go tracing hook (go-server-sdk/ldotel) already emits the specified types and supports set.id, so this also removes a cross-SDK inconsistency.

Why the types matter

Consumers matching on the typed value silently never matched. Our own OTel documentation gives this collector filter:

- 'not ((name == "feature_flag" and attributes["feature_flag.result.reason.inExperiment"] == true) or name == "exception")'

== true does not match the string "true", so that documented example did not work against spans produced by this hook.

Changes

  • variationIndex → int, inExperimentTrue
  • New HookOptions.environment_id; when set, emits feature_flag.set.id (req 1.2.2.9.1.1)
  • Invalid environment_id (non-string or empty) is ignored and logged to the ldclient.otel logger, equivalent to unset (reqs 1.2.4.1, 1.2.4.2)
  • attributes annotated Dict[str, AttributeValue] so mypy accepts the mixed value types
  • 7 new tests, including an explicit type-contract test to stop the strings regressing

Not implemented: req 1.2.2.9.2

Sourcing the environment ID from EvaluationSeriesContext when it is not configured is not implementable today. launchdarkly-server-sdk 9.14.1 exposes no environment ID on either EvaluationSeriesContext (key / context / default_value / method only) or plugin EnvironmentMetadata (sdk / sdk_key / application). Go has seriesContext.EnvironmentID(); Python has no equivalent. Only the config path (1.2.2.9.1) is covered here.

Compatibility

This changes the wire type of two attributes. Anyone filtering on the string forms ("true", "0") in a downstream OTel backend will need to match the typed values instead.

No impact on LaunchDarkly Observability data. Ingest stringifies span event attributes via fmt.Sprintf("%v", v) (backend/clickhouse/trace_row.go attributesToMap) into String columns, so True"true" and 0"0" — byte-identical to the previous output.

Testing

  • make test — 19 passed (12 pre-existing, 7 new)
  • make lintmypy, isort, pycodestyle all clean

Note for reviewers

Spec req 1.2.3.3 looks internally inconsistent and I did not touch it: it says the variation span must carry feature_flag.context.key, but its own prose cross-references 1.2.2.5, which defines feature_flag.context.id. Both this hook and the Go hook set context.id. Separately, the public observability docs state that a flag span event is identified by carrying feature_flag.context.key. Worth reconciling, but that's a spec decision rather than a code fix.

🤖 Generated with Claude Code


Note

Overview
Brings feature_flag span events into OTEL spec conformance by fixing attribute types and adding optional environment ID support.

variationIndex is now emitted as an int (was a string), and inExperiment as a boolean (was 'true'). Downstream filters matching on typed values will now work; filters that matched the old string forms need updating.

Adds optional HookOptions.environment_id, which emits feature_flag.set.id when set to a non-empty string. Invalid values are ignored with a warning on the ldclient.otel logger.

Reviewed by Cursor Bugbot for commit 4060136. Bugbot is set up for automated code reviews on this repo. Configure here.

The OTEL spec (launchdarkly/sdk-specs specs/OTEL-openteletry-integration)
types `feature_flag.result.variationIndex` as an int (req 1.2.2.11) and
`feature_flag.result.reason.inExperiment` as a boolean (req 1.2.2.10).
This hook was emitting both as strings, so consumers matching on the typed
value did not match. The collector filter example in our own OTel docs
matches `attributes["feature_flag.result.reason.inExperiment"] == true`,
which never matched spans produced by this hook. The Go tracing hook
already emits the specified types.

Also adds the `environment_id` option (req 1.2.4) and the resulting
`feature_flag.set.id` attribute (req 1.2.2.9), neither of which was
implemented here. Invalid values are ignored and logged (req 1.2.4.1,
1.2.4.2).

Req 1.2.2.9.2 -- sourcing the environment ID from EvaluationSeriesContext
when it is not configured -- remains unimplemented, because
launchdarkly-server-sdk does not expose an environment ID on either
EvaluationSeriesContext or plugin EnvironmentMetadata.

Note for LaunchDarkly Observability: stored values are unchanged. The
ingest path stringifies span event attributes with fmt.Sprintf("%v"),
so `True` -> "true" and `0` -> "0", byte-identical to the previous output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Vadman97
Vadman97 marked this pull request as ready for review August 11, 2026 21:49
@Vadman97
Vadman97 requested a review from a team as a code owner August 11, 2026 21:49

@kinyoklion kinyoklion left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs the update for reading the environment ID from the stream headers. The ability to configure it is a fallback.

The hook should include the environment ID. If the go SDK is currently only doing this, then it needs updated as well and I can check that.

Example of setting it from the .Net SDK: https://github.com/launchdarkly/dotnet-core/blob/98cf36ab260906284bc5276b8c656c399925d14e/pkgs/telemetry/src/TracingHook.cs#L235

Python has the eventsource embedded, so it should be a very small change really.

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.

2 participants