-
Notifications
You must be signed in to change notification settings - Fork 284
add Custom Trace Attributes to extend observability #708
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
base: main
Are you sure you want to change the base?
Conversation
|
One quick question - why wouldn't these trace attributes live in the customers application code. Are these attributes part of the LLM call, if so why? Is the idea that these multi-tenant attributes must be sent up for tracking certain customers? Wouldn't it make more sense to simply accept and x-plano-trace-customer-attribute- prefix and rotate through all those and write them to the trace. Why build a static config that needs to get update all the time? |
These attributes are intentionally configured at the gateway so we can consistently attach tenant/workspace/user metadata to every trace without requiring each customer app/SDK to know our trace schema or emit spans themselves. They’re not part of the LLM payload; we read them from the incoming request headers and add them to the trace for operational filtering across routing/LLM/agent spans. the goal is multi‑tenant attribution so we can slice traces by customer/tenant/user reliably across all requests per the customers choice.
We use a gateway allowlist instead of a wildcard prefix so trace schemas stay stable and auditable. The config maps header → attribute key with type parsing (str, int, etc.), which prevents sensitive or arbitary headers from being logged and keeps queries consistent. A prefix would let any client inject new "keys" and leak data/over permissive in data. |
03605d9 to
40c959e
Compare
salmanap
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some comments on documentation and the name of the config attribute. Please fix those and this is ready to ship
config/arch_config_schema.yaml
Outdated
| trace_arch_internal: | ||
| type: boolean | ||
| additionalProperties: false | ||
| custom_attribute_prefixes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably call this span_attribute_header_perfixes
| None => continue, | ||
| }; | ||
|
|
||
| let suffix = header_name.strip_prefix(prefix).unwrap_or(""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some semantics here that I don't think we've made explicit to the developer that we will transform x-user-id to user.id in the traces. We should add a comment here and add a section in the docs to talk about this.
…refixes in configuration and related handlers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for putting this togehter. I do have some suggestions,
Name can be a bit better while I don't have an answer here but it should be sth like. Notice the static block too,
tracing:
...
span_attributes: # or trace_attributes or just attributes
header_prefixes: # or from_header
- x-katanemo-
static:
environment: production
service.version: "1.0.0"
deployment.region: us-west-2
Placement of attributes in span - I see that in the screenshot attached to plano(llm) span. For a single request will we see these attributes in all spans that are created in a trace? I think these attributes should be part of just the root trace.
| type: integer | ||
| trace_arch_internal: | ||
| type: boolean | ||
| additionalProperties: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we still need additionalProperties: false here
| let custom_attrs = extract_custom_trace_attributes( | ||
| &request_headers, | ||
| tracing_config | ||
| .as_ref() | ||
| .as_ref() | ||
| .and_then(|tracing| tracing.span_attribute_header_prefixes.as_deref()), | ||
| ); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should go other way where only look at request headers if custom tracing attributes are set - this will optimize the run time
| for (key, value) in &custom_attrs { | ||
| span_builder = span_builder.with_attribute(key, value); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is it repeating - I saw another instance of this code at line 285
| // ! we reached the limit of the number of arguments for a function | ||
| #[allow(clippy::too_many_arguments)] | ||
| pub async fn llm_chat( | ||
| request: Request<hyper::body::Incoming>, | ||
| router_service: Arc<RouterService>, | ||
| full_qualified_llm_provider_url: String, | ||
| model_aliases: Arc<Option<HashMap<String, ModelAlias>>>, | ||
| llm_providers: Arc<RwLock<LlmProviders>>, | ||
| trace_collector: Arc<TraceCollector>, | ||
| tracing_config: Arc<Option<Tracing>>, // ! right here | ||
| state_storage: Option<Arc<dyn StateStorage>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these comments are of no value - remove them
| let custom_attrs = extract_custom_trace_attributes( | ||
| &request_headers, | ||
| tracing_config | ||
| .as_ref() | ||
| .as_ref() | ||
| .and_then(|tracing| tracing.span_attribute_header_prefixes.as_deref()), | ||
| ); | ||
| let request_id: String = match request_headers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go from other way that is if span_attributes are defined only then extract the attributes from header
crates/brightstaff/src/main.rs
Outdated
| )); | ||
|
|
||
| let model_aliases = Arc::new(arch_config.model_aliases.clone()); | ||
| let tracing_config = Arc::new(arch_config.tracing.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't send entire object may just send custom tracing block
…ust related handlers
… clarify configuration
| (String::new(), None) | ||
| }; | ||
|
|
||
| let apply_custom_attrs = |mut builder: SpanBuilder| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you want a lambda function in the file. Feels a bit misplaced. I think we should ask ourselves, whats the canonical way to append attributes after a span has been built. A simple for loop feels okay. Although I might move some of that functionality in commons
| .as_ref() | ||
| .and_then(|tracing| tracing.span_attribute_header_prefixes.as_deref()), | ||
| ); | ||
| let mut header_prefixes: Option<&[String]> = None; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can merge this into a single utility that both llm.rs and agent_chat_completions.rs work off of. Else its duplicate code and will be maintained in two places.

Introduces Custom Trace Attributes to allow developers to extend business logic!
Today, traces include request path and system metadata, but not application-specific identifiers. This makes it hard to troubleshoot per-tenant, per-workspace, or per-user issues.
#707