shared-go Auth, Errors & Metrics
tektree beginner 4 min read
ELI5
shared-go is the toolbox every service grabs on the way out of the locker room: the same hammer (JWT validator), the same red error stickers, the same stopwatch (Prometheus metric). Every service measures and fails the same way so the monitoring wall reads consistently.
Technical Deep Dive
libs/shared-go/pkg/ ships five packages: auth, config, logging, errors, metrics. Every Go service imports the ones it needs; nothing in here is service-specific.
Package Class Diagram
classDiagram class BaseConfig { +string ServiceName +string Environment +int Port +string MongoURI +string MongoDatabase +string RedisURL +string JWTPublicKey +string JWTPrivateKey +string LogLevel +bool DebugMode } class Claims { +string UserID +string Tier +string Role +RegisteredClaims } class TokenValidator { +rsa.PublicKey Pub +Validate(raw string) (*Claims, error) } class AppError { +ErrorCode Code +string Message +int Status +map Details +string TraceID +error Err } class Logger { +zap.Logger inner +WithTraceID(id) Logger +WithUserID(id) Logger +WithService(name) Logger } class ServiceMetrics { +CounterVec RequestsTotal // method, endpoint, status +HistogramVec RequestDuration // method, endpoint +Gauge RequestsInFlight +CounterVec ErrorsTotal // type } class EventMetrics { +CounterVec EventsPublished // event_type +CounterVec EventsConsumed // event_type +HistogramVec EventLatency // event_type +CounterVec EventErrors // event_type, error_type } BaseConfig <.. TokenValidator : reads JWTPublicKey Claims <-- TokenValidator AppError --> ErrorCodeError Codes
libs/shared-go/pkg/errors/errors.go:
ErrorCode | Default HTTP Status |
|---|---|
INVALID_REQUEST | 400 |
UNAUTHORIZED | 401 |
FORBIDDEN | 403 |
NOT_FOUND | 404 |
CONFLICT | 409 |
VALIDATION_ERROR | 422 |
RATE_LIMIT_EXCEEDED | 429 |
INTERNAL_ERROR | 500 |
SERVICE_UNAVAILABLE | 503 |
AppError is wrapped via Err so errors.Is/As chains keep working. Handlers should return AppError and a single response writer translates it to JSON {error: {code, message, status, details, trace_id, documentation_url}} (the contract in docs/docs/api/API_CONTRACTS.md).
Logging
Logger is a Zap wrapper with three context helpers (WithTraceID, WithUserID, WithService) — the standard fields required by OBSERVABILITY_PLAN.md. Production uses JSON encoding with ISO8601 timestamps; dev can switch to console.
Metrics
Two metric structs are shipped as primitives:
ServiceMetrics— HTTP-shaped, for any service handling requests. Labels aremethod,endpoint,statuson the counter; histograms exclude status.EventMetrics— for services that publish or consume events. Labels areevent_type(and additionallyerror_typeon errors).
Names are prefixed by service in production scrape: gamification_requests_total{...} etc., per OBSERVABILITY_PLAN.md.
Why Centralised
A new service should import shared-go/pkg/auth for JWT verification rather than reading the env and parsing the key itself — this keeps the validator, claim shape, and error-on-failure consistent. Drift in any of those fields is the most common cause of “this service rejects a token the gateway accepted.”
Key Terms
BaseConfig→ the env-driven config every service embeds.Claims→ the only canonical JWT shape; do not duplicate per service.AppError→ the only error type that should leave a handler.ServiceMetrics/EventMetrics→ standard Prometheus collectors a service registers and increments.
Q&A
Q: A handler wants to reject with “you don’t own this resource.” Which ErrorCode?
A: FORBIDDEN. Use UNAUTHORIZED only when no valid identity was presented; the user is authenticated, just not allowed.
Q: A panic in a worker goroutine logs without a trace_id. How do you fix it?
A: Use Logger.WithTraceID(traceID) at the top of the goroutine entry (or pass a context-bound logger). Naked Logger writes do not auto-attach trace context.
Q: Two services emit *_request_duration_seconds with different bucket bounds. Is that okay?
A: Functionally yes (Prometheus per-metric-name buckets are independent), but it makes cross-service histograms incomparable. Use the constructor in metrics so all services land on the same bucket grid.
Examples
A new service bootstrap:
cfg := config.MustLoad[MyConfig](config.Defaults())log := logging.New(cfg.LogLevel).WithService(cfg.ServiceName)val := auth.NewTokenValidator(cfg.JWTPublicKey)sm := metrics.NewServiceMetrics(cfg.ServiceName)prometheus.MustRegister(sm.RequestsTotal, sm.RequestDuration, sm.RequestsInFlight, sm.ErrorsTotal)neighbors on the map
- FNP Observability & Prometheus Metrics monitoring FNP systems