telemetry

OpenTelemetry

Trace-ek, metrikák és logok egy hívással, HTTP, adatbázis, queue és S3 instrumentáció, standard OTEL_* konfigurációval.

Az OpenTelemetry a kit observability-gerince: minden span, metrika és exportált logrekord rajta megy keresztül, a Sentry is csak egy plusz exporter a tetején. A Go-ökoszisztémában az elosztott trace-elés standard, gyártófüggetlen infrastruktúra: a kit ezt kapcsolja be úgy, hogy nem kell hozzá megtanulnod az OTel SDK-t.

otelx: a bootstrap

import "github.com/gp-system/telemetry/otelx"

func Setup(ctx context.Context, cfg Config, opts ...Option) (*Handle, error)

func (h *Handle) SlogHandler() slog.Handler              // OTLP log-bridge (dev módban nil)
func (h *Handle) Shutdown(ctx context.Context) error     // providerek flush + stop

func WithSpanExporter(exp sdktrace.SpanExporter) Option  // plusz span-sink a fő OTLP mellé

Egy hívás beköti:

  • Resource: service.name / service.version plusz host-detektorok, minden szignálon.
  • Tracer, meter és logger provider OTLP exporterekkel; a Handle.SlogHandler() a hivatalos otelslog bridge, amit a telemetry a log-fanoutba fűz, így minden slog.InfoContext(ctx, ...) trace/span ID-val exportálódik.
  • Propagáció: W3C TraceContext + Baggage, tehát az upstream hívók trace-e automatikusan folytatódik, a tieid pedig továbbadódnak.

Közvetlenül ritkán hívod: a chassis a telemetry.Setup-on keresztül futtatja. A WithSpanExporter is elsősorban a telemetry csomagnak létezik: így kapja meg a Sentry ugyanazokat a spaneket egy második batcherrel (a nil exportert csendben eldobja, ezért feltétel nélkül átadható).

Az alkalmazáskód soha nem importál OTel SDK-csomagot: az OTel logs SDK még 1.0 előtti, és a verzió-churn a kit otelx csomagjában marad, nem a projektjeidben. Amit importálsz, az a stabil go.opentelemetry.io/otel API (lásd lentebb, a saját spaneknél).

Konfiguráció: két kit-kapcsoló, minden más standard

type Config struct {
    ServiceName    string `env:"OTEL_SERVICE_NAME"`                    // dev módon kívül kötelező
    ServiceVersion string `env:"OTEL_SERVICE_VERSION" envDefault:"dev"`
    Dev            bool   `env:"OTEL_DEV_MODE" envDefault:"false"`
}

Az exporter-végpontok szándékosan nem kit-konfiguráció. Az exportereket a contrib/exporters/autoexport választja ki a standard OTel változókból, így a deploymentedet ugyanúgy konfigurálod, mint bármely más OTel-instrumentált appot: a platformcsapat OTel-tudása és minden vendor-dokumentáció változtatás nélkül érvényes:

OTEL_SERVICE_NAME=shop
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
# szignálonkénti felülbírálás (default mindenhol: otlp):
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp     # vagy: prometheus → scrape-endpoint OTLP-push helyett
OTEL_LOGS_EXPORTER=otlp
# sampling is OTel-standard:
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

Dev módban (OTEL_DEV_MODE=true) egyáltalán nem készül exporter: a trace-ek és metrikák no-opok, OTLP log-bridge sincs, a logok a konzol-handlerre mennek. Nem kell collector, nem kell semmi. Egyetlen kivétel: ha a WithSpanExporter-rel kap egy extra sinket (a gyakorlatban: dev módban beállított SENTRY_DSN), akkor egy dev-only tracer provider mégis felépül, és a spanek collector nélkül, egyenesen oda mennek.

Mit instrumentál a kit magától

Ez a „részletes metrikák" sztori: nem neked kell span-kódot írnod, a kit minden rétege eleve instrumentálva érkezik.

RétegInstrumentációMit kapsz
HTTP (chi)otelhttp.NewHandler a router körülhttp.server szerver-spanek + http.server.request.duration és társai, standard szemantikus konvenciókkal
PostgreSQL (pgx)otelpgx tracer a poolonspan minden query-re, a SQL-lel (rövidített span-névvel) és időzítéssel
PostgreSQL (bun)bunotel query hookugyanez a bun-os projektekben, formázott query-vel
Queue (asynq)producer-span az Enqueue-ban, consumer-span a workerben; az Envelope traceparent-et hordoza trace átér a Valkeyen: a listener spanje a kiváltó HTTP-kérés trace-ében ül
S3otelaws middleware az AWS SDK-nspan minden storage-hívásra
Logokotelslog bridgeminden ctx-szel logolt rekord trace/span ID-val exportálódik

A metrikákat az autoexport által épített MeterProvider gyűjti: alapból OTLP-push a collectorba, OTEL_METRICS_EXPORTER=prometheus-szal pedig Prometheus scrape-endpoint. Grafana-dashboardhoz a HTTP-réteg metrikái (kérés-időtartam hisztogram, aktív kérések) azonnal ott vannak, anélkül, hogy egy sor mérőkódot írtál volna.

