Configure a local trace exporter, without overriding one already set - #254
Draft
jat255 wants to merge 1 commit into
Draft
Configure a local trace exporter, without overriding one already set#254jat255 wants to merge 1 commit into
jat255 wants to merge 1 commit into
Conversation
`enable_trajectory_tracing(log=True)` turns on GenAI content capture and, when nobody has installed a tracer provider, installs one that writes spans to a local file. It reports whether commons will emit conversation spans into a configured provider. Where a provider the caller installed sends them is the caller's to know, so commons does not inspect it. Two things do not carry over from R, both checked against opentelemetry-exporter-otlp-json-file rather than assumed. The Python file exporter appends to one path: no `%N` template, no `trace-latest.jsonl` hardlink, and it ignores `OTEL_EXPORTER_OTLP_TRACES_FILE`. So commons picks the filename, claiming it with an exclusive create, and sets the variable after the provider is installed, because a reader uses it to find the directory and must not be sent to a file nothing writes to. And nothing in Python reads `OTEL_TRACES_EXPORTER` outside auto-instrumentation, so respecting an explicit exporter means checking that variable and the installed provider by hand. A trace file holds whole conversations, so the default directory is created per process, readable only by this user, and the files are `0600`. Leaving them at a predictable path under the shared temporary directory would let other local users read them. `COMMONS_TRACES_DIR` still points somewhere durable, and is what another process needs to read them back. An installed no-op provider counts as configured, not as absent, so commons leaves it alone. `set_tracer_provider` refuses to replace it anyway, and treating it as absent would claim a file and repoint the variable while the provider silently stayed no-op. Installation holds a lock, reads both the exporter variable and the provider under it, and confirms the provider it built became the global one, so a caller configuring tracing from another thread is not overridden and cannot leave the variable naming an empty file. The default directory has its own lock, because installation resolves the directory while holding the setup one. Which files a reader picks up is shared with the R package, since one writes them and the other reads them, and a name outside the set fails silently: the reader finds nothing. `tests/shared/traces.json` pins it, R checks `local_traces_pattern()` against the cases, and Python checks that every name it chooses satisfies them. The rest of the trace contract, the span names and attributes, belongs in that file too and arrives with the code that emits them (kata 4frb). Spans are written as each one ends rather than batched, because a batch is lost when the process dies and the last turn of a conversation is the worst span to lose. Part of kata bvcv (M7).
jat255
marked this pull request as draft
September 2, 2026 02:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third piece of M7 tracing (kata
vxv8). Stacked on #246; review that first, and this base moves tomainonce it merges.enable_trajectory_tracing(log=True)turns on GenAI content capture and, when nobody has installed a tracer provider, installs one that writes spans to a local file. It reports whether commons will emit conversation spans into a configured provider. Where a provider the caller installed sends them is the caller's to know, so commons does not inspect it, andtracing_configured()is named for what it can actually tell you.Two things do not carry over from R, both checked against the Python exporter rather than assumed.
FileSpanExporterappends to one path: no%Ntemplate, notrace-latest.jsonlhardlink, and it ignoresOTEL_EXPORTER_OTLP_TRACES_FILE. So commons picks the filename and sets the variable itself, because a reader uses it to find the directory. And nothing in Python readsOTEL_TRACES_EXPORTERoutside auto-instrumentation, so respecting an explicit exporter is a check commons has to make rather than something the SDK does.A trace file holds whole conversations, so the default directory is per process and readable only by this user, and the files are
0600. A predictable path under the shared temporary directory would let other local users read them. This is where Python and R differ in a way that is easy to miss: R's default sits undertempdir(), which R already makes private per session, while Python'stempfile.gettempdir()is just/tmp.COMMONS_TRACES_DIRstill points somewhere durable, and is what another process needs to read the files back.Which files a reader picks up is shared with the R package, since one writes them and the other reads them, and a name outside the set fails silently: the reader finds nothing.
tests/shared/traces.jsonpins it. The rest of the trace contract, the span names and attributes, belongs in that file too and arrives with the code that emits them (kata4frb).Worth a look: the concurrency handling. Installation holds a lock, reads both the exporter variable and the current provider under it, and confirms the provider it built became the global one before pointing the variable at its file. The default directory has a separate lock, because installation resolves the directory while holding the setup one. There are tests for the two windows, including one that takes the lock itself to hold a window open.
R changes
No R source changed, and no R behaviour differs. One test was added to
test-trajectories.R:local_traces_pattern()is checked against the shared naming cases, replacing nothing.The reason it is here rather than in a later PR is that this is the first time anything outside R writes files that
read_local_spans()has to find. The pattern was previously exercised only incidentally, by two tests that writetrace-0.jsonlandtrace-latest.jsonlinto a directory and count what comes back. Those still stand; this adds a direct check of the accept and reject set, including the three names nothing covered before,trace.jsonl,trace-1.jsonl.bakandtraces-1.jsonl.pkg-r/tests/testthat/fixtures/shared/traces.jsonis generated byscripts/sync-shared-fixtures.sh; skip it in review. The existingverify-shared-fixturesworkflow fails on a stale copy.Blast radius is one test file. Evidence the fixture drives the R suite rather than sitting beside it: mutating a case in the synced copy fails the R test, and
test_file("test-trajectories.R")gives 157 passing with 5 skipped, the skips being snapshots that skip non-interactively.Worth your scrutiny: whether
trace.jsonl, with no number, should really be read. R's pattern accepts it today and I pinned current behaviour rather than changing it, but nothing commons writes produces that name, so it may be an accident worth removing on the R side.