Skip to content

Commit e5d1c1f

Browse files
yurishkuroclaude
andcommitted
feat(query): Add structured query filters to trace query protos
Add a structured filter model to both the public api_v3 and the storage/v2 TraceQueryParameters, as the proto/IDL foundation (milestone M1) for RFC 0005. - AttributeFilter leaf: a predicate targeting either a level-qualified attribute (key + level: span|resource|instrumentation|event|link) or a built-in property (duration|name|service|status|kind|…), compared with an operator (eq|ne|gt|lt|regex|exists). - FilterExpression + BooleanOp: a boolean expression tree (AND/OR/NOT) over those leaves. - A repeated FilterExpression `filters` field on TraceQueryParameters; the top-level list is implicitly ANDed. level and op are typed string enumerations (short REST tokens, closed value sets declared in the api_v3 OpenAPI schema via the gnostic enum annotation); property is an open documented vocabulary. 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; 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 e5d1c1f

3 files changed

Lines changed: 231 additions & 0 deletions

File tree

proto/api_v3/query_service.proto

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

46+
// AttributeFilter is a single predicate — a leaf of a FilterExpression tree.
47+
// It targets EITHER a level-qualified attribute (set `key`, optionally `level`)
48+
// OR a built-in property (set `property`); the two targets are mutually
49+
// exclusive. The value is compared with `op`. See RFC 0005 §5–§6.
50+
//
51+
// `level` 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.2.
55+
message AttributeFilter {
56+
// key is the attribute name (e.g. "http.status_code"). Empty when `property`
57+
// is set.
58+
string key = 1;
59+
60+
// level optionally restricts an attribute predicate to a single OpenTelemetry
61+
// attribute level. Empty searches span-or-resource. Level restriction is
62+
// best-effort: a backend rejects a level it cannot honor (see RFC 0005 §7).
63+
// span — Span.attributes (per-operation metadata)
64+
// resource — ResourceSpans.resource.attributes (service/host metadata)
65+
// instrumentation — ScopeSpans.scope.attributes (InstrumentationScope metadata)
66+
// event — Span.events[].attributes (timestamped annotations)
67+
// link — Span.links[].attributes (cross-trace references)
68+
string level = 2 [
69+
(openapi.v3.property) = {
70+
type: "string",
71+
enum: [{yaml: "span"}, {yaml: "resource"}, {yaml: "instrumentation"}, {yaml: "event"}, {yaml: "link"}]
72+
}
73+
];
74+
75+
// property names a built-in span/trace value instead of an attribute, and is
76+
// mutually exclusive with `key`/`level`. The vocabulary is an open, documented
77+
// set (RFC 0005 §5.2): e.g. duration, name, service, status, kind.
78+
string property = 3;
79+
80+
// op is the predicate to apply. Empty (omitted) is treated as "eq". Backends
81+
// reject unimplemented operators with gRPC status UNIMPLEMENTED. One of:
82+
// eq — exact match (default)
83+
// ne — negated exact match
84+
// gt — numeric greater-than
85+
// lt — numeric less-than
86+
// regex — regular-expression match
87+
// exists — key/property presence; `value` is ignored
88+
string op = 4 [
89+
(openapi.v3.property) = {
90+
type: "string",
91+
enum: [{yaml: "eq"}, {yaml: "ne"}, {yaml: "gt"}, {yaml: "lt"}, {yaml: "regex"}, {yaml: "exists"}]
92+
}
93+
];
94+
95+
// value is the string representation of the value to compare against,
96+
// interpreted per the target's type (e.g. numeric for durations). Ignored
97+
// when `op` is "exists".
98+
string value = 5;
99+
}
100+
101+
// BooleanOp combines sub-expressions with a boolean operator. Backends that
102+
// support only conjunctive queries (e.g. flat-index stores) reject "or"/"not".
103+
message BooleanOp {
104+
// op is "and", "or", or "not" ("not" takes a single operand).
105+
string op = 1 [
106+
(openapi.v3.property) = {
107+
type: "string",
108+
enum: [{yaml: "and"}, {yaml: "or"}, {yaml: "not"}]
109+
}
110+
];
111+
repeated FilterExpression operands = 2;
112+
}
113+
114+
// FilterExpression is a node in the filter tree: either a leaf predicate or a
115+
// boolean combination of sub-expressions.
116+
message FilterExpression {
117+
oneof node {
118+
AttributeFilter predicate = 1;
119+
BooleanOp bool = 2;
120+
}
121+
}
122+
46123
// Query parameters to find traces.
47124
//
48125
// All fields form a conjunction (e.g., "service_name='X' AND operation_name='Y' AND ..."),
@@ -110,6 +187,21 @@ message TraceQueryParameters {
110187
//
111188
// This field is optional.
112189
bool raw_traces = 9;
190+
191+
// filters is the structured query filter: a boolean expression tree whose
192+
// leaves are predicates over level-qualified attributes or properties. The
193+
// top-level list is implicitly ANDed (and ANDed with the legacy `attributes`
194+
// field), so the common conjunction reads as a flat array while any element
195+
// may nest into a boolean node. See RFC 0005 §6.
196+
//
197+
// The HTTP API expects this as a URL-encoded JSON array.
198+
// Example: [{"predicate":{"key":"http.status_code","value":"500","level":"span"}}]
199+
repeated FilterExpression filters = 10 [
200+
(openapi.v3.property) = {
201+
type: "string",
202+
example: { yaml: "'[{\"predicate\":{\"key\":\"http.status_code\",\"value\":\"500\",\"level\":\"span\"}}]'" }
203+
}
204+
];
113205
}
114206

