@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.
@@ -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 { started } = pending.get(evt.properties.requestID);
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(""), "Build failed", info.handler);
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(" ", 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
@@ -40,7 +40,6 @@ version(program);
40
40
  telemetry(program);
41
41
  types(program);
42
42
  if ("setSourceMapsEnabled" in process) {
43
- // @ts-expect-error
44
43
  process.setSourceMapsEnabled(true);
45
44
  }
46
45
  process.removeAllListeners("uncaughtException");
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.5",
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": "18.11.9",
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.5",
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": "18.11.9",
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
+ }