Wymcp. Telemetry. Logger
(Wymcp v0.8.3)
View Source
Renders wymcp's telemetry events as structured Logger lines under one
policy — attached at boot by default, off with
config :wymcp, logger: false.
The library emits, this handler renders, the consuming application
chooses. Wymcp.Telemetry owns the events and their metadata; this module
owns nothing but the lines, so a consumer wanting different levels, keys or
destinations attaches a handler of its own against the same events and
turns this one off — that is what telemetry is for, and it is why there is
no level knob here.
Nothing connects this to MCP's own logging capability, which wymcp does
not implement; this module renders wymcp's telemetry to the BEAM
Logger, and the two level vocabularies are unrelated.
Configuration
config :wymcp, logger: false stops Wymcp.Application attaching the
handler at boot; the default is true. The key is read exactly once, at
boot, through enabled?/0. attach/0 and detach/0 ignore it — a test
that detaches can re-attach without consulting configuration, and the boot
read stays the one place the key is consulted.
Turning it off means turning off every line below, including the fault
lines: a tool that faults is contained and answered as an isError result,
so with no handler attached nothing about that fault reaches the adapter
and no line exists. That is the consumer's choice to make, and it is why
the default is on.
Line policy
One sentence: a client-triggerable fact renders at :info, a
consumer-code or wymcp fault at :error, and nothing renders at
:warning.
A public MCP endpoint's rejections are attacker-triggerable volume, so a
:warning per bad request is a flood the attacker chooses the size of.
Nothing a client can provoke is therefore louder than :info. A start
event renders no line; one call renders one line, plus a fault line where
consumer code broke — an auth module that raises renders both
wymcp.auth.error and the rejection's own line, because the two say
different things and the fault is the consumer's to fix.
Client- and consumer-controlled values are
bounded on render, through
Wymcp.Bound.value/1 — a client able to put a newline on a line is a
client able to forge one. The events themselves carry the raw values,
because a consumer's own handler shapes its own line.
The message string is the dotted event name — wymcp.wire.reject — so one
grep handle works in every formatter, and the facts are metadata. There is
no event metadata key: that name is common in application line
vocabularies and a collision is the consumer's to resolve, not wymcp's to
cause.
Line table
A ★ marks a value rendered through Wymcp.Bound.value/1 — the line form,
so an atom, the consumer's own, passes as written and a message_id rides
as the integer it is.
| event | level | line keys |
|---|---|---|
wire.reject | :info | rejecter, reason, status, http_method, method★, message_id★, message★ |
tool.start | not rendered | — |
tool.stop | :info | tool_name★, action★, is_error, error_kind, result_type, duration_ms |
tool.error | :error | tool_name★, action★, message_id★, exception, error★, duration_ms, crash_reason |
help.called | not rendered | — |
auth.error | :error | auth_module, exception, error★, http_method, method★, message_id★, crash_reason |
method.error | :error | method★, message_id★, exception, error★, crash_reason |
This table is the metadata allowlist a consuming application copies into
its own Logger :metadata config. wymcp ships no allowlist of its own:
that config belongs to the application.
tool.start renders nothing because it carries nothing tool.stop lacks —
the same tool and action, without the outcome — so a start line would
be a second line per call saying less. Start events serve the metrics and
tracing consumers, who read the event. The one case a start line alone
would trace, a tool that never returns, is accepted.
help.called renders nothing because a help call is a tool call and
already renders as tool.stop with tool_name: "help" and the target
action, error answers included. What help.called adds — the target tool,
the answer level — is product signal rather than an operator's line, and
rendering it beside tool.stop would break one line per call.
Totality
:telemetry detaches a handler that raises, from every event it was
attached to, and logs one error; after that every line is silently gone.
So every clause here reads its metadata with Map.get/3 and matches no key
in a head, and a value of an unexpected shape is rendered rather than
matched. crash_reason is forwarded verbatim and never inspected.
Every ★ value renders through Wymcp.Bound.value/1, which is
total: a value that made it raise would take
these lines with it.
Summary
Functions
Attaches the handler to every event it renders, unconditionally.
Detaches the handler. Idempotent: detaching when nothing is attached
answers :ok.
Whether the handler is attached at boot — the config :wymcp, logger: …
key, default true.
Functions
Attaches the handler to every event it renders, unconditionally.
Idempotent: a second call is a no-op, so a test that detached in setup
can re-attach on exit without coordinating with boot.
Detaches the handler. Idempotent: detaching when nothing is attached
answers :ok.
Whether the handler is attached at boot — the config :wymcp, logger: …
key, default true.
Wymcp.Application is the only caller: attach/0 and detach/0
deliberately do not consult it.