Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions app/vtselect/traces/jaeger/jaeger.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,17 @@ func parseJaegerTraceQueryParam(_ context.Context, r *http.Request) (*query.Trac
// 1. retrieved from `span_attr:otel.status_description` field directly.
// 2. converted from `status_message` field for Jaeger API.
for k, v := range p.Attributes {
// Jaeger UI encodes key!=value as JSON {"key!": "value"}; strip "!" before field mapping.
negated := strings.HasSuffix(k, "!")
if negated {
k = strings.TrimSuffix(k, "!")
}
store := func(field string) {
if negated {
field = "!" + field
}
attributesFilter[field] = v
}
// convert to OpenTelemetry field name in storage.
if field, ok := spanAttributeMap[k]; ok {
// 2 special cases that need to converted value as well.
Expand All @@ -380,11 +391,11 @@ func parseJaegerTraceQueryParam(_ context.Context, r *http.Request) (*query.Trac
case "span.kind":
v = spanKindMap[v]
}
attributesFilter[field] = v
store(field)
} else if strings.HasPrefix(k, otelpb.InstrumentationScopeAttrPrefix) || strings.HasPrefix(k, otelpb.ResourceAttrPrefix) {
attributesFilter[k] = v
store(k)
} else {
attributesFilter[otelpb.SpanAttrPrefixField+k] = v
store(otelpb.SpanAttrPrefixField + k)
}
}
p.Attributes = attributesFilter
Expand Down
19 changes: 16 additions & 3 deletions app/vtselect/traces/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,24 @@ func getTraceIDList(ctx context.Context, cp *tracecommon.CommonParams, param *Tr
}
if len(param.Attributes) > 0 {
for k, v := range param.Attributes {
negated := strings.HasPrefix(k, "!")
if negated {
k = strings.TrimPrefix(k, "!")
}
if strings.HasPrefix(v, "~") {
// ~ prefix forces regex (e.g. tags={"key":"~value.*"})
qStr += fmt.Sprintf(`AND %q:re(%s) `, k, strconv.Quote(v[1:]))
// ~ prefix forces regex (e.g. tags={"key":"~value.*"}); Jaeger uses {"key!":"~pat"} for key!=~pat.
pat := strconv.Quote(v[1:])
if negated {
qStr += fmt.Sprintf(`AND !(%q:re(%s)) `, k, pat)
} else {
qStr += fmt.Sprintf(`AND %q:re(%s) `, k, pat)
}
} else {
qStr += fmt.Sprintf(`AND %q:=%q `, k, v)
if negated {
qStr += fmt.Sprintf(`AND !(%q:=%q) `, k, v)
} else {
qStr += fmt.Sprintf(`AND %q:=%q `, k, v)
}
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions app/vtselect/traces/query/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package query

import (
"testing"
"time"

"github.com/VictoriaMetrics/VictoriaLogs/lib/logstorage"
)

func TestCheckTraceIDList(t *testing.T) {
Expand All @@ -21,3 +24,16 @@ func TestCheckTraceIDList(t *testing.T) {
f("abcd bcad", false)
f("abcd\"", false)
}

func TestInvertedTagFilterQueryParses(t *testing.T) {
t.Parallel()
ts := time.Now().UnixNano()
for _, qStr := range []string{
`* AND !("span_attr:foo":re("pat")) | last 1 by (_time) partition by (trace_id) | fields _time, trace_id | sort by (_time) desc`,
`* AND !("span_attr:foo":="bar") | last 1 by (_time) partition by (trace_id) | fields _time, trace_id | sort by (_time) desc`,
} {
if _, err := logstorage.ParseQueryAtTimestamp(qStr, ts); err != nil {
t.Fatalf("parse %q: %v", qStr, err)
}
}
}
46 changes: 46 additions & 0 deletions apptest/tests/otlp_ingestion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,52 @@ func getDefaultIngestRequestAndAssertFunc(tc *at.TestCase, sut at.VictoriaTraces
cmpopts.IgnoreFields(at.JaegerAPITracesResponse{}, "Errors", "Limit", "Offset", "Total"),
},
})

// inverted regex (Jaeger encodes key!=~pattern as {"key!": "~pattern"}): inner regex does not match tag -> trace included
tc.Assert(&at.AssertOptions{
Msg: "unexpected /select/jaeger/api/traces response (inverted regex, non-matching pattern)",
Got: func() any {
return sut.JaegerAPITraces(t, at.JaegerQueryParam{
TraceQueryParam: query.TraceQueryParam{
ServiceName: serviceName,
StartTimeMin: spanTime.Add(-10 * time.Minute),
StartTimeMax: spanTime.Add(10 * time.Minute),
Attributes: map[string]string{
"testTag!": "~other.*",
},
},
}, at.QueryOpts{})
},
Want: &at.JaegerAPITracesResponse{
Data: expectTraceData,
},
CmpOpts: []cmp.Option{
cmpopts.IgnoreFields(at.JaegerAPITracesResponse{}, "Errors", "Limit", "Offset", "Total"),
},
})

// inverted regex: inner regex matches tag -> trace excluded
tc.Assert(&at.AssertOptions{
Msg: "unexpected /select/jaeger/api/traces response (inverted regex, matching pattern)",
Got: func() any {
return sut.JaegerAPITraces(t, at.JaegerQueryParam{
TraceQueryParam: query.TraceQueryParam{
ServiceName: serviceName,
StartTimeMin: spanTime.Add(-10 * time.Minute),
StartTimeMax: spanTime.Add(10 * time.Minute),
Attributes: map[string]string{
"testTag!": "~test.*",
},
},
}, at.QueryOpts{})
},
Want: &at.JaegerAPITracesResponse{
Data: []at.TracesResponseData{},
},
CmpOpts: []cmp.Option{
cmpopts.IgnoreFields(at.JaegerAPITracesResponse{}, "Errors", "Limit", "Offset", "Total"),
},
})
}
return req, assertFunc
}
2 changes: 2 additions & 0 deletions docs/victoriatraces/changelog/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The following `tip` changes can be tested by building VictoriaTraces components

## tip

* FEATURE: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and vtselect in [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): support inverted tag filters in the Jaeger query API. Jaeger UI encodes `key!=value` as JSON `{"key!": "value"}` (and regex with `~` as in positive filters). See [#120](https://github.com/VictoriaMetrics/VictoriaTraces/issues/120).

* BUGFIX: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and vtinsert in [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): fix OTLP/gRPC failure with TLS enabled during HTTP/2 ALPN negotiation. See [#108](https://github.com/VictoriaMetrics/VictoriaTraces/issues/108) for details. Thank @hklhai for [the pull request #136](https://github.com/VictoriaMetrics/VictoriaTraces/pull/136).

## [v0.8.1](https://github.com/VictoriaMetrics/VictoriaTraces/releases/tag/v0.8.1)
Expand Down