Skip to content

Commit c51f472

Browse files
aqandrewEhabY
andauthored
feat: buffer connection logs and flush them on failure (#1100)
Implements RFC requirement 13 / DEVEX-669: buffer connection debug logs in memory below the current log level and flush them on a genuine connection failure, so a support bundle captures the detail leading up to the failure without the user having enabled debug logging beforehand. ## What this does - Adds a `BufferingLogger` decorator (`src/logging/logBuffer.ts`) that wraps the "Coder" output channel and keeps a bounded, in-memory ring of the entries that sit **below** the channel's current level, which it would otherwise drop. Only below-level entries are buffered, so nothing already written is duplicated. Entries are formatted (via `safeStringify`) at record time. - On a connection failure, `flush(reason)` re-emits the captured entries into the output channel at the least-verbose level the channel still persists. The first physical line of each entry carries a `[buffered]` marker with its original ISO timestamp and level; continuation lines carry the bare marker. Capture is best-effort — the channel writes on its own schedule — but a later failure flush replays anything a bundle missed. - Collecting a support bundle flushes the buffer (`flush("support_bundle", { retain: true })`) before the CLI runs, and **retains** the ring so a later failure flush still has the entries. - When the channel is at **Off**, flush keeps the entries buffered instead of discarding them. - The buffer is bounded by both entry count and characters (`MAX_BUFFERED_CHARS = 2_000_000`); `trim()` enforces both budgets with oldest-eviction, and `flush()` replays in chunks of 100 to avoid a single oversized write. - Wires the buffer into `ServiceContainer` and adds the `coder.connectionLogBuffer.size` setting (default `1000`, capped at `10000`, `0` disables). The setting readers live in `src/settings/logger.ts` and the container reads/watches the size through a single `readSize()` helper via `watchConfigurationChanges`. The setting is included in `COLLECTED_SETTINGS`. - Flushes only on genuine connection failures, gated by an explicit `failure?: boolean` on the connection-log reason (not on transient reconnects, a handshake `401`, or intentional teardown): - a reconnecting WebSocket terminal failure (`unrecoverable_close`, `unrecoverable_http` where the status is not `401`, or `certificate_error`); - a failure while opening a workspace (canceled build, missing agent, timeout, or CLI/certificate error), funnelled through `Remote.closeRemote()`. - Connection-failure flushes are funnelled through `BufferingLogger.onConnectionFailure(reason, route)` (part of `ConnectionLogBuffer`), so the extension and remote paths flush `${reason} ${route}` identically; the socket option defaults to a noop when no observer is supplied. - The flush reason carries the failing **route** for attribution; the route is seeded on the socket so even a first-connect failure logs a real route instead of `unknown`. - Handshake status is parsed by a shared `handshakeStatus(error)` (`src/websocket/utils.ts`) covering both `ws` (`Unexpected server response: <code>`) and `eventsource` (`Non-200 status code (<code>)`), so a host/port such as `127.0.0.1:4040` is no longer misread as HTTP 404 and the SSE path is handled. `CoderApi.is404Error` compares against `HttpStatusCode.NOT_FOUND`. - Redacts `registration_access_token` in HTTP body logging alongside the other sensitive fields. - Documents the behavior, config, hard-kill/OOM loss limitation, and SSH log scope in `CONTRIBUTING.md`. ## WebSocket event fix `OneWayWebSocket` now registers `open`/`close`/`error` via DOM-style `addEventListener`, so `close` consumers receive a real `CloseEvent` with `.code`/`.reason`. This makes `unrecoverable_close` reachable in production (message events still use `ws.on("message", ...)` for JSON parsing). Server-initiated normal closes (`1000`/`1001`) now go through `scheduleReconnect` rather than parking the socket, so the now-unreachable `normal_close` reason is dropped from the telemetry unions and `EVENTS.md`. ## Scope notes - Extension SSH debug logs that pass through the shared logger are buffered; the CLI `ProxyCommand` file logs under `coder.proxyLogDirectory` are not, since support bundles already collect them from disk. - The buffer lives in memory, so a hard kill or out-of-memory event loses it (documented). - Flushing after N consecutive failed reconnect attempts against an unreachable server is deferred to follow-up #1112. - Reading the handshake status from the libraries' public event APIs (instead of parsing their error strings, which a real-library test now guards) is deferred to follow-up #1118. ## Testing - `pnpm typecheck`, `pnpm format:check`, and `pnpm lint` are clean. - Affected/dependent unit suites pass (`logBuffer`, `settings/logger`, `formatters`, `reconnectingWebSocket`, `oneWayWebSocket`, `coderApi`, `workspaceMonitor`, `workspaceStateMachine`, `remote`, `commands.supportBundle`, `instrumentation/websocket`). <details> <summary>Implementation plan &amp; design decisions</summary> ### Design - **Buffer:** bounded by entry count and characters; captures only calls whose severity is below the channel's current level; formats entries at record time; oldest-eviction; live-resizable via config; replays in chunks. - **Flush target (D3):** replay into the existing "Coder" output channel at a level that still persists, with a `[buffered]` marker plus original level/timestamp, chronologically next to the real failure logs. Support bundles already collect the on-disk VS Code logs and also trigger a flush before appending them, so no separate sink is needed. At Off, entries are retained rather than discarded. - **Flush reasons (D4):** genuine, surfaced connection failures only, gated by an explicit `failure?: boolean` — reconnecting-socket terminal failures (except a `401` handshake) and a workspace-open failure funnelled through `closeRemote()`. Never on transient `retrying` drops or intentional teardown (`manual_disconnect`, `replaced`, dispose/deactivate/reload). The callback carries the failing route. - **SSH scope (D5):** buffer extension SSH debug passing through the shared `Logger`; do not buffer CLI `ProxyCommand` file logs already handled via `coder.proxyLogDirectory`. ### Decisions - D1: buffer all below-level session logs. - D2: bound by entry count (and a character budget to cap memory). - D3: replay into the existing Coder output channel at a persisted level with `[buffered]` marker/original level/timestamp; retain at Off; also flush on support-bundle collection. - D4: flush only on genuine connection failure; not transient, not a `401`, and not intentional teardown. - D5: buffer extension SSH debug through the shared `Logger`; not CLI ProxyCommand file logs. </details> --- 🤖 Generated with Coder Agents. Reviewed and authored on behalf of @aqandrew. --------- Co-authored-by: Ehab Younes <ehab.alyounes@gmail.com>
1 parent e772660 commit c51f472

31 files changed

Lines changed: 1166 additions & 166 deletions

‎CHANGELOG.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
from published versions since it shows up in the VS Code extension changelog
66
tab and is confusing to users. Add it back between releases if needed. -->
77

8+
## Unreleased
9+
10+
### Added
11+
12+
- Add `coder.connectionLogBuffer.size` to keep recent below-level Coder output
13+
channel entries in memory and replay them into the channel when a connection
14+
fails terminally or you collect a support bundle, so Support can diagnose
15+
connection failures without asking you to reproduce with debug logging on. Set
16+
to `0` to disable.
17+
18+
### Fixed
19+
20+
- Stop retrying after an unrecoverable WebSocket close (`1002`/`1003`) and
21+
replay the buffered connection logs instead. Close codes never reached the
22+
reconnect logic, so these closes retried forever. Server-initiated normal
23+
closes (`1000`/`1001`) keep reconnecting.
24+
825
## [v1.16.3](https://github.com/coder/vscode-coder/releases/tag/v1.16.3) 2026-09-14
926

1027
### Changed

‎CONTRIBUTING.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,32 @@ next to the code:
132132

133133
**[`src/instrumentation/CONVENTIONS.md`](src/instrumentation/CONVENTIONS.md)**
134134

135+
## Logging
136+
137+
The extension logs to the "Coder" output channel, a `LogOutputChannel` that gates
138+
messages by the level chosen in its gear menu. To help Support diagnose
139+
connection failures without asking users to reproduce with debug logging enabled,
140+
a `BufferingLogger` ([`src/logging/logBuffer.ts`](src/logging/logBuffer.ts))
141+
wraps the channel and keeps a bounded, in-memory ring of the entries that sit
142+
**below** the current level, which the channel would otherwise drop.
143+
144+
When a WebSocket fails terminally, a remote session closes after a failed or
145+
canceled open, or you collect a support bundle, the extension replays the ring
146+
into the channel. The first physical line of each replayed entry carries a
147+
`[buffered]` marker with its original timestamp and level, and any continuation
148+
lines carry the bare marker.
149+
Capture is best-effort: the channel writes on its own schedule, so a bundle may
150+
miss the most recent lines, but a later failure flush still replays them.
151+
Transient reconnects and intentional teardown never flush, and neither does a
152+
handshake `401` (a 401 explains itself, and with OAuth a refresh reconnects the
153+
same socket). Nothing is recorded or flushed while the channel is at `Off`.
154+
155+
The buffer size is set by `coder.connectionLogBuffer.size` (number of entries;
156+
`0` disables it) and lives in memory, so a hard kill or out-of-memory event
157+
loses it. Extension SSH debug logs that pass through the shared logger are
158+
buffered; the CLI `ProxyCommand` file logs under `coder.proxyLogDirectory` are
159+
not, since support bundles already collect them from disk.
160+
135161
## Testing
136162

137163
There are a few ways you can test the "Open in VS Code" flow:

‎package.json‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@
215215
"minimum": 0,
216216
"default": 250
217217
},
218+
"coder.connectionLogBuffer.size": {
219+
"markdownDescription": "Maximum number of log entries below the Coder output channel's log level to keep in memory. When a connection fails terminally or you collect a support bundle, the extension replays them into the channel so a bundle can carry the detail leading up to the failure without debug logging enabled beforehand. Capture is best-effort, since the channel writes on its own schedule. Set to `0` to disable.",
220+
"type": "number",
221+
"minimum": 0,
222+
"maximum": 10000,
223+
"default": 1000
224+
},
218225
"coder.httpClientLogLevel": {
219226
"markdownDescription": "Controls the verbosity of HTTP client logging. This affects what details are logged for each HTTP request and response.",
220227
"type": "string",

‎src/api/coderApi.ts‎

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ import {
2626
} from "../logging/httpLogger";
2727
import { HttpRequestsTelemetry } from "../logging/httpRequestsTelemetry";
2828
import {
29-
HttpClientLogLevel,
3029
type RequestConfigWithMeta,
30+
type HttpClientLogLevel,
3131
} from "../logging/types";
3232
import { sizeOf } from "../logging/utils";
3333
import { AuthConfigTracker } from "../settings/authConfig";
3434
import { getHeaderCommand } from "../settings/headers";
35+
import { readHttpClientLogLevel } from "../settings/logger";
3536
import {
3637
NOOP_TELEMETRY_REPORTER,
3738
type TelemetryReporter,
@@ -48,6 +49,7 @@ import {
4849
type SocketFactory,
4950
} from "../websocket/reconnectingWebSocket";
5051
import { SseConnection } from "../websocket/sseConnection";
52+
import { handshakeStatus } from "../websocket/utils";
5153

5254
import { getRefreshCommand, refreshCertificates } from "./certificateRefresh";
5355
import {
@@ -69,6 +71,7 @@ import type {
6971
} from "coder/site/src/api/typesGenerated";
7072
import type { ClientOptions } from "ws";
7173

74+
import type { ConnectionStateReason } from "../instrumentation/websocket";
7275
import type { Logger } from "../logging/logger";
7376
import type {
7477
CloseEvent,
@@ -125,6 +128,10 @@ export class CoderApi extends Api implements vscode.Disposable {
125128
private readonly telemetry: TelemetryReporter,
126129
private readonly httpRequestsTelemetry: HttpRequestsTelemetry,
127130
private readonly authConfigTracker: AuthConfigTracker,
131+
private readonly onConnectionFailure?: (
132+
reason: ConnectionStateReason,
133+
route: string,
134+
) => void,
128135
) {
129136
super();
130137
wrapWithValidation(this);
@@ -145,6 +152,10 @@ export class CoderApi extends Api implements vscode.Disposable {
145152
token: string | undefined,
146153
output: Logger,
147154
telemetry: TelemetryReporter = NOOP_TELEMETRY_REPORTER,
155+
onConnectionFailure?: (
156+
reason: ConnectionStateReason,
157+
route: string,
158+
) => void,
148159
): CoderApi {
149160
const httpRequestsTelemetry = new HttpRequestsTelemetry(telemetry);
150161
const authConfigTracker = new AuthConfigTracker();
@@ -153,6 +164,7 @@ export class CoderApi extends Api implements vscode.Disposable {
153164
telemetry,
154165
httpRequestsTelemetry,
155166
authConfigTracker,
167+
onConnectionFailure,
156168
);
157169
client.getAxiosInstance().defaults.timeout = DEFAULT_REQUEST_TIMEOUT_MS;
158170
client.getAxiosInstance().defaults.headers.common[BAGGAGE_HEADER] =
@@ -282,9 +294,10 @@ export class CoderApi extends Api implements vscode.Disposable {
282294
watchTargets: string[],
283295
options?: ClientOptions,
284296
) => {
285-
return this.createReconnectingSocket(() =>
297+
const apiRoute = "/api/v2/notifications/inbox/watch";
298+
return this.createReconnectingSocket(apiRoute, () =>
286299
this.createOneWayWebSocket<GetInboxNotificationResponse>({
287-
apiRoute: "/api/v2/notifications/inbox/watch",
300+
apiRoute,
288301
searchParams: {
289302
format: "plaintext",
290303
templates: watchTemplates.join(","),
@@ -296,9 +309,10 @@ export class CoderApi extends Api implements vscode.Disposable {
296309
};
297310

298311
watchWorkspace = async (workspace: Workspace, options?: ClientOptions) => {
299-
return this.createReconnectingSocket(() =>
312+
const apiRoute = `/api/v2/workspaces/${workspace.id}/watch-ws`;
313+
return this.createReconnectingSocket(apiRoute, () =>
300314
this.createStreamWithSseFallback({
301-
apiRoute: `/api/v2/workspaces/${workspace.id}/watch-ws`,
315+
apiRoute,
302316
fallbackApiRoute: `/api/v2/workspaces/${workspace.id}/watch`,
303317
options,
304318
}),
@@ -309,9 +323,10 @@ export class CoderApi extends Api implements vscode.Disposable {
309323
agentId: WorkspaceAgent["id"],
310324
options?: ClientOptions,
311325
) => {
312-
return this.createReconnectingSocket(() =>
326+
const apiRoute = `/api/v2/workspaceagents/${agentId}/watch-metadata-ws`;
327+
return this.createReconnectingSocket(apiRoute, () =>
313328
this.createStreamWithSseFallback({
314-
apiRoute: `/api/v2/workspaceagents/${agentId}/watch-metadata-ws`,
329+
apiRoute,
315330
fallbackApiRoute: `/api/v2/workspaceagents/${agentId}/watch-metadata`,
316331
options,
317332
}),
@@ -530,24 +545,26 @@ export class CoderApi extends Api implements vscode.Disposable {
530545
* Check if an error is a 404 Not Found error.
531546
*/
532547
private is404Error(error: unknown): boolean {
533-
const msg = error instanceof Error ? error.message : String(error);
534-
return msg.includes(String(HttpStatusCode.NOT_FOUND));
548+
return handshakeStatus(error) === HttpStatusCode.NOT_FOUND;
535549
}
536550

537551
/**
538552
* Create a ReconnectingWebSocket and track it for lifecycle management.
539553
*/
540554
private async createReconnectingSocket<TData>(
555+
apiRoute: string,
541556
socketFactory: SocketFactory<TData>,
542557
): Promise<ReconnectingWebSocket<TData>> {
543558
const options: ReconnectingWebSocketOptions = {
559+
route: apiRoute,
544560
onCertificateRefreshNeeded: async () => {
545561
const refreshCommand = getRefreshCommand();
546562
if (!refreshCommand) {
547563
return false;
548564
}
549565
return refreshCertificates(refreshCommand, this.output);
550566
},
567+
onConnectionFailure: this.onConnectionFailure,
551568
telemetry: this.telemetry,
552569
};
553570

@@ -814,12 +831,5 @@ function getSize(headers: AxiosHeaders, data: unknown): number | undefined {
814831
}
815832

816833
function getLogLevel(): HttpClientLogLevel {
817-
const logLevelStr = vscode.workspace
818-
.getConfiguration()
819-
.get(
820-
"coder.httpClientLogLevel",
821-
HttpClientLogLevel[HttpClientLogLevel.BASIC],
822-
)
823-
.toUpperCase();
824-
return HttpClientLogLevel[logLevelStr as keyof typeof HttpClientLogLevel];
834+
return readHttpClientLogLevel(vscode.workspace.getConfiguration());
825835
}

‎src/commands.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import type { MementoManager } from "./core/mementoManager";
8484
import type { PathResolver } from "./core/pathResolver";
8585
import type { SecretsManager, SessionAuth } from "./core/secretsManager";
8686
import type { DeploymentManager } from "./deployment/deploymentManager";
87+
import type { ConnectionLogBuffer } from "./logging/logBuffer";
8788
import type { Logger } from "./logging/logger";
8889
import type { LoginCoordinator, LoginMethod } from "./login/loginCoordinator";
8990
import type { TelemetryService } from "./telemetry/service";
@@ -168,6 +169,7 @@ export class Commands {
168169
private readonly authTelemetry: AuthTelemetry;
169170
private readonly diagnosticTelemetry: DiagnosticTelemetry;
170171
private readonly workspaceOpenTelemetry: WorkspaceOpenTelemetry;
172+
private readonly connectionLogBuffer: ConnectionLogBuffer;
171173

172174
// These will only be populated when actively connected to a workspace and are
173175
// used in commands. Because commands can be executed by the user, it is not
@@ -193,6 +195,7 @@ export class Commands {
193195
this.telemetryService,
194196
);
195197
this.logger = serviceContainer.getLogger();
198+
this.connectionLogBuffer = serviceContainer.getConnectionLogBuffer();
196199
this.pathResolver = serviceContainer.getPathResolver();
197200
this.mementoManager = serviceContainer.getMementoManager();
198201
this.secretsManager = serviceContainer.getSecretsManager();
@@ -486,6 +489,11 @@ export class Commands {
486489
: [];
487490

488491
progress.report({ message: "Collecting diagnostics..." });
492+
// Flush buffered below-level connection logs before the CLI runs so
493+
// the channel has time to write them to disk; retain the ring so a
494+
// later failure flush still replays them. Best-effort: the channel
495+
// writes on its own schedule, so the tail may not land in this bundle.
496+
this.connectionLogBuffer.flush("support_bundle", { retain: true });
489497
await cliExec.supportBundle(env, workspaceId, {
490498
outputPath: outputUri.fsPath,
491499
agentName,

‎src/core/container.ts‎

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import * as vscode from "vscode";
22

3+
import { watchConfigurationChanges } from "../configWatcher";
34
import { AuthTelemetry } from "../instrumentation/auth";
5+
import {
6+
BufferingLogger,
7+
type ConnectionLogBuffer,
8+
} from "../logging/logBuffer";
49
import { prefixLogger } from "../logging/prefixLogger";
510
import { shortId } from "../logging/utils";
611
import { LoginCoordinator } from "../login/loginCoordinator";
712
import { OAuthCallback } from "../oauth/oauthCallback";
13+
import {
14+
CONNECTION_LOG_BUFFER_SIZE_SETTING,
15+
readConnectionLogBufferSize,
16+
} from "../settings/logger";
817
import { buildSession, extractExtensionVersion } from "../telemetry/event";
918
import { TelemetryService } from "../telemetry/service";
1019
import { LocalJsonlSink } from "../telemetry/sinks/localJsonlSink";
@@ -29,7 +38,8 @@ import type { Logger } from "../logging/logger";
2938
*/
3039
export class ServiceContainer implements vscode.Disposable {
3140
private readonly outputChannel: vscode.LogOutputChannel;
32-
private readonly logger: Logger;
41+
private readonly logger: BufferingLogger;
42+
private readonly connectionLogBufferConfigSubscription: vscode.Disposable;
3343
private readonly pathResolver: PathResolver;
3444
private readonly mementoManager: MementoManager;
3545
private readonly secretsManager: SecretsManager;
@@ -48,9 +58,21 @@ export class ServiceContainer implements vscode.Disposable {
4858
this.outputChannel = vscode.window.createOutputChannel("Coder", {
4959
log: true,
5060
});
51-
this.logger = prefixLogger(
61+
const readSize = () =>
62+
readConnectionLogBufferSize(vscode.workspace.getConfiguration());
63+
this.logger = new BufferingLogger(
64+
prefixLogger(this.outputChannel, `[session ${shortId(sessionId)}]`),
5265
this.outputChannel,
53-
`[session ${shortId(sessionId)}]`,
66+
readSize(),
67+
);
68+
this.connectionLogBufferConfigSubscription = watchConfigurationChanges(
69+
[
70+
{
71+
setting: CONNECTION_LOG_BUFFER_SIZE_SETTING,
72+
getValue: readSize,
73+
},
74+
],
75+
() => this.logger.setCapacity(readSize()),
5476
);
5577
this.pathResolver = new PathResolver(
5678
context.globalStorageUri.fsPath,
@@ -148,6 +170,11 @@ export class ServiceContainer implements vscode.Disposable {
148170
return this.logger;
149171
}
150172

173+
/** The connection log buffer that replays below-level entries on failure. */
174+
getConnectionLogBuffer(): ConnectionLogBuffer {
175+
return this.logger;
176+
}
177+
151178
getCliManager(): CliManager {
152179
return this.cliManager;
153180
}
@@ -193,6 +220,7 @@ export class ServiceContainer implements vscode.Disposable {
193220
this.commandManager.dispose();
194221
this.contextManager.dispose();
195222
this.loginCoordinator.dispose();
223+
this.connectionLogBufferConfigSubscription.dispose();
196224
try {
197225
await this.telemetryService.dispose();
198226
} finally {

‎src/extension.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ async function doActivate(
141141
deploymentSessionAuth?.token,
142142
output,
143143
telemetryService,
144+
serviceContainer.getConnectionLogBuffer().onConnectionFailure,
144145
);
145146
ctx.subscriptions.push(client);
146147

‎src/instrumentation/EVENTS.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ Emitted by `WebSocketTelemetry`.
428428
These events share one value set, **ConnectionStateReason**: `initial_connect`,
429429
`manual_reconnect`, `certificate_refresh`, `scheduled_reconnect`, `open`,
430430
`disconnect`, `dispose`, `unrecoverable_close`, `unrecoverable_http`,
431-
`certificate_error`, `connection_error`, `normal_close`, `unexpected_close`.
431+
`certificate_error`, `connection_error`, `unexpected_close`.
432432

433433
### Logs
434434

@@ -451,11 +451,11 @@ These events share one value set, **ConnectionStateReason**: `initial_connect`,
451451
Emitted as an error log (with the `error` block) when a socket error caused
452452
the drop.
453453

454-
| Attribute | Values |
455-
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
456-
| `cause` | `manual_disconnect`, `replaced`, `unrecoverable_close`, `normal_close`, `unexpected_close`, `disposed`, `error` |
457-
| `close_code` | WebSocket close code (when known) |
458-
| `connection_duration_ms` (measurement) | time the connection was open |
454+
| Attribute | Values |
455+
| -------------------------------------- | ----------------------------------------------------------------------------------------------- |
456+
| `cause` | `manual_disconnect`, `replaced`, `unrecoverable_close`, `unexpected_close`, `disposed`, `error` |
457+
| `close_code` | WebSocket close code (when known) |
458+
| `connection_duration_ms` (measurement) | time the connection was open |
459459

460460
#### `connection.reconnect_resolved`
461461

‎src/instrumentation/websocket.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@ export type ConnectionStateReason =
1616
| "unrecoverable_http"
1717
| "certificate_error"
1818
| "connection_error"
19-
| "normal_close"
2019
| "unexpected_close";
2120

2221
export type ConnectionDropCause =
2322
| "manual_disconnect"
2423
| "replaced"
2524
| "unrecoverable_close"
26-
| "normal_close"
2725
| "unexpected_close"
2826
| "disposed"
2927
| "error";

‎src/logging/formatters.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const SENSITIVE_BODY_FIELDS: ReadonlySet<Lowercase<string>> = new Set([
2424
"id_token",
2525
"password",
2626
"refresh_token",
27+
"registration_access_token",
2728
"token",
2829
]);
2930

0 commit comments

Comments
 (0)