PhoenixGenApi.Tracer (PhoenixGenApi v2.23.1)

Copy Markdown View Source

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_term read to know whether tracing is enabled at all
  • two in-memory membership checks against the enabled request_type / user_id sets
  • 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_levelLogger level 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.log

When 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 Logger output emitted by any process that touches the request (event=log lines with level, pid, mfa and message)

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 metadata

Events written per traced request:

  • event=request_start — emitted when the executor begins handling the request, includes the full request (including args)
  • event=config_lookup — the service config was resolved (ok), missing (not_found) or disabled
  • event=hook_before — a before_execute hook ran (ok) or failed
  • event=rate_limit — rate limiter decision: allowed, limited (with retry_after_ms) or error
  • event=permission — emitted after the permission check, includes permission=allowed|denied and permission_mode
  • event=arguments — argument conversion succeeded (with count) or failed
  • event=execution — the MFA was invoked, with mode=local|remote and mfa
  • event=error — execution raised/exited/errored, with kind and error
  • event=retry / event=retry_exhausted — local and remote retry attempts
  • event=rpc_fallback — a remote node failed and the request fell back
  • event=async / event=stream — async/stream dispatch (queued, queue_full, started, timeout, error)
  • event=hook_after — an after_execute hook ran (ok) or failed
  • event=log — a raw Logger line emitted during the trace
  • event=request_end — emitted when execution finishes, includes success, async, duration_us and error (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

apply_trace_metadata(request)

@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.

begin_trace(request)

@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.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

clear()

@spec clear() :: :ok

Clears all traced request types and user ids.

configure(opts)

@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.

disable_request_type(request_types)

@spec disable_request_type(String.t() | [String.t()]) :: :ok

Disables tracing for one or more request types.

disable_user_id(user_ids)

@spec disable_user_id(String.t() | [String.t()]) :: :ok

Disables tracing for one or more user ids.

enable_request_type(request_types)

@spec enable_request_type(String.t() | [String.t()]) :: :ok

Enables tracing for one or more request types.

Accepts a single binary or a list of binaries.

enable_user_id(user_ids)

@spec enable_user_id(String.t() | [String.t()]) :: :ok

Enables tracing for one or more user ids.

enabled?()

@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.

enabled_request_types()

@spec enabled_request_types() :: [String.t()]

Returns the list of currently traced request types.

enabled_user_ids()

@spec enabled_user_ids() :: [String.t()]

Returns the list of currently traced user ids.

end_trace(arg1)

@spec end_trace({keyword(), term()} | nil) :: :ok

Ends a trace context started with begin_trace/1, restoring the process metadata to its previous state.

flush()

@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.

set_enabled(enabled)

@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.

start_link(opts \\ [])

Starts the tracer writer process and loads the :tracer config.

status()

@spec status() :: map()

Returns a status snapshot including config, enabled keys and open trace files.

trace_event(event, extra)

@spec trace_event(String.t(), map()) :: :ok

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).

trace_permission(request, fun_config, result)

@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}}).

trace_request(request)

@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.

trace_result(request, result, duration_us)

@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).