# Logging and OpenTelemetry

The Zuplo MCP Gateway emits structured logs alongside its analytics events. Each
log entry is keyed by an `event` string, carries auto- attached fields for the
route and authenticated user, and explicitly excludes any credentials. Pair the
logs with one of Zuplo's log plugins or the OpenTelemetry plugin to route them
to your provider.

## What the gateway logs

Every log entry uses the structured-first form:

```ts
context.log.info(
  { event: "mcp_gateway_oauth_token_issued", subjectId, operationId },
  "Gateway issued an OAuth access token to an MCP client",
);
```

The `event` field is the searchable key. Naming convention is snake_case
throughout — for example, `mcp_gateway_oauth_token_issued`,
`mcp_gateway_oauth_token_revoked`,
`mcp_gateway_upstream_connection_established`.

Severity follows three audiences:

- **Audit** entries (`info`) — OAuth token issuance, consent approval, upstream
  connection established, token revocation.
- **Debug / visibility** entries (`debug` / `info`) — request acceptance,
  response shaping.
- **Errors** (`error`) — every error includes flattened error-chain fields
  (`errName`, `errMessage`, `causeName`, `causeMessage`, up to four cause
  levels) so the original failure context survives the rethrows.

## Fields attached to every request log

Three identifying fields are attached to every log entry emitted during an MCP
request:

- `operationId` — the route identity, the same value used as the
  `virtualServerName` in analytics.
- `upstreamServerId` — the upstream id from the token exchange policy, populated
  once the upstream is resolved.
- `subjectId` — the authenticated user's stable subject id, populated once the
  bearer token is validated.

These fields make it trivial to filter your log provider by route, upstream, or
user without scanning message bodies.

You can attach additional custom log properties from a policy or handler using
`context.log.setLogProperties()`. See
[Logging → Custom log properties](../../articles/logging.mdx#custom-log-properties).

## What the gateway never logs

The gateway is deliberately strict about what it doesn't emit:

- Bearer tokens (downstream or upstream)
- OAuth authorization codes
- PKCE code verifiers
- Client secrets
- Raw signed JWTs (state, session, browser-ticket)
- Full redirect URIs (only the host is logged, via a `safeHost()` helper)
- Customer request bodies

If you need to inspect any of these for debugging, do it through purpose-built
tooling — never widen the gateway's log surface.

## Routing logs to your provider

The MCP Gateway logs flow through the standard Zuplo logging pipeline. Enable
one of the log plugins in [`zuplo.runtime.ts`](../../articles/logging.mdx) to
forward the data to your provider of choice. The plugin choice is independent of
the MCP Gateway — once the plugin is wired up, gateway logs appear alongside the
rest of your project's logs.

Supported destinations include:

- [AWS CloudWatch](../../articles/log-plugin-aws-cloudwatch.mdx)
- [Datadog](../../articles/log-plugin-datadog.mdx)
- [Dynatrace](../../articles/log-plugin-dynatrace.mdx)
- [Google Cloud Logging](../../articles/log-plugin-gcp.mdx)
- [Loki](../../articles/log-plugin-loki.mdx)
- [New Relic](../../articles/log-plugin-new-relic.mdx)
- [Splunk](../../articles/log-plugin-splunk.mdx)
- [Sumo Logic](../../articles/log-plugin-sumo.mdx)
- [VMware Log Insight](../../articles/log-plugin-vmware-log-insight.mdx)

For destinations not in this list, build a
[custom logging plugin](../../articles/custom-logging-example.mdx).

## OpenTelemetry

The gateway integrates with Zuplo's
[OpenTelemetry plugin](../../articles/opentelemetry.mdx) to export traces and
logs in OTLP format. Register the plugin in `modules/zuplo.runtime.ts` alongside
`McpGatewayPlugin`:

```ts
// modules/zuplo.runtime.ts
import { RuntimeExtensions } from "@zuplo/runtime";
import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway";
import { OpenTelemetryPlugin } from "@zuplo/runtime";

export function runtimeInit(runtime: RuntimeExtensions) {
  runtime.addPlugin(new McpGatewayPlugin());
  runtime.addPlugin(
    new OpenTelemetryPlugin({
      url: process.env.OTLP_TRACES_ENDPOINT,
      logUrl: process.env.OTLP_LOGS_ENDPOINT,
      authorization: process.env.OTLP_AUTHORIZATION,
    }),
  );
}
```

Traces include spans for the request, every inbound policy (`mcp-*-inbound`),
the handler, and the upstream fetch. Logs export the same structured entries
described above with their `event` field preserved.

### Example: Grafana Cloud OTLP

Grafana Cloud is one common destination. Define the endpoint and credentials in
`.env`:

```bash
# .env
GRAFANA_OTLP_ENDPOINT=https://otlp-gateway-prod-<region>.grafana.net/otlp
GRAFANA_OTLP_INSTANCE_ID=<instance-id>
GRAFANA_OTLP_API_TOKEN=glc_<token>
```

Use the base OTLP endpoint that ends in `/otlp` — the runtime appends
`/v1/traces` and `/v1/logs` itself. Other OTLP-compatible destinations
(Honeycomb, Dynatrace, New Relic, Tempo, self-hosted Jaeger) work the same way;
substitute the endpoint and credential headers.

:::note

Metrics export isn't supported by the current OpenTelemetry plugin. The plugin
exports traces and logs only. For metrics, use a dedicated
[metrics plugin](../../articles/metrics-plugins.mdx).

:::

## Cross-referencing logs and analytics

The gateway uses the same identifiers in logs and analytics events, so you can
pivot between the two:

- An analytics event's `reasonCode` matches the `reasonCode` field on the
  corresponding log entry (e.g., `missing_token`, `invalid_audience`,
  `connect_required`).
- The analytics `virtualServerName` is the same value as the log entry's
  `operationId`.
- The analytics `subjectId` is the log entry's `subjectId`.
- The analytics `upstreamServerName` is the log entry's `upstreamServerId`.

When debugging an entry in the [Analytics](./analytics.mdx) dashboard, filter
your log provider by the same fields to surface every related log entry for that
request.

## Related

- [Logging](../../articles/logging.mdx) — Zuplo's general logging guide,
  including custom log properties and the full plugin list.
- [OpenTelemetry](../../articles/opentelemetry.mdx) — trace and log export
  configuration, including the available options on the plugin.
- [Metrics plugins](../../articles/metrics-plugins.mdx) — metrics destinations
  (separate from the OTel plugin).
- [Analytics](./analytics.mdx) — the dashboard view of the same data.
