@omen.foundation/node-microservice-runtime 0.1.4 → 0.1.6
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/dist/logger.cjs +46 -6
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +73 -12
- package/dist/logger.js.map +1 -1
- package/package.json +2 -1
- package/src/logger.ts +77 -15
package/dist/logger.cjs
CHANGED
|
@@ -37,6 +37,22 @@ exports.createLogger = createLogger;
|
|
|
37
37
|
const pino_1 = __importStar(require("pino"));
|
|
38
38
|
const node_stream_1 = require("node:stream");
|
|
39
39
|
const env_js_1 = require("./env.js");
|
|
40
|
+
function isRunningInContainer() {
|
|
41
|
+
if (process.env.DOTNET_RUNNING_IN_CONTAINER === 'true' ||
|
|
42
|
+
process.env.CONTAINER === 'beamable' ||
|
|
43
|
+
!!process.env.ECS_CONTAINER_METADATA_URI ||
|
|
44
|
+
!!process.env.KUBERNETES_SERVICE_HOST) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (process.env.NAME_PREFIX || process.env.ROUTING_KEY) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const hasBeamableEnvVars = !!(process.env.CID && process.env.PID && process.env.HOST && process.env.SECRET);
|
|
51
|
+
if (hasBeamableEnvVars) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
40
56
|
function mapPinoLevelToBeamableLevel(level) {
|
|
41
57
|
switch (level) {
|
|
42
58
|
case 10:
|
|
@@ -129,6 +145,7 @@ function createBeamableLogFormatter() {
|
|
|
129
145
|
function createLogger(env, options = {}) {
|
|
130
146
|
var _a, _b, _c;
|
|
131
147
|
const configuredDestination = (_a = options.destinationPath) !== null && _a !== void 0 ? _a : process.env.LOG_PATH;
|
|
148
|
+
const inContainer = isRunningInContainer();
|
|
132
149
|
const pinoOptions = {
|
|
133
150
|
name: (_b = options.name) !== null && _b !== void 0 ? _b : 'beamable-node-runtime',
|
|
134
151
|
level: env.logLevel,
|
|
@@ -145,13 +162,36 @@ function createLogger(env, options = {}) {
|
|
|
145
162
|
timestamp: pino_1.default.stdTimeFunctions.isoTime,
|
|
146
163
|
};
|
|
147
164
|
if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {
|
|
165
|
+
if (inContainer) {
|
|
166
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
167
|
+
beamableFormatter.pipe(process.stdout);
|
|
168
|
+
return (0, pino_1.default)(pinoOptions, beamableFormatter);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
try {
|
|
172
|
+
const pinoPretty = require('pino-pretty');
|
|
173
|
+
const prettyStream = pinoPretty({
|
|
174
|
+
colorize: true,
|
|
175
|
+
translateTime: 'HH:MM:ss.l',
|
|
176
|
+
ignore: 'pid,hostname',
|
|
177
|
+
singleLine: false,
|
|
178
|
+
});
|
|
179
|
+
return (0, pino_1.default)(pinoOptions, prettyStream);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return (0, pino_1.default)(pinoOptions, process.stdout);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const resolvedDestination = configuredDestination === 'temp' ? (0, env_js_1.ensureWritableTempDirectory)() : configuredDestination;
|
|
187
|
+
if (inContainer) {
|
|
148
188
|
const beamableFormatter = createBeamableLogFormatter();
|
|
149
|
-
|
|
189
|
+
const fileStream = (0, pino_1.destination)({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
190
|
+
beamableFormatter.pipe(fileStream);
|
|
150
191
|
return (0, pino_1.default)(pinoOptions, beamableFormatter);
|
|
151
192
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return (0, pino_1.default)(pinoOptions, beamableFormatter);
|
|
193
|
+
else {
|
|
194
|
+
const fileStream = (0, pino_1.destination)({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
195
|
+
return (0, pino_1.default)(pinoOptions, fileStream);
|
|
196
|
+
}
|
|
157
197
|
}
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAa,EAAe,KAAK,MAAM,EAAsB,MAAM,MAAM,CAAC;AAG1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAa,EAAe,KAAK,MAAM,EAAsB,MAAM,MAAM,CAAC;AAG1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA0CpD,UAAU,oBAAoB;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAwHD,wBAAgB,YAAY,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,GAAE,oBAAyB,GAAG,MAAM,CA+D/F"}
|
package/dist/logger.js
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import pino, { destination } from 'pino';
|
|
2
2
|
import { Transform } from 'node:stream';
|
|
3
3
|
import { ensureWritableTempDirectory } from './env.js';
|
|
4
|
+
/**
|
|
5
|
+
* Detects if we're running in a container (deployed environment).
|
|
6
|
+
* This is used to determine log format: pretty for local dev, JSON for containers.
|
|
7
|
+
*
|
|
8
|
+
* Key indicators we're in a container:
|
|
9
|
+
* - Has CID, PID, HOST, SECRET (Beamable env vars)
|
|
10
|
+
* - NO routing key (deployed services don't use routing keys)
|
|
11
|
+
* - OR explicit container indicators (DOTNET_RUNNING_IN_CONTAINER, ECS, K8s)
|
|
12
|
+
*
|
|
13
|
+
* Key indicators we're running locally:
|
|
14
|
+
* - Has routing key (local dev always has a routing key)
|
|
15
|
+
* - Running via tsx/node directly (not in Docker)
|
|
16
|
+
*/
|
|
17
|
+
function isRunningInContainer() {
|
|
18
|
+
// Explicit container indicators (highest priority)
|
|
19
|
+
if (process.env.DOTNET_RUNNING_IN_CONTAINER === 'true' ||
|
|
20
|
+
process.env.CONTAINER === 'beamable' ||
|
|
21
|
+
!!process.env.ECS_CONTAINER_METADATA_URI ||
|
|
22
|
+
!!process.env.KUBERNETES_SERVICE_HOST) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
// If we have a routing key, we're definitely running locally (not in a deployed container)
|
|
26
|
+
// Local dev always sets NAME_PREFIX or ROUTING_KEY
|
|
27
|
+
if (process.env.NAME_PREFIX || process.env.ROUTING_KEY) {
|
|
28
|
+
return false; // Local development
|
|
29
|
+
}
|
|
30
|
+
// If we have Beamable env vars but NO routing key, we're in a deployed container
|
|
31
|
+
const hasBeamableEnvVars = !!(process.env.CID && process.env.PID && process.env.HOST && process.env.SECRET);
|
|
32
|
+
if (hasBeamableEnvVars) {
|
|
33
|
+
return true; // Deployed container (has env vars but no routing key)
|
|
34
|
+
}
|
|
35
|
+
// Default: assume local development
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
4
38
|
/**
|
|
5
39
|
* Maps Pino log levels to Beamable log levels
|
|
6
40
|
* Pino levels: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal
|
|
@@ -117,6 +151,7 @@ function createBeamableLogFormatter() {
|
|
|
117
151
|
}
|
|
118
152
|
export function createLogger(env, options = {}) {
|
|
119
153
|
const configuredDestination = options.destinationPath ?? process.env.LOG_PATH;
|
|
154
|
+
const inContainer = isRunningInContainer();
|
|
120
155
|
const pinoOptions = {
|
|
121
156
|
name: options.name ?? 'beamable-node-runtime',
|
|
122
157
|
level: env.logLevel,
|
|
@@ -136,20 +171,46 @@ export function createLogger(env, options = {}) {
|
|
|
136
171
|
// For deployed services, always log to stdout so container orchestrator can collect logs
|
|
137
172
|
// For local development, log to stdout unless a specific file path is provided
|
|
138
173
|
if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {
|
|
139
|
-
|
|
140
|
-
|
|
174
|
+
if (inContainer) {
|
|
175
|
+
// In containers: Use Beamable JSON format for CloudWatch collection
|
|
176
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
177
|
+
beamableFormatter.pipe(process.stdout);
|
|
178
|
+
return pino(pinoOptions, beamableFormatter);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// Local development: Use Pino's pretty printing for human-readable logs
|
|
182
|
+
// Try to use pino-pretty if available (optional dependency)
|
|
183
|
+
// If not available, fall back to default Pino JSON output
|
|
184
|
+
try {
|
|
185
|
+
// Check if pino-pretty is available
|
|
186
|
+
const pinoPretty = require('pino-pretty');
|
|
187
|
+
// Create a pretty stream with formatting options
|
|
188
|
+
const prettyStream = pinoPretty({
|
|
189
|
+
colorize: true,
|
|
190
|
+
translateTime: 'HH:MM:ss.l',
|
|
191
|
+
ignore: 'pid,hostname',
|
|
192
|
+
singleLine: false,
|
|
193
|
+
});
|
|
194
|
+
// Use pino with the pretty stream
|
|
195
|
+
return pino(pinoOptions, prettyStream);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// pino-pretty not available, use default Pino output (JSON but readable)
|
|
199
|
+
return pino(pinoOptions, process.stdout);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// For file logging: Use Beamable format in containers, default Pino format locally
|
|
204
|
+
const resolvedDestination = configuredDestination === 'temp' ? ensureWritableTempDirectory() : configuredDestination;
|
|
205
|
+
if (inContainer) {
|
|
141
206
|
const beamableFormatter = createBeamableLogFormatter();
|
|
142
|
-
|
|
143
|
-
|
|
207
|
+
const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
208
|
+
beamableFormatter.pipe(fileStream);
|
|
144
209
|
return pino(pinoOptions, beamableFormatter);
|
|
145
210
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Pipe the formatted output to the file stream
|
|
151
|
-
// Note: destination() returns a SonicBoom stream which is compatible with Node streams
|
|
152
|
-
beamableFormatter.pipe(fileStream);
|
|
153
|
-
return pino(pinoOptions, beamableFormatter);
|
|
211
|
+
else {
|
|
212
|
+
const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
213
|
+
return pino(pinoOptions, fileStream);
|
|
214
|
+
}
|
|
154
215
|
}
|
|
155
216
|
//# sourceMappingURL=logger.js.map
|
package/dist/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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","sourcesContent":["import pino, { destination, type Logger, type LoggerOptions } from 'pino';\r\nimport { Transform } from 'node:stream';\r\nimport { ensureWritableTempDirectory } from './env.js';\r\nimport type { EnvironmentConfig } from './types.js';\r\n\r\ninterface LoggerFactoryOptions {\r\n name?: string;\r\n destinationPath?: string;\r\n}\r\n\r\n/**\r\n * Maps Pino log levels to Beamable log levels\r\n * Pino levels: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal\r\n * Beamable levels: Debug, Info, Warning, Error, Fatal\r\n */\r\nfunction mapPinoLevelToBeamableLevel(level: number): string {\r\n switch (level) {\r\n case 10: // trace\r\n return 'Debug';\r\n case 20: // debug\r\n return 'Debug';\r\n case 30: // info\r\n return 'Info';\r\n case 40: // warn\r\n return 'Warning';\r\n case 50: // error\r\n return 'Error';\r\n case 60: // fatal\r\n return 'Fatal';\r\n default:\r\n return 'Info';\r\n }\r\n}\r\n\r\n/**\r\n * Creates a transform stream that converts Pino JSON logs to Beamable's expected format.\r\n * Beamable expects logs with __t (timestamp), __l (level), and __m (message) fields.\r\n * Pino writes JSON strings (one per line) to the stream.\r\n */\r\nfunction createBeamableLogFormatter(): Transform {\r\n return new Transform({\r\n objectMode: false, // Pino writes strings, not objects\r\n transform(chunk: Buffer, _encoding, callback) {\r\n try {\r\n const line = chunk.toString();\r\n // Skip empty lines\r\n if (!line.trim()) {\r\n callback();\r\n return;\r\n }\r\n \r\n // Parse Pino's JSON log line\r\n const pinoLog = JSON.parse(line);\r\n \r\n // Extract timestamp - Pino uses 'time' field (ISO 8601 string or milliseconds)\r\n // Convert to ISO 8601 string for Beamable\r\n let timestamp: string;\r\n if (typeof pinoLog.time === 'string') {\r\n timestamp = pinoLog.time;\r\n } else if (typeof pinoLog.time === 'number') {\r\n timestamp = new Date(pinoLog.time).toISOString();\r\n } else {\r\n timestamp = new Date().toISOString();\r\n }\r\n \r\n // Map Pino level to Beamable level\r\n const level = mapPinoLevelToBeamableLevel(pinoLog.level);\r\n \r\n // Build the message - combine msg with any additional fields\r\n // Pino's 'msg' field contains the log message\r\n const messageParts: string[] = [];\r\n if (pinoLog.msg) {\r\n messageParts.push(pinoLog.msg);\r\n }\r\n \r\n // Include error information if present\r\n if (pinoLog.err) {\r\n const err = pinoLog.err;\r\n const errMsg = err.message || err.msg || 'Error';\r\n const errStack = err.stack ? `\\n${err.stack}` : '';\r\n messageParts.push(`${errMsg}${errStack}`);\r\n }\r\n \r\n // Build the Beamable log format\r\n const beamableLog: Record<string, unknown> = {\r\n __t: timestamp,\r\n __l: level,\r\n __m: messageParts.length > 0 ? messageParts.join(' ') : 'No message',\r\n };\r\n \r\n // Include additional context fields that might be useful\r\n // These are included in the message object but not as top-level fields\r\n const contextFields: Record<string, unknown> = {};\r\n if (pinoLog.cid) contextFields.cid = pinoLog.cid;\r\n if (pinoLog.pid) contextFields.pid = pinoLog.pid;\r\n if (pinoLog.routingKey) contextFields.routingKey = pinoLog.routingKey;\r\n if (pinoLog.service) contextFields.service = pinoLog.service;\r\n if (pinoLog.component) contextFields.component = pinoLog.component;\r\n \r\n // Include any other fields that aren't standard Pino fields\r\n const standardPinoFields = ['level', 'time', 'pid', 'hostname', 'name', 'msg', 'err', 'v', 'cid', 'pid', 'routingKey', 'sdkVersionExecution', 'service', 'component'];\r\n for (const [key, value] of Object.entries(pinoLog)) {\r\n if (!standardPinoFields.includes(key) && value !== undefined && value !== null) {\r\n contextFields[key] = value;\r\n }\r\n }\r\n \r\n // If there are context fields, include them in the log\r\n if (Object.keys(contextFields).length > 0) {\r\n beamableLog.__c = contextFields;\r\n }\r\n \r\n // Output as a single-line JSON string (required for CloudWatch)\r\n const output = JSON.stringify(beamableLog) + '\\n';\r\n callback(null, Buffer.from(output, 'utf8'));\r\n } catch (error) {\r\n // If parsing fails, output a fallback log entry\r\n const fallbackLog = {\r\n __t: new Date().toISOString(),\r\n __l: 'Error',\r\n __m: `Failed to parse log entry: ${chunk.toString().substring(0, 200)}`,\r\n };\r\n callback(null, Buffer.from(JSON.stringify(fallbackLog) + '\\n', 'utf8'));\r\n }\r\n },\r\n });\r\n}\r\n\r\nexport function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptions = {}): Logger {\r\n const configuredDestination = options.destinationPath ?? process.env.LOG_PATH;\r\n\r\n const pinoOptions: LoggerOptions = {\r\n name: options.name ?? 'beamable-node-runtime',\r\n level: env.logLevel,\r\n base: {\r\n cid: env.cid,\r\n pid: env.pid,\r\n routingKey: env.routingKey ?? null,\r\n sdkVersionExecution: env.sdkVersionExecution,\r\n },\r\n redact: {\r\n paths: ['secret', 'refreshToken'],\r\n censor: '***',\r\n },\r\n // Use timestamp in milliseconds (Pino default) for accurate conversion\r\n timestamp: pino.stdTimeFunctions.isoTime,\r\n };\r\n\r\n // For deployed services, always log to stdout so container orchestrator can collect logs\r\n // For local development, log to stdout unless a specific file path is provided\r\n if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {\r\n // Create a transform stream that formats logs for Beamable\r\n // Pino outputs JSON to the transform, which converts it to Beamable format\r\n const beamableFormatter = createBeamableLogFormatter();\r\n beamableFormatter.pipe(process.stdout);\r\n \r\n // Create Pino logger that writes to the formatter\r\n return pino(pinoOptions, beamableFormatter);\r\n }\r\n\r\n // For file logging, also use Beamable format\r\n const resolvedDestination = configuredDestination === 'temp' ? ensureWritableTempDirectory() : configuredDestination;\r\n const beamableFormatter = createBeamableLogFormatter();\r\n const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });\r\n // Pipe the formatted output to the file stream\r\n // Note: destination() returns a SonicBoom stream which is compatible with Node streams\r\n beamableFormatter.pipe(fileStream as unknown as NodeJS.WritableStream);\r\n \r\n return pino(pinoOptions, beamableFormatter);\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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","sourcesContent":["import pino, { destination, type Logger, type LoggerOptions } from 'pino';\r\nimport { Transform } from 'node:stream';\r\nimport { ensureWritableTempDirectory } from './env.js';\r\nimport type { EnvironmentConfig } from './types.js';\r\n\r\n/**\r\n * Detects if we're running in a container (deployed environment).\r\n * This is used to determine log format: pretty for local dev, JSON for containers.\r\n * \r\n * Key indicators we're in a container:\r\n * - Has CID, PID, HOST, SECRET (Beamable env vars)\r\n * - NO routing key (deployed services don't use routing keys)\r\n * - OR explicit container indicators (DOTNET_RUNNING_IN_CONTAINER, ECS, K8s)\r\n * \r\n * Key indicators we're running locally:\r\n * - Has routing key (local dev always has a routing key)\r\n * - Running via tsx/node directly (not in Docker)\r\n */\r\nfunction isRunningInContainer(): boolean {\r\n // Explicit container indicators (highest priority)\r\n if (\r\n process.env.DOTNET_RUNNING_IN_CONTAINER === 'true' ||\r\n process.env.CONTAINER === 'beamable' ||\r\n !!process.env.ECS_CONTAINER_METADATA_URI ||\r\n !!process.env.KUBERNETES_SERVICE_HOST\r\n ) {\r\n return true;\r\n }\r\n \r\n // If we have a routing key, we're definitely running locally (not in a deployed container)\r\n // Local dev always sets NAME_PREFIX or ROUTING_KEY\r\n if (process.env.NAME_PREFIX || process.env.ROUTING_KEY) {\r\n return false; // Local development\r\n }\r\n \r\n // If we have Beamable env vars but NO routing key, we're in a deployed container\r\n const hasBeamableEnvVars = !!(process.env.CID && process.env.PID && process.env.HOST && process.env.SECRET);\r\n if (hasBeamableEnvVars) {\r\n return true; // Deployed container (has env vars but no routing key)\r\n }\r\n \r\n // Default: assume local development\r\n return false;\r\n}\r\n\r\ninterface LoggerFactoryOptions {\r\n name?: string;\r\n destinationPath?: string;\r\n}\r\n\r\n/**\r\n * Maps Pino log levels to Beamable log levels\r\n * Pino levels: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal\r\n * Beamable levels: Debug, Info, Warning, Error, Fatal\r\n */\r\nfunction mapPinoLevelToBeamableLevel(level: number): string {\r\n switch (level) {\r\n case 10: // trace\r\n return 'Debug';\r\n case 20: // debug\r\n return 'Debug';\r\n case 30: // info\r\n return 'Info';\r\n case 40: // warn\r\n return 'Warning';\r\n case 50: // error\r\n return 'Error';\r\n case 60: // fatal\r\n return 'Fatal';\r\n default:\r\n return 'Info';\r\n }\r\n}\r\n\r\n/**\r\n * Creates a transform stream that converts Pino JSON logs to Beamable's expected format.\r\n * Beamable expects logs with __t (timestamp), __l (level), and __m (message) fields.\r\n * Pino writes JSON strings (one per line) to the stream.\r\n */\r\nfunction createBeamableLogFormatter(): Transform {\r\n return new Transform({\r\n objectMode: false, // Pino writes strings, not objects\r\n transform(chunk: Buffer, _encoding, callback) {\r\n try {\r\n const line = chunk.toString();\r\n // Skip empty lines\r\n if (!line.trim()) {\r\n callback();\r\n return;\r\n }\r\n \r\n // Parse Pino's JSON log line\r\n const pinoLog = JSON.parse(line);\r\n \r\n // Extract timestamp - Pino uses 'time' field (ISO 8601 string or milliseconds)\r\n // Convert to ISO 8601 string for Beamable\r\n let timestamp: string;\r\n if (typeof pinoLog.time === 'string') {\r\n timestamp = pinoLog.time;\r\n } else if (typeof pinoLog.time === 'number') {\r\n timestamp = new Date(pinoLog.time).toISOString();\r\n } else {\r\n timestamp = new Date().toISOString();\r\n }\r\n \r\n // Map Pino level to Beamable level\r\n const level = mapPinoLevelToBeamableLevel(pinoLog.level);\r\n \r\n // Build the message - combine msg with any additional fields\r\n // Pino's 'msg' field contains the log message\r\n const messageParts: string[] = [];\r\n if (pinoLog.msg) {\r\n messageParts.push(pinoLog.msg);\r\n }\r\n \r\n // Include error information if present\r\n if (pinoLog.err) {\r\n const err = pinoLog.err;\r\n const errMsg = err.message || err.msg || 'Error';\r\n const errStack = err.stack ? `\\n${err.stack}` : '';\r\n messageParts.push(`${errMsg}${errStack}`);\r\n }\r\n \r\n // Build the Beamable log format\r\n const beamableLog: Record<string, unknown> = {\r\n __t: timestamp,\r\n __l: level,\r\n __m: messageParts.length > 0 ? messageParts.join(' ') : 'No message',\r\n };\r\n \r\n // Include additional context fields that might be useful\r\n // These are included in the message object but not as top-level fields\r\n const contextFields: Record<string, unknown> = {};\r\n if (pinoLog.cid) contextFields.cid = pinoLog.cid;\r\n if (pinoLog.pid) contextFields.pid = pinoLog.pid;\r\n if (pinoLog.routingKey) contextFields.routingKey = pinoLog.routingKey;\r\n if (pinoLog.service) contextFields.service = pinoLog.service;\r\n if (pinoLog.component) contextFields.component = pinoLog.component;\r\n \r\n // Include any other fields that aren't standard Pino fields\r\n const standardPinoFields = ['level', 'time', 'pid', 'hostname', 'name', 'msg', 'err', 'v', 'cid', 'pid', 'routingKey', 'sdkVersionExecution', 'service', 'component'];\r\n for (const [key, value] of Object.entries(pinoLog)) {\r\n if (!standardPinoFields.includes(key) && value !== undefined && value !== null) {\r\n contextFields[key] = value;\r\n }\r\n }\r\n \r\n // If there are context fields, include them in the log\r\n if (Object.keys(contextFields).length > 0) {\r\n beamableLog.__c = contextFields;\r\n }\r\n \r\n // Output as a single-line JSON string (required for CloudWatch)\r\n const output = JSON.stringify(beamableLog) + '\\n';\r\n callback(null, Buffer.from(output, 'utf8'));\r\n } catch (error) {\r\n // If parsing fails, output a fallback log entry\r\n const fallbackLog = {\r\n __t: new Date().toISOString(),\r\n __l: 'Error',\r\n __m: `Failed to parse log entry: ${chunk.toString().substring(0, 200)}`,\r\n };\r\n callback(null, Buffer.from(JSON.stringify(fallbackLog) + '\\n', 'utf8'));\r\n }\r\n },\r\n });\r\n}\r\n\r\nexport function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptions = {}): Logger {\r\n const configuredDestination = options.destinationPath ?? process.env.LOG_PATH;\r\n const inContainer = isRunningInContainer();\r\n\r\n const pinoOptions: LoggerOptions = {\r\n name: options.name ?? 'beamable-node-runtime',\r\n level: env.logLevel,\r\n base: {\r\n cid: env.cid,\r\n pid: env.pid,\r\n routingKey: env.routingKey ?? null,\r\n sdkVersionExecution: env.sdkVersionExecution,\r\n },\r\n redact: {\r\n paths: ['secret', 'refreshToken'],\r\n censor: '***',\r\n },\r\n // Use timestamp in milliseconds (Pino default) for accurate conversion\r\n timestamp: pino.stdTimeFunctions.isoTime,\r\n };\r\n\r\n // For deployed services, always log to stdout so container orchestrator can collect logs\r\n // For local development, log to stdout unless a specific file path is provided\r\n if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {\r\n if (inContainer) {\r\n // In containers: Use Beamable JSON format for CloudWatch collection\r\n const beamableFormatter = createBeamableLogFormatter();\r\n beamableFormatter.pipe(process.stdout);\r\n return pino(pinoOptions, beamableFormatter);\r\n } else {\r\n // Local development: Use Pino's pretty printing for human-readable logs\r\n // Try to use pino-pretty if available (optional dependency)\r\n // If not available, fall back to default Pino JSON output\r\n try {\r\n // Check if pino-pretty is available\r\n const pinoPretty = require('pino-pretty');\r\n // Create a pretty stream with formatting options\r\n const prettyStream = pinoPretty({\r\n colorize: true,\r\n translateTime: 'HH:MM:ss.l',\r\n ignore: 'pid,hostname',\r\n singleLine: false,\r\n });\r\n // Use pino with the pretty stream\r\n return pino(pinoOptions, prettyStream);\r\n } catch {\r\n // pino-pretty not available, use default Pino output (JSON but readable)\r\n return pino(pinoOptions, process.stdout);\r\n }\r\n }\r\n }\r\n\r\n // For file logging: Use Beamable format in containers, default Pino format locally\r\n const resolvedDestination = configuredDestination === 'temp' ? ensureWritableTempDirectory() : configuredDestination;\r\n if (inContainer) {\r\n const beamableFormatter = createBeamableLogFormatter();\r\n const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });\r\n beamableFormatter.pipe(fileStream as unknown as NodeJS.WritableStream);\r\n return pino(pinoOptions, beamableFormatter);\r\n } else {\r\n const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });\r\n return pino(pinoOptions, fileStream);\r\n }\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omen.foundation/node-microservice-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Beamable microservice runtime for Node.js/TypeScript services.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"keytar": "^7.9.0",
|
|
34
34
|
"mongodb": "^6.10.0",
|
|
35
35
|
"pino": "^9.0.0",
|
|
36
|
+
"pino-pretty": "^13.0.0",
|
|
36
37
|
"reflect-metadata": "^0.2.1",
|
|
37
38
|
"tar-stream": "^2.2.0",
|
|
38
39
|
"undici": "^6.19.6",
|
package/src/logger.ts
CHANGED
|
@@ -3,6 +3,46 @@ import { Transform } from 'node:stream';
|
|
|
3
3
|
import { ensureWritableTempDirectory } from './env.js';
|
|
4
4
|
import type { EnvironmentConfig } from './types.js';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Detects if we're running in a container (deployed environment).
|
|
8
|
+
* This is used to determine log format: pretty for local dev, JSON for containers.
|
|
9
|
+
*
|
|
10
|
+
* Key indicators we're in a container:
|
|
11
|
+
* - Has CID, PID, HOST, SECRET (Beamable env vars)
|
|
12
|
+
* - NO routing key (deployed services don't use routing keys)
|
|
13
|
+
* - OR explicit container indicators (DOTNET_RUNNING_IN_CONTAINER, ECS, K8s)
|
|
14
|
+
*
|
|
15
|
+
* Key indicators we're running locally:
|
|
16
|
+
* - Has routing key (local dev always has a routing key)
|
|
17
|
+
* - Running via tsx/node directly (not in Docker)
|
|
18
|
+
*/
|
|
19
|
+
function isRunningInContainer(): boolean {
|
|
20
|
+
// Explicit container indicators (highest priority)
|
|
21
|
+
if (
|
|
22
|
+
process.env.DOTNET_RUNNING_IN_CONTAINER === 'true' ||
|
|
23
|
+
process.env.CONTAINER === 'beamable' ||
|
|
24
|
+
!!process.env.ECS_CONTAINER_METADATA_URI ||
|
|
25
|
+
!!process.env.KUBERNETES_SERVICE_HOST
|
|
26
|
+
) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// If we have a routing key, we're definitely running locally (not in a deployed container)
|
|
31
|
+
// Local dev always sets NAME_PREFIX or ROUTING_KEY
|
|
32
|
+
if (process.env.NAME_PREFIX || process.env.ROUTING_KEY) {
|
|
33
|
+
return false; // Local development
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If we have Beamable env vars but NO routing key, we're in a deployed container
|
|
37
|
+
const hasBeamableEnvVars = !!(process.env.CID && process.env.PID && process.env.HOST && process.env.SECRET);
|
|
38
|
+
if (hasBeamableEnvVars) {
|
|
39
|
+
return true; // Deployed container (has env vars but no routing key)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Default: assume local development
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
6
46
|
interface LoggerFactoryOptions {
|
|
7
47
|
name?: string;
|
|
8
48
|
destinationPath?: string;
|
|
@@ -128,6 +168,7 @@ function createBeamableLogFormatter(): Transform {
|
|
|
128
168
|
|
|
129
169
|
export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptions = {}): Logger {
|
|
130
170
|
const configuredDestination = options.destinationPath ?? process.env.LOG_PATH;
|
|
171
|
+
const inContainer = isRunningInContainer();
|
|
131
172
|
|
|
132
173
|
const pinoOptions: LoggerOptions = {
|
|
133
174
|
name: options.name ?? 'beamable-node-runtime',
|
|
@@ -149,22 +190,43 @@ export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptio
|
|
|
149
190
|
// For deployed services, always log to stdout so container orchestrator can collect logs
|
|
150
191
|
// For local development, log to stdout unless a specific file path is provided
|
|
151
192
|
if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
193
|
+
if (inContainer) {
|
|
194
|
+
// In containers: Use Beamable JSON format for CloudWatch collection
|
|
195
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
196
|
+
beamableFormatter.pipe(process.stdout);
|
|
197
|
+
return pino(pinoOptions, beamableFormatter);
|
|
198
|
+
} else {
|
|
199
|
+
// Local development: Use Pino's pretty printing for human-readable logs
|
|
200
|
+
// Try to use pino-pretty if available (optional dependency)
|
|
201
|
+
// If not available, fall back to default Pino JSON output
|
|
202
|
+
try {
|
|
203
|
+
// Check if pino-pretty is available
|
|
204
|
+
const pinoPretty = require('pino-pretty');
|
|
205
|
+
// Create a pretty stream with formatting options
|
|
206
|
+
const prettyStream = pinoPretty({
|
|
207
|
+
colorize: true,
|
|
208
|
+
translateTime: 'HH:MM:ss.l',
|
|
209
|
+
ignore: 'pid,hostname',
|
|
210
|
+
singleLine: false,
|
|
211
|
+
});
|
|
212
|
+
// Use pino with the pretty stream
|
|
213
|
+
return pino(pinoOptions, prettyStream);
|
|
214
|
+
} catch {
|
|
215
|
+
// pino-pretty not available, use default Pino output (JSON but readable)
|
|
216
|
+
return pino(pinoOptions, process.stdout);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
159
219
|
}
|
|
160
220
|
|
|
161
|
-
// For file logging,
|
|
221
|
+
// For file logging: Use Beamable format in containers, default Pino format locally
|
|
162
222
|
const resolvedDestination = configuredDestination === 'temp' ? ensureWritableTempDirectory() : configuredDestination;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
223
|
+
if (inContainer) {
|
|
224
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
225
|
+
const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
226
|
+
beamableFormatter.pipe(fileStream as unknown as NodeJS.WritableStream);
|
|
227
|
+
return pino(pinoOptions, beamableFormatter);
|
|
228
|
+
} else {
|
|
229
|
+
const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
230
|
+
return pino(pinoOptions, fileStream);
|
|
231
|
+
}
|
|
170
232
|
}
|