-
Notifications
You must be signed in to change notification settings - Fork 893
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add BeforeEnd to have a callback where the span is still writeable #1089
Comments
/cc @arminru |
Required for dynamic resource-like attributes desired in every span but computed at run-time because they're not constant, eg. instance uptime and recent CPU utilisation. Injecting them in the exporter gives results that become less accurate during the spikes we're most interested in. |
IIRC it was discussed to make spans writeable in OnEnd but this was left out because users will not always have full control over the order in which span processors are registered and in turn invoked, which could result in unreliable behavior. @carlosalberto you seemed to remember some of that discussion as well in the meeting yesterday. Do you know the rationale for that decision? |
Why these attributes cannot be set in |
Reading the spec again, I noticed that it explicitly says that spans are already marked as ended at the time they are passed to SpanProcessor.OnEnd:
We would also have to change either of those requirements if we wanted to make spans writeable in OnEnd and we would need to understand the consequences well. |
Exactly. The difficult to get right (but specified!) order (exporting may happen before the other OnEnd processor) is another problem, but the main problem is that spans are already ended, as @arminru said. This was already discussed in #669 (comment). There are two ways we could go forward here:
We definitely can't pass a writable span to OnEnd. |
Why not define it as your Span Processor must assume it gets the Span in the form it is ended with and not expect to have any changes from previous processors visible? |
@iNikem my understanding is that it is because I'd be fine with a |
No, the main issue is that in OnEnd the span is already ended, and for an ended span, SetAttribute should be a no-op. Changing that seems liable to opening a can of worms. |
Ah, ok. Yea, I wouldn't want to change the fact that ended means read-only but instead move the "ending" to after the processors. But this isn't possible since one of the processors is what takes the "ended" span and passes it to the exporter... I didn't like exporting being part of processors because of this. And probably too late to argue that sending to the exporter should be separate and unrelated to processors, leaving OnEnd processors to act like "BeforeEnd" :( |
Maybe we would even need a special state where the span is semi-ended, i.e. it has IsEnded==true and has an end timestamp, but is still recording. |
I don't understand. If you have a |
@iNikem it only receives a write-able span for |
It seems I still don't understand your use-case: what are these attributes that are not available right away but become available only when some spans were already started? And even more: how does |
An HTTP user id parsed out of headers is the best example I can think of, but actual honeycomb users (@garthk @gugahoa ?) probably have more. The initial request span is started right away, before user code parses out headers they care about, a child span is started when the user's logic is called at which point they parse out the user-id and want to put it on all spans in that trace but the span created when the HTTP handler got the request has already run |
That's a good characterization, @tsloughter. This has been a "normal" way of using Honeycomb's Beeline library for a long time. It buffers the trace span tree in memory, and can distribute attributes (called "fields" there) up and down the span tree before sending the spans to the server. It stores a set of fields on the trace itself, in a field on the |
I am curios why do you need to still add events/attributes to the |
Agree with @bogdandrutu. I'm not sure which implemenation language you are using but in Java there is https://github.com/open-telemetry/opentelemetry-java/blob/v0.9.1/sdk_extensions/tracing_incubator/src/main/java/io/opentelemetry/sdk/extensions/incubator/trace/data/SpanDataBuilder.java Unfortunately, #158 is not resolved so this is all a bit vague in the spec. |
"that we will export" Where is there a pluggable place to modify If you are saying |
Processor can certainly modify |
The exporter interface. You can make your own exporter that wraps an existing one and does the modification. |
True, but in order for the "modification" (SpanData is immutable at least in Java, so it actually creates an updated copy) to be picked up by an exporter, that exporter must be invoked in the same OnEnd. |
That would mean writing my own processor which will export to the configured exporters, but I wouldn't want to write my own processor for that and instead it would be preferable to rely on tried-and-true processors such as the BatchProcessor
I'm not sure this translates well to Erlang, but I need to try it out first to see how it goes |
That is what we hoped not to have to do and I believe is already the way people have been doing it. But I guess if its a wrapper that can take any exporter it and be a new exporter that acts on the attributes and then calls the wrapped exporter with the modified batch it wouldn't be too bad. It would certainly be more efficient to update "on end", but it sounds like a wrapper exporter is the way to go -- assuming there aren't issues with the lifetime of the shared attributes on a trace, will have to talk to those using it. |
It does. We have an exporter behaviour, And has to call the |
Reading through the comments, the arguments not to do this seem to be:
But asking users to do this in span exporter is inconvenient and unintuitive - this is clearly a "processing" type of task so why would a user expect to have to do it in an exporter? And yes, by doing this in a processor you have to pay attention to processor registration order, but that's expected and matches common sense expectations for processors. Notice that nobody expects to ignore processor ordering configuration in the collector. File configuration makes the registration order of processors easy to configure. This is the type of thing that is easy to do and fills an obvious conceptual flaw in the SDK design, where we've defined processors which can only process in a very constrained manner. Let's just fix it with the obvious / simple solution, which is to add a new optional beforeEnd method to SpanProcessor. |
@jack-berg the end result I think was the replace the |
Why introduce a new concept / SDK extension point instead of enhancing an existing? Limiting SDK extension points reduces cognitive load for users. |
@jack-berg to make it possible to do similar to what this wants to do in OnStart as well. |
Or no, that wasn't the reason... I swear there was a good reason though! hah |
Loose naming idea: Making a writeable spans on SpanProcessor's OnEnd(Span) for Go will be breaking change as |
Discussed in the 4/9/2024 Spec SIG meeting. There was agreement that we should look into solutions to accommodate a variety of use cases that require modifying spans near the end, including:
Two broad families of solutions were suggested:
@jackshirazi / @JonasKunz are either (or anyone else) interested / able to own this? |
I'm happy to own this. |
Hi @JonasKunz - you've been assigned to the issue. Please reach out to me if needed. Thanks! |
I'd like to share my thoughts on the proposed families of solutions.
I think evolving the existing For the Here is a draft implementation showing how this new callback could be implemented in Java.
IIRC the main concern here from @jack-berg was that this introduction of a new component type might introduce too much complexity for SDK users and I share that concern. However, having a pipeline-like structure where one stage can pass a wrapped, immutable span to the next stage is a lot more flexible than just the
I think we can get this flexibility without introducing an entirely new component and therefore with a lot less complexity. The idea is to add a new parameter to At least in Java, this functionality is very easy and cleanly addable in a backwards compatible way as shown by my implementation. However, I don't know if this is also the case for the other languages. An alternative way to achieve the same capability would be to add a new This alternative solution is not quiet as elegant as my provided Java solution with the It would be great to get your feedback here! As you might have noticed, I would prefer the decoration/chaining approach over |
I agree but, but would also add that whether or not the
The idea that a processor isn't able to generically mutate AND filter data makes it feel misnamed. With that said, the concept of a sampler at least partially overlaps with the filter use case. I wonder if its better to keep things separate, and instead of introducing a filter-chain concept in SpanProcessor, extend Sampler so there is a way to filter spans out on span end. Obviously any filtering that is done after the start of a span introduces the potential for broken spans, since downstream spans make sampling decisions based on the sampling status of their parent, which no longer reflects whether the parent span actually made it to the backend. Still, this use case comes up enough that its worth considering supporting and warning users to make sure they know the consequences. Can anyone involved in the original trace SDK recall / comment on the reasoning why SpanProcessor wasn't initially designed with chaining in mind? The fact that it wasn't and therefore prevents the SpanProcessor filter use case seems deliberate rather than an oversight. |
…4024) Fixes open-telemetry#1089. In addition to the comments on the issue, this was discussed in the spec SIG Meeting on 2024/23/04: * The filtering use-case explained in [this comment](open-telemetry#1089 (comment)) should rather be solved by the upcoming samplerV2 instead of `SpanProcessor`s due to better conceptual fit * The buffering use-case also explained in [this comment](open-telemetry#1089 (comment)) seems to be not relevant enough to influence the design decision * Apparently there was a discussion around building the `SpanProcessor`s in a chaining fashion during the initial SDK spec design and it was actively decided against it. However, no one could recall the reason why.
What are you trying to achieve?
Set an attribute to all spans in a trace
What did you expect to see?
Writeable spans on SpanProcessor's OnEnd(Span)
Additional context.
What I tried to implement is a feature called trace field in Honeycomb (reference:
AddFieldToTrace
)For that I first tried to use
SpanProcessor
, but soon found out thatOnEnd(Span)
is not writeable.The solution I arrived was to write a custom exporter which was able to read from a thread-local storage (at the time of the implementation, Baggage had yet to be spec'ed out) to list all attributes related to a Trace and insert into the span before exporting.
From what I understood from the
SpanProcessor
specification, that's the place where I should have placed this logic, as it's a transformation that should happen before the span is sent to any exporterThe text was updated successfully, but these errors were encountered: