jigar

package module
v0.0.0-...-5d75d48 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 10, 2026 License: MIT Imports: 17 Imported by: 0

README

jigar

Go Reference CI Go Report Card

A small, batteries-included OpenTelemetry tracing helper for Go services.

jigar wraps OTLP exporter setup, per-request sampling, and span ergonomics so application code can add tracing in a few lines without learning the full OpenTelemetry API. It ships with middlewares for Gin, Fiber, and net/http.

"jigar" (جیگر) is Persian for liver — also a term of endearment for someone or something you adore. This package is the friendly little instrument that keeps your traces healthy.


Features

  • One-call setup for an OTLP/gRPC or OTLP/HTTP exporter.
  • Per-request opt-in tracing via the X-Should-Trace header.
  • Probabilistic sampling via TraceRatio for the rest of your traffic.
  • Drop-in middlewares for Gin, Fiber, and net/http.
  • A *Span handle with typed setters (SetString, SetInt, SetJSON, ...) that are safe no-ops when a request isn't sampled.
  • No internal globals leaking out, no log.Fatal in library code, no panics.

Install

go get github.com/amkarkhi/jigar

For framework support, also install the matching sub-package:

go get github.com/amkarkhi/jigar/gin       # Gin
go get github.com/amkarkhi/jigar/fiber     # Fiber
go get github.com/amkarkhi/jigar/nethttp   # net/http

Try it locally with Jaeger

The repo ships a docker-compose.yml that runs the Jaeger all-in-one image with the OTLP receivers enabled:

docker compose up -d
# then run one of the examples, e.g.
go run ./examples/http
# in another terminal
curl -H 'X-Should-Trace: true' http://localhost:8080/hello

Open http://localhost:16686, pick your service from the dropdown, and the trace will be there. Stop it with docker compose down.

Quick start

package main

import (
    "context"
    "log"
    "net/http"
    "time"

    "github.com/amkarkhi/jigar"
    jigarhttp "github.com/amkarkhi/jigar/nethttp"
)

func main() {
    ctx := context.Background()
    t, err := jigar.Init(ctx,
        jigar.WithService("my-service"),
        jigar.WithEndpoint("localhost:4317"),
        jigar.WithTraceRatio(0.01), // sample 1% of requests
    )
    if err != nil {
        log.Fatal(err)
    }
    defer func() {
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        _ = t.Shutdown(ctx)
    }()

    mux := http.NewServeMux()
    mux.HandleFunc("/work", func(w http.ResponseWriter, r *http.Request) {
        _, span := jigar.Start(r.Context(), "do-work")
        defer span.End()
        span.SetString("user.id", r.Header.Get("X-User-Id"))
        // ... do work ...
        if err := doWork(); err != nil {
            span.EndWithError(err)
            http.Error(w, err.Error(), 500)
            return
        }
        _, _ = w.Write([]byte("ok"))
    })

    log.Fatal(http.ListenAndServe(":8080", jigarhttp.Middleware(mux, "my-service")))
}

Per-request sampling

Every middleware checks two things:

  1. If the incoming request has X-Should-Trace: true, the request is always traced — useful for forcing a trace on a specific call from production.
  2. Otherwise, the request is sampled with probability TraceRatio.

Set TraceRatio to 0 to disable probabilistic sampling and only trace requests that carry the header. Set it to 1 to trace every request.

Inside handlers, jigar.Start is a no-op for unsampled requests, so calls like span.SetJSON(...) cost nothing when tracing is off.

Framework middlewares

Gin
import jigargin "github.com/amkarkhi/jigar/gin"

r := gin.New()
r.Use(jigargin.Middleware())
Fiber
import jigarfiber "github.com/amkarkhi/jigar/fiber"

app := fiber.New()
app.Use(jigarfiber.Middleware())
net/http
import jigarhttp "github.com/amkarkhi/jigar/nethttp"

handler := jigarhttp.Middleware(mux, "service-name")
http.ListenAndServe(":8080", handler)

Full runnable programs are under examples/.

Span API

ctx, span := jigar.Start(ctx, "fetch-user")
defer span.End()

span.SetString("user.id", id)
span.SetInt("rows.scanned", n)
span.SetBool("cache.hit", false)
span.SetFloat("duration.ms", 12.3)
span.SetJSON("query", queryStruct)         // marshals to JSON

if err := db.QueryRow(...).Scan(...); err != nil {
    span.EndWithError(err)                 // records + ends
    return err
}

All setters are safe to call on an unsampled span — they simply do nothing.

Configuration

Option Default Notes
WithService(name) required Service name reported on every span.
WithEndpoint(addr) required OTLP collector address, e.g. localhost:4317.
WithServiceVersion(v) unset Optional semantic version of the service.
WithProtocol(p) ProtocolGRPC ProtocolGRPC or ProtocolHTTP.
WithInsecure(b) true Disable TLS to the collector.
WithTraceRatio(r) 0 Probabilistic sampling rate (0–1).
WithTracerName(name) service OpenTelemetry tracer name.
WithResourceAttributes(kv...) none Extra OTel resource attributes.
WithExporter(e) OTLP Override the exporter (tests, custom sinks).

