Skip to content

Commit e1a0428

Browse files
yurishkuroclaude
andcommitted
feat(query): Add scope-qualified attribute filters to trace query protos
Add an AttributeFilter message to both the public api_v3 and the storage/v2 TraceQueryParameters, plus a repeated attribute_filters field on each. This is the proto/IDL foundation (milestone M1) for RFC 0005 (Qualified Attribute Queries): structured attribute predicates that may optionally restrict the search to a single OpenTelemetry attribute scope (span/resource/scope/event/ link) and carry an operator (eq/ne/gt/lt/regex/exists). scope and op are typed string enumerations rather than proto enums: the REST/JSON representation carries short human-readable tokens ("span", "eq") instead of verbose CONSTANT_CASE names, and the closed value set is declared in the api_v3 OpenAPI schema via the gnostic enum annotation (rendered as type: string, enum: [...]). See RFC 0005 section 6.6 for the trade-off analysis. The existing unqualified attributes field is untouched, so the change is purely additive. Only the .proto sources and the regenerated api_v3 OpenAPI spec are committed; the Go stubs are regenerated downstream in the jaeger repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Yuri Shkuro <github@ysh.us>
1 parent 0daa719 commit e1a0428

3 files changed

Lines changed: 152 additions & 0 deletions

File tree

proto/api_v3/query_service.proto

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,55 @@ message GetTraceRequest {
4343
bool raw_traces = 4;
4444
}
4545

46+
// AttributeFilter is a structured attribute predicate with an optional scope
47+
// qualifier and operator. Unlike the unqualified `attributes` map, a filter may
48+
// restrict the search to a single attribute scope. See RFC 0005
49+
// (Qualified Attribute Queries).
50+
//
51+
// `scope` and `op` are string enumerations (not proto enums) so the REST/JSON
52+
// representation carries short, human-readable tokens (e.g. "span", "eq") rather
53+
// than verbose CONSTANT_CASE names; the closed value set is declared in the
54+
// OpenAPI schema via the annotations below. See RFC 0005 §6.6.
55+
message AttributeFilter {
56+
// key is the attribute name (e.g. "http.status_code").
57+
string key = 1;
58+
59+
// value is the string representation of the attribute value.
60+
// Ignored when `op` is "exists".
61+
string value = 2;
62+
63+
// scope optionally restricts the search to a single OpenTelemetry attribute
64+
// scope. Empty (omitted) searches all scopes. Scope restriction is
65+
// best-effort: backends that cannot distinguish scopes return a superset
66+
// (see RFC 0005). One of:
67+
// span — Span.attributes (per-operation metadata)
68+
// resource — ResourceSpans.resource.attributes (service/host metadata)
69+
// scope — ScopeSpans.scope.attributes (instrumentation library metadata)
70+
// event — Span.events[].attributes (timestamped annotations)
71+
// link — Span.links[].attributes (cross-trace references)
72+
string scope = 3 [
73+
(openapi.v3.property) = {
74+
type: "string",
75+
enum: [{yaml: "span"}, {yaml: "resource"}, {yaml: "scope"}, {yaml: "event"}, {yaml: "link"}]
76+
}
77+
];
78+
79+
// op is the predicate to apply. Empty (omitted) is treated as "eq". Backends
80+
// reject unimplemented operators with gRPC status UNIMPLEMENTED. One of:
81+
// eq — exact match (default)
82+
// ne — negated exact match
83+
// gt — numeric greater-than
84+
// lt — numeric less-than
85+
// regex — regular-expression match
86+
// exists — key presence; `value` is ignored
87+
string op = 4 [
88+
(openapi.v3.property) = {
89+
type: "string",
90+
enum: [{yaml: "eq"}, {yaml: "ne"}, {yaml: "gt"}, {yaml: "lt"}, {yaml: "regex"}, {yaml: "exists"}]
91+
}
92+
];
93+
}
94+
4695
// Query parameters to find traces.
4796
//
4897
// All fields form a conjunction (e.g., "service_name='X' AND operation_name='Y' AND ..."),
@@ -110,6 +159,22 @@ message TraceQueryParameters {
110159
//
111160
// This field is optional.
112161
bool raw_traces = 9;
162+
163+
// attribute_filters contains structured attribute predicates with optional
164+
// scope and operator qualification. Each filter is ANDed with the others and
165+
// with the unqualified `attributes` field. Unlike `attributes` (which searches
166+
// all scopes with equality), a filter may restrict the search to a specific
167+
// attribute scope. Scope restriction is best-effort: backends that cannot
168+
// distinguish scopes return a superset. See RFC 0005 (Qualified Attribute Queries).
169+
//
170+
// The HTTP API expects this as a URL-encoded JSON array.
171+
// Example: [{"key":"http.status_code","value":"200","scope":"span"}]
172+
repeated AttributeFilter attribute_filters = 10 [
173+
(openapi.v3.property) = {
174+
type: "string",
175+
example: { yaml: "'[{\"key\":\"http.status_code\",\"value\":\"200\",\"scope\":\"span\"}]'" }
176+
}
177+
];
113178
}
114179

115180
// Request object to search traces.