115207
// Request object to search traces.

proto/storage/v2/trace_storage.proto

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

93+
// AttributeFilter is a single predicate — a leaf of a FilterExpression tree.
94+
// It targets EITHER a level-qualified attribute (key[+level]) OR a built-in
95+
// property; the two are mutually exclusive. Mirrors jaeger.api_v3.AttributeFilter.
96+
// `level` and `op` are string enumerations (not proto enums) so plugins receive
97+
// short, forgiving tokens that align with the best-effort contract. See RFC 0005 §5–§6.
98+
message AttributeFilter {
99+
string key = 1; // attribute key; empty when property is set
100+
string level = 2; // one of: span|resource|instrumentation|event|link; empty = span-or-resource
101+
string property = 3; // built-in value (duration|name|service|status|kind|…); mutually exclusive with key/level
102+
string op = 4; // one of: eq|ne|gt|lt|regex|exists; empty = eq
103+
string value = 5; // ignored when op = "exists"
104+
}
105+
106+
// BooleanOp combines sub-expressions with "and"|"or"|"not" ("not" takes one
107+
// operand). Conjunction-only backends (flat-index stores) reject "or"/"not".
108+
message BooleanOp {
109+
string op = 1;
110+
repeated FilterExpression operands = 2;
111+
}
112+
113+
// FilterExpression is a node in the filter tree: a leaf predicate or a boolean
114+
// combination of sub-expressions.
115+
message FilterExpression {
116+
oneof node {
117+
AttributeFilter predicate = 1;
118+
BooleanOp bool = 2;
119+
}
120+
}
121+
93122
// TraceQueryParameters contains query parameters to find traces. For a detailed
94123
// definition of each field in this message, refer to `TraceQueryParameters` in `jaeger.api_v3`
95124
// (https://github.com/jaegertracing/jaeger-idl/blob/main/proto/api_v3/query_service.proto).
@@ -102,6 +131,10 @@ message TraceQueryParameters {
102131
google.protobuf.Duration duration_min = 6;
103132
google.protobuf.Duration duration_max = 7;
104133
int32 search_depth = 8;
134+
// filters is the structured query filter: a boolean expression tree whose
135+
// leaves are attribute/property predicates. The top-level list is implicitly
136+
// ANDed (and ANDed with `attributes`). See RFC 0005 §6.
137+
repeated FilterExpression filters = 9;
105138
}
106139

107140
// FindTracesRequest represents a request to find traces.

