Skip to content

Logging: per-request log level on modern servers (era fork) (#1629)#1724

Merged
cliffhall merged 8 commits into
v2/mainfrom
v2/logging-era-fork-1629
Jul 19, 2026
Merged

Logging: per-request log level on modern servers (era fork) (#1629)#1724
cliffhall merged 8 commits into
v2/mainfrom
v2/logging-era-fork-1629

Conversation

@cliffhall

@cliffhall cliffhall commented Jul 19, 2026

Copy link
Copy Markdown
Member

Closes #1629

Forks the Logging UX by protocol era. On modern (2026-07-28) servers logging/setLevel and the session-scoped level are gone — the client opts into logs per request via the io.modelcontextprotocol/logLevel _meta key, and the SDK does not attach it automatically, so server logs are silently absent until the client stamps it.

Core (InspectorClient)

  • setModernLogLevel(level | undefined) / getModernLogLevel() store the opt-in level.
  • mergeMeta stamps io.modelcontextprotocol/logLevel on every outgoing request, gated on the negotiated modern era so legacy connections (which use logging/setLevel) are never touched.
  • The opt-in is cleared on disconnect so it can't leak into the next connection.

Web UI

  • LogControls forks on protocolEra:
    • Legacy — unchanged: the level Select + "Set" button → logging/setLevel.
    • Modern — a per-request opt-in Select (Off + the 8 levels), applied immediately (no "Set"), with copy explaining that logs arrive on the originating request's stream and that choosing Off stops requesting logs. The legacy selector is gated out.
  • Threaded protocolEra / modernLogLevel / onSetModernLogLevel through LoggingScreen and InspectorView from App, with a separate modernLogLevel state (null = not opted in, the modern default).

Acceptance

  • Legacy servers: the level selector works as today.
  • Modern servers: logs appear only when the client stamps logLevel; the UI reflects per-request semantics and the setLevel selector is hidden.
  • npm run ci green (validate → coverage gate → smoke → Storybook).

Tests

  • Unit: modern LogControls/LoggingScreen fork (control renders, legacy "Set" hidden, Off↔level selection callbacks), plus new stories.
  • Integration: against a real modern test server, the logLevel _meta key is stamped on outgoing requests once opted in and disappears when cleared; on a legacy connection setting the modern level is a no-op on the wire.

Screenshots

Verified end-to-end against real legacy and modern logging test servers (test-servers/configs/logging-{legacy,modern}-http.json).

Legacy era — session-scoped logging/setLevel selector + Set button, logs streaming:

logging-legacy-era

Modern era — per-request "Log Level per Request" opt-in control (Set button gated out):

logging-modern-era-control

Modern era — the tools/call request body carries _meta["io.modelcontextprotocol/logLevel"] (the client stamp, Network tab):

logging-modern-era-loglevel-stamp

Server Settings — the new "Log Level per Request" option (defaults to Debug / opted-in):

logging-modern-server-setting

🤖 Generated with Claude Code

https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

On modern (2026-07-28) servers `logging/setLevel` and the session-scoped
level are gone: the client opts into logs per request via the
`io.modelcontextprotocol/logLevel` `_meta` key, and the SDK does not
attach it automatically, so server logs are silently absent until stamped.

Core (`InspectorClient`):
- `setModernLogLevel(level | undefined)` / `getModernLogLevel()` store the
  opt-in level; `mergeMeta` stamps it on every outgoing request, gated on
  the negotiated modern era so legacy connections are untouched.
- Cleared on disconnect so it can't leak into the next connection.

Web UI:
- `LogControls` forks on `protocolEra`: legacy keeps the `logging/setLevel`
  selector + Set button; modern shows a per-request opt-in Select (Off +
  levels, applied immediately) with copy explaining logs arrive on the
  originating request's stream.
- Threaded `protocolEra` / `modernLogLevel` / `onSetModernLogLevel` through
  LoggingScreen and InspectorView from App.

Tests: unit coverage for the modern LogControls/LoggingScreen fork;
integration coverage proving the `logLevel` `_meta` is stamped on a real
modern server once opted in (and not on legacy).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Jul 19, 2026
cliffhall and others added 4 commits July 19, 2026 08:39
…1629)

Add two manual-testing configs and README docs for the logging era fork:

- logging-legacy-http.json: plain streamable-HTTP server with `logging: true`
  and a `send_notification` tool. The Logs tab shows the legacy session-scoped
  "Set Active Level" selector + Set button; calling the tool streams logs.
- logging-modern-http.json: same, with `transport.modern: true`. Connect with
  Protocol Era = Modern and the Logs tab shows the per-request opt-in control;
  the client stamps `_meta["io.modelcontextprotocol/logLevel"]` on every
  request (visible in the Network tab request body).

pr-screenshots/ documents the verified behavior end-to-end against both real
servers: the legacy control with logs streaming, the modern per-request
control, and the modern tools/call request body carrying the stamped logLevel
_meta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
…ern logs stream (#1629)

The `send_notification` tool sent its `notifications/message` through the
global `server.server.notification()`. On the modern (2026-07-28) stateless
leg that isn't tied to the in-flight request, so the SDK answered the tool
call with plain JSON and dropped the log — nothing reached the Inspector's
Logs panel.

Prefer the per-request `extra.sendNotification` (rebuilt from
`ctx.mcpReq.notify`): scoped to the current request, it makes the SDK upgrade
the response to SSE and stream the log back on the originating request's
stream. Falls back to the global notifier (legacy path / no per-request
context). Verified end-to-end: opting in via the Logs-tab "Log Level per
Request" control and calling send_notification now streams the entry on the
modern server; legacy behavior unchanged (server-notification integration
tests green).

README documents the modern logging showcase and the caveat that this test
server streams whenever a tool emits — the SDK beta strips the per-request
envelope from the handler context, so the tool can't gate on the client's
logLevel opt-in (the client-side `_meta` stamp #1629 owns is still fully
visible in the Network tab).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
…faithful modern gating (#1629)

The previous fix routed `send_notification` through `extra.sendNotification`
(`ctx.mcpReq.notify`), which streams on the modern leg but emits
unconditionally — the log showed up even when the client hadn't opted in via
the per-request `logLevel`.

The SDK already provides the correct primitive: `ctx.mcpReq.log(level, data,
logger)` (added in `McpServer.buildContext`). It applies the era-correct
threshold gating itself — on the modern (2026-07-28) leg it reads the
per-request `logLevel` opt-in from the request envelope and drops the message
when the client didn't opt in or the level is below the requested severity; on
legacy it honors the session level from `logging/setLevel` — and emits through
`notify`, so the modern response still upgrades to SSE and the log rides the
originating request's stream.

Surface it as `extra.log` (`toHandlerExtra` maps `ctx.mcpReq.log`) and have
`send_notification` prefer it, falling back to the global notifier for callers
without per-request context. Verified end to end: opting in via the Logs-tab
"Log Level per Request" control stamps `logLevel` in the request `_meta` and
streams the log; setting it back to Off omits the key and the server gates the
log (plain JSON response, no notification). Legacy server-notification
integration tests still green. (No SDK change needed — the earlier apparent
gap was our test server using the wrong low-level API.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
…ting to on (#1629)

Adds the modern per-request log level as a persisted Server Settings option
(Options → "Log Level per Request", below Protocol Era), so a server can opt
into logs by default instead of the Logs-tab control starting at Off.

- types.ts: `ModernLogLevel` (`"off"` | the 8 levels), `DEFAULT_MODERN_LOG_LEVEL`
  = "debug" (default-on so a modern connection surfaces server logs out of the
  box), `MODERN_LOG_LEVELS`, and an `isModernLogLevel` guard. Field added to
  `InspectorServerSettings` and `StoredMCPServer`.
- serverList.ts: round-trip the field (lift on read, omit-on-default on write,
  drop unknown literals) mirroring `protocolEra`; add to the persisted field
  allowlist.
- remote/node/server.ts: validateSettings accepts/validates it and the
  normalize path drops a malformed literal.
- inspectorClient.ts: the constructor seeds `modernLogLevel` from the setting
  (`"off"` → not opted in), so a modern connection stamps the level from the
  first request without UI interaction. mergeMeta still gates on the modern era.
- ServerSettingsForm/Modal: a Select (Off + 8 levels) with a modern-era
  description. App seeds the live Logs-tab control from the setting on connect
  so the two stay in sync.

Tests: form Select (default/onChange/off/reflect), modal handler, serverList
round-trips, route validation (accept/reject/drop), and constructor seeding
(seeded level stamped from the first request; "off" opts out). The prior
modern-era "stamps once set" test is updated for the new default-on behavior.
Coverage gate green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 2m 41s —— View job


Review — Logging: per-request log level on modern servers (#1629)

  • Read core changes (inspectorClient.ts, types.ts, serverList.ts, remote server)
  • Read web UI changes (LogControls, LoggingScreen, App, ServerSettingsForm)
  • Read test-server changes + configs
  • Review tests

Overall this is a clean, well-scoped, and well-documented change. The era fork is implemented exactly where it should be (gated in mergeMeta on the negotiated era), the opt-in is seeded from the server setting and cleared on disconnect, and the persistence layer (round-trip + wire validation + malformed-drop) is complete and mirrors the existing protocolEra handling precisely. Test coverage is thorough across unit, story, and real-server integration paths. Nice work.

One thing must be fixed before merge; the rest are minor.


🔴 Must fix — a debugging artifact was committed to the repo root

inspector-network-logging-modern-2026-07-19T15-11-27.013Z.json (+109 lines) is a Network-tab export dump that was accidentally committed in ca2aa526. It's not source and shouldn't ship.

Notably, .gitignore:15-22 already has a section specifically for "exported Inspector history dumps" dropped at the repo root — but its pattern is /inspector-history-*.json, which doesn't match the inspector-network-* export naming, so this file slipped through. Two-part fix:

  1. git rm the stray file.
  2. Broaden the ignore pattern so future network exports are caught too, e.g. add /inspector-network-*.json (or generalize to /inspector-*.json).

Fix this →


🟡 Minor / optional

1. Duplicated modern-log-level guard. ServerSettingsForm.tsx defines its own MODERN_LOG_LEVEL_VALUES set + isModernLogLevelValue() (lines ~94-107), which duplicates the exported isModernLogLevel() in core/mcp/types.ts. It could import and reuse the core guard. That said, this mirrors the existing local isProtocolEra() pattern in the same file, so it's a consistency-vs-DRY judgment call — fine to leave.

2. Two different "off" representations. LogControls uses a MODERN_OFF_VALUE = "__off__" sentinel (because its modernLogLevel prop is LoggingLevel | null), while ServerSettingsForm uses the real "off" literal from MODERN_LOG_LEVELS (its type is ModernLogLevel). Both are correct and documented, but the divergence is a small readability snag for the next reader. Not worth churning.

3. Default is opted-in at debug. DEFAULT_MODERN_LOG_LEVEL = "debug" means every modern connection stamps _meta["io.modelcontextprotocol/logLevel"]: "debug" on every outgoing request by default. This is a deliberate, well-justified choice for a debugging tool (and clearly documented in types.ts), so no change needed — just flagging it as an intentional behavioral default in case reviewers expected "not opted in" as the out-of-box state.

4. pr-screenshots/*.png tracked in the tree. This PR swaps the pagination screenshots for logging ones. The root-level .gitignore /*.png doesn't reach this subdirectory, so these binaries are committed to source. Pre-existing pattern and not introduced by the logic here, but keeping rendered PNGs in the repo adds permanent weight — worth deciding whether these belong in-tree at all.


Things I checked and confirmed are correct

  • mergeMeta era gate (protocolEra === "modern") means legacy connections never get the stamp — verified by the legacy integration test.
  • Spread order (defaultslogMetacallMetadata) correctly lets an explicit per-call value win, and the Object.keys(merged).length > 0 ? … : undefined guard preserves the old "return undefined when empty" contract.
  • Opt-in cleared on disconnect() so it can't leak into the next connection (verified in inspectorClient.ts disconnect path).
  • Seeding is consistent between the client constructor and App (?? DEFAULT_MODERN_LOG_LEVEL, "off" → not opted in).
  • send_notification now emits via the SDK's request-scoped extra.log with a global-notification fallback for in-process/older callers — matches the spec's per-request gating.
  • Persistence: non-default lifted to settings and back, default omitted from disk (byte-stable round-trip), unknown literal dropped on read, malformed value dropped/rejected at the remote-server boundary — all with tests.
    · branch v2/logging-era-fork-1629

…, broaden ignore

- Prettier-format ServerSettingsForm.tsx (the format:check failure that broke
  CI — my pre-push `npm run format` from the repo root was a silent no-op since
  there is no root format script; formatting is per-client).
- Remove `inspector-network-logging-modern-2026-07-19T15-11-27.013Z.json`, a
  Network-tab export debugging artifact accidentally committed in ca2aa52
  (flagged by review).
- Broaden the .gitignore debugging-artifacts section with
  `/inspector-network-*.json` so future network exports are ignored (the
  existing `/inspector-history-*.json` didn't match the network export naming).

Verified with the full `npm run ci` (validate → coverage → smoke → storybook),
green across all clients.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
@cliffhall

Copy link
Copy Markdown
Member Author

Thanks for the thorough review! Addressed in 9ad6dcac.

🔴 Must fix — stray network export. Done exactly as suggested:

  1. git rm'd inspector-network-logging-modern-2026-07-19T15-11-27.013Z.json.
  2. Broadened the .gitignore debugging-artifacts section with /inspector-network-*.json (kept it parallel to the existing /inspector-history-*.json rather than generalizing to /inspector-*.json, to stay narrow). The second untracked export at the repo root is now ignored too.

That commit also fixes the CI failure (a Prettier format:check on ServerSettingsForm.tsx) — root cause was that there's no root format script, so my pre-push npm run format from the repo root was a silent no-op; formatting is per-client. Re-verified with the full npm run ci (validate → coverage → smoke → Storybook), green across all clients.

🟡 Minor / optional — leaving as-is, per your notes:

  1. Duplicated guard in ServerSettingsForm — intentionally mirrors the existing local isProtocolEra() in the same file, as you noted. Kept for consistency.
  2. Two "off" representationsLogControls needs the "__off__" sentinel because its prop is LoggingLevel | null (no room for a real "off"), while the setting's type is ModernLogLevel which has a real "off". Both documented; not churning.
  3. Default opted-in at debug — intentional and documented; it's the fix for the exact confusion that surfaced during manual testing (no logs out of the box). Confirmed as the desired behavior.
  4. pr-screenshots/*.png tracked — these are intentional PR proof screenshots requested for this PR; the .gitignore /*.png deliberately doesn't reach the subdir. Left in place.

@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 1m 21s —— View job


Re-review — Logging: per-request log level on modern servers (#1629)

  • Verified the prior 🔴 fix landed (stray export removed + .gitignore broadened)
  • Re-read core changes (inspectorClient.ts, types.ts, serverList.ts)
  • Re-read web UI changes (LogControls, LoggingScreen, InspectorView, ServerSettingsForm, ServerSettingsModal, App)
  • Re-read test-server changes + configs + integration tests

Verdict: LGTM. Both items from the first pass are resolved, and I re-walked the full change set — it's clean and I found no new blockers.


🔴 → resolved

The stray inspector-network-*.json dump is gone (git ls-files shows no inspector-network-*/inspector-history-* tracked), and .gitignore now carries /inspector-network-*.json right beside the existing /inspector-history-*.json — narrow and parallel, exactly as described. The ServerSettingsForm.tsx Prettier fix that unblocked CI is in 9ad6dcac too.

Re-verified as correct

  • Full prop thread is complete and typed end-to-end. App → InspectorView → LoggingScreen → LogControls all carry protocolEra / modernLogLevel / onSetModernLogLevel; protocolEra is genuinely in scope in InspectorView (InspectorView.tsx:409,600,1126,1136). ServerSettingsModal supplies the now-required onModernLogLevelChange via handleModernLogLevelChange, so ServerSettingsForm's non-optional prop can't be dropped.
  • mergeMeta era gate + spread orderprotocolEra === "modern" && this.modernLogLevel means legacy never gets stamped; defaults → logMeta → callMetadata lets an explicit per-call value win; the Object.keys(merged).length > 0 ? … : undefined guard preserves the old empty-→-undefined contract.
  • Lifecycle — seeded from serverSettings.modernLogLevel ?? DEFAULT_MODERN_LOG_LEVEL ("off" → not opted in) identically in the client constructor and in App on connect; cleared to undefined/null on disconnect() and in App's reset so it can't leak into the next connection.
  • Persistence (serverList.ts) — mirrors protocolEra precisely: default omitted from disk (byte-stable round-trip), unknown literal dropped on read via isModernLogLevel, key added to StoredInspectorFields and INSPECTOR_FIELD_KEY_MAP.
  • Integration coverage is strong: stamp-on-seed(debug) → change(warning) → clear(none) across three real tools/call frames on a live modern server, plus seed-from-setting(info) and "off"→no-stamp, plus the legacy no-op-on-the-wire case.
  • send_notification prefers the SDK's request-scoped extra.log (era-correct threshold gating, rides the request's SSE stream) with an unconditional server.notification() fallback for callers without per-request context — faithful to the modern per-request gating spec.

🟡 Minor / optional (all previously flagged; nothing new)

The four minor items from the first pass (duplicated isModernLogLevel guard, the "__off__" vs "off" divergence, the opted-in-at-debug default, and tracked pr-screenshots/*.png) were each acknowledged with a rationale — I agree with leaving them as-is; none is a correctness issue.

One tiny observation, not blocking: test-servers/src/test-server-fixtures.ts includes a few Prettier-only reflows (e.g. the Record<string, z.ZodType> | undefined and city .meta(...) chains) unrelated to logging — harmless, just diff noise.

CI on 9ad6dcac is currently in progress; the author reports a full local npm run ci was green.
· branch v2/logging-era-fork-1629

@cliffhall

Copy link
Copy Markdown
Member Author

Thanks for the re-review — appreciate the thorough re-walk. 🎉

On the one non-blocking observation: the Prettier reflows in test-servers/src/test-server-fixtures.ts (the Record<string, z.ZodType> | undefined / .meta(...) chains) came from running prettier --write over the whole file while editing send_notification. They're correct Prettier output on lines that weren't previously formatted (test-servers isn't under a client format:check gate, so it had drifted). I'm leaving them — reverting would just reintroduce non-Prettier formatting for no benefit.

Nothing else actionable from either pass. CI on 9ad6dcac should now be green (full local npm run ci — validate → coverage → smoke → Storybook — passed across all clients).

cliffhall and others added 2 commits July 19, 2026 15:29
The modern-era suite covered the client-side `_meta` logLevel stamp but not
that a log actually flows back — the legacy suite already has that
(`should receive server-initiated notifications via SSE/streamable-http`), so
the modern side was the gap.

Add two live e2e tests against a modern (`createMcpHandler`) server serving the
`send_notification` tool with `logging: true`:
- opted-in (server setting = a level): calling the tool delivers a
  `notifications/message` over the request's SSE stream, with the right
  level/logger/data.
- not opted in (setting "off"): the tool still returns a result, but the server
  gates the log — no `notifications/message` is received.

This exercises the full path end to end: the client seeding + `_meta` opt-in,
and the server's `send_notification` → `ctx.mcpReq.log` era-correct gating.

Verified with the full `npm run ci` (green across all clients).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
…tions (#1629)

The per-request log level is meaningless on a legacy server (which uses
logging/setLevel), so hide it in Server Settings when the era is legacy:
- protocolEra pinned to "legacy" → hidden (no connection needed), or
- protocolEra "auto" that negotiated legacy at connect time → hidden.

It stays visible for a pinned "modern" server, and for an "auto" server that is
either not yet connected (era unknown) or resolved to modern.

Threads the negotiated era down: App passes `negotiatedEra` to
ServerSettingsModal only when the settings target is the live-connected server
(`connectionStatus === "connected" && settingsModalTargetId === activeServerId`);
the modal forwards it to ServerSettingsForm, which gates the control on
`configuredEra === "modern" || (configuredEra === "auto" && negotiatedEra !== "legacy")`.

Tests: visibility matrix on the form (legacy hidden; auto+legacy hidden;
auto+unconnected and auto+modern and modern shown) and a modal hide case; the
existing modern-log tests now base on a modern-era settings object. Verified in
the browser (legacy hides, switching to modern reveals it reactively) and with
the full `npm run ci` (green across all clients).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
@cliffhall

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cliffhall's task in 1m 55s —— View job


Review — Logging: per-request log level on modern servers (#1629)

  • Sync branch & inspect full diff vs origin/v2/main
  • Review core (inspectorClient.ts, types.ts, serverList.ts, remote server)
  • Review web UI (LogControls, LoggingScreen, InspectorView, ServerSettings*, App)
  • Review test-server changes + configs + tests
  • Post review

Verdict: LGTM. I re-walked the full change set (including the new commit 5d4e5bd5 that landed since the last pass). It's clean, well-typed, and thoroughly tested — no blockers. CI is green on the latest commit (29702830696).


New since the last review — 5d4e5bd5 (hide the modern control on legacy)

This is a real correctness improvement, and the era gate is right:

const configuredEra = settings.protocolEra ?? DEFAULT_PROTOCOL_ERA; // legacy
const showModernLogLevel =
  configuredEra === "modern" ||
  (configuredEra === "auto" && negotiatedEra !== "legacy");

I checked every branch against its test:

  • pinned legacy / default → hidden ✅
  • pinned modern → shown (unconditional — config-time, no connection needed) ✅
  • auto + not yet connected (negotiatedEra undefined) → shown (era unknown) ✅
  • auto → negotiated modern → shown ✅
  • auto → negotiated legacy → hidden ✅

Each is covered by a dedicated ServerSettingsForm test. The negotiatedEra plumbing (AppServerSettingsModalServerSettingsForm) is only supplied when the modal targets the live-connected active server (connectionStatus === "connected" && settingsModalTargetId === activeServerId), which is exactly correct — otherwise the era is unknowable.

Re-verified as correct

  • mergeMeta era gate + spread orderprotocolEra === "modern" && this.modernLogLevel guarantees legacy is never stamped; defaults → logMeta → callMetadata lets an explicit per-call value win; Object.keys(merged).length > 0 ? … : undefined preserves the old empty-→-undefined contract.
  • Lifecycle — seeded identically in the client constructor and in App on connect (?? DEFAULT_MODERN_LOG_LEVEL, "off" → not opted in); cleared to undefined/null on disconnect() and in App's reset so it can't leak into the next connection.
  • Persistence (serverList.ts + remote server.ts) mirrors protocolEra precisely — default omitted from disk (byte-stable round-trip), unknown literal dropped on read via isModernLogLevel, rejected at the /api/servers write boundary, keys added to StoredInspectorFields/INSPECTOR_FIELD_KEY_MAP.
  • LogControls forkisModernEra(protocolEra) (negotiated era, per eraUtils) swaps the legacy Set selector for the per-request opt-in; the MODERN_OFF_VALUE sentinel can't collide with a real LoggingLevel. Off↔level callbacks tested both directions.
  • send_notification emits via the SDK's request-scoped extra.log (era-correct threshold gating; rides the request's SSE stream) with a server.notification() fallback for callers without per-request context — faithful to the modern per-request gating spec.

🟡 Minor / optional (nothing new; all previously acknowledged)

The four earlier items (duplicated isModernLogLevel guard in ServerSettingsForm, "__off__" vs "off" divergence, opted-in-at-debug default, tracked pr-screenshots/*.png) were each acknowledged with a rationale — I agree with leaving them.

One tiny observation for the next reader, not blocking: there are two knobs for the modern level — the Server Settings modal (persisted default, seeds the next connection at connect time) and the Logs tab control (the live opt-in). Changing the modal's modernLogLevel while connected persists it but doesn't retroactively change the live stamp until reconnect. That's consistent with how protocolEra and other connect-time settings behave, so it's the right call — just worth knowing the modal is the "next connect" knob and the Logs tab is the "right now" knob.

Nice work — ship it. 🎉
· branch v2/logging-era-fork-1629

@cliffhall

Copy link
Copy Markdown
Member Author

Thanks! 🎉 Agreed on the non-blocking note: the Server Settings Log Level per Request is the persisted next-connect default (seeds the client at connect time), and the Logs tab control is the live right-now opt-in — same connect-time-settings semantics as protocolEra. That's the intended split. Nothing else to address.

@cliffhall cliffhall linked an issue Jul 19, 2026 that may be closed by this pull request
@cliffhall
cliffhall merged commit ec185f1 into v2/main Jul 19, 2026
3 checks passed
@cliffhall
cliffhall deleted the v2/logging-era-fork-1629 branch July 19, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logging: per-request log level on modern servers (era fork)

1 participant