@omen.foundation/node-microservice-runtime 0.1.2 → 0.1.3
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 +98 -3
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +130 -4
- package/dist/logger.js.map +1 -1
- package/package.json +1 -1
- package/src/logger.ts +136 -4
package/dist/logger.cjs
CHANGED
|
@@ -35,7 +35,97 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.createLogger = createLogger;
|
|
37
37
|
const pino_1 = __importStar(require("pino"));
|
|
38
|
+
const node_stream_1 = require("node:stream");
|
|
38
39
|
const env_js_1 = require("./env.js");
|
|
40
|
+
function mapPinoLevelToBeamableLevel(level) {
|
|
41
|
+
switch (level) {
|
|
42
|
+
case 10:
|
|
43
|
+
return 'Debug';
|
|
44
|
+
case 20:
|
|
45
|
+
return 'Debug';
|
|
46
|
+
case 30:
|
|
47
|
+
return 'Info';
|
|
48
|
+
case 40:
|
|
49
|
+
return 'Warning';
|
|
50
|
+
case 50:
|
|
51
|
+
return 'Error';
|
|
52
|
+
case 60:
|
|
53
|
+
return 'Fatal';
|
|
54
|
+
default:
|
|
55
|
+
return 'Info';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function createBeamableLogFormatter() {
|
|
59
|
+
return new node_stream_1.Transform({
|
|
60
|
+
objectMode: false,
|
|
61
|
+
transform(chunk, _encoding, callback) {
|
|
62
|
+
try {
|
|
63
|
+
const line = chunk.toString();
|
|
64
|
+
if (!line.trim()) {
|
|
65
|
+
callback();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const pinoLog = JSON.parse(line);
|
|
69
|
+
let timestamp;
|
|
70
|
+
if (typeof pinoLog.time === 'string') {
|
|
71
|
+
timestamp = pinoLog.time;
|
|
72
|
+
}
|
|
73
|
+
else if (typeof pinoLog.time === 'number') {
|
|
74
|
+
timestamp = new Date(pinoLog.time).toISOString();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
timestamp = new Date().toISOString();
|
|
78
|
+
}
|
|
79
|
+
const level = mapPinoLevelToBeamableLevel(pinoLog.level);
|
|
80
|
+
const messageParts = [];
|
|
81
|
+
if (pinoLog.msg) {
|
|
82
|
+
messageParts.push(pinoLog.msg);
|
|
83
|
+
}
|
|
84
|
+
if (pinoLog.err) {
|
|
85
|
+
const err = pinoLog.err;
|
|
86
|
+
const errMsg = err.message || err.msg || 'Error';
|
|
87
|
+
const errStack = err.stack ? `\n${err.stack}` : '';
|
|
88
|
+
messageParts.push(`${errMsg}${errStack}`);
|
|
89
|
+
}
|
|
90
|
+
const beamableLog = {
|
|
91
|
+
__t: timestamp,
|
|
92
|
+
__l: level,
|
|
93
|
+
__m: messageParts.length > 0 ? messageParts.join(' ') : 'No message',
|
|
94
|
+
};
|
|
95
|
+
const contextFields = {};
|
|
96
|
+
if (pinoLog.cid)
|
|
97
|
+
contextFields.cid = pinoLog.cid;
|
|
98
|
+
if (pinoLog.pid)
|
|
99
|
+
contextFields.pid = pinoLog.pid;
|
|
100
|
+
if (pinoLog.routingKey)
|
|
101
|
+
contextFields.routingKey = pinoLog.routingKey;
|
|
102
|
+
if (pinoLog.service)
|
|
103
|
+
contextFields.service = pinoLog.service;
|
|
104
|
+
if (pinoLog.component)
|
|
105
|
+
contextFields.component = pinoLog.component;
|
|
106
|
+
const standardPinoFields = ['level', 'time', 'pid', 'hostname', 'name', 'msg', 'err', 'v', 'cid', 'pid', 'routingKey', 'sdkVersionExecution', 'service', 'component'];
|
|
107
|
+
for (const [key, value] of Object.entries(pinoLog)) {
|
|
108
|
+
if (!standardPinoFields.includes(key) && value !== undefined && value !== null) {
|
|
109
|
+
contextFields[key] = value;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (Object.keys(contextFields).length > 0) {
|
|
113
|
+
beamableLog.__c = contextFields;
|
|
114
|
+
}
|
|
115
|
+
const output = JSON.stringify(beamableLog) + '\n';
|
|
116
|
+
callback(null, Buffer.from(output, 'utf8'));
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const fallbackLog = {
|
|
120
|
+
__t: new Date().toISOString(),
|
|
121
|
+
__l: 'Error',
|
|
122
|
+
__m: `Failed to parse log entry: ${chunk.toString().substring(0, 200)}`,
|
|
123
|
+
};
|
|
124
|
+
callback(null, Buffer.from(JSON.stringify(fallbackLog) + '\n', 'utf8'));
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
39
129
|
function createLogger(env, options = {}) {
|
|
40
130
|
var _a, _b, _c;
|
|
41
131
|
const configuredDestination = (_a = options.destinationPath) !== null && _a !== void 0 ? _a : process.env.LOG_PATH;
|
|
@@ -52,11 +142,16 @@ function createLogger(env, options = {}) {
|
|
|
52
142
|
paths: ['secret', 'refreshToken'],
|
|
53
143
|
censor: '***',
|
|
54
144
|
},
|
|
145
|
+
timestamp: pino_1.default.stdTimeFunctions.isoTime,
|
|
55
146
|
};
|
|
56
147
|
if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {
|
|
57
|
-
|
|
148
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
149
|
+
beamableFormatter.pipe(process.stdout);
|
|
150
|
+
return (0, pino_1.default)(pinoOptions, beamableFormatter);
|
|
58
151
|
}
|
|
59
152
|
const resolvedDestination = configuredDestination === 'temp' ? (0, env_js_1.ensureWritableTempDirectory)() : configuredDestination;
|
|
60
|
-
const
|
|
61
|
-
|
|
153
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
154
|
+
const fileStream = (0, pino_1.destination)({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
155
|
+
beamableFormatter.pipe(fileStream);
|
|
156
|
+
return (0, pino_1.default)(pinoOptions, beamableFormatter);
|
|
62
157
|
}
|
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;
|
|
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;AAEpD,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,CAyC/F"}
|
package/dist/logger.js
CHANGED
|
@@ -1,5 +1,120 @@
|
|
|
1
1
|
import pino, { destination } from 'pino';
|
|
2
|
+
import { Transform } from 'node:stream';
|
|
2
3
|
import { ensureWritableTempDirectory } from './env.js';
|
|
4
|
+
/**
|
|
5
|
+
* Maps Pino log levels to Beamable log levels
|
|
6
|
+
* Pino levels: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal
|
|
7
|
+
* Beamable levels: Debug, Info, Warning, Error, Fatal
|
|
8
|
+
*/
|
|
9
|
+
function mapPinoLevelToBeamableLevel(level) {
|
|
10
|
+
switch (level) {
|
|
11
|
+
case 10: // trace
|
|
12
|
+
return 'Debug';
|
|
13
|
+
case 20: // debug
|
|
14
|
+
return 'Debug';
|
|
15
|
+
case 30: // info
|
|
16
|
+
return 'Info';
|
|
17
|
+
case 40: // warn
|
|
18
|
+
return 'Warning';
|
|
19
|
+
case 50: // error
|
|
20
|
+
return 'Error';
|
|
21
|
+
case 60: // fatal
|
|
22
|
+
return 'Fatal';
|
|
23
|
+
default:
|
|
24
|
+
return 'Info';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates a transform stream that converts Pino JSON logs to Beamable's expected format.
|
|
29
|
+
* Beamable expects logs with __t (timestamp), __l (level), and __m (message) fields.
|
|
30
|
+
* Pino writes JSON strings (one per line) to the stream.
|
|
31
|
+
*/
|
|
32
|
+
function createBeamableLogFormatter() {
|
|
33
|
+
return new Transform({
|
|
34
|
+
objectMode: false, // Pino writes strings, not objects
|
|
35
|
+
transform(chunk, _encoding, callback) {
|
|
36
|
+
try {
|
|
37
|
+
const line = chunk.toString();
|
|
38
|
+
// Skip empty lines
|
|
39
|
+
if (!line.trim()) {
|
|
40
|
+
callback();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Parse Pino's JSON log line
|
|
44
|
+
const pinoLog = JSON.parse(line);
|
|
45
|
+
// Extract timestamp - Pino uses 'time' field (ISO 8601 string or milliseconds)
|
|
46
|
+
// Convert to ISO 8601 string for Beamable
|
|
47
|
+
let timestamp;
|
|
48
|
+
if (typeof pinoLog.time === 'string') {
|
|
49
|
+
timestamp = pinoLog.time;
|
|
50
|
+
}
|
|
51
|
+
else if (typeof pinoLog.time === 'number') {
|
|
52
|
+
timestamp = new Date(pinoLog.time).toISOString();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
timestamp = new Date().toISOString();
|
|
56
|
+
}
|
|
57
|
+
// Map Pino level to Beamable level
|
|
58
|
+
const level = mapPinoLevelToBeamableLevel(pinoLog.level);
|
|
59
|
+
// Build the message - combine msg with any additional fields
|
|
60
|
+
// Pino's 'msg' field contains the log message
|
|
61
|
+
const messageParts = [];
|
|
62
|
+
if (pinoLog.msg) {
|
|
63
|
+
messageParts.push(pinoLog.msg);
|
|
64
|
+
}
|
|
65
|
+
// Include error information if present
|
|
66
|
+
if (pinoLog.err) {
|
|
67
|
+
const err = pinoLog.err;
|
|
68
|
+
const errMsg = err.message || err.msg || 'Error';
|
|
69
|
+
const errStack = err.stack ? `\n${err.stack}` : '';
|
|
70
|
+
messageParts.push(`${errMsg}${errStack}`);
|
|
71
|
+
}
|
|
72
|
+
// Build the Beamable log format
|
|
73
|
+
const beamableLog = {
|
|
74
|
+
__t: timestamp,
|
|
75
|
+
__l: level,
|
|
76
|
+
__m: messageParts.length > 0 ? messageParts.join(' ') : 'No message',
|
|
77
|
+
};
|
|
78
|
+
// Include additional context fields that might be useful
|
|
79
|
+
// These are included in the message object but not as top-level fields
|
|
80
|
+
const contextFields = {};
|
|
81
|
+
if (pinoLog.cid)
|
|
82
|
+
contextFields.cid = pinoLog.cid;
|
|
83
|
+
if (pinoLog.pid)
|
|
84
|
+
contextFields.pid = pinoLog.pid;
|
|
85
|
+
if (pinoLog.routingKey)
|
|
86
|
+
contextFields.routingKey = pinoLog.routingKey;
|
|
87
|
+
if (pinoLog.service)
|
|
88
|
+
contextFields.service = pinoLog.service;
|
|
89
|
+
if (pinoLog.component)
|
|
90
|
+
contextFields.component = pinoLog.component;
|
|
91
|
+
// Include any other fields that aren't standard Pino fields
|
|
92
|
+
const standardPinoFields = ['level', 'time', 'pid', 'hostname', 'name', 'msg', 'err', 'v', 'cid', 'pid', 'routingKey', 'sdkVersionExecution', 'service', 'component'];
|
|
93
|
+
for (const [key, value] of Object.entries(pinoLog)) {
|
|
94
|
+
if (!standardPinoFields.includes(key) && value !== undefined && value !== null) {
|
|
95
|
+
contextFields[key] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// If there are context fields, include them in the log
|
|
99
|
+
if (Object.keys(contextFields).length > 0) {
|
|
100
|
+
beamableLog.__c = contextFields;
|
|
101
|
+
}
|
|
102
|
+
// Output as a single-line JSON string (required for CloudWatch)
|
|
103
|
+
const output = JSON.stringify(beamableLog) + '\n';
|
|
104
|
+
callback(null, Buffer.from(output, 'utf8'));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// If parsing fails, output a fallback log entry
|
|
108
|
+
const fallbackLog = {
|
|
109
|
+
__t: new Date().toISOString(),
|
|
110
|
+
__l: 'Error',
|
|
111
|
+
__m: `Failed to parse log entry: ${chunk.toString().substring(0, 200)}`,
|
|
112
|
+
};
|
|
113
|
+
callback(null, Buffer.from(JSON.stringify(fallbackLog) + '\n', 'utf8'));
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
3
118
|
export function createLogger(env, options = {}) {
|
|
4
119
|
const configuredDestination = options.destinationPath ?? process.env.LOG_PATH;
|
|
5
120
|
const pinoOptions = {
|
|
@@ -15,15 +130,26 @@ export function createLogger(env, options = {}) {
|
|
|
15
130
|
paths: ['secret', 'refreshToken'],
|
|
16
131
|
censor: '***',
|
|
17
132
|
},
|
|
133
|
+
// Use timestamp in milliseconds (Pino default) for accurate conversion
|
|
134
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
18
135
|
};
|
|
19
136
|
// For deployed services, always log to stdout so container orchestrator can collect logs
|
|
20
137
|
// For local development, log to stdout unless a specific file path is provided
|
|
21
138
|
if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {
|
|
22
|
-
//
|
|
23
|
-
|
|
139
|
+
// Create a transform stream that formats logs for Beamable
|
|
140
|
+
// Pino outputs JSON to the transform, which converts it to Beamable format
|
|
141
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
142
|
+
beamableFormatter.pipe(process.stdout);
|
|
143
|
+
// Create Pino logger that writes to the formatter
|
|
144
|
+
return pino(pinoOptions, beamableFormatter);
|
|
24
145
|
}
|
|
146
|
+
// For file logging, also use Beamable format
|
|
25
147
|
const resolvedDestination = configuredDestination === 'temp' ? ensureWritableTempDirectory() : configuredDestination;
|
|
26
|
-
const
|
|
27
|
-
|
|
148
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
149
|
+
const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
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);
|
|
28
154
|
}
|
|
29
155
|
//# 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":"AAAA,OAAO,IAAI,EAAE,EAAE,WAAW,EAAmC,MAAM,MAAM,CAAC;AAC1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAQvD,MAAM,UAAU,YAAY,CAAC,GAAsB,EAAE,UAAgC,EAAE;IACrF,MAAM,qBAAqB,GAAG,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IAE9E,MAAM,WAAW,GAAkB;QACjC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,uBAAuB;QAC7C,KAAK,EAAE,GAAG,CAAC,QAAQ;QACnB,IAAI,EAAE;YACJ,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,IAAI;YAClC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;SAC7C;QACD,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,QAAQ,EAAE,cAAc,CAAC;YACjC,MAAM,EAAE,KAAK;SACd;KACF,CAAC;IAEF,yFAAyF;IACzF,+EAA+E;IAC/E,IAAI,CAAC,qBAAqB,IAAI,qBAAqB,KAAK,GAAG,IAAI,qBAAqB,KAAK,QAAQ,IAAI,qBAAqB,KAAK,SAAS,EAAE,CAAC;QACzI,wFAAwF;QACxF,OAAO,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,mBAAmB,GAAG,qBAAqB,KAAK,MAAM,CAAC,CAAC,CAAC,2BAA2B,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACrH,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClG,OAAO,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AACnC,CAAC","sourcesContent":["import pino, { destination, type Logger, type LoggerOptions } from 'pino';\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\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 };\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 // Log to stdout (default pino behavior) - this is critical for container log collection\r\n return pino(pinoOptions, process.stdout);\r\n }\r\n\r\n const resolvedDestination = configuredDestination === 'temp' ? ensureWritableTempDirectory() : configuredDestination;\r\n const stream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });\r\n return pino(pinoOptions, stream);\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\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"]}
|
package/package.json
CHANGED
package/src/logger.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pino, { destination, type Logger, type LoggerOptions } from 'pino';
|
|
2
|
+
import { Transform } from 'node:stream';
|
|
2
3
|
import { ensureWritableTempDirectory } from './env.js';
|
|
3
4
|
import type { EnvironmentConfig } from './types.js';
|
|
4
5
|
|
|
@@ -7,6 +8,124 @@ interface LoggerFactoryOptions {
|
|
|
7
8
|
destinationPath?: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Maps Pino log levels to Beamable log levels
|
|
13
|
+
* Pino levels: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal
|
|
14
|
+
* Beamable levels: Debug, Info, Warning, Error, Fatal
|
|
15
|
+
*/
|
|
16
|
+
function mapPinoLevelToBeamableLevel(level: number): string {
|
|
17
|
+
switch (level) {
|
|
18
|
+
case 10: // trace
|
|
19
|
+
return 'Debug';
|
|
20
|
+
case 20: // debug
|
|
21
|
+
return 'Debug';
|
|
22
|
+
case 30: // info
|
|
23
|
+
return 'Info';
|
|
24
|
+
case 40: // warn
|
|
25
|
+
return 'Warning';
|
|
26
|
+
case 50: // error
|
|
27
|
+
return 'Error';
|
|
28
|
+
case 60: // fatal
|
|
29
|
+
return 'Fatal';
|
|
30
|
+
default:
|
|
31
|
+
return 'Info';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a transform stream that converts Pino JSON logs to Beamable's expected format.
|
|
37
|
+
* Beamable expects logs with __t (timestamp), __l (level), and __m (message) fields.
|
|
38
|
+
* Pino writes JSON strings (one per line) to the stream.
|
|
39
|
+
*/
|
|
40
|
+
function createBeamableLogFormatter(): Transform {
|
|
41
|
+
return new Transform({
|
|
42
|
+
objectMode: false, // Pino writes strings, not objects
|
|
43
|
+
transform(chunk: Buffer, _encoding, callback) {
|
|
44
|
+
try {
|
|
45
|
+
const line = chunk.toString();
|
|
46
|
+
// Skip empty lines
|
|
47
|
+
if (!line.trim()) {
|
|
48
|
+
callback();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Parse Pino's JSON log line
|
|
53
|
+
const pinoLog = JSON.parse(line);
|
|
54
|
+
|
|
55
|
+
// Extract timestamp - Pino uses 'time' field (ISO 8601 string or milliseconds)
|
|
56
|
+
// Convert to ISO 8601 string for Beamable
|
|
57
|
+
let timestamp: string;
|
|
58
|
+
if (typeof pinoLog.time === 'string') {
|
|
59
|
+
timestamp = pinoLog.time;
|
|
60
|
+
} else if (typeof pinoLog.time === 'number') {
|
|
61
|
+
timestamp = new Date(pinoLog.time).toISOString();
|
|
62
|
+
} else {
|
|
63
|
+
timestamp = new Date().toISOString();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Map Pino level to Beamable level
|
|
67
|
+
const level = mapPinoLevelToBeamableLevel(pinoLog.level);
|
|
68
|
+
|
|
69
|
+
// Build the message - combine msg with any additional fields
|
|
70
|
+
// Pino's 'msg' field contains the log message
|
|
71
|
+
const messageParts: string[] = [];
|
|
72
|
+
if (pinoLog.msg) {
|
|
73
|
+
messageParts.push(pinoLog.msg);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Include error information if present
|
|
77
|
+
if (pinoLog.err) {
|
|
78
|
+
const err = pinoLog.err;
|
|
79
|
+
const errMsg = err.message || err.msg || 'Error';
|
|
80
|
+
const errStack = err.stack ? `\n${err.stack}` : '';
|
|
81
|
+
messageParts.push(`${errMsg}${errStack}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Build the Beamable log format
|
|
85
|
+
const beamableLog: Record<string, unknown> = {
|
|
86
|
+
__t: timestamp,
|
|
87
|
+
__l: level,
|
|
88
|
+
__m: messageParts.length > 0 ? messageParts.join(' ') : 'No message',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Include additional context fields that might be useful
|
|
92
|
+
// These are included in the message object but not as top-level fields
|
|
93
|
+
const contextFields: Record<string, unknown> = {};
|
|
94
|
+
if (pinoLog.cid) contextFields.cid = pinoLog.cid;
|
|
95
|
+
if (pinoLog.pid) contextFields.pid = pinoLog.pid;
|
|
96
|
+
if (pinoLog.routingKey) contextFields.routingKey = pinoLog.routingKey;
|
|
97
|
+
if (pinoLog.service) contextFields.service = pinoLog.service;
|
|
98
|
+
if (pinoLog.component) contextFields.component = pinoLog.component;
|
|
99
|
+
|
|
100
|
+
// Include any other fields that aren't standard Pino fields
|
|
101
|
+
const standardPinoFields = ['level', 'time', 'pid', 'hostname', 'name', 'msg', 'err', 'v', 'cid', 'pid', 'routingKey', 'sdkVersionExecution', 'service', 'component'];
|
|
102
|
+
for (const [key, value] of Object.entries(pinoLog)) {
|
|
103
|
+
if (!standardPinoFields.includes(key) && value !== undefined && value !== null) {
|
|
104
|
+
contextFields[key] = value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If there are context fields, include them in the log
|
|
109
|
+
if (Object.keys(contextFields).length > 0) {
|
|
110
|
+
beamableLog.__c = contextFields;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Output as a single-line JSON string (required for CloudWatch)
|
|
114
|
+
const output = JSON.stringify(beamableLog) + '\n';
|
|
115
|
+
callback(null, Buffer.from(output, 'utf8'));
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// If parsing fails, output a fallback log entry
|
|
118
|
+
const fallbackLog = {
|
|
119
|
+
__t: new Date().toISOString(),
|
|
120
|
+
__l: 'Error',
|
|
121
|
+
__m: `Failed to parse log entry: ${chunk.toString().substring(0, 200)}`,
|
|
122
|
+
};
|
|
123
|
+
callback(null, Buffer.from(JSON.stringify(fallbackLog) + '\n', 'utf8'));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
10
129
|
export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptions = {}): Logger {
|
|
11
130
|
const configuredDestination = options.destinationPath ?? process.env.LOG_PATH;
|
|
12
131
|
|
|
@@ -23,16 +142,29 @@ export function createLogger(env: EnvironmentConfig, options: LoggerFactoryOptio
|
|
|
23
142
|
paths: ['secret', 'refreshToken'],
|
|
24
143
|
censor: '***',
|
|
25
144
|
},
|
|
145
|
+
// Use timestamp in milliseconds (Pino default) for accurate conversion
|
|
146
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
26
147
|
};
|
|
27
148
|
|
|
28
149
|
// For deployed services, always log to stdout so container orchestrator can collect logs
|
|
29
150
|
// For local development, log to stdout unless a specific file path is provided
|
|
30
151
|
if (!configuredDestination || configuredDestination === '-' || configuredDestination === 'stdout' || configuredDestination === 'console') {
|
|
31
|
-
//
|
|
32
|
-
|
|
152
|
+
// Create a transform stream that formats logs for Beamable
|
|
153
|
+
// Pino outputs JSON to the transform, which converts it to Beamable format
|
|
154
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
155
|
+
beamableFormatter.pipe(process.stdout);
|
|
156
|
+
|
|
157
|
+
// Create Pino logger that writes to the formatter
|
|
158
|
+
return pino(pinoOptions, beamableFormatter);
|
|
33
159
|
}
|
|
34
160
|
|
|
161
|
+
// For file logging, also use Beamable format
|
|
35
162
|
const resolvedDestination = configuredDestination === 'temp' ? ensureWritableTempDirectory() : configuredDestination;
|
|
36
|
-
const
|
|
37
|
-
|
|
163
|
+
const beamableFormatter = createBeamableLogFormatter();
|
|
164
|
+
const fileStream = destination({ dest: resolvedDestination, mkdir: true, append: true, sync: false });
|
|
165
|
+
// Pipe the formatted output to the file stream
|
|
166
|
+
// Note: destination() returns a SonicBoom stream which is compatible with Node streams
|
|
167
|
+
beamableFormatter.pipe(fileStream as unknown as NodeJS.WritableStream);
|
|
168
|
+
|
|
169
|
+
return pino(pinoOptions, beamableFormatter);
|
|
38
170
|
}
|