feat: add span customizers (SDK-316) - #217
Andrew Kent (realark) wants to merge 1 commit into
Conversation
de41942 to
10d8e13
Compare
Stephen Belanger (Qard)
left a comment
There was a problem hiding this comment.
Generally LGTM.
I'm wondering if we should align the plugins system that just landed in bt trace with the span customization work. They're very similar, but plugins also have a context object with a couple things:
That has env, if the operation is an insert or a merge, and the session ID. It also has source, which is the name of the coding agent, but probably unnecessary for languages to report their own language when you're already in that language in the hook.
We could basically just add the context parameter to the span customization spec with just those first three properties. What do you think?
10d8e13 to
057d20f
Compare
| super(endpoint: endpoint, headers: {"Authorization" => "Bearer #{api_key}"}) | ||
| end | ||
|
|
||
| def export(span_data, timeout: nil) |
There was a problem hiding this comment.
This is a bit messy: its a long function with somewhat duplicated operations, which makes it harder to reason about and the behaviors (origin, customization) harder to isolate from one other. The goal is to make Exporter know as little about span customization as possible, just how to call it for what it needs.
I'd prefer, if possible, to follow the example of SpanOrigin where we isolate its behaviors in a separate component then compose them back in. Because a customizer is state, it'd make more sense for this to be a class instead of a module, maybe SpanCustomizers. In this class you'd isolate the private methods for customize and expose only the public ones that need to be exported.
A clean export function is a good sign where it does simple transforms back to back (e.g. SpanOrigin then SpanCustomizer then super) without lots of if/else clauses and repeated, nearly identical calls (e.g. group_by and super()). We used prepend before to even make SpanOrigin automatic behavior (no code changes to Exporter) which was nice; would be cool here, but might be a tall order.
Given we seem to like to mutate on export, it may make more sense to reformulate our exporter as some kind of middleware stack pattern: might make it easier to reliably compose these behaviors.
There was a problem hiding this comment.
done. AI summary:
Addressed the feedback.
• Added SpanCustomizers to own registration, hook ordering, tracing suppression, writable attributes, and identity validation.
• Simplified the exporter to compose origin → customization → grouping → encoding → transport, with one grouping pass.
• Added SpanOrigin.enrich_batch, shared by explicit composition and the existing prepend behavior.
• Kept whole-batch serialization validation before transmission and the existing no-customizer transport path.
057d20f to
da3eff7
Compare
da3eff7 to
39c575a
Compare
AI Generated Description
Summary
Implements SDK-316 using the span customizer specification and the Java exporter reference.
Braintrust::SpanCustomizer#on_span_exportand orderedspan_customizers:registration through init/config/exporter APIs.Verification
Tradeoffs
Configured customization deep-copies SDK SpanData and buffers encoded destination groups before sending, providing fail-closed batch behavior. Transport retries reuse encoded bytes; an explicit export resubmission reruns hooks. Arbitrary non-marshallable state added to SpanData subclasses fails closed.