Skip to content

iw4p/spancheck

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

spancheck

Reusable runtime OpenTelemetry tracing contract validation for Go.

spancheck lets an app define its tracing contract in YAML, load it at startup, and validate emitted spans at runtime through an OpenTelemetry SpanProcessor without changing existing tracer.Start(...) callsites.

Flow

flowchart TB
    subgraph Setup
        A["App-owned YAML contract"]
        B["spancheck loads and validates contract"]
        C["Registry + Validation Processor"]
        D["TracerProvider configured"]
        A --> B --> C --> D
    end

    subgraph Runtime
        E["Existing app code<br/>tracer.Start(...)"]
        F["Spans end"]
        G["Processor validates span"]
        H["Warnings logged"]
        E --> F --> G --> H
    end
Loading

Package

Public import path:

import "github.com/iw4p/spancheck/tracing"

Usage

The library accepts raw YAML bytes. That means apps can supply the contract with embed, os.ReadFile(...), or any other config source.

Embedded YAML is a good default when the contract is versioned with the app and deployed together with it.

Option 1: Embed YAML

package main

import (
	_ "embed"
	"log/slog"

	"go.opentelemetry.io/otel"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"

	"github.com/iw4p/spancheck/tracing"
)

//go:embed tracing-conventions.yml
var contractYAML []byte

func setupTracing(logger *slog.Logger, exporter sdktrace.SpanExporter) error {
	registry, err := tracing.LoadRegistry(contractYAML)
	if err != nil {
		return err
	}

	processor := tracing.NewValidationProcessor(registry, logger)
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithSpanProcessor(processor),
		sdktrace.WithBatcher(exporter),
	)

	otel.SetTracerProvider(tp)
	return nil
}

Option 2: Read YAML from file

package main

import (
	"log/slog"
	"os"

	"go.opentelemetry.io/otel"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"

	"github.com/iw4p/spancheck/tracing"
)

func setupTracing(logger *slog.Logger, exporter sdktrace.SpanExporter) error {
	contractYAML, err := os.ReadFile("tracing-conventions.yml")
	if err != nil {
		return err
	}

	registry, err := tracing.LoadRegistry(contractYAML)
	if err != nil {
		return err
	}

	processor := tracing.NewValidationProcessor(registry, logger)
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithSpanProcessor(processor),
		sdktrace.WithBatcher(exporter),
	)

	otel.SetTracerProvider(tp)
	return nil
}

Example tracing-conventions.yml:

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

Runtime behavior

  • Unknown span names produce a warning with type unknown_span
  • Missing required attributes produce a warning with type missing_required_attribute
  • Startup contract errors are returned normally from LoadContract(...) and LoadRegistry(...)
  • Existing tracer.Start(...) callsites do not need wrappers or changes

Demo

Run the sample app:

go run ./demo

The demo embeds its own contract file and shows both valid spans and runtime warnings for contract violations.

Development

Run tests:

go test ./...

About

Runtime OpenTelemetry span contract validation from YAML (Go)

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages