@intelligems/sst 2.49.6-ig.5 → 2.49.6-ig.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/dev.js +80 -3
- package/cli/sst.js +0 -1
- package/iot.js +30 -0
- package/package.json +2 -2
- package/package.json.bak +2 -2
- package/runtime/debug-bridge-logging.d.ts +24 -0
- package/runtime/debug-bridge-logging.js +98 -0
- package/runtime/debug-file-logger.d.ts +80 -0
- package/runtime/debug-file-logger.js +148 -0
- package/runtime/event-trace-logging.d.ts +27 -0
- package/runtime/event-trace-logging.js +69 -0
- package/runtime/handlers/node.js +8 -9
- package/runtime/handlers.d.ts +2 -0
- package/runtime/handlers.js +110 -65
- package/runtime/iot.js +23 -3
- package/runtime/mono-build-config.d.ts +55 -0
- package/runtime/mono-build-config.js +80 -0
- package/runtime/request-utils.d.ts +21 -0
- package/runtime/request-utils.js +102 -0
- package/runtime/runtime.d.ts +1 -0
- package/runtime/server.js +54 -7
- package/runtime/worker-pool-logging.js +65 -70
- package/runtime/workers.d.ts +26 -0
- package/runtime/workers.js +213 -16
- package/support/bridge/live-lambda.mjs +38 -38
- package/support/nodejs-runtime/index.mjs +17 -4
- package/support/python-runtime/runtime.py +20 -20
package/cli/commands/dev.js
CHANGED
|
@@ -36,6 +36,13 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
36
36
|
const colors = ["#01cdfe", "#ff71ce", "#05ffa1", "#b967ff"];
|
|
37
37
|
let index = 0;
|
|
38
38
|
const pending = new Map();
|
|
39
|
+
// Track warmup request IDs to suppress their logs
|
|
40
|
+
const warmupRequestIDs = new Set();
|
|
41
|
+
// Helper to check if an event payload is a warmup request
|
|
42
|
+
function isWarmupEvent(event) {
|
|
43
|
+
return event && typeof event === 'object' &&
|
|
44
|
+
('ding' in event || 'warmer' in event || event.__sst_warmup === true);
|
|
45
|
+
}
|
|
39
46
|
function prefix(requestID) {
|
|
40
47
|
const exists = pending.get(requestID);
|
|
41
48
|
if (exists) {
|
|
@@ -53,14 +60,54 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
53
60
|
// index--;
|
|
54
61
|
// if (index < 0) index = colors.length - 1;
|
|
55
62
|
pending.delete(requestID);
|
|
63
|
+
warmupRequestIDs.delete(requestID);
|
|
56
64
|
}
|
|
65
|
+
// Warmup progress bar state
|
|
66
|
+
let warmupSpinner = null;
|
|
67
|
+
let warmupTotal = 0;
|
|
68
|
+
let warmupCompleted = 0;
|
|
69
|
+
bus.subscribe("warmup.start", async (evt) => {
|
|
70
|
+
warmupTotal = evt.properties.count;
|
|
71
|
+
warmupCompleted = 0;
|
|
72
|
+
warmupSpinner = createSpinner({
|
|
73
|
+
color: "cyan",
|
|
74
|
+
text: Colors.dim(` Warming up workers... 0/${warmupTotal}`),
|
|
75
|
+
}).start();
|
|
76
|
+
});
|
|
77
|
+
bus.subscribe("warmup.progress", async (evt) => {
|
|
78
|
+
warmupCompleted = evt.properties.completed;
|
|
79
|
+
if (warmupSpinner) {
|
|
80
|
+
warmupSpinner.text = Colors.dim(` Warming up workers... ${warmupCompleted}/${warmupTotal}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
bus.subscribe("warmup.complete", async (evt) => {
|
|
84
|
+
if (warmupSpinner) {
|
|
85
|
+
warmupSpinner.succeed(Colors.dim(` Warmed up ${evt.properties.success} workers in ${evt.properties.elapsedMs}ms`));
|
|
86
|
+
warmupSpinner = null;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
57
89
|
bus.subscribe("function.invoked", async (evt) => {
|
|
90
|
+
// Check if this is a warmup request and track it
|
|
91
|
+
if (isWarmupEvent(evt.properties.event)) {
|
|
92
|
+
warmupRequestIDs.add(evt.properties.requestID);
|
|
93
|
+
return; // Don't log warmup invocations
|
|
94
|
+
}
|
|
58
95
|
Colors.line(prefix(evt.properties.requestID), Colors.dim.bold("Invoked"), Colors.dim(useFunctions().fromID(evt.properties.functionID)?.handler));
|
|
59
96
|
});
|
|
60
97
|
bus.subscribe("worker.stdout", async (evt) => {
|
|
98
|
+
if (evt.properties.message.includes('[LOG]')) {
|
|
99
|
+
console.log(evt.properties.message);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Skip warmup request logs
|
|
103
|
+
if (warmupRequestIDs.has(evt.properties.requestID))
|
|
104
|
+
return;
|
|
61
105
|
const info = useFunctions().fromID(evt.properties.functionID);
|
|
62
106
|
prefix(evt.properties.requestID);
|
|
63
|
-
const
|
|
107
|
+
const pendingReq = pending.get(evt.properties.requestID);
|
|
108
|
+
if (!pendingReq)
|
|
109
|
+
return; // Safety check
|
|
110
|
+
const { started } = pendingReq;
|
|
64
111
|
for (let line of evt.properties.message.split("\n")) {
|
|
65
112
|
// Remove prefix from container logs
|
|
66
113
|
if (info?.runtime === "container") {
|
|
@@ -104,22 +151,46 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
104
151
|
if (info.enableLiveDev === false)
|
|
105
152
|
return;
|
|
106
153
|
Colors.gap();
|
|
107
|
-
Colors.line(Colors.danger("
|
|
154
|
+
Colors.line(Colors.danger("═".repeat(60)));
|
|
155
|
+
Colors.line(Colors.danger(" FUNCTION BUILD FAILED - SST IS EXITING"));
|
|
156
|
+
Colors.line(Colors.danger("═".repeat(60)));
|
|
157
|
+
Colors.gap();
|
|
158
|
+
Colors.line(Colors.danger("✖ "), "Handler:", info.handler);
|
|
159
|
+
Colors.line(Colors.danger("✖ "), "Function ID:", evt.properties.functionID);
|
|
160
|
+
Colors.gap();
|
|
161
|
+
Colors.line(Colors.danger(" Errors:"));
|
|
108
162
|
for (const line of evt.properties.errors) {
|
|
109
|
-
Colors.line("
|
|
163
|
+
Colors.line(Colors.danger(" → "), line);
|
|
110
164
|
}
|
|
111
165
|
Colors.gap();
|
|
166
|
+
Colors.line(Colors.danger("═".repeat(60)));
|
|
167
|
+
Colors.line(Colors.danger(" Fix the error above and restart `sst dev`"));
|
|
168
|
+
Colors.line(Colors.danger("═".repeat(60)));
|
|
169
|
+
Colors.gap();
|
|
170
|
+
process.exit(1);
|
|
112
171
|
});
|
|
113
172
|
bus.subscribe("function.success", async (evt) => {
|
|
173
|
+
// Skip warmup request logs
|
|
174
|
+
if (warmupRequestIDs.has(evt.properties.requestID)) {
|
|
175
|
+
end(evt.properties.requestID);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
114
178
|
// stdout logs sometimes come in after
|
|
115
179
|
const p = prefix(evt.properties.requestID);
|
|
116
180
|
const req = pending.get(evt.properties.requestID);
|
|
181
|
+
if (!req)
|
|
182
|
+
return; // Safety check
|
|
117
183
|
setTimeout(() => {
|
|
118
184
|
Colors.line(p, Colors.dim(`Done in ${Date.now() - req.started - 100}ms`));
|
|
119
185
|
end(evt.properties.requestID);
|
|
120
186
|
}, 100);
|
|
121
187
|
});
|
|
122
188
|
bus.subscribe("function.error", async (evt) => {
|
|
189
|
+
// Skip warmup request logs
|
|
190
|
+
if (warmupRequestIDs.has(evt.properties.requestID)) {
|
|
191
|
+
end(evt.properties.requestID);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
123
194
|
setTimeout(() => {
|
|
124
195
|
Colors.line(prefix(evt.properties.requestID), Colors.danger.bold("Error:"), Colors.danger.bold(evt.properties.errorMessage));
|
|
125
196
|
for (const line of evt.properties.trace || []) {
|
|
@@ -342,6 +413,12 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
342
413
|
import("./plugins/warmer.js").then((mod) => mod.useRDSWarmer()),
|
|
343
414
|
useFunctionLogger(),
|
|
344
415
|
]);
|
|
416
|
+
// Trigger warmup by invoking Lambda functions with warmup payloads
|
|
417
|
+
// This creates workers through the real request flow
|
|
418
|
+
import("../../runtime/workers.js").then(async (mod) => {
|
|
419
|
+
const workers = await mod.useRuntimeWorkers();
|
|
420
|
+
await workers.triggerWarmup(30);
|
|
421
|
+
});
|
|
345
422
|
}
|
|
346
423
|
catch (e) {
|
|
347
424
|
await exitWithError(e);
|
package/cli/sst.js
CHANGED
package/iot.js
CHANGED
|
@@ -3,6 +3,9 @@ import { useAWSClient, useAWSCredentials } from "./credentials.js";
|
|
|
3
3
|
import { VisibleError } from "./error.js";
|
|
4
4
|
import { lazy } from "./util/lazy.js";
|
|
5
5
|
import { Logger } from "./logger.js";
|
|
6
|
+
import { logIotRx } from "./runtime/debug-bridge-logging.js";
|
|
7
|
+
import { logEventTrace } from "./runtime/event-trace-logging.js";
|
|
8
|
+
import { getRequestPath, getCorrelationId, getApiGatewayRequestId, getHttpMethod } from "./runtime/request-utils.js";
|
|
6
9
|
export const useIOTEndpoint = lazy(async () => {
|
|
7
10
|
const iot = useAWSClient(IoTClient);
|
|
8
11
|
Logger.debug("Getting IoT endpoint");
|
|
@@ -120,6 +123,19 @@ export const useIOT = lazy(async () => {
|
|
|
120
123
|
device.on("message", (_topic, buffer) => {
|
|
121
124
|
const fragment = JSON.parse(buffer.toString());
|
|
122
125
|
if (!fragment.id) {
|
|
126
|
+
const requestID = fragment.properties?.requestID;
|
|
127
|
+
logIotRx(`Received ${fragment.type} reqId=${requestID?.slice(0, 8) || 'N/A'}`);
|
|
128
|
+
if (fragment.type === "function.invoked" && requestID) {
|
|
129
|
+
const event = fragment.properties?.event;
|
|
130
|
+
logEventTrace("IOT_RECEIVED", {
|
|
131
|
+
requestID,
|
|
132
|
+
functionID: fragment.properties?.functionID,
|
|
133
|
+
path: getRequestPath(event),
|
|
134
|
+
method: getHttpMethod(event),
|
|
135
|
+
correlationId: getCorrelationId(event),
|
|
136
|
+
apiGwReqId: getApiGatewayRequestId(event),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
123
139
|
bus.publish(fragment.type, fragment.properties);
|
|
124
140
|
return;
|
|
125
141
|
}
|
|
@@ -138,6 +154,20 @@ export const useIOT = lazy(async () => {
|
|
|
138
154
|
const evt = JSON.parse(data);
|
|
139
155
|
if (evt.sourceID === bus.sourceID)
|
|
140
156
|
return;
|
|
157
|
+
const requestID = evt.properties?.requestID;
|
|
158
|
+
logIotRx(`Received ${evt.type} reqId=${requestID?.slice(0, 8) || 'N/A'} (${fragment.count} fragments)`);
|
|
159
|
+
if (evt.type === "function.invoked" && requestID) {
|
|
160
|
+
const event = evt.properties?.event;
|
|
161
|
+
logEventTrace("IOT_RECEIVED", {
|
|
162
|
+
requestID,
|
|
163
|
+
functionID: evt.properties?.functionID,
|
|
164
|
+
path: getRequestPath(event),
|
|
165
|
+
method: getHttpMethod(event),
|
|
166
|
+
correlationId: getCorrelationId(event),
|
|
167
|
+
apiGwReqId: getApiGatewayRequestId(event),
|
|
168
|
+
fragments: fragment.count,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
141
171
|
bus.publish(evt.type, evt.properties);
|
|
142
172
|
}
|
|
143
173
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sideEffects": false,
|
|
3
3
|
"name": "@intelligems/sst",
|
|
4
|
-
"version": "2.49.6-ig.
|
|
4
|
+
"version": "2.49.6-ig.7",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sst": "cli/sst.js"
|
|
7
7
|
},
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"@types/babel__generator": "^7.6.4",
|
|
114
114
|
"@types/cross-spawn": "^6.0.2",
|
|
115
115
|
"@types/express": "^4.17.14",
|
|
116
|
-
"@types/node": "
|
|
116
|
+
"@types/node": "22.13.14",
|
|
117
117
|
"@types/react": "^18.0.28",
|
|
118
118
|
"@types/uuid": "^8.3.4",
|
|
119
119
|
"@types/ws": "8.5.3",
|
package/package.json.bak
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
},
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"name": "@intelligems/sst",
|
|
8
|
-
"version": "2.49.6-ig.
|
|
8
|
+
"version": "2.49.6-ig.7",
|
|
9
9
|
"bin": {
|
|
10
10
|
"sst": "cli/sst.js"
|
|
11
11
|
},
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"@types/babel__generator": "^7.6.4",
|
|
124
124
|
"@types/cross-spawn": "^6.0.2",
|
|
125
125
|
"@types/express": "^4.17.14",
|
|
126
|
-
"@types/node": "
|
|
126
|
+
"@types/node": "22.13.14",
|
|
127
127
|
"@types/react": "^18.0.28",
|
|
128
128
|
"@types/uuid": "^8.3.4",
|
|
129
129
|
"@types/ws": "8.5.3",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log debug message for workers component
|
|
3
|
+
* Outputs to console and optionally to .sst/debug-workers.log
|
|
4
|
+
*/
|
|
5
|
+
export declare function logWorkers(message: string, details?: Record<string, any>): void;
|
|
6
|
+
/**
|
|
7
|
+
* Log debug message for server component
|
|
8
|
+
* Outputs to console and optionally to .sst/debug-server.log
|
|
9
|
+
*/
|
|
10
|
+
export declare function logServer(message: string, details?: Record<string, any>): void;
|
|
11
|
+
/**
|
|
12
|
+
* Log debug message for IoT component (publishing)
|
|
13
|
+
* Outputs to console and optionally to .sst/debug-iot.log
|
|
14
|
+
*/
|
|
15
|
+
export declare function logIot(message: string, details?: Record<string, any>): void;
|
|
16
|
+
/**
|
|
17
|
+
* Log debug message for IoT component (receiving)
|
|
18
|
+
* Outputs to console and optionally to .sst/debug-iot.log
|
|
19
|
+
*/
|
|
20
|
+
export declare function logIotRx(message: string, details?: Record<string, any>): void;
|
|
21
|
+
/**
|
|
22
|
+
* Check if bridge debug logging is enabled
|
|
23
|
+
*/
|
|
24
|
+
export declare function isDebugBridgeEnabled(): boolean;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createDebugFileLogger } from "./debug-file-logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Debug logging for SST dev bridge components
|
|
4
|
+
*
|
|
5
|
+
* Enable with: SST_DEBUG_BRIDGE=true
|
|
6
|
+
*
|
|
7
|
+
* Log files:
|
|
8
|
+
* - .sst/debug-workers.log - Worker pool and invocation handling
|
|
9
|
+
* - .sst/debug-server.log - Runtime server request/response handling
|
|
10
|
+
* - .sst/debug-iot.log - IoT message publishing and receiving
|
|
11
|
+
*/
|
|
12
|
+
const DEBUG_BRIDGE = process.env.SST_DEBUG_BRIDGE === "true";
|
|
13
|
+
// Lazy-initialized loggers
|
|
14
|
+
let workersLogger = null;
|
|
15
|
+
let serverLogger = null;
|
|
16
|
+
let iotLogger = null;
|
|
17
|
+
function getWorkersLogger() {
|
|
18
|
+
if (!DEBUG_BRIDGE)
|
|
19
|
+
return null;
|
|
20
|
+
if (!workersLogger) {
|
|
21
|
+
workersLogger = createDebugFileLogger({
|
|
22
|
+
filePath: ".sst/debug-workers.log",
|
|
23
|
+
sessionName: "WORKERS",
|
|
24
|
+
width: 120,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return workersLogger;
|
|
28
|
+
}
|
|
29
|
+
function getServerLogger() {
|
|
30
|
+
if (!DEBUG_BRIDGE)
|
|
31
|
+
return null;
|
|
32
|
+
if (!serverLogger) {
|
|
33
|
+
serverLogger = createDebugFileLogger({
|
|
34
|
+
filePath: ".sst/debug-server.log",
|
|
35
|
+
sessionName: "SERVER",
|
|
36
|
+
width: 120,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return serverLogger;
|
|
40
|
+
}
|
|
41
|
+
function getIotLogger() {
|
|
42
|
+
if (!DEBUG_BRIDGE)
|
|
43
|
+
return null;
|
|
44
|
+
if (!iotLogger) {
|
|
45
|
+
iotLogger = createDebugFileLogger({
|
|
46
|
+
filePath: ".sst/debug-iot.log",
|
|
47
|
+
sessionName: "IOT",
|
|
48
|
+
width: 120,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return iotLogger;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Log debug message for workers component
|
|
55
|
+
* Outputs to console and optionally to .sst/debug-workers.log
|
|
56
|
+
*/
|
|
57
|
+
export function logWorkers(message, details) {
|
|
58
|
+
const logger = getWorkersLogger();
|
|
59
|
+
if (logger) {
|
|
60
|
+
logger.log("WORKERS", { msg: message, ...details });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Log debug message for server component
|
|
65
|
+
* Outputs to console and optionally to .sst/debug-server.log
|
|
66
|
+
*/
|
|
67
|
+
export function logServer(message, details) {
|
|
68
|
+
const logger = getServerLogger();
|
|
69
|
+
if (logger) {
|
|
70
|
+
logger.log("SERVER", { msg: message, ...details });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Log debug message for IoT component (publishing)
|
|
75
|
+
* Outputs to console and optionally to .sst/debug-iot.log
|
|
76
|
+
*/
|
|
77
|
+
export function logIot(message, details) {
|
|
78
|
+
const logger = getIotLogger();
|
|
79
|
+
if (logger) {
|
|
80
|
+
logger.log("IOT", { msg: message, ...details });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Log debug message for IoT component (receiving)
|
|
85
|
+
* Outputs to console and optionally to .sst/debug-iot.log
|
|
86
|
+
*/
|
|
87
|
+
export function logIotRx(message, details) {
|
|
88
|
+
const logger = getIotLogger();
|
|
89
|
+
if (logger) {
|
|
90
|
+
logger.log("IOT-RX", { msg: message, ...details });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if bridge debug logging is enabled
|
|
95
|
+
*/
|
|
96
|
+
export function isDebugBridgeEnabled() {
|
|
97
|
+
return DEBUG_BRIDGE;
|
|
98
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for creating a debug file logger
|
|
5
|
+
*/
|
|
6
|
+
export interface DebugFileLoggerConfig {
|
|
7
|
+
/** Log file path (relative paths are relative to CWD) */
|
|
8
|
+
filePath: string;
|
|
9
|
+
/** Optional environment variable to enable/disable logging (if not set, logging is always enabled) */
|
|
10
|
+
envVar?: string;
|
|
11
|
+
/** Name for session markers (e.g., "POOL", "TRACE") */
|
|
12
|
+
sessionName: string;
|
|
13
|
+
/** Optional header line content for session start */
|
|
14
|
+
sessionHeader?: string;
|
|
15
|
+
/** Column width for padding (default: 80) */
|
|
16
|
+
width?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Debug file logger instance
|
|
20
|
+
*/
|
|
21
|
+
export interface DebugFileLogger {
|
|
22
|
+
/** Log a message with timestamp and formatted details */
|
|
23
|
+
log: (action: string, details?: Record<string, any>) => void;
|
|
24
|
+
/** Log a raw line (no formatting) */
|
|
25
|
+
logRaw: (line: string) => void;
|
|
26
|
+
/** Check if logging is enabled */
|
|
27
|
+
isEnabled: () => boolean;
|
|
28
|
+
/** Write session end marker and close the stream */
|
|
29
|
+
close: (summary?: string) => void;
|
|
30
|
+
/** Get the write stream (for advanced use cases) */
|
|
31
|
+
getStream: () => fs.WriteStream | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a debug file logger with consistent formatting
|
|
35
|
+
*
|
|
36
|
+
* Features:
|
|
37
|
+
* - File-based logging to configurable path
|
|
38
|
+
* - Optional enable/disable via environment variable
|
|
39
|
+
* - Session start/end markers
|
|
40
|
+
* - Consistent timestamp formatting (HH:MM:SS.mmm)
|
|
41
|
+
* - Automatic directory creation
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const logger = createDebugFileLogger({
|
|
46
|
+
* filePath: ".sst/debug-workers.log",
|
|
47
|
+
* envVar: "SST_DEBUG_WORKERS",
|
|
48
|
+
* sessionName: "WORKERS",
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* logger.log("REQUEST", { path: "/api/test", elapsed: 50 });
|
|
52
|
+
* // Output: [14:30:45.123] REQUEST path=/api/test elapsed=50
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function createDebugFileLogger(config: DebugFileLoggerConfig): DebugFileLogger;
|
|
56
|
+
/**
|
|
57
|
+
* Create a simple console+file logger for debug output
|
|
58
|
+
* Logs to both console and a file with consistent formatting
|
|
59
|
+
*/
|
|
60
|
+
export interface ConsoleFileLoggerConfig {
|
|
61
|
+
/** Log file path */
|
|
62
|
+
filePath: string;
|
|
63
|
+
/** Prefix for console output (e.g., "[WORKERS]") */
|
|
64
|
+
prefix: string;
|
|
65
|
+
/** Session name for file markers */
|
|
66
|
+
sessionName: string;
|
|
67
|
+
}
|
|
68
|
+
export interface ConsoleFileLogger {
|
|
69
|
+
/** Log a message to both console and file */
|
|
70
|
+
log: (message: string) => void;
|
|
71
|
+
/** Check if file logging is enabled */
|
|
72
|
+
isEnabled: () => boolean;
|
|
73
|
+
/** Close the file stream */
|
|
74
|
+
close: () => void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a logger that writes to both console and a file
|
|
78
|
+
* Always writes to console, writes to file when SST_DEBUG_BRIDGE is set
|
|
79
|
+
*/
|
|
80
|
+
export declare function createConsoleFileLogger(config: ConsoleFileLoggerConfig): ConsoleFileLogger;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
// Active loggers registry (for cleanup)
|
|
4
|
+
const activeLoggers = new Map();
|
|
5
|
+
// Cleanup on process exit
|
|
6
|
+
process.on("exit", () => {
|
|
7
|
+
for (const stream of activeLoggers.values()) {
|
|
8
|
+
try {
|
|
9
|
+
stream.end();
|
|
10
|
+
}
|
|
11
|
+
catch { }
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Create a debug file logger with consistent formatting
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - File-based logging to configurable path
|
|
19
|
+
* - Optional enable/disable via environment variable
|
|
20
|
+
* - Session start/end markers
|
|
21
|
+
* - Consistent timestamp formatting (HH:MM:SS.mmm)
|
|
22
|
+
* - Automatic directory creation
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const logger = createDebugFileLogger({
|
|
27
|
+
* filePath: ".sst/debug-workers.log",
|
|
28
|
+
* envVar: "SST_DEBUG_WORKERS",
|
|
29
|
+
* sessionName: "WORKERS",
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* logger.log("REQUEST", { path: "/api/test", elapsed: 50 });
|
|
33
|
+
* // Output: [14:30:45.123] REQUEST path=/api/test elapsed=50
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function createDebugFileLogger(config) {
|
|
37
|
+
const { filePath, envVar, sessionName, sessionHeader, width = 80 } = config;
|
|
38
|
+
let stream = null;
|
|
39
|
+
let initialized = false;
|
|
40
|
+
function isEnabled() {
|
|
41
|
+
// If no envVar specified, always enabled
|
|
42
|
+
if (!envVar)
|
|
43
|
+
return true;
|
|
44
|
+
return process.env[envVar] === "true";
|
|
45
|
+
}
|
|
46
|
+
function initLogFile() {
|
|
47
|
+
if (initialized)
|
|
48
|
+
return stream !== null;
|
|
49
|
+
initialized = true;
|
|
50
|
+
if (!isEnabled())
|
|
51
|
+
return false;
|
|
52
|
+
try {
|
|
53
|
+
const logDir = path.dirname(filePath);
|
|
54
|
+
if (!fs.existsSync(logDir)) {
|
|
55
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
stream = fs.createWriteStream(filePath, { flags: "w" });
|
|
58
|
+
activeLoggers.set(filePath, stream);
|
|
59
|
+
// Write session start marker
|
|
60
|
+
const header = sessionHeader ? ` | ${sessionHeader}` : "";
|
|
61
|
+
stream.write("\n" +
|
|
62
|
+
"=".repeat(width) +
|
|
63
|
+
"\n" +
|
|
64
|
+
`[${sessionName} SESSION START] ${new Date().toISOString()}${header}\n` +
|
|
65
|
+
"=".repeat(width) +
|
|
66
|
+
"\n");
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function formatTimestamp() {
|
|
74
|
+
return new Date().toISOString().slice(11, 23); // HH:MM:SS.mmm
|
|
75
|
+
}
|
|
76
|
+
function formatDetails(details) {
|
|
77
|
+
return Object.entries(details)
|
|
78
|
+
.filter(([_, v]) => v !== undefined)
|
|
79
|
+
.map(([k, v]) => {
|
|
80
|
+
if (typeof v === "object") {
|
|
81
|
+
return `${k}=${JSON.stringify(v)}`;
|
|
82
|
+
}
|
|
83
|
+
return `${k}=${v}`;
|
|
84
|
+
})
|
|
85
|
+
.join(" ");
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
log(action, details = {}) {
|
|
89
|
+
if (!initLogFile() || !stream)
|
|
90
|
+
return;
|
|
91
|
+
const timestamp = formatTimestamp();
|
|
92
|
+
const detailStr = formatDetails(details);
|
|
93
|
+
const line = `[${timestamp}] ${action.padEnd(20)} ${detailStr}\n`;
|
|
94
|
+
stream.write(line);
|
|
95
|
+
},
|
|
96
|
+
logRaw(line) {
|
|
97
|
+
if (!initLogFile() || !stream)
|
|
98
|
+
return;
|
|
99
|
+
stream.write(line.endsWith("\n") ? line : line + "\n");
|
|
100
|
+
},
|
|
101
|
+
isEnabled,
|
|
102
|
+
close(summary) {
|
|
103
|
+
if (!stream)
|
|
104
|
+
return;
|
|
105
|
+
const endMarker = "\n" +
|
|
106
|
+
"-".repeat(width) +
|
|
107
|
+
"\n" +
|
|
108
|
+
`[${sessionName} SESSION END] ${new Date().toISOString()}\n` +
|
|
109
|
+
(summary ? summary + "\n" : "") +
|
|
110
|
+
"-".repeat(width) +
|
|
111
|
+
"\n";
|
|
112
|
+
stream.write(endMarker);
|
|
113
|
+
stream.end();
|
|
114
|
+
activeLoggers.delete(filePath);
|
|
115
|
+
stream = null;
|
|
116
|
+
},
|
|
117
|
+
getStream() {
|
|
118
|
+
initLogFile();
|
|
119
|
+
return stream;
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create a logger that writes to both console and a file
|
|
125
|
+
* Always writes to console, writes to file when SST_DEBUG_BRIDGE is set
|
|
126
|
+
*/
|
|
127
|
+
export function createConsoleFileLogger(config) {
|
|
128
|
+
const { filePath, prefix, sessionName } = config;
|
|
129
|
+
const fileLogger = createDebugFileLogger({
|
|
130
|
+
filePath,
|
|
131
|
+
envVar: "SST_DEBUG_BRIDGE",
|
|
132
|
+
sessionName,
|
|
133
|
+
width: 100,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
log(message) {
|
|
137
|
+
// Always log to console
|
|
138
|
+
console.log(`${prefix} ${message}`);
|
|
139
|
+
// Also log to file if enabled
|
|
140
|
+
if (fileLogger.isEnabled()) {
|
|
141
|
+
const timestamp = new Date().toISOString().slice(11, 23);
|
|
142
|
+
fileLogger.logRaw(`[${timestamp}] ${prefix} ${message}`);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
isEnabled: fileLogger.isEnabled,
|
|
146
|
+
close: () => fileLogger.close(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type EventType = "IOT_RECEIVED" | "IOT_ACK" | "WORKER_START" | "WORKER_LOG" | "WORKER_END";
|
|
2
|
+
interface EventTraceDetails {
|
|
3
|
+
requestID: string;
|
|
4
|
+
functionID?: string;
|
|
5
|
+
workerID?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
status?: "success" | "error";
|
|
8
|
+
elapsed?: number;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Log an event trace entry with requestID as the common identifier
|
|
13
|
+
*
|
|
14
|
+
* @param event - The event type
|
|
15
|
+
* @param details - Event details including requestID (required)
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* logEventTrace("IOT_RECEIVED", { requestID: "abc123", functionID: "MyFunc" });
|
|
19
|
+
* logEventTrace("WORKER_LOG", { requestID: "abc123", message: "[LOG] User action" });
|
|
20
|
+
* logEventTrace("WORKER_END", { requestID: "abc123", status: "success", elapsed: 150 });
|
|
21
|
+
*/
|
|
22
|
+
export declare function logEventTrace(event: EventType, details: EventTraceDetails): void;
|
|
23
|
+
/**
|
|
24
|
+
* Check if event trace logging is enabled
|
|
25
|
+
*/
|
|
26
|
+
export declare function isEventTraceEnabled(): boolean;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createDebugFileLogger } from "./debug-file-logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Event Trace Logging for SST Dev Bridge
|
|
4
|
+
*
|
|
5
|
+
* Tracks the complete lifecycle of a request from IOT reception to worker completion.
|
|
6
|
+
* All events share a common `requestID` for end-to-end tracing.
|
|
7
|
+
*
|
|
8
|
+
* Enable with: SST_EVENT_TRACE=true
|
|
9
|
+
*
|
|
10
|
+
* Log file: .sst/event-trace.log
|
|
11
|
+
*
|
|
12
|
+
* Event Types:
|
|
13
|
+
* - IOT_RECEIVED : IOT event received from Lambda
|
|
14
|
+
* - IOT_ACK : IOT acknowledgment published
|
|
15
|
+
* - WORKER_START : Worker function started
|
|
16
|
+
* - WORKER_LOG : Worker function log with [LOG] prefix
|
|
17
|
+
* - WORKER_END : Worker function completed (success/error)
|
|
18
|
+
*
|
|
19
|
+
* Tracing from Frontend:
|
|
20
|
+
* - Send X-Correlation-ID header from frontend for easy correlation
|
|
21
|
+
* - Or use the path to filter by endpoint
|
|
22
|
+
* - Or use apiGwReqId (visible in browser as x-amzn-requestid response header)
|
|
23
|
+
*/
|
|
24
|
+
const EVENT_TRACE_ENABLED = process.env.SST_EVENT_TRACE === "true";
|
|
25
|
+
let eventLogger = null;
|
|
26
|
+
function getEventLogger() {
|
|
27
|
+
if (!EVENT_TRACE_ENABLED)
|
|
28
|
+
return null;
|
|
29
|
+
if (!eventLogger) {
|
|
30
|
+
eventLogger = createDebugFileLogger({
|
|
31
|
+
filePath: ".sst/event-trace.log",
|
|
32
|
+
sessionName: "EVENT_TRACE",
|
|
33
|
+
width: 140,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return eventLogger;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Log an event trace entry with requestID as the common identifier
|
|
40
|
+
*
|
|
41
|
+
* @param event - The event type
|
|
42
|
+
* @param details - Event details including requestID (required)
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* logEventTrace("IOT_RECEIVED", { requestID: "abc123", functionID: "MyFunc" });
|
|
46
|
+
* logEventTrace("WORKER_LOG", { requestID: "abc123", message: "[LOG] User action" });
|
|
47
|
+
* logEventTrace("WORKER_END", { requestID: "abc123", status: "success", elapsed: 150 });
|
|
48
|
+
*/
|
|
49
|
+
export function logEventTrace(event, details) {
|
|
50
|
+
const logger = getEventLogger();
|
|
51
|
+
if (!logger)
|
|
52
|
+
return;
|
|
53
|
+
// Format requestID for consistent display (first 8 chars)
|
|
54
|
+
const reqId = details.requestID?.slice(0, 8) || "N/A";
|
|
55
|
+
// Build the log message with requestID prominently displayed
|
|
56
|
+
const logDetails = {
|
|
57
|
+
reqId,
|
|
58
|
+
...details,
|
|
59
|
+
};
|
|
60
|
+
// Remove the full requestID since we're using the shortened version
|
|
61
|
+
delete logDetails.requestID;
|
|
62
|
+
logger.log(event, logDetails);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if event trace logging is enabled
|
|
66
|
+
*/
|
|
67
|
+
export function isEventTraceEnabled() {
|
|
68
|
+
return EVENT_TRACE_ENABLED;
|
|
69
|
+
}
|