Kövesd a rendelést: egyetlen trace

A shop POST /api/v1/shop/orders kérése így néz ki a trace-nézőben (Jaeger, Grafana Tempo, Sentry, bármelyik):

POST /api/v1/shop/orders                        ← HTTP szerver-span (chi middleware)
├── INSERT INTO orders ...                      ← otelpgx, a tranzakción belül
├── UPDATE inventory SET ...                    ← otelpgx
├── INSERT INTO outbox ...                      ← otelpgx (az esemény a tranzakcióval együtt commitol)
└── task event:orderPlaced                      ← consumer-span a workerben: a trace átjött az outboxon és a Valkeyen
    └── enqueue listener:orderPlaced:sendOrderConfirmation   ← fan-out producer-span
        └── task listener:orderPlaced:sendOrderConfirmation  ← itt fut a listener
            └── (a listener spanjei: DB, S3, SMTP, ami a kódjában történik)

A kulcs a queue Envelope: a dispatcher a kérés contextjéből W3C traceparent-et injektál bele, az envelope túléli az outbox-táblát és a Valkeyt, a worker consumer-spanje pedig kicsomagolja és folytatja a trace-t. (Az outbox-relay saját feladó-spanje a relay polling-loopjához tartozik, nem ehhez a trace-hez: a staféta maga az envelope.) A HTTP-kéréstől a visszaigazoló e-mailig egyetlen trace ID, pontosan az, amit a mintaalkalmazás „kövesd a rendelést" táblázata ígér. Ha közben bárhol hiba történik, a Sentry eventje erre a trace-re linkelődik.

Trace-ek megnézése lokálisan

Dev módban a trace-elés alapból no-op, de két olcsó út van, ha látni akarod, mit termel a kit:

A leggyorsabb: dev mód + Sentry

Állíts be egy dev Sentry-projekt DSN-t az OTEL_DEV_MODE=true mellé: a kit ilyenkor egy Sentry-only tracer providert épít, és a spanek collector nélkül a Sentry trace-nézetében landolnak. Részletek: Sentry.

A teljes kép: lokális collector

A generált compose.yml-ben kikommentezve ott az otel-collector service. Élesítsd, tedd le a hivatkozott deploy/otel-collector.yaml configot (egy minimál OTLP-receiver + a kedvenc backended exportere, Jaeger, Tempo, bármi), majd:

OTEL_DEV_MODE=false OTEL_SERVICE_NAME=shop \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
go run ./cmd/shop

Innentől a service pontosan úgy exportál, mint élesben: a logok is, OTLP-n.

A worker binárist ugyanezekkel a változókkal indítsd (saját OTEL_SERVICE_NAME-mel, pl. shop-worker), különben a „kövesd a rendelést" trace-nek csak a HTTP-oldali felét látod.

Saját spanek és metrikák

A kit elve itt is érvényes: centralizál, nem absztrahál. Nincs kit-wrapper a trace-API körül: ahol üzleti szempontból értelmes mérési pont kell, ott a stabil OTel API-t hívod közvetlenül:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
)

var tracer = otel.Tracer("github.com/acme/shop/internal/modules/shop")
var meter = otel.Meter("github.com/acme/shop/internal/modules/shop")

func (s *OrderService) PlaceOrder(ctx context.Context, in PlaceOrder) error {
    ctx, span := tracer.Start(ctx, "shop.place_order")
    defer span.End()
    span.SetAttributes(attribute.Int("order.items", len(in.Items)))

    // ... a span automatikusan a HTTP szerver-span gyereke,
    // mert ugyanazt a ctx-et viszed tovább
}

A otel.Tracer / otel.Meter a globális providerekből dolgozik, amiket a Setup állított be. Dev módban ugyanez a kód no-op, nem kell feltételekkel körbeírni. Számlálóhoz, hisztogramhoz ugyanígy a meter.Int64Counter(...) és társai. Az upstream OTel-dokumentáció egy az egyben érvényes.

A saját span contextjét (ctx) mindig add tovább lefelé: a repository-hívásoknak, a slog.*Context-nek, a dispatchernek. A szülő-gyerek viszonyt kizárólag a context hordozza; egy context.Background()-dal indított hívás kiszakad a trace-ből.

Shutdown

A Handle.Shutdown fordított sorrendben állítja le a providereket (előbb a logok, hivatkozhatnak spanekre, utoljára a trace-ek), és a chassis az életciklus legvégén hívja, minden closered után. Egy SIGTERM után a leállás közben nyitott spanek is elérik a collectort.

Az elérhetetlen collector-végpont nem dönti be a service-t: az exporterek a háttérben újrapróbálkoznak, és végül eldobják a batcheiket, a kérések kiszolgálását nem érinti. A telemetria best-effort, sosem a rendelkezésre állás ára.

Használt patternek

  • Isolation layer a pre-1.0 OTel log SDK köré (Handle, az alkalmazáskód sosem importál OTel SDK-csomagot); lásd Design patternek.
  • LIFO shutdown closer-lánccal: a logok előbb, a trace-ek utoljára állnak le.

Az OpenTelemetry Go-specifikus dokumentációja: opentelemetry.io/docs/languages/go.

Copyright © 2026