Versioning

jigar follows Semantic Versioning. The API is unstable until v1.0.0; breaking changes will be called out in CHANGELOG.md.

Contributing

Contributions are welcome — see CONTRIBUTING.md.

License

MIT

Documentation

Overview

Package jigar is a small, batteries-included OpenTelemetry tracing helper for Go services.

It wraps OTLP exporter setup, request-level sampling via a header, and ergonomic span helpers so application code can add tracing in a few lines without learning the full OpenTelemetry API.

Quick start:

t, err := jigar.Init(ctx,
    jigar.WithService("my-service"),
    jigar.WithEndpoint("localhost:4317"),
    jigar.WithTraceRatio(0.01),
)
if err != nil { return err }
defer t.Shutdown(context.Background())

ctx, span := jigar.Start(ctx, "do-work")
defer span.End()
span.SetString("user.id", userID)

Per-request sampling is controlled by the X-Should-Trace header (forced on when "true") or the configured TraceRatio (probabilistic). Set TraceRatio to 1 to sample every request, or 0 to disable probabilistic sampling and only trace requests that carry the header.

Index

Constants

View Source
const ShouldTraceHeader = "X-Should-Trace"

ShouldTraceHeader is the HTTP header that forces a request to be traced when set to "true", regardless of the configured sampling ratio.

Variables

View Source
var (
	// ErrEndpointRequired is returned by Init/New when WithEndpoint was not set.
	ErrEndpointRequired = errors.New("jigar: endpoint is required")
	// ErrServiceRequired is returned by Init/New when WithService was not set.
	ErrServiceRequired = errors.New("jigar: service is required")
	// ErrNotInitialized is returned by package-level helpers when called
	// before Init.
	ErrNotInitialized = errors.New("jigar: tracer not initialized; call jigar.Init first")
)

Functions

func DecideShouldTrace

func DecideShouldTrace(headerValue string, ratio float64) bool

DecideShouldTrace returns true when the X-Should-Trace header forces sampling on ("true") or, failing that, when a draw against the configured ratio succeeds. ratio<=0 disables probabilistic sampling; ratio>=1 always samples.

Exposed so framework integrations (and tests) can reuse the same decision logic without duplicating it.

func ShouldTrace

func ShouldTrace(ctx context.Context) bool

ShouldTrace reports whether the context was marked for tracing by a jigar middleware. Returns false when the value is absent.

func WithShouldTrace

func WithShouldTrace(ctx context.Context, on bool) context.Context

WithShouldTrace returns a context that carries the should-trace decision. Middleware sets this; Start consults it.

Types

type Config

type Config struct {
	Service        string
	ServiceVersion string
	Endpoint       string
	Protocol       Protocol
	Insecure       bool
	TraceRatio     float64
	TracerName     string
	ResourceAttrs  []attribute.KeyValue
	// Exporter, if set, is used instead of building one from Endpoint/Protocol.
	Exporter sdktrace.SpanExporter
}

Config holds tracer configuration. Build with Option functions rather than constructing this directly; the zero value is not usable.

type Option

type Option func(*Config)

Option configures a Tracer.

func WithEndpoint

func WithEndpoint(endpoint string) Option

WithEndpoint sets the OTLP collector endpoint, e.g. "localhost:4317". Required unless WithExporter is used.

func WithExporter

func WithExporter(e sdktrace.SpanExporter) Option

WithExporter installs a custom span exporter, bypassing the built-in OTLP exporter. Useful for tests or non-OTLP backends.

func WithInsecure

func WithInsecure(insecure bool) Option

WithInsecure controls whether the OTLP connection uses TLS. Default is true, set false for collectors that require TLS.

func WithProtocol

func WithProtocol(p Protocol) Option

WithProtocol selects gRPC (default) or HTTP transport for the default exporter.

func WithResourceAttributes

func WithResourceAttributes(attrs ...attribute.KeyValue) Option

WithResourceAttributes adds extra OpenTelemetry resource attributes.

func WithService

func WithService(name string) Option

WithService sets the service name reported in spans. Required.

func WithServiceVersion

func WithServiceVersion(v string) Option

WithServiceVersion sets the service version reported in spans.

func WithTraceRatio

func WithTraceRatio(ratio float64) Option

WithTraceRatio sets the probabilistic sampling rate for requests that do not carry the X-Should-Trace header. 0 disables probabilistic sampling (only header-forced requests are traced); 1 samples every request.

func WithTracerName

func WithTracerName(name string) Option

WithTracerName overrides the OpenTelemetry tracer name. Defaults to the service name.

type Protocol

type Protocol string

Protocol selects the OTLP transport used by the default exporter.

const (
	// ProtocolGRPC sends spans over OTLP/gRPC. Default.
	ProtocolGRPC Protocol = "grpc"
	// ProtocolHTTP sends spans over OTLP/HTTP (protobuf).
	ProtocolHTTP Protocol = "http"
)

type Span

type Span struct {
	// contains filtered or unexported fields
}

