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 aServer-Timingheader 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 viaBier.ServerTiming.measure/2and accumulated for the request; a phase that did no work for a given request reports0.0(never a fabricated share of the total). The metric set does not depend on the action —withTimingbranches only on the config key (App.hs), so anOPTIONSresponse reportsplanandtransactiontoo, both0.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 sendsServer: postgrest/<version>(set once as a Warp server setting —setServerName,App.hs), but aServerheader 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-levelfilter (Bier.RequestLog.should_log?/2, PostgRESTLogger.hsshouldLogResponse) emits one Apache-combined access line through Elixir'sLogger— at:errorfor 5xx,:warningfor 4xx,:infootherwise — with the resolved request role as the user field. Withlog-queryon, the SQL the request executed (recorded intoBier.RequestLog's process-scoped accumulator at the execution sites) is logged under the same filter, each statement single-lined.log-levelnever 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.