proto/storage/v2/trace_storage.proto

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ message ArrayValue {
9090
repeated AnyValue values = 1;
9191
}
9292

93+
// AttributeFilter is a structured attribute predicate with an optional scope
94+
// qualifier and operator. Mirrors jaeger.api_v3.AttributeFilter. `scope` and
95+
// `op` are string enumerations (not proto enums) so plugins receive short,
96+
// forgiving tokens that align with the best-effort contract — an unrecognized
97+
// scope is simply treated as unqualified. See RFC 0005 §6.6.
98+
message AttributeFilter {
99+
string key = 1;
100+
string value = 2; // ignored when op is "exists"
101+
string scope = 3; // one of: span|resource|scope|event|link; empty = all scopes
102+
string op = 4; // one of: eq|ne|gt|lt|regex|exists; empty = eq
103+
}
104+
93105
// TraceQueryParameters contains query parameters to find traces. For a detailed
94106
// definition of each field in this message, refer to `TraceQueryParameters` in `jaeger.api_v3`
95107
// (https://github.com/jaegertracing/jaeger-idl/blob/main/proto/api_v3/query_service.proto).
@@ -102,6 +114,9 @@ message TraceQueryParameters {
102114
google.protobuf.Duration duration_min = 6;
103115
google.protobuf.Duration duration_max = 7;
104116
int32 search_depth = 8;
117+
// attribute_filters carries structured, optionally scope-qualified attribute
118+
// predicates, ANDed with each other and with `attributes`. See RFC 0005.
119+
repeated AttributeFilter attribute_filters = 9;
105120
}
106121

107122
// FindTracesRequest represents a request to find traces.

swagger/api_v3/query_service.openapi.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,63 @@ components:
411411
$ref: '#/components/schemas/google.protobuf.Any'
412412
description: A list of messages that carry the error details. There is a common set of message types for APIs to use.
413413
description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).'
414+
jaeger.api_v3.AttributeFilter:
415+
type: object
416+
properties:
417+
key:
418+
type: string
419+
description: key is the attribute name (e.g. "http.status_code").
420+
value:
421+
type: string
422+
description: |-
423+
value is the string representation of the attribute value.
424+
Ignored when `op` is "exists".
425+
scope:
426+
enum:
427+
- span
428+
- resource
429+
- scope
430+
- event
431+
- link
432+
type: string
433+
description: |-
434+
scope optionally restricts the search to a single OpenTelemetry attribute
435+
scope. Empty (omitted) searches all scopes. Scope restriction is
436+
best-effort: backends that cannot distinguish scopes return a superset
437+
(see RFC 0005). One of:
438+
span — Span.attributes (per-operation metadata)
439+
resource — ResourceSpans.resource.attributes (service/host metadata)
440+
scope — ScopeSpans.scope.attributes (instrumentation library metadata)
441+
event — Span.events[].attributes (timestamped annotations)
442+
link — Span.links[].attributes (cross-trace references)
443+
op:
444+
enum:
445+
- eq
446+
- ne
447+
- gt
448+
- lt
449+
- regex
450+
- exists
451+
type: string
452+
description: |-
453+
op is the predicate to apply. Empty (omitted) is treated as "eq". Backends
454+
reject unimplemented operators with gRPC status UNIMPLEMENTED. One of:
455+
eq — exact match (default)
456+
ne — negated exact match
457+
gt — numeric greater-than
458+
lt — numeric less-than
459+
regex — regular-expression match
460+
exists — key presence; `value` is ignored
461+
description: |-
462+
AttributeFilter is a structured attribute predicate with an optional scope
463+
qualifier and operator. Unlike the unqualified `attributes` map, a filter may
464+
restrict the search to a single attribute scope. See RFC 0005
465+
(Qualified Attribute Queries).
466+
467+
`scope` and `op` are string enumerations (not proto enums) so the REST/JSON
468+
representation carries short, human-readable tokens (e.g. "span", "eq") rather
469+
than verbose CONSTANT_CASE names; the closed value set is declared in the
470+
OpenAPI schema via the annotations below. See RFC 0005 §6.6.
414471
jaeger.api_v3.DependenciesResponse:
415472
required:
416473
- dependencies
@@ -581,6 +638,21 @@ components:
581638
The trace will be returned exactly as stored.
582639
583640
This field is optional.
641+
attributeFilters:
642+
example: '[{"key":"http.status_code","value":"200","scope":"span"}]'
643+
type: string
644+
items:
645+
$ref: '#/components/schemas/jaeger.api_v3.AttributeFilter'
646+
description: |-
647+
attribute_filters contains structured attribute predicates with optional
648+
scope and operator qualification. Each filter is ANDed with the others and
649+
with the unqualified `attributes` field. Unlike `attributes` (which searches
650+
all scopes with equality), a filter may restrict the search to a specific
651+
attribute scope. Scope restriction is best-effort: backends that cannot
652+
distinguish scopes return a superset. See RFC 0005 (Qualified Attribute Queries).
653+
654+
The HTTP API expects this as a URL-encoded JSON array.
655+
Example: [{"key":"http.status_code","value":"200","scope":"span"}]
584656
description: |-
585657
Query parameters to find traces.
586658

0 commit comments

Comments
 (0)