DEV Community

Nima Akbarzadeh
Nima Akbarzadeh

Posted on

You’re Not Validating Your Traces (And That’s a Problem)

gopher and otel

You validate:

  • API inputs
  • database schemas
  • configs

But your traces?

👉 Nothing.

So in production you get:

  • spans missing attributes
  • inconsistent naming across services
  • “valid-looking” traces that are actually wrong

And nobody notices.


The Hidden Problem with OpenTelemetry

OpenTelemetry gives you flexibility.

Too much flexibility.

You can write this:

ctx, span := tracer.Start(ctx, "payment")
Enter fullscreen mode Exit fullscreen mode

And this somewhere else:

ctx, span := tracer.Start(ctx, "payment_authorize")
Enter fullscreen mode Exit fullscreen mode

Or forget attributes:

span.SetAttributes() // missing critical fields
Enter fullscreen mode Exit fullscreen mode

Nothing breaks.

But your observability?

👉 Silently degrades over time.


Meet spancheck

Instead of relying on discipline, you define a tracing contract.

And enforce it at runtime.


The Idea: Tracing Contracts

Define what “correct tracing” looks like — in YAML:

version: 1

attributes:
  - payment.processor
  - payment.id
  - payment.job_name

spans:
  payment.authorize:
    kind: internal
    required_attributes:
      - payment.processor
      - payment.id

  payment.reconcile:
    kind: consumer
    required_attributes:
      - payment.job_name
Enter fullscreen mode Exit fullscreen mode

This becomes your source of truth.


No Code Changes Required

This is the key part.

You don’t touch your existing tracing calls.

Just plug into OpenTelemetry:

registry, _ := tracing.LoadRegistry(contractYAML)

processor := tracing.NewValidationProcessor(registry, logger)

tp := sdktrace.NewTracerProvider(
    sdktrace.WithSpanProcessor(processor),
)

otel.SetTracerProvider(tp)
Enter fullscreen mode Exit fullscreen mode

Your existing code stays exactly the same:

ctx, span := tracer.Start(ctx, "payment.authorize")
Enter fullscreen mode Exit fullscreen mode

What Happens at Runtime

When spans finish, SpanCheck validates them.

You get warnings like:

  • ⚠️ unknown span → unknown_span
  • ⚠️ missing attribute → missing_required_attribute

No crashes. No breaking changes.

But now:

👉 you see when your tracing is wrong


Why Runtime Validation > Linting

Static tools can guess.

Runtime sees reality.

Static linting Runtime validation
checks code checks actual spans
misses dynamic paths sees everything
requires patterns zero assumptions
partial safety full coverage

Why This Matters

Bad traces don’t fail loudly.

They fail silently.

That means:

  • dashboards look fine
  • incidents take longer
  • debugging becomes guesswork

You think you have observability.

You don’t.


What This Unlocks

Once you have contracts:

  • consistent span naming across services
  • guaranteed required attributes
  • shared conventions across teams
  • confidence in your traces

This is the missing layer in most observability setups.


Try It

go get github.com/iw4p/spancheck
Enter fullscreen mode Exit fullscreen mode

Then plug it into your tracer and add a YAML contract.

That’s it.


Final Thought

We enforce:

  • types
  • schemas
  • configs

But not traces?

Observability should be correct by default — not optional.


👉 Check the code here spancheck


Top comments (0)