Add structured logging, MCP request logging, and an OpenTelemetry pipeline across server apps#1328
Open
FelineStateMachine wants to merge 2 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Outside of
apps/cloud, the server apps have no configured logger (Effect'sdefault pretty printer), no log or metric export, and no tracer, so the
roughly 145 existing
Effect.withSpancall sites in core packages exportnothing. MCP protocol traffic is only visible through the
EXECUTOR_MCP_DEBUGconsole hook. This makes self-hosted and local deployments hard to operate and
debug.
This PR adds structured logging, MCP request logging, and an OTLP export
pipeline (traces, logs, metrics) to all four server apps. When no OTLP
endpoint is configured, export is disabled and only the structured console
logger is active. Runtime behavior is otherwise unchanged.
Changes
Two commits.
1. Structured logging, MCP request logging, and OTLP telemetry
packages/core/observability(@executor-js/observability).It exports
observabilityLayer({ serviceName, endpoint, headers, logLevel }),which provides a structured JSON console logger and OTLP/HTTP exporters for
traces, logs, and metrics. It is built on
effect/unstable/observability(fetch-based, no
async_hooks), so the same layer runs on Bun and onCloudflare workerd. No new npm dependencies.
mcp.*log events inpackages/hosts/mcpandpackages/hosts/cloudflare:mcp.tool.startandmcp.tool.endwithoutcome (success, paused, approval_required, error) and duration,
mcp.tool.internal_errorwith a correlation id, session lifecycle events(created, closed, idle_expire, init_failed, alarm),
mcp.auth.outcome,dispatch results, and elicitation events at debug level. Two metrics:
mcp.tool.calls(counter) andmcp.tool.duration_ms(histogram), tagged bytool and outcome. Code payloads are not logged; only
mcp.execute.code_length, matching the existing span attribute convention.apps/local,apps/host-selfhost,apps/host-cloudflare, andapps/cloud. Cloud keeps its existing trace pipeline and error-capturecorrelation and gains only the logs and metrics layers (the shared package
takes
traces: falsefor this).apps/localadditionally runs itsin-process MCP surface through a dedicated runtime so tool calls log through
the same pipeline and flush on shutdown.
AGENTS.md, configuration section inRUNNING.md.EXECUTOR_MCP_DEBUGis unchanged and coexists with the new events.2.
SpanKind.CONSUMERon MCP tool-call spansmcp.host.tool.executeandmcp.host.tool.resumespans now carry OTelSpanKind.CONSUMER. Without a kind, these are rootINTERNALspans with noHTTP attributes wherever no HTTP server span wraps the call (for example the
stdio transport), and span classifiers in observability backends commonly
discard such roots.
CONSUMERmarks them as remotely initiated units of workprocessed by this server, which matches their semantics.
Design notes
Bun, bindings on Workers) and merges it into the
bootseam ofExecutorApp.make. Shared packages contain no environment reads and noconditional wiring.
stdio transport uses the process's stdout as its JSON-RPC channel. A stdout
logger corrupts the protocol stream; the existing stdio integration test
catches this.
trace_idandspan_idfrom the fiber's current span, soconsole output correlates with exported traces the same way OTLP log records
do.
re-enters it per SDK callback, so the host app's logger and tracer reach
every tool call without new plumbing. SDK lifecycle callbacks that run
outside Effect (for example transport close) emit single-line JSON directly.
so console and OTLP both receive every record.
Configuration
OTEL_EXPORTER_OTLP_ENDPOINT/v1/{traces,logs,metrics}appended. Unset disables export.OTEL_EXPORTER_OTLP_HEADERSkey=value,key2=value2format.LOG_LEVELinfoOn the Workers apps these are bindings rather than process env.
Testing and verification
format:check,lint,typecheck, andtestpass.disabled-when-unconfigured behavior, OTLP layer teardown; MCP host tests
assert
mcp.tool.start/mcp.tool.endfire with outcome, duration, andexecution id, and that defects produce the correlation-id error log.
/v1/traces, trace-correlated log records on/v1/logs, both metric serieson
/v1/metrics.apps/host-selfhostand
apps/local). Screenshots below. With the endpoint unset, there is nonetwork activity and existing e2e tests (including the cloud
error-correlation contract test) pass unchanged.
MCP tool call classified as a task via
SpanKind.CONSUMER, with toolattributes and child spans:
Endpoint grouped by parametrized route with database child spans:
Disclosure
This change was developed with an LLM coding assistant (Claude), directed and
reviewed by the submitter. Design decisions, code, tests, and the verification
above were produced in that workflow and checked against the repository's full
test gates before submission.