Span is a thin wrapper around an OpenTelemetry span that:

  • is safe to use when tracing is disabled (all methods become no-ops),
  • exposes convenience setters for the common attribute types,
  • lets you marshal arbitrary values to JSON for inspection in the UI.

Always pair Start with span.End, typically with defer.

func Start

func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, *Span)

Start begins a new span on the package's default tracer (set by Init). If the context isn't marked for tracing or Init wasn't called, the returned Span is a no-op and the context is returned unchanged.

func (*Span) AddError

func (s *Span) AddError(err error)

AddError records err on the span and marks the span as errored. No-op when err is nil or tracing is disabled.

func (*Span) End

func (s *Span) End(opts ...trace.SpanEndOption)

End completes the span. No-op if tracing is disabled or the span is nil.

func (*Span) EndWithError

func (s *Span) EndWithError(err error)

EndWithError records err and ends the span. Equivalent to AddError + End.

func (*Span) SetAttributes

func (s *Span) SetAttributes(kv ...attribute.KeyValue)

SetAttributes sets one or more raw OpenTelemetry attributes on the span. Use this when none of the typed helpers fit.

func (*Span) SetBool

func (s *Span) SetBool(key string, value bool)

SetBool sets a bool attribute on the span.

func (*Span) SetFloat

func (s *Span) SetFloat(key string, value float64)

SetFloat sets a float64 attribute on the span.

func (*Span) SetInt

func (s *Span) SetInt(key string, value int64)

SetInt sets an int64 attribute on the span.

func (*Span) SetJSON

func (s *Span) SetJSON(key string, value any)

SetJSON JSON-marshals value and stores it as a string attribute. Useful for dumping a struct into a span for debugging. If marshaling fails the call is a no-op.

func (*Span) SetString

func (s *Span) SetString(key, value string)

SetString sets a string attribute on the span.

func (*Span) SpanID

func (s *Span) SpanID() string

SpanID returns the span ID as a hex string. Returns an empty string if the span is nil, tracing is disabled, or the span context is invalid.

func (*Span) TraceID

func (s *Span) TraceID() string

TraceID returns the trace ID as a hex string. Returns an empty string if the span is nil, tracing is disabled, or the span context is invalid.

func (*Span) TraceInfo

func (s *Span) TraceInfo() (traceID, spanID string)

TraceInfo returns both the trace ID and span ID as hex strings for easy linking to trace backends like Jaeger. Returns empty strings if the span is nil, tracing is disabled, or the span context is invalid.

func (*Span) Underlying

func (s *Span) Underlying() trace.Span

Underlying returns the wrapped OpenTelemetry span. May be nil when tracing is disabled.

type Tracer

type Tracer struct {
	// contains filtered or unexported fields
}

Tracer is a configured OpenTelemetry tracer with an OTLP exporter and the helpers in this package. Create one with New or Init.

func Default

func Default() *Tracer

Default returns the tracer installed by Init, or nil if Init has not been called. The package-level Start uses this on every span — kept lock-free.

func Init

func Init(ctx context.Context, opts ...Option) (*Tracer, error)

Init builds a Tracer and stores it as the package default used by the package-level Start, Default, Shutdown and the middleware sub-packages. Call this once at process startup.

Init is safe to call concurrently with reads of Default but should not be called more than once; subsequent calls return the existing tracer and ignore the new options.

func New

func New(ctx context.Context, opts ...Option) (*Tracer, error)

New builds a Tracer from the given options without touching package-level state. Use this when you want explicit control of the tracer's lifetime or when wiring multiple tracers in tests.

The returned Tracer's TracerProvider is also installed as the OpenTelemetry global TracerProvider so that third-party instrumentations pick it up. Call Shutdown to flush and close.

func (*Tracer) Config

func (t *Tracer) Config() Config

Config returns a copy of the configuration the tracer was built with.

func (*Tracer) Provider

func (t *Tracer) Provider() trace.TracerProvider

Provider returns the underlying OpenTelemetry TracerProvider. Useful when wiring third-party instrumentations that take a provider explicitly.

func (*Tracer) Shutdown

func (t *Tracer) Shutdown(ctx context.Context) error

Shutdown flushes pending spans and shuts down the underlying provider. Safe to call on a nil receiver.

func (*Tracer) Start

func (t *Tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, *Span)

Start begins a new span. If the context isn't marked for tracing (see middleware or WithShouldTrace), the returned Span is a no-op. A nil receiver is safe and also yields a no-op span.

Directories

Path Synopsis
examples
fiber command
Example: minimal Fiber server instrumented with jigar.
Example: minimal Fiber server instrumented with jigar.
gin command
Example: minimal Gin server instrumented with jigar.
Example: minimal Gin server instrumented with jigar.
http command
Example: minimal net/http server instrumented with jigar.
Example: minimal net/http server instrumented with jigar.
Package fiber provides a Fiber middleware that integrates with jigar.
Package fiber provides a Fiber middleware that integrates with jigar.
Package gin provides a Gin middleware that integrates with jigar.
Package gin provides a Gin middleware that integrates with jigar.
Package nethttp provides a net/http middleware that integrates with jigar.
Package nethttp provides a net/http middleware that integrates with jigar.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL