jaegertracing/jaeger · error · ErrFilterUnsupported

%w: it does not support the built-in field %q of the %q leve

Error message

%w: it does not support the built-in field %q of the %q level

What it means

When a TraceQueryParams carries a Filter but the Reader does not support filters, the query is rewritten into the legacy scalar fields (service name, operation name, duration bounds, attribute map). Only three built-in fields have a legacy equivalent; a predicate on any other built-in field (e.g. span status, span kind, event count) has nowhere to go and is refused with ErrFilterUnsupported wrapping this message. This is a static capability limit of the legacy-shape fallback, not a data or configuration problem.

Source

Thrown at internal/storage/v2/api/tracestore/shape.go:208

	if _, ok := query.Attributes.Get(ref.Key); ok {
		return errRepeatedPredicateOn(ref.Key)
	}
	query.Attributes.PutStr(ref.Key, text)
	return nil
}

// applyField writes a predicate on a built-in field into the legacy field that holds it. Only
// three of the built-ins have one, and a predicate on any of the others is refused.
func applyField(query *TraceQueryParams, op expression.Operator, ref *expression.FieldRef, value expression.Expression) error {
	switch {
	case isField(ref, expression.LevelResource, expression.ResourceFieldService):
		return applyText(&query.ServiceName, op, ref.Name, value)
	case isField(ref, expression.LevelSpan, expression.SpanFieldName):
		return applyText(&query.OperationName, op, ref.Name, value)
	case isField(ref, expression.LevelSpan, expression.SpanFieldDuration):
		return applyDurationBound(query, op, ref.Name, value)
	default:
		return fmt.Errorf("%w: it does not support the built-in field %q of the %q level",
			ErrFilterUnsupported, ref.Name, ref.Level)
	}
}

// isField reports whether the reference names that built-in field. It takes both the level and
// the name because neither identifies a field on its own.
func isField(ref *expression.FieldRef, level expression.Level, name string) bool {
	return ref.Level == level && ref.Name == name
}

// applyText writes an equality on a string-valued legacy field.
func applyText(target *string, op expression.Operator, name string, value expression.Expression) error {
	if op != expression.OpEq {
		return errUnsupportedOperatorOn(op, name)
	}
	text, err := textConstant(name, value)
	if err != nil {
		return err

View on GitHub (pinned to 806f444784)

Solutions

  1. Rewrite the predicate as an AttributeRef or restrict the filter to the three supported built-in fields (resource service, span name, span duration).
  2. Use a Reader/backend that declares native filter support so the filter is passed through instead of downconverted.
  3. Handle errors.Is(err, tracestore.ErrFilterUnsupported) and surface a 'query not supported by this backend' message to the user instead of retrying.

Example fix

// before
filter := &expression.Call{Op: expression.OpEq, Args: []expression.Expression{
  &expression.FieldRef{Level: expression.LevelSpan, Name: expression.SpanFieldStatus},
  &expression.StringValue{Value: "error"}}}
// after
filter := &expression.Call{Op: expression.OpEq, Args: []expression.Expression{
  &expression.AttributeRef{Key: "error", Level: expression.LevelSpan},
  &expression.StringValue{Value: "true"}}}
Defensive patterns

Strategy: validation

Validate before calling

func usesOnlyLegacyFields(f *expression.Call) bool {
  preds, err := flatten(f)
  if err != nil { return false }
  for _, p := range preds {
    ref, ok := p.Args[0].(*expression.FieldRef)
    if !ok { continue }
    ok = (ref.Level == expression.LevelResource && ref.Name == expression.ResourceFieldService) ||
         (ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldName) ||
         (ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldDuration)
    if !ok { return false }
  }
  return true
}

Type guard

func isLegacyFieldRef(e expression.Expression) bool {
  ref, ok := e.(*expression.FieldRef)
  if !ok { return false }
  switch {
  case ref.Level == expression.LevelResource && ref.Name == expression.ResourceFieldService: return true
  case ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldName: return true
  case ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldDuration: return true
  }
  return false
}

Try / catch

qp, err := params.ToLegacyShape()
if err != nil {
  if errors.Is(err, tracestore.ErrFilterUnsupported) {
    return fmt.Errorf("backend cannot serve this filter: %w", err)
  }
  return err
}

Prevention

When it happens

Trigger: Calling TraceQueryParams.ToLegacyShape() (directly or via a Reader lacking filter support) with a Filter whose flat conjunction contains a *expression.FieldRef predicate whose (Level, Name) is not (LevelResource, service), (LevelSpan, name), or (LevelSpan, duration) — e.g. span:status = 'error'.

Common situations: A query interceptor or UI builds a filter on a built-in field like span.status or span.kind assuming full field support; the backend only declares legacy scalar support; version drift where newer expression fields are sent to an older backend path.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/ed7e4a662cfdaa42. Report an issue: GitHub.