Bier.Plugs.Observability (bier v0.1.0)

Copy Markdown View Source

Cross-cutting observability middleware, applied to every request before it reaches Bier.Plugs.ActionController. It mirrors PostgREST's two request/response-spanning concerns:

  • Server-Timing (server-timing-enabled): when enabled, every response carries a Server-Timing header with the per-phase durations PostgREST reports — jwt, parse, plan, transaction, response — joined by ", " in that fixed order, each rendered <name>;dur=<ms> with EXACTLY one fractional digit (showFFloat (Just 1), Performance.hs). The durations are measured: each phase is timed at its real call site via Bier.ServerTiming.measure/2 and accumulated for the request; a phase that did no work for a given request reports 0.0 (never a fabricated share of the total). The metric set does not depend on the action — withTiming branches only on the config key (App.hs), so an OPTIONS response reports plan and transaction too, both 0.0 (it builds no plan and opens no transaction). When disabled the header is omitted entirely.

  • Server version header: every response carries Server: bier/<version> (Bier.version/0). This is a deliberate divergence (#122): upstream sends Server: postgrest/<version> (set once as a Warp server setting — setServerName, App.hs), but a Server header names the software that built the response. Like upstream's, the header is unconditional — no config key gates it and it rides on every response whatever the method, status or content type, error responses included.

  • Trace header passthrough (server-trace-header): when configured with a header name (e.g. X-Request-Id), the incoming value of that header is echoed verbatim on the response. An empty/nil configuration is a no-op — the header is not echoed.

  • Access log + log-level + log-query (#28): every response whose status passes the log-level filter (Bier.RequestLog.should_log?/2, PostgREST Logger.hs shouldLogResponse) emits one Apache-combined access line through Elixir's Logger — at :error for 5xx, :warning for 4xx, :info otherwise — with the resolved request role as the user field. With log-query on, the SQL the request executed (recorded into Bier.RequestLog's process-scoped accumulator at the execution sites) is logged under the same filter, each statement single-lined. log-level never alters the response itself.

The headers are written in Plug.Conn.register_before_send/2 callbacks, which fire synchronously while the response is sent — at which point every phase the request ran (recorded into Bier.ServerTiming's process-scoped accumulator as it went) is available, including response, since Bier.Render records its rendering time before the caller calls send_resp.