mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-20 18:30:41 +01:00
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description - A logs filter on a bare key that lives in **both** resource and another context (body or scope) ANDed the two: the resource candidate built the `__resource_filter` fingerprint CTE while the other candidate landed as a required main-query term, so the query matched almost nothing. - `ResolveLogicalFields` only preferred resource over `attribute`. Generalized it to prefer resource over **any** other context (attribute, body, scope, …); other contexts stay reachable via their qualified names (e.g. `body.service.name`). #### Issues closed by this PR Closes SigNoz/engineering-pod#6086 Part of https://github.com/SigNoz/platform-pod/issues/3158 #### Additional Information Generalized rather than special-casing body/scope, since any future context would hit the same fingerprint-CTE trap.
276 lines
12 KiB
Go
276 lines
12 KiB
Go
package querybuilder
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/SigNoz/signoz/pkg/flagger"
|
|
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
|
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// familiesOn returns a flagger with resolve_semconv_families on.
|
|
func familiesOn(t *testing.T) flagger.Flagger {
|
|
return flaggertest.WithBooleanFlags(t, map[string]bool{
|
|
flagger.FeatureResolveSemconvFamilies.String(): true,
|
|
})
|
|
}
|
|
|
|
func memberNames(logical *telemetrytypes.LogicalField) []string {
|
|
names := make([]string, 0, len(logical.Members))
|
|
for _, member := range logical.Members {
|
|
names = append(names, member.Name)
|
|
}
|
|
return names
|
|
}
|
|
|
|
// The deployment.environment(.name) family (enabled in pkg/semconv) drives the
|
|
// grouping tests below.
|
|
|
|
// With the resolve_semconv_families flag off, matches stay single-member and
|
|
// selectors stay literal, even when the metadata map has both spellings.
|
|
func TestFamiliesOffByDefault(t *testing.T) {
|
|
fieldKeys := map[string][]*telemetrytypes.TelemetryFieldKey{
|
|
"deployment.environment.name": {{
|
|
Name: "deployment.environment.name",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
"deployment.environment": {{
|
|
Name: "deployment.environment",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
}
|
|
|
|
fields := matchingLogicalFields(false, telemetrytypes.SignalUnspecified, &telemetrytypes.TelemetryFieldKey{Name: "deployment.environment.name"}, fieldKeys)
|
|
require.Len(t, fields, 1)
|
|
assert.False(t, fields[0].IsFamily())
|
|
assert.Equal(t, []string{"deployment.environment.name"}, memberNames(fields[0]))
|
|
|
|
selectors := []*telemetrytypes.FieldKeySelector{
|
|
{Name: "deployment.environment.name", Signal: telemetrytypes.SignalTraces, SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeExact},
|
|
}
|
|
assert.Len(t, ExpandKeySelectorsForFamilies(context.Background(), valuer.UUID{}, flaggertest.New(t), selectors), 1)
|
|
}
|
|
|
|
func TestMatchingLogicalFieldsGroupsFamilyMembers(t *testing.T) {
|
|
fieldKeys := map[string][]*telemetrytypes.TelemetryFieldKey{
|
|
"deployment.environment.name": {{
|
|
Name: "deployment.environment.name",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
"deployment.environment": {{
|
|
Name: "deployment.environment",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
}
|
|
|
|
for _, requested := range []string{"deployment.environment.name", "deployment.environment"} {
|
|
fields := matchingLogicalFields(true, telemetrytypes.SignalUnspecified, &telemetrytypes.TelemetryFieldKey{Name: requested}, fieldKeys)
|
|
require.Len(t, fields, 1, "a family is one logical field, requested via %s", requested)
|
|
logical := fields[0]
|
|
assert.Equal(t, requested, logical.Name, "response identity is the requested spelling")
|
|
assert.Equal(t, telemetrytypes.FieldContextResource, logical.FieldContext)
|
|
assert.True(t, logical.IsFamily())
|
|
assert.Equal(t, []string{"deployment.environment.name", "deployment.environment"}, memberNames(logical),
|
|
"members are current-first regardless of the requested spelling")
|
|
}
|
|
}
|
|
|
|
// Member precedence is the family's current-first order, not lookup arrival
|
|
// order: a current-name key found only under its context-prefixed spelling
|
|
// arrives in the second lookup pass yet must still sort first.
|
|
func TestMatchingLogicalFieldsOrdersMembersByFamilyRank(t *testing.T) {
|
|
fieldKeys := map[string][]*telemetrytypes.TelemetryFieldKey{
|
|
"deployment.environment": {{
|
|
Name: "deployment.environment",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
"resource.deployment.environment.name": {{
|
|
Name: "resource.deployment.environment.name",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
}
|
|
|
|
fields := matchingLogicalFields(true, telemetrytypes.SignalUnspecified, &telemetrytypes.TelemetryFieldKey{
|
|
Name: "deployment.environment.name",
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
}, fieldKeys)
|
|
|
|
require.Len(t, fields, 1)
|
|
assert.Equal(t, []string{"resource.deployment.environment.name", "deployment.environment"}, memberNames(fields[0]))
|
|
}
|
|
|
|
// Non-trace signals have no family support: the requested spelling stays
|
|
// literal, and a family member name never pulls in its siblings.
|
|
func TestMatchingLogicalFieldsKeepsLogsLiteral(t *testing.T) {
|
|
logsKey := func(name string) *telemetrytypes.TelemetryFieldKey {
|
|
return &telemetrytypes.TelemetryFieldKey{
|
|
Name: name,
|
|
Signal: telemetrytypes.SignalLogs,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}
|
|
}
|
|
fieldKeys := map[string][]*telemetrytypes.TelemetryFieldKey{
|
|
"deployment.environment.name": {logsKey("deployment.environment.name")},
|
|
"deployment.environment": {logsKey("deployment.environment")},
|
|
}
|
|
|
|
fields := matchingLogicalFields(true, telemetrytypes.SignalUnspecified, &telemetrytypes.TelemetryFieldKey{Name: "deployment.environment.name"}, fieldKeys)
|
|
require.Len(t, fields, 1)
|
|
assert.False(t, fields[0].IsFamily())
|
|
assert.Equal(t, []string{"deployment.environment.name"}, memberNames(fields[0]))
|
|
}
|
|
|
|
// A family and a genuine same-name collision stack cleanly: the family stays
|
|
// one logical field, the collision adds another, and resource preference keeps
|
|
// the family as a unit.
|
|
func TestResolveLogicalFieldsKeepsFamilyThroughAmbiguity(t *testing.T) {
|
|
fieldKeys := map[string][]*telemetrytypes.TelemetryFieldKey{
|
|
"deployment.environment.name": {
|
|
{
|
|
Name: "deployment.environment.name",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
},
|
|
{
|
|
Name: "deployment.environment.name",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextAttribute,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
},
|
|
},
|
|
"deployment.environment": {{
|
|
Name: "deployment.environment",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
}
|
|
|
|
requested := &telemetrytypes.TelemetryFieldKey{Name: "deployment.environment.name"}
|
|
fields := matchingLogicalFields(true, telemetrytypes.SignalUnspecified, requested, fieldKeys)
|
|
require.Len(t, fields, 2, "resource family + attribute collision")
|
|
|
|
resolved, warning := ResolveLogicalFields(requested, fields)
|
|
assert.NotEmpty(t, warning)
|
|
require.Len(t, resolved, 1)
|
|
assert.Equal(t, telemetrytypes.FieldContextResource, resolved[0].FieldContext)
|
|
assert.Equal(t, []string{"deployment.environment.name", "deployment.environment"}, memberNames(resolved[0]))
|
|
}
|
|
|
|
// Resource wins over every other context, not just attribute: a bare key that
|
|
// also lives in body or scope must collapse to resource alone, so the surviving
|
|
// candidate does not AND against the resource fingerprint CTE.
|
|
func TestResolveLogicalFieldsResourceWinsOverOtherContexts(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
other telemetrytypes.FieldContext
|
|
}{
|
|
{name: "ResourceOverBody", other: telemetrytypes.FieldContextBody},
|
|
{name: "ResourceOverScope", other: telemetrytypes.FieldContextScope},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
requested := &telemetrytypes.TelemetryFieldKey{Name: "service.name"}
|
|
fields := []*telemetrytypes.LogicalField{
|
|
telemetrytypes.SingleLogicalField("service.name", &telemetrytypes.TelemetryFieldKey{
|
|
Name: "service.name",
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}),
|
|
telemetrytypes.SingleLogicalField("service.name", &telemetrytypes.TelemetryFieldKey{
|
|
Name: "service.name",
|
|
FieldContext: testCase.other,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}),
|
|
}
|
|
|
|
resolved, warning := ResolveLogicalFields(requested, fields)
|
|
assert.NotEmpty(t, warning)
|
|
require.Len(t, resolved, 1)
|
|
assert.Equal(t, telemetrytypes.FieldContextResource, resolved[0].FieldContext)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Members of a family with different data types never merge: the identity
|
|
// (signal, context, data type) separates them into distinct logical fields.
|
|
func TestMatchingLogicalFieldsNeverMergesAcrossDataTypes(t *testing.T) {
|
|
fieldKeys := map[string][]*telemetrytypes.TelemetryFieldKey{
|
|
"deployment.environment.name": {{
|
|
Name: "deployment.environment.name",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeString,
|
|
}},
|
|
"deployment.environment": {{
|
|
Name: "deployment.environment",
|
|
Signal: telemetrytypes.SignalTraces,
|
|
FieldContext: telemetrytypes.FieldContextResource,
|
|
FieldDataType: telemetrytypes.FieldDataTypeNumber,
|
|
}},
|
|
}
|
|
|
|
fields := matchingLogicalFields(true, telemetrytypes.SignalUnspecified, &telemetrytypes.TelemetryFieldKey{Name: "deployment.environment.name"}, fieldKeys)
|
|
require.Len(t, fields, 2)
|
|
for _, logical := range fields {
|
|
assert.False(t, logical.IsFamily())
|
|
}
|
|
}
|
|
|
|
func TestExpandKeySelectorsForFamilies(t *testing.T) {
|
|
selectors := []*telemetrytypes.FieldKeySelector{
|
|
{Name: "deployment.environment.name", Signal: telemetrytypes.SignalTraces, SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeExact},
|
|
{Name: "service.name", Signal: telemetrytypes.SignalTraces, SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeExact},
|
|
{Name: "deployment.environment.name", Signal: telemetrytypes.SignalLogs, SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeExact},
|
|
}
|
|
|
|
expanded := ExpandKeySelectorsForFamilies(context.Background(), valuer.UUID{}, familiesOn(t), selectors)
|
|
|
|
names := make([]string, 0, len(expanded))
|
|
for _, selector := range expanded {
|
|
names = append(names, selector.Name)
|
|
}
|
|
assert.Equal(t, []string{
|
|
"deployment.environment.name",
|
|
"service.name",
|
|
"deployment.environment.name",
|
|
"deployment.environment",
|
|
}, names, "one sibling selector for the trace family member; logs and non-family names untouched")
|
|
|
|
sibling := expanded[len(expanded)-1]
|
|
assert.Equal(t, telemetrytypes.SignalTraces, sibling.Signal)
|
|
assert.Equal(t, telemetrytypes.FieldSelectorMatchTypeExact, sibling.SelectorMatchType)
|
|
}
|
|
|
|
func TestExpandKeySelectorsForFamiliesDeduplicatesAndSkipsFuzzy(t *testing.T) {
|
|
both := []*telemetrytypes.FieldKeySelector{
|
|
{Name: "deployment.environment.name", Signal: telemetrytypes.SignalTraces, SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeExact},
|
|
{Name: "deployment.environment", Signal: telemetrytypes.SignalTraces, SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeExact},
|
|
}
|
|
assert.Len(t, ExpandKeySelectorsForFamilies(context.Background(), valuer.UUID{}, familiesOn(t), both), 2, "both spellings already referenced")
|
|
|
|
fuzzy := []*telemetrytypes.FieldKeySelector{
|
|
{Name: "deployment.environment.name", Signal: telemetrytypes.SignalTraces, SelectorMatchType: telemetrytypes.FieldSelectorMatchTypeFuzzy},
|
|
}
|
|
assert.Len(t, ExpandKeySelectorsForFamilies(context.Background(), valuer.UUID{}, familiesOn(t), fuzzy), 1, "fuzzy (search-style) selectors never expand")
|
|
}
|