Conversation
apalis-core/src/task/metadata.rs
Outdated
| span_id: Option<String>, | ||
| trace_flags: Option<u8>, | ||
| trace_state: Option<String>, | ||
| traceparent: Option<String>, |
There was a problem hiding this comment.
traceparent is enough to recreate the otel context!
Do we really need the tracestate ?
There was a problem hiding this comment.
to keep may be for vendor specific otel context
There was a problem hiding this comment.
I am curious about removing these fields. Arent there cases that one would want to use the removed fields? Maybe we need to have a type called OtelTraceContext for otel specific contexts and keep TraceContext for non-otel cases. I am not the expert with telemetry so bear with me if I am not viewing this the right way.
There was a problem hiding this comment.
Well I think you are right in a way !
TraceContext is too generic we can rename it OtelTraceContext !
But TraceContext could be used for another purpose but IMO not for forwarding trace_id, span_id, etc ... thoses are too otel specific.
Btw traceparent contains all removed fields just formatted according to otel spec
There was a problem hiding this comment.
TraceContext is too generic we can rename it OtelTraceContext !
I think we can have both and implement From<TraceContext> for OtelTraceContext
This would also mean we dont change anything in apalis-core. OtelTraceContext can go to the apalis crate and behind the opentelemetry feature flag. What do you think?
There was a problem hiding this comment.
Ok but which one do we need to send with tasks ?
Do you mean the user must be able to choose between TraceContext and OtelTraceContext ? if yes the From<TraceContext> for OtelTraceContext is useless ?
There was a problem hiding this comment.
the advantage with OtelTraceContext is that on creation OtelTraceContext::current() we automatically get the ctx filled if any, no need to fill each field! while with TraceContext we must do that
There was a problem hiding this comment.
One last remark, TraceContext contains IMO otel specific field (trace_id, span_id, etc) it may be better to have TraceContext as a wrapper over a HashMap for generic ctx transmission
and have OtelTraceContext for opentelemetry with traceparent=trace_id+span_id+...
There was a problem hiding this comment.
From<TraceContext> for OtelTraceContextis useless ?
yesFrom<TraceContext> for OtelTraceContextmight not be that valuable.
But I am assuming cases that One wants to transfer the context from one application to another or a scenario where OtelTraceContext::current() might not cut it (Maybe its not the same span)
To use the OtelTraceContext, we can have something like:
.layer(TraceLayer::new().make_span_with(OtelContextualSpan::new()))
.layer(OpenTelemetryMetricsLayer::default())
and a user can do:
async fn produce_task_with_ctx(storage: &mut JsonStorage<Email>) -> Result<()> {
let email = Email {
to: "test@example".to_string(),
text: "Test background job from apalis".to_string(),
subject: "Welcome Sentry Email".to_string(),
};
// This might come from a http request etc
let context = OtelTraceContext::current();
let task = Task::builder(email).meta(context).build();
storage.push_task(task).await?;
Ok(())
}
In some backends such as redis and postgres, metadata is stored in a k->v structure meaning its easier to query it than a string.
I think we might also want something like FromStr for OtelTraceContext (or use OtelTraceContext::new(string) for flexibility. Since it might not always be the case that one can build from current scope.
it may be better to have TraceContext as a wrapper over a HashMap for generic ctx transmission
Sure, I will look into this
There was a problem hiding this comment.
I see you have pushed some of the changes, Let me look through
apalis-core/src/task/metadata.rs
Outdated
| pub fn with_trace_flags(mut self, trace_flags: u8) -> Self { | ||
| self.trace_flags = Some(trace_flags); | ||
| self | ||
| pub fn current() -> Self { |
There was a problem hiding this comment.
May be this should be the new fn ?
fn new is currently private... it's needed only for a test
| } | ||
| }; | ||
|
|
||
| tracing_ctx.restore(&span); |
There was a problem hiding this comment.
magic happens here
|
|
||
| impl Error for InvalidEmailError {} | ||
|
|
||
| fn print_otel_context(message: &str) { |
There was a problem hiding this comment.
will generate this king of logs:
INFO produce_task: produce_task trace_id=6e288a541db0109a841fada148b7bfc7 span_id=fc6ace54ead0dc2b
INFO produce_task_with_ctx: produce_task_with_ctx trace_id=9ff8092f18c1f5a09c72502004c8fc6a span_id=6a1ccde6e61308cd
...
INFO email_service trace_id=b60bb828c0c54dca3ea7063919acc0f3 span_id=3a2e9333c835397b
INFO email_service trace_id=9ff8092f18c1f5a09c72502004c8fc6a span_id=9da96fa05218cc5e
As expect produce_task does not transmit the Otel context so we do not see it in the email_service
BUT produce_task_with_ctx running with trace_id 9ff8092f18c1f5a09c72502004c8fc6a forward the otel context resulting to the same trace_id in email_service as expected
examples/monitor/src/main.rs
Outdated
| .with_trace_flags(1) | ||
| .with_trace_state("key=value"); | ||
| // Capture whichever tracing context is currently active. | ||
| let _guard = tracing::info_span!("enqueue-email").entered(); |
There was a problem hiding this comment.
Shouldnt there be already an existing span in this case? Is there a reason to enter the new span here?
There was a problem hiding this comment.
Not really, a span is not automatically created on must:
- create it with tracing::xxx_span! macro
- use the #[instrument] macro
- or use actix or axum (or any other lib) with tracing enabled to delegate the span creation on each request for example
I have removed it anyway from this example (monitor) because not really related to otel (tracing example is better)
apalis-core/src/task/metadata.rs
Outdated
| span_id: Option<String>, | ||
| trace_flags: Option<u8>, | ||
| trace_state: Option<String>, | ||
| traceparent: Option<String>, |
There was a problem hiding this comment.
I am curious about removing these fields. Arent there cases that one would want to use the removed fields? Maybe we need to have a type called OtelTraceContext for otel specific contexts and keep TraceContext for non-otel cases. I am not the expert with telemetry so bear with me if I am not viewing this the right way.
| }; | ||
|
|
||
| #[cfg(feature = "opentelemetry")] | ||
| OtelTraceContext::from(tracing_ctx).restore(&span); |
There was a problem hiding this comment.
Hmmm.. Shouldn't OtelTraceContext have its own specific span?
There was a problem hiding this comment.
What do you mean by own specific span ?
The span is created here before running a task. Then we restore the context by putting a parent (ie we tell to the newly created span that the trace_id is the one comming from the producer, but the span_id is new and the parent_span is the one comming from the producer).
Then we "enter" the span
| /// This type provides a clearer API surface for users that explicitly work with | ||
| /// OpenTelemetry context propagation, while preserving `apalis-core` compatibility. | ||
| #[derive(Debug, Default, Clone)] | ||
| pub struct OtelTraceContext(pub(crate) TracingContext); |
Description
TracingContextto carry W3C propagation headers (traceparent,tracestate) as opaque metadata.TracingContextviaExtractor/Injector.TracingContext::current()to capture context fromSpan::current()TracingContext::restore(&span)to restore parent context during task span creationContextualTaskSpanto restore parent OTel context from task metadata.tracing-opentelemetrysubscriber/tracer setup and print span context for verification.apalis/tests/otel_context_propagation.rsType of Change
Testing
cargo fmtandcargo clippyChecklist
Additional Notes
cargo run -p tracing-examplecargo test -p apalis --test otel_context_propagation -- --nocapturetraceparenton contextual task execution.