Request tracing for PhoenixGenApi with per-key log files.
Traces requests that match a configured request_type or user_id, writing
each trace as a key=value line to a dedicated log file (one file per
request type, one file per user id).
Why this is fast
The hot-path check performed on every executed request is deliberately tiny:
- a single
:persistent_termread to know whether tracing is enabled at all - two in-memory membership checks against the enabled
request_type/user_idsets - if nothing matches, the call returns immediately
Only when a request matches does it dispatch an asynchronous message to the writer process, which performs the file I/O. Tracing therefore never blocks request execution and adds negligible overhead when disabled or unmatched.
Enabling tracing
Tracing is fully opt-in. It is disabled by default and only writes when a request matches an explicitly enabled request type or user id.
At runtime (functions)
# Trace a single request type
PhoenixGenApi.Tracer.enable_request_type("get_user")
# Trace several request types at once
PhoenixGenApi.Tracer.enable_request_type(["get_user", "create_order"])
# Trace all requests for a specific user
PhoenixGenApi.Tracer.enable_user_id("user_123")
# Stop tracing
PhoenixGenApi.Tracer.disable_request_type("get_user")
PhoenixGenApi.Tracer.disable_user_id("user_123")Via configuration
config :phoenix_gen_api, :tracer,
enabled: true,
log_dir: "log/phoenix_gen_api_traces",
max_file_bytes: 50_000_000,
max_backup_files: 5,
log_level: :debug,
request_types: ["get_user"],
user_ids: ["user_123"]Config options:
:enabled— global on/off switch (default:false):log_dir— directory for the per-key trace files (default:"log/phoenix_gen_api_traces"):max_file_bytes— rotate a trace file once it exceeds this size (default:50_000_000= 50 MB):max_backup_files— how many rotated.1,.2, ... backup files to keep (default:5):log_level—Loggerlevel temporarily raised globally while tracing is enabled, so debug/info output is captured (default::debug):request_types— request types to trace at startup:user_ids— user ids to trace at startup
Trace files
Files are named after the traced key:
log/phoenix_gen_api_traces/request_type-get_user.log
log/phoenix_gen_api_traces/user_id-user_123.logWhen a request matches both an enabled request type and an enabled user id, one line is written to each matching file.
Each line is space-separated key=value, e.g.:
timestamp=2026-08-17T12:00:00.000Z node=app@host event=request_start request_id=req_1 user_id=user_123 device_id=dev_1 request_type=get_user service=user_service version=nil args="%{"id" => "u1"}"Tracing every action of a request
Tracing does not stop at the three lifecycle events. While a request is being traced, the trace file records every action the request takes:
- structured milestone events (config lookup, hooks, rate limit, permission, argument conversion, execution, retries, RPC fallback, async/stream dispatch, errors)
- raw
Loggeroutput emitted by any process that touches the request (event=loglines withlevel,pid,mfaandmessage)
Raw log capture is driven by process metadata: begin_trace/1 attaches
phoenix_gen_api_trace and phoenix_gen_api_trace_request metadata to the
current process, and worker processes re-apply that metadata so their logs are
captured too. While tracing is enabled the global Logger level is raised to
:log_level so debug/info messages reach the capture handler, and it is
restored when tracing is disabled.
The trace context can also be managed by hand:
ctx = PhoenixGenApi.Tracer.begin_trace(request) # nil when not traced
PhoenixGenApi.Tracer.trace_event("my_step", %{"status" => "ok"})
PhoenixGenApi.Tracer.end_trace(ctx) # restores metadataEvents written per traced request:
event=request_start— emitted when the executor begins handling the request, includes the full request (includingargs)event=config_lookup— the service config was resolved (ok), missing (not_found) or disabledevent=hook_before— abefore_executehook ran (ok) or failedevent=rate_limit— rate limiter decision:allowed,limited(withretry_after_ms) orerrorevent=permission— emitted after the permission check, includespermission=allowed|deniedandpermission_modeevent=arguments— argument conversion succeeded (withcount) or failedevent=execution— the MFA was invoked, withmode=local|remoteandmfaevent=error— execution raised/exited/errored, withkindanderrorevent=retry/event=retry_exhausted— local and remote retry attemptsevent=rpc_fallback— a remote node failed and the request fell backevent=async/event=stream— async/stream dispatch (queued,queue_full,started,timeout,error)event=hook_after— anafter_executehook ran (ok) or failedevent=log— a rawLoggerline emitted during the traceevent=request_end— emitted when execution finishes, includessuccess,async,duration_usanderror(if any)
Inspecting state
PhoenixGenApi.Tracer.enabled?()
PhoenixGenApi.Tracer.enabled_request_types()
PhoenixGenApi.Tracer.enabled_user_ids()
PhoenixGenApi.Tracer.status()
Summary
Functions
Attaches trace metadata to the current process when the given request is traced.
Starts a trace context for a request in the current process.
Returns a specification to start this module under a supervisor.
Clears all traced request types and user ids.
Updates writer settings at runtime.
Disables tracing for one or more request types.
Disables tracing for one or more user ids.
Enables tracing for one or more request types.
Enables tracing for one or more user ids.
Returns true when tracing is globally enabled.
Returns the list of currently traced request types.
Returns the list of currently traced user ids.
Ends a trace context started with begin_trace/1, restoring the process
metadata to its previous state.
Blocks until the writer has processed all pending trace messages.
Enables or disables tracing globally at runtime.
Starts the tracer writer process and loads the :tracer config.
Returns a status snapshot including config, enabled keys and open trace files.
Writes a structured milestone event for the request currently being traced in the current process.
Traces the permission check result for a request.
Hot-path hook called by the executor when a request starts executing.
Traces the final execution result of a request.
Functions
@spec apply_trace_metadata(PhoenixGenApi.Structs.Request.t()) :: :ok
Attaches trace metadata to the current process when the given request is traced.
Used by processes that continue request execution in a separate process
(e.g. PhoenixGenApi.StreamCall), so their Logger output is captured.
@spec begin_trace(PhoenixGenApi.Structs.Request.t()) :: {keyword(), [{atom(), String.t()}]} | nil
Starts a trace context for a request in the current process.
When the request matches an enabled request type or user id, this writes the
request_start line and attaches trace metadata to the current process so
that both trace_event/2 calls and raw Logger output emitted while the
request is processed are captured into the matching trace files.
Returns nil when the request is not traced; otherwise a context that must
be passed to end_trace/1 to restore the process metadata.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear() :: :ok
Clears all traced request types and user ids.
@spec configure(keyword()) :: :ok | {:error, :not_started}
Updates writer settings at runtime.
Accepts a keyword list with any of :log_dir, :max_file_bytes, or
:max_backup_files. Open trace files are not relocated; the new settings
apply to subsequently opened files.
Disables tracing for one or more request types.
Disables tracing for one or more user ids.
Enables tracing for one or more request types.
Accepts a single binary or a list of binaries.
Enables tracing for one or more user ids.
@spec enabled?() :: boolean()
Returns true when tracing is globally enabled.
Note: tracing is only written for requests that also match an enabled request type or user id.
@spec enabled_request_types() :: [String.t()]
Returns the list of currently traced request types.
@spec enabled_user_ids() :: [String.t()]
Returns the list of currently traced user ids.
Ends a trace context started with begin_trace/1, restoring the process
metadata to its previous state.
@spec flush() :: :ok | :error
Blocks until the writer has processed all pending trace messages.
Intended for tests and administrative use; normal production code should rely on the asynchronous writer.
@spec set_enabled(boolean()) :: :ok
Enables or disables tracing globally at runtime.
While tracing is enabled, the primary log level is raised to the configured
:log_level (default :debug) so that debug/info output from traced
requests can be captured; the previous level is restored when tracing is
disabled. Low-level messages that do not belong to a traced request remain
filtered out of the console.
Starts the tracer writer process and loads the :tracer config.
@spec status() :: map()
Returns a status snapshot including config, enabled keys and open trace files.
Writes a structured milestone event for the request currently being traced in the current process.
Requires an active trace context (set by begin_trace/1 or
apply_trace_metadata/1). The event line includes the traced request's
context fields plus extra (a string-keyed map of key => value).
@spec trace_permission( PhoenixGenApi.Structs.Request.t(), PhoenixGenApi.Structs.FunConfig.t(), :allowed | :denied ) :: :ok
Traces the permission check result for a request.
result is :allowed or :denied. Includes the permission mode
(e.g. {:arg, "user_id"} or {:callback, {mod, fun, args}}).
@spec trace_request(PhoenixGenApi.Structs.Request.t()) :: :ok
Hot-path hook called by the executor when a request starts executing.
No-op when tracing is disabled or the request matches nothing. Cheap by design: two in-memory membership lookups and, only on match, an asynchronous message to the writer.
@spec trace_result(PhoenixGenApi.Structs.Request.t(), term(), non_neg_integer()) :: :ok
Traces the final execution result of a request.
result is the value returned by the executor (a %Response{}, an
{:ok, :no_response} tuple, or an exception-derived error).