OpenTelemetry
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.versionplusz host-detektorok, minden szignálon. - Tracer, meter és logger provider OTLP exporterekkel; a
Handle.SlogHandler()a hivatalosotelslogbridge, amit atelemetrya log-fanoutba fűz, így mindenslog.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éteg | Instrumentáció | Mit kapsz |
|---|---|---|
| HTTP (chi) | otelhttp.NewHandler a router körül | http.server szerver-spanek + http.server.request.duration és társai, standard szemantikus konvenciókkal |
| PostgreSQL (pgx) | otelpgx tracer a poolon | span minden query-re, a SQL-lel (rövidített span-névvel) és időzítéssel |
| PostgreSQL (bun) | bunotel query hook | ugyanez a bun-os projektekben, formázott query-vel |
| Queue (asynq) | producer-span az Enqueue-ban, consumer-span a workerben; az Envelope traceparent-et hordoz | a trace átér a Valkeyen: a listener spanje a kiváltó HTTP-kérés trace-ében ül |
| S3 | otelaws middleware az AWS SDK-n | span minden storage-hívásra |
| Logok | otelslog bridge | minden 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.
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.
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.