Files
go-app/AGENTS.md
T
2026-07-04 17:19:09 -04:00

4.3 KiB

AGENTS.md — go-app

A small framework for bootstrapping logging, OTEL, and HTTP/gRPC servers from config + environment. Apps embed *config.AppConfig in a custom config struct and register services; the framework runs the servers and lifecycle.

The app init pattern (current, v0.17+)

ctx, cncl := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
defer cncl()

// Custom config that embeds *config.AppConfig:
ctx, cfg := app.MustLoadConfigInto(ctx, &config.ServiceConfig{})
cfg.LoadEnv() // SEE GOTCHA 1 — custom-field env is the caller's job

a := &app.App{AppContext: ctx}
a.InitOTEL()

for _, svc := range appServices { sdFunc, _ := svc.Init(ctx, cfg); ... }
a.GRPC, a.HTTP = service.MergeServices(ctx, appServices...) // app-side helper
a.MustRun()
<-a.Done()
// then run collected ShutdownFuncs within a grace period
  • MustLoadConfigInto loads the -config YAML + env into a struct that embeds *config.AppConfig; MustSetupConfigAndLogging is the simple (AppConfig-only) variant. Config loading owns flag.Parse, so register app flags in init().
  • MustRun starts gRPC then HTTP then a monitor goroutine; panics if neither is enabled. Done() unblocks on ctx cancel or a server exit.

Registering servers

Fill option structs and assign to App.GRPC / App.HTTP (or use a service.AppService + MergeServices convenience layer per app):

  • gRPC (pkg/srv/grpc/opts.AppGRPC): append *GRPCService{Name, Type (*grpc.ServiceDesc), Service (impl), GwRegistrationFuncs}. At least one service must add a transport-credentials dial option (e.g. insecure.NewCredentials()) so the grpc-gateway can dial the server. Instrumentation, protovalidate, reflection, and the gateway are auto-wired from GRPCConfig.
  • HTTP (pkg/srv/http/opts.AppHTTP): Funcs (path + handler), Handlers (HTTPHandler{Prefix|Pattern, StripPrefix, Handler} for muxes), and HealthChecks. /health and the Prometheus endpoint are mounted for you.
  • MCP is not a framework feature: mount an MCP streamable-HTTP handler as an HTTPHandler{Prefix: "/api/mcp", Handler: ...}.

OTEL / Prometheus

InitOTEL() sets up tracer + meter providers (no-op if disabled). The meter provider always includes a Prometheus reader; the HTTP server mounts promhttp at OTEL.PrometheusPath (default /metrics). OTLP export auto-enables when OTEL_EXPORTER_OTLP_ENDPOINT is set. Helpers: otel.GetTracer(ctx, name), otel.GetMeter(ctx, name).

Config & schema

AppConfig covers APP_* env (name/env/version, http, grpc, otel, logging). Custom fields are validated for schema via app.CustomSchema(&cfg). Precedence for AppConfig: defaults → env → YAML file.

Known gotchas (fix candidates)

  1. Custom-field env is NOT applied. MustLoadConfigInto only parses env for the embedded AppConfig; its own doc says custom env is "up to the caller." So env:"..." tags on custom fields do nothing unless the app runs env.Parse itself. MustSetupConfigAndLoggingInto[T] looks like the intended helper but is currently an empty stub (returns its args unchanged). → Good candidate: make MustLoadConfigInto/that stub run env.Parse over the custom struct (skipping the embedded AppConfig to preserve precedence).
  2. grpc-gateway path strip defaults to false. GRPCGatewayPath defaults to /grpc-api but GRPCGatewayPathStrip defaults false, so the gateway sees the full prefixed path and 404s every route. Apps must set grpcGatewayPathStrip: true (or accept requests must include the prefix in the proto google.api.http path). Consider defaulting strip to true.
  3. Prometheus unit suffixing. The OTEL→Prometheus exporter appends the OTEL unit as a name suffix, so metric.WithUnit("°F") on econet_setpoint_fahrenheit yields ..._fahrenheit_F. Either don't set WithUnit (bake units into names) or enable prometheus.WithoutUnits() in the meter setup.
  4. README's example main.go is stale (references removed app.AppHTTP / srv.HTTPFunc); trust the template app instead.

Layout

pkg/app (bootstrap/lifecycle/schema) · pkg/config (AppConfig, env/yaml) · pkg/logging (zerolog) · pkg/otel (providers + helpers) · pkg/srv/grpc (+gateway) · pkg/srv/http (+health, +prometheus).