Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ac90e60
Adopt canonical log lines across SDK and container
whoiskatrin Mar 3, 2026
188ef58
Merge branch 'origin/main' into better-logs - resolve logging conflic…
whoiskatrin Mar 3, 2026
0f35b74
Keep pretty logs on a single line
whoiskatrin Mar 3, 2026
e63fdca
Fix JS executor path resolution and spawn logs
whoiskatrin Mar 3, 2026
41d58b2
Trigger image rebuild
whoiskatrin Mar 3, 2026
1f24c82
feat: add global error handlers to prevent fragmented stack traces in…
whoiskatrin Mar 3, 2026
cde9543
Stop forwarding SANDBOX_LOG_FORMAT to container
whoiskatrin Mar 3, 2026
86e25b1
Remove scattered service-layer error logs in favor of wide events
whoiskatrin Mar 3, 2026
58ac880
Prevent multi-line log splitting in dashboard
whoiskatrin Mar 4, 2026
6aa0d51
Resolve merge conflict with main
whoiskatrin Mar 4, 2026
6d535de
Use descriptive wide event names for dashboard readability
whoiskatrin Mar 4, 2026
9797c87
Include status code in HTTP log message, drop redundant fields
whoiskatrin Mar 4, 2026
ffe7ef3
Trigger CI
whoiskatrin Mar 4, 2026
cc31a9e
Address review feedback for canonical log lines
ask-bonk[bot] Mar 5, 2026
cc4e282
Fixed errors, naming, truncation, changeset
ask-bonk[bot] Mar 5, 2026
d2881bb
Merge branch 'main' into better-logs
ask-bonk[bot] Mar 5, 2026
79f93c7
Add canonical log lines across all container services
whoiskatrin Mar 5, 2026
9416405
ci: trigger
whoiskatrin Mar 5, 2026
71a7c98
ci: trigger
whoiskatrin Mar 5, 2026
bb8e6ad
Remove logging-best-practices skill from codebase
whoiskatrin Mar 5, 2026
6544d3b
Remove logging-best-practices skill from Claude and Windsurf
whoiskatrin Mar 5, 2026
55a4e36
Remove logging-best-practices skill from .pi
whoiskatrin Mar 5, 2026
765c7c8
ci: trigger
whoiskatrin Mar 5, 2026
96c961c
ci: trigger
whoiskatrin Mar 5, 2026
de6be33
Address review feedback across logging PR
ask-bonk[bot] Mar 5, 2026
38bd49d
Merge remote-tracking branch 'origin/main' into improve-logging
ask-bonk[bot] Mar 9, 2026
b98a95d
Merge origin/main into improve-logging
whoiskatrin Mar 23, 2026
0e36fdd
Pass GitLogger to GitService to preserve credential sanitization in logs
whoiskatrin Mar 23, 2026
90c2ced
Fix backup error log levels
whoiskatrin Mar 23, 2026
829437b
Redact git credentials in cloneRepository outer catch block
whoiskatrin Mar 23, 2026
b322542
Fix websocket headers and backup logs
whoiskatrin Mar 23, 2026
10c3516
Update packages/sandbox-container/src/services/file-service.ts
whoiskatrin Mar 23, 2026
fc93bd8
Update packages/sandbox-container/src/services/file-service.ts
whoiskatrin Mar 23, 2026
86e9cb7
Update packages/sandbox-container/src/services/file-service.ts
whoiskatrin Mar 23, 2026
4d0888a
Update packages/sandbox-container/src/services/git-service.ts
whoiskatrin Mar 23, 2026
ade7f6e
Merge origin/main into improve-logging
whoiskatrin Mar 23, 2026
7835309
Merge remote-tracking branch 'origin/main' into improve-logging
ghostwriternr Mar 24, 2026
d36b68c
Make canonical logs queryable, scannable, and credential-safe (#520)
ghostwriternr Mar 26, 2026
320da09
Update changeset
ghostwriternr Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/logging-wide-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cloudflare/sandbox': patch
---

Structured logging for Workers Observability and Containers Logs. All operations emit queryable fields (`event`, `outcome`, `durationMs`, `command`, `exitCode`, `sessionId`) that can be filtered and aggregated in the dashboard. Presigned R2 URL parameters and embedded git URL credentials are redacted from all log fields. Set `SANDBOX_LOG_FORMAT=pretty` for readable local dev output.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions packages/sandbox-container/src/core/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,7 @@ export class Container {
sessionManager
);
const portService = new PortService(portStore, securityAdapter, logger);
const gitService = new GitService(
securityAdapter,
gitLogger,
sessionManager
);
const gitService = new GitService(securityAdapter, sessionManager, gitLogger);
const interpreterService = new InterpreterService(logger);
const backupService = new BackupService(logger, sessionManager);
const desktopService = new DesktopService(logger);
Expand Down
3 changes: 2 additions & 1 deletion packages/sandbox-container/src/core/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class Router {
// Create request context
const context: RequestContext = {
sessionId: this.extractSessionId(request),
sandboxId: request.headers.get('X-Sandbox-Id') ?? undefined,
corsHeaders: this.getCorsHeaders(),
requestId: this.generateRequestId(),
timestamp: new Date()
Expand Down Expand Up @@ -204,7 +205,7 @@ export class Router {
private getCorsHeaders(): Record<string, string> {
return {
'Access-Control-Allow-Headers':
'Content-Type, Authorization, X-Session-Id',
'Content-Type, Authorization, X-Session-Id, X-Sandbox-Id, X-Trace-Id',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Origin': '*'
};
Expand Down
2 changes: 2 additions & 0 deletions packages/sandbox-container/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface Handler<TRequest, TResponse> {

export interface RequestContext {
sessionId?: string;
sandboxId?: string;
corsHeaders: Record<string, string>;
requestId: string;
timestamp: Date;
Expand Down Expand Up @@ -278,6 +279,7 @@ export interface ProcessOptions {
cwd?: string;
encoding?: string;
autoCleanup?: boolean;
origin?: 'user' | 'internal';
}

export interface CommandResult {
Expand Down
9 changes: 6 additions & 3 deletions packages/sandbox-container/src/handlers/execute-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export class ExecuteHandler extends BaseHandler<Request, Response> {
sessionId,
timeoutMs: body.timeoutMs,
env: body.env,
cwd: body.cwd
cwd: body.cwd,
origin: body.origin
}
);

Expand All @@ -81,7 +82,8 @@ export class ExecuteHandler extends BaseHandler<Request, Response> {
sessionId,
timeoutMs: body.timeoutMs,
env: body.env,
cwd: body.cwd
cwd: body.cwd,
origin: body.origin
});

if (!result.success) {
Expand Down Expand Up @@ -116,7 +118,8 @@ export class ExecuteHandler extends BaseHandler<Request, Response> {
const processResult = await this.processService.startProcess(body.command, {
sessionId,
env: body.env,
cwd: body.cwd
cwd: body.cwd,
origin: body.origin
});

if (!processResult.success) {
Expand Down
7 changes: 0 additions & 7 deletions packages/sandbox-container/src/handlers/git-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ export class GitHandler extends BaseHandler<Request, Response> {

return this.createTypedResponse(response, context);
} else {
this.logger.error('Repository clone failed', undefined, {
requestId: context.requestId,
repoUrl: body.repoUrl,
errorCode: result.error.code,
errorMessage: result.error.message
});

return this.createErrorResponse(result.error, context);
}
}
Expand Down
30 changes: 20 additions & 10 deletions packages/sandbox-container/src/handlers/pty-ws-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ export class PtyWebSocketHandler {

async onOpen(ws: ServerWebSocket<PtyWSData>): Promise<void> {
const { sessionId, connectionId, cols, rows, shell } = ws.data;

this.logger.debug('PTY WebSocket opened', { sessionId, connectionId });
// Lifecycle captured in onClose canonical log line

const result = await this.sessionManager.getPty(sessionId, {
cols,
Expand Down Expand Up @@ -82,7 +81,10 @@ export class PtyWebSocketHandler {
const conn = this.connections.get(connectionId);

if (!conn) {
this.logger.warn('Message for unknown PTY connection', { connectionId });
this.logger.warn('pty.message', {
connectionId,
outcome: 'unknown_connection'
});
return;
}

Expand All @@ -96,11 +98,12 @@ export class PtyWebSocketHandler {
onClose(ws: ServerWebSocket<PtyWSData>, code: number, reason: string): void {
const { connectionId, sessionId } = ws.data;

this.logger.debug('PTY WebSocket closed', {
this.logger.debug('pty.connection', {
sessionId,
connectionId,
code,
reason
reason,
outcome: 'closed'
});

const conn = this.connections.get(connectionId);
Expand All @@ -112,7 +115,7 @@ export class PtyWebSocketHandler {

onDrain(ws: ServerWebSocket<PtyWSData>): void {
const { connectionId } = ws.data;
this.logger.debug('PTY WebSocket drained', { connectionId });
this.logger.debug('pty.drain', { connectionId });
}

private sendPtyData(
Expand All @@ -123,7 +126,7 @@ export class PtyWebSocketHandler {
const result = ws.sendBinary(data);

if (result === 0) {
this.logger.debug('PTY send failed - connection dead', { connectionId });
this.logger.debug('pty.send', { connectionId, outcome: 'dead' });
const conn = this.connections.get(connectionId);
if (conn) {
conn.subscription.dispose();
Expand All @@ -150,10 +153,17 @@ export class PtyWebSocketHandler {
}
pty.resize(control.cols, control.rows);
} else {
this.logger.warn('Unknown PTY control message', { control });
this.logger.warn('pty.control', {
connectionId: ws.data.connectionId,
controlType: control.type,
outcome: 'unknown_type'
});
}
} catch (err) {
this.logger.error('Failed to parse PTY control message', err as Error);
this.logger.error('pty.control', err as Error, {
connectionId: ws.data.connectionId,
outcome: 'parse_error'
});
this.sendStatus(ws, {
type: 'error',
message: 'Invalid control message'
Expand All @@ -168,7 +178,7 @@ export class PtyWebSocketHandler {
try {
ws.send(JSON.stringify(status));
} catch (err) {
this.logger.error('Failed to send PTY status', err as Error);
this.logger.error('pty.sendStatus', err as Error);
}
}
}
38 changes: 18 additions & 20 deletions packages/sandbox-container/src/handlers/ws-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,20 @@ export class WebSocketAdapter {
/**
* Handle WebSocket connection open
*/
onOpen(ws: ServerWebSocket<WSData>): void {
this.logger.debug('WebSocket connection opened', {
connectionId: ws.data.connectionId
});
onOpen(_ws: ServerWebSocket<WSData>): void {
// Lifecycle captured in onClose canonical log line
}

/**
* Handle WebSocket connection close
* Handle WebSocket connection close — canonical log line for connection lifecycle
*/
onClose(ws: ServerWebSocket<WSData>, code: number, reason: string): void {
const connectionId = ws.data.connectionId;
this.logger.debug('WebSocket connection closed', {
this.logger.debug('ws.connection', {
connectionId,
code,
reason
reason,
outcome: 'closed'
});

for (const [requestId, stream] of this.activeStreams) {
Expand Down Expand Up @@ -136,20 +135,18 @@ export class WebSocketAdapter {

const request = parsed as WSRequest;

this.logger.debug('WebSocket request received', {
connectionId: ws.data.connectionId,
id: request.id,
method: request.method,
path: request.path
});

try {
await this.handleRequest(ws, request);
} catch (error) {
this.logger.error(
'Error handling WebSocket request',
'ws.request',
error instanceof Error ? error : new Error(String(error)),
{ requestId: request.id }
{
connectionId: ws.data.connectionId,
requestId: request.id,
method: request.method,
path: request.path
}
);
this.sendError(
ws,
Expand Down Expand Up @@ -375,9 +372,9 @@ export class WebSocketAdapter {
}

this.logger.error(
'Error reading stream',
'ws.stream',
error instanceof Error ? error : new Error(String(error)),
{ requestId }
{ connectionId: ws.data.connectionId, requestId }
);
this.sendError(
ws,
Expand All @@ -404,8 +401,9 @@ export class WebSocketAdapter {
return true;
} catch (error) {
this.logger.error(
'Failed to send WebSocket message, closing connection',
error instanceof Error ? error : new Error(String(error))
'ws.send',
error instanceof Error ? error : new Error(String(error)),
{ connectionId: ws.data.connectionId }
);
try {
ws.close(1011, 'Send failed'); // 1011 = unexpected condition
Expand Down
Loading
Loading