swagger/api_v3/query_service.openapi.yaml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,88 @@ 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: |-
420+
key is the attribute name (e.g. "http.status_code"). Empty when `property`
421+
is set.
422+
level:
423+
enum:
424+
- span
425+
- resource
426+
- instrumentation
427+
- event
428+
- link
429+
type: string
430+
description: |-
431+
level optionally restricts an attribute predicate to a single OpenTelemetry
432+
attribute level. Empty searches span-or-resource. Level restriction is
433+
best-effort: a backend rejects a level it cannot honor (see RFC 0005 §7).
434+
span — Span.attributes (per-operation metadata)
435+
resource — ResourceSpans.resource.attributes (service/host metadata)
436+
instrumentation — ScopeSpans.scope.attributes (InstrumentationScope metadata)
437+
event — Span.events[].attributes (timestamped annotations)
438+
link — Span.links[].attributes (cross-trace references)
439+
property:
440+
type: string
441+
description: |-
442+
property names a built-in span/trace value instead of an attribute, and is
443+
mutually exclusive with `key`/`level`. The vocabulary is an open, documented
444+
set (RFC 0005 §5.2): e.g. duration, name, service, status, kind.
445+
op:
446+
enum:
447+
- eq
448+
- ne
449+
- gt
450+
- lt
451+
- regex
452+
- exists
453+
type: string
454+
description: |-
455+
op is the predicate to apply. Empty (omitted) is treated as "eq". Backends
456+
reject unimplemented operators with gRPC status UNIMPLEMENTED. One of:
457+
eq — exact match (default)
458+
ne — negated exact match
459+
gt — numeric greater-than
460+
lt — numeric less-than
461+
regex — regular-expression match
462+
exists — key/property presence; `value` is ignored
463+
value:
464+
type: string
465+
description: |-
466+
value is the string representation of the value to compare against,
467+
interpreted per the target's type (e.g. numeric for durations). Ignored
468+
when `op` is "exists".
469+
description: |-
470+
AttributeFilter is a single predicate — a leaf of a FilterExpression tree.
471+
It targets EITHER a level-qualified attribute (set `key`, optionally `level`)
472+
OR a built-in property (set `property`); the two targets are mutually
473+
exclusive. The value is compared with `op`. See RFC 0005 §5–§6.
474+
475+
`level` and `op` are string enumerations (not proto enums) so the REST/JSON
476+
representation carries short, human-readable tokens (e.g. "span", "eq") rather
477+
than verbose CONSTANT_CASE names; the closed value set is declared in the
478+
OpenAPI schema via the annotations below. See RFC 0005 §6.2.
479+
jaeger.api_v3.BooleanOp:
480+
type: object
481+
properties:
482+
op:
483+
enum:
484+
- and
485+
- or
486+
- not
487+
type: string
488+
description: op is "and", "or", or "not" ("not" takes a single operand).
489+
operands:
490+
type: array
491+
items:
492+
$ref: '#/components/schemas/jaeger.api_v3.FilterExpression'
493+
description: |-
494+
BooleanOp combines sub-expressions with a boolean operator. Backends that
495+
support only conjunctive queries (e.g. flat-index stores) reject "or"/"not".
414496
jaeger.api_v3.DependenciesResponse:
415497
required:
416498
- dependencies
@@ -433,6 +515,16 @@ components:
433515
type: string
434516
callCount:
435517
type: string
518+
jaeger.api_v3.FilterExpression:
519+
type: object
520+
properties:
521+
predicate:
522+
$ref: '#/components/schemas/jaeger.api_v3.AttributeFilter'
523+
bool:
524+
$ref: '#/components/schemas/jaeger.api_v3.BooleanOp'
525+
description: |-
526+
FilterExpression is a node in the filter tree: either a leaf predicate or a
527+
boolean combination of sub-expressions.
436528
jaeger.api_v3.FindTraceSummariesRequest:
437529
type: object
438530
properties:
@@ -581,6 +673,20 @@ components:
581673
The trace will be returned exactly as stored.
582674
583675
This field is optional.
676+
filters:
677+
example: '[{"predicate":{"key":"http.status_code","value":"500","level":"span"}}]'
678+
type: string
679+
items:
680+
$ref: '#/components/schemas/jaeger.api_v3.FilterExpression'
681+
description: |-
682+
filters is the structured query filter: a boolean expression tree whose
683+
leaves are predicates over level-qualified attributes or properties. The
684+
top-level list is implicitly ANDed (and ANDed with the legacy `attributes`
685+
field), so the common conjunction reads as a flat array while any element
686+
may nest into a boolean node. See RFC 0005 §6.
687+
688+
The HTTP API expects this as a URL-encoded JSON array.
689+
Example: [{"predicate":{"key":"http.status_code","value":"500","level":"span"}}]
584690
description: |-
585691
Query parameters to find traces.
586692

0 commit comments

Comments
 (0)