Skip to content

Configure a local trace exporter, without overriding one already set - #254

Draft
jat255 wants to merge 1 commit into
mainfrom
jat255/vxv8-exporter
Draft

jat255 wants to merge 1 commit into
mainfrom
jat255/vxv8-exporter

Conversation

@jat255

@jat255 jat255 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Third piece of M7 tracing (kata vxv8). Stacked on #246, and this base moves to main once that merges.

enable_trajectory_tracing(log=True) turns on GenAI content capture, and installs a file-writing tracer provider when nobody else has installed one. It returns whether commons will emit conversation spans at all. Where a provider the caller installed sends them is the caller's to know, so commons does not inspect it. A trace file holds whole conversations, so the default directory is per process and readable only by this user.

Which file names a reader picks up is shared with the R package, since Python writes the files and R reads them. tests/shared/traces.json pins that set. Span names and attributes belong there too and arrive with the code that emits them (kata 4frb).

Worth a look: the concurrency handling in _tracing.py. Installation holds a lock, reads both the exporter variable and the current provider under it, and confirms its own provider became the global one before pointing the variable at its file. Two tests hold each window open.

Design notes (agent-written)

Two things do not carry across from the R implementation, both checked against the Python exporter rather than assumed. FileSpanExporter appends to one path: no %N template, no trace-latest.jsonl hardlink, and it ignores OTEL_EXPORTER_OTLP_TRACES_FILE. So commons picks the file name and sets that variable itself, because a reader uses it to find the directory. And nothing in Python reads OTEL_TRACES_EXPORTER outside auto-instrumentation, so respecting an explicitly configured exporter is a check commons has to make.

The private default directory is where the packages differ in a way that is easy to miss. R's default sits under tempdir(), which R already makes private per session. Python's tempfile.gettempdir() is /tmp, so a predictable path there would let other local users read whole conversations. COMMONS_TRACES_DIR still points somewhere durable, and is what another process needs to read the files back.

The default directory has a lock of its own, separate from the setup lock, because installation resolves the directory while already holding that one.

R changes

No R source changed, and no R behaviour differs. One test was added to test-trajectories.R, running local_traces_pattern() against the shared naming cases. No hand-written R test was deleted or replaced.

It is here rather than in a later PR because 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 write trace-0.jsonl and trace-latest.jsonl into a directory and count what comes back. Those still stand. This adds a direct check of the accept and reject set, including trace.jsonl, trace-1.jsonl.bak and traces-1.jsonl, which nothing covered before.

Blast radius is one test file, and no call site of an R function changes. Evidence that the fixture drives the R suite rather than sitting beside it: mutating a case in the synced copy fails the test, and test-trajectories.R gives 177 passing with 1 skipped, the skip being the one that needs a Connect vanity URL.

Worth your scrutiny: whether trace.jsonl, with no number, should be read at all. The R 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.

pkg-r/tests/testthat/fixtures/shared/traces.json is generated by scripts/sync-shared.sh, so skip it in review.

@jat255
jat255 marked this pull request as draft September 2, 2026 02:32
@jat255 jat255 added this to the py-M7: OTel Tracing milestone Sep 2, 2026
@jat255 jat255 added py Affects the Python implementation needs-manual-review Agent-created work that needs a human review labels Sep 2, 2026
@jat255
jat255 added this pull request to stack #332 September 10, 2026 03:53
@jat255
jat255 force-pushed the jat255/vxv8-exporter branch from bad6ea0 to 6921d36 Compare September 10, 2026 04:01
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Preview deployed to Connect (dogfood.team.pct.posit.it): https://dogfood.team.pct.posit.it/connect/#/apps/d7a36cae-8f27-448b-a478-61b81fbe3942/draft/369530

Deployed from commit ad148b5.

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Preview deployed to Connect (connect.staging.pct.posit.it): https://connect.staging.pct.posit.it/connect/#/apps/ad662e1b-5048-4acc-9ad7-f9478c92274e/draft/2779

Deployed from commit ad148b5.

@jat255
jat255 force-pushed the jat255/vxv8-exporter branch from 6921d36 to 57bf17f Compare September 10, 2026 04:43
Base automatically changed from jat255/wbx8-span-helpers to main September 10, 2026 04:44
`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
jat255 force-pushed the jat255/vxv8-exporter branch from 57bf17f to ad148b5 Compare September 10, 2026 04:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-manual-review Agent-created work that needs a human review py Affects the Python implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant