@terreno/api 0.20.1 → 0.21.0
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/.ai/guidelines/core.md +71 -0
- package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
- package/README.md +54 -1
- package/dist/__tests__/versionCheckPlugin.test.js +29 -7
- package/dist/actions.openApi.test.js +13 -11
- package/dist/api.js +98 -11
- package/dist/api.query.test.js +31 -1
- package/dist/api.test.js +211 -0
- package/dist/auth.test.js +10 -10
- package/dist/betterAuth.d.ts +1 -1
- package/dist/consentApp.test.js +1 -0
- package/dist/example.js +4 -4
- package/dist/expressServer.d.ts +0 -22
- package/dist/expressServer.js +1 -125
- package/dist/expressServer.test.js +90 -91
- package/dist/githubAuth.test.js +22 -22
- package/dist/logger.d.ts +154 -0
- package/dist/logger.js +445 -26
- package/dist/logger.test.js +435 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +58 -1
- package/dist/middleware.test.js +159 -0
- package/dist/openApi.test.js +10 -17
- package/dist/openApiBuilder.test.js +18 -10
- package/dist/realtime/changeStreamWatcher.d.ts +4 -4
- package/dist/realtime/changeStreamWatcher.js +2 -4
- package/dist/realtime/queryMatcher.d.ts +1 -1
- package/dist/realtime/queryMatcher.js +39 -14
- package/dist/realtime/types.d.ts +3 -3
- package/dist/requestContext.d.ts +61 -0
- package/dist/requestContext.js +74 -0
- package/dist/secretProviders.test.js +335 -0
- package/dist/terrenoApp.d.ts +27 -15
- package/dist/terrenoApp.js +24 -14
- package/dist/terrenoApp.test.js +52 -0
- package/dist/tests/bunSetup.js +61 -7
- package/dist/tests.js +27 -4
- package/package.json +1 -1
- package/src/__tests__/versionCheckPlugin.test.ts +43 -15
- package/src/actions.openApi.test.ts +12 -10
- package/src/api.query.test.ts +24 -1
- package/src/api.test.ts +169 -0
- package/src/api.ts +71 -0
- package/src/auth.test.ts +10 -10
- package/src/betterAuth.ts +1 -1
- package/src/consentApp.test.ts +1 -0
- package/src/example.ts +4 -4
- package/src/expressServer.test.ts +82 -85
- package/src/expressServer.ts +1 -213
- package/src/githubAuth.test.ts +22 -22
- package/src/logger.test.ts +466 -1
- package/src/logger.ts +477 -14
- package/src/middleware.test.ts +74 -2
- package/src/middleware.ts +57 -0
- package/src/openApi.test.ts +10 -17
- package/src/openApiBuilder.test.ts +18 -10
- package/src/realtime/changeStreamWatcher.ts +15 -10
- package/src/realtime/queryMatcher.ts +54 -27
- package/src/realtime/types.ts +4 -4
- package/src/requestContext.ts +86 -0
- package/src/secretProviders.test.ts +219 -1
- package/src/terrenoApp.test.ts +38 -0
- package/src/terrenoApp.ts +37 -15
- package/src/tests/bunSetup.ts +16 -3
- package/src/tests.ts +17 -4
package/dist/logger.js
CHANGED
|
@@ -43,6 +43,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
43
|
return result;
|
|
44
44
|
};
|
|
45
45
|
})();
|
|
46
|
+
var __values = (this && this.__values) || function(o) {
|
|
47
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
48
|
+
if (m) return m.call(o);
|
|
49
|
+
if (o && typeof o.length === "number") return {
|
|
50
|
+
next: function () {
|
|
51
|
+
if (o && i >= o.length) o = void 0;
|
|
52
|
+
return { value: o && o[i++], done: !o };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
56
|
+
};
|
|
46
57
|
var __read = (this && this.__read) || function (o, n) {
|
|
47
58
|
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
48
59
|
if (!m) return o;
|
|
@@ -68,23 +79,39 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
68
79
|
}
|
|
69
80
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
70
81
|
};
|
|
71
|
-
var __values = (this && this.__values) || function(o) {
|
|
72
|
-
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
73
|
-
if (m) return m.call(o);
|
|
74
|
-
if (o && typeof o.length === "number") return {
|
|
75
|
-
next: function () {
|
|
76
|
-
if (o && i >= o.length) o = void 0;
|
|
77
|
-
return { value: o && o[i++], done: !o };
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
81
|
-
};
|
|
82
82
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
83
83
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
84
84
|
};
|
|
85
85
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
86
|
-
exports.setupLogging = exports.logger = exports.winstonLogger = void 0;
|
|
86
|
+
exports.setupLogging = exports.createFeatureFlaggedLogger = exports.createScopedLogger = exports.logger = exports.winstonLogger = exports.formatLogContextSuffix = void 0;
|
|
87
|
+
/**
|
|
88
|
+
* Backend logging for `@terreno/api`.
|
|
89
|
+
*
|
|
90
|
+
* Three building blocks cooperate so a single request or background job can be followed across
|
|
91
|
+
* many log lines, both in plain-text consoles and in structured transports (Google Cloud Logging,
|
|
92
|
+
* Sentry):
|
|
93
|
+
*
|
|
94
|
+
* - **{@link logger}** – the global logger (`debug` / `info` / `warn` / `error` / `catch`). Use it
|
|
95
|
+
* for one-off messages.
|
|
96
|
+
* - **{@link createScopedLogger}** – returns a logger that prepends a stable `prefix` and/or
|
|
97
|
+
* attaches `labels` (workflow dimensions such as `invoiceId`) to every line. Use it when a
|
|
98
|
+
* handler, job, or service runs multiple steps that should share identifiers.
|
|
99
|
+
* - **{@link createFeatureFlaggedLogger}** – wraps any {@link ScopedLogger} behind an
|
|
100
|
+
* `isEnabled()` predicate so verbose diagnostics can be toggled with a feature flag or env var
|
|
101
|
+
* without a redeploy.
|
|
102
|
+
*
|
|
103
|
+
* **Correlation** is automatic: while a request/job AsyncLocalStorage scope is active (see
|
|
104
|
+
* `requestContext.ts` – HTTP middleware or `runWithRequestContext`), every log line is enriched
|
|
105
|
+
* with `requestId`, `userId`, `traceId`, etc. and a nested `terrenoRequestLog` object, regardless
|
|
106
|
+
* of which logger above emitted it.
|
|
107
|
+
*
|
|
108
|
+
* @see {@link createScopedLogger}
|
|
109
|
+
* @see {@link createFeatureFlaggedLogger}
|
|
110
|
+
* @see {@link formatLogContextSuffix}
|
|
111
|
+
* @module logger
|
|
112
|
+
*/
|
|
87
113
|
var node_fs_1 = __importDefault(require("node:fs"));
|
|
114
|
+
var node_path_1 = require("node:path");
|
|
88
115
|
var node_util_1 = require("node:util");
|
|
89
116
|
var Sentry = __importStar(require("@sentry/bun"));
|
|
90
117
|
var winston_1 = __importDefault(require("winston"));
|
|
@@ -97,23 +124,95 @@ var formatWithInspect = function (val) {
|
|
|
97
124
|
var shouldFormat = typeof val !== "string";
|
|
98
125
|
return prefix + (shouldFormat ? (0, node_util_1.inspect)(val, { colors: true, depth: null }) : val);
|
|
99
126
|
};
|
|
127
|
+
var buildTerrenoRequestLog = function (active) {
|
|
128
|
+
var _a;
|
|
129
|
+
return {
|
|
130
|
+
requestId: active.requestId,
|
|
131
|
+
userId: (_a = active.userId) !== null && _a !== void 0 ? _a : null,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
var mergeActiveRequestIntoInfo = function (info, active) {
|
|
135
|
+
var next = __assign(__assign({}, info), { requestId: active.requestId, terrenoRequestLog: buildTerrenoRequestLog(active) });
|
|
136
|
+
if (active.jobId) {
|
|
137
|
+
next.jobId = active.jobId;
|
|
138
|
+
}
|
|
139
|
+
if (active.sessionId) {
|
|
140
|
+
next.sessionId = active.sessionId;
|
|
141
|
+
}
|
|
142
|
+
if (active.userId) {
|
|
143
|
+
next.userId = active.userId;
|
|
144
|
+
}
|
|
145
|
+
if (active.spanId) {
|
|
146
|
+
next.spanId = active.spanId;
|
|
147
|
+
}
|
|
148
|
+
if (active.traceId) {
|
|
149
|
+
next.traceId = active.traceId;
|
|
150
|
+
}
|
|
151
|
+
if (active.traceSampled !== undefined) {
|
|
152
|
+
next.traceSampled = active.traceSampled;
|
|
153
|
+
}
|
|
154
|
+
return next;
|
|
155
|
+
};
|
|
100
156
|
var addRequestContextFormat = winston_1.default.format(function (info) {
|
|
101
|
-
var
|
|
102
|
-
|
|
157
|
+
var active = (0, requestContext_1.getCurrentRequestContext)();
|
|
158
|
+
if (!active) {
|
|
159
|
+
return __assign({}, info);
|
|
160
|
+
}
|
|
161
|
+
return mergeActiveRequestIntoInfo(info, active);
|
|
103
162
|
});
|
|
104
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Builds the ` key=value ...` suffix appended to console/file log lines after the message.
|
|
165
|
+
* Request-scoped fields come from AsyncLocalStorage via Winston metadata; `terrenoLabels` and
|
|
166
|
+
* `terrenoLogPrefix` come from {@link createScopedLogger}. Nested `terrenoRequestLog`
|
|
167
|
+
* (`requestId` + `userId` including `null` when anonymous) is attached on the Winston info
|
|
168
|
+
* object for structured transports only, not repeated in this suffix.
|
|
169
|
+
*/
|
|
170
|
+
var formatLogContextSuffix = function (fields) {
|
|
171
|
+
var e_1, _a;
|
|
105
172
|
var contextParts = [
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
173
|
+
fields.requestId ? "requestId=".concat(fields.requestId) : undefined,
|
|
174
|
+
fields.jobId ? "jobId=".concat(fields.jobId) : undefined,
|
|
175
|
+
fields.sessionId ? "sessionId=".concat(fields.sessionId) : undefined,
|
|
176
|
+
fields.userId ? "userId=".concat(fields.userId) : undefined,
|
|
177
|
+
fields.traceId ? "traceId=".concat(fields.traceId) : undefined,
|
|
178
|
+
fields.terrenoLogPrefix ? "logPrefix=".concat(fields.terrenoLogPrefix) : undefined,
|
|
111
179
|
].filter(Boolean);
|
|
180
|
+
if (fields.terrenoLabels && typeof fields.terrenoLabels === "object") {
|
|
181
|
+
var sortedKeys = Object.keys(fields.terrenoLabels).sort();
|
|
182
|
+
try {
|
|
183
|
+
for (var sortedKeys_1 = __values(sortedKeys), sortedKeys_1_1 = sortedKeys_1.next(); !sortedKeys_1_1.done; sortedKeys_1_1 = sortedKeys_1.next()) {
|
|
184
|
+
var key = sortedKeys_1_1.value;
|
|
185
|
+
var value = fields.terrenoLabels[key];
|
|
186
|
+
if (value !== undefined && value !== "") {
|
|
187
|
+
contextParts.push("".concat(key, "=").concat(value));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
192
|
+
finally {
|
|
193
|
+
try {
|
|
194
|
+
if (sortedKeys_1_1 && !sortedKeys_1_1.done && (_a = sortedKeys_1.return)) _a.call(sortedKeys_1);
|
|
195
|
+
}
|
|
196
|
+
finally { if (e_1) throw e_1.error; }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
112
199
|
if (contextParts.length === 0) {
|
|
113
200
|
return "";
|
|
114
201
|
}
|
|
115
202
|
return " ".concat(contextParts.join(" "));
|
|
116
203
|
};
|
|
204
|
+
exports.formatLogContextSuffix = formatLogContextSuffix;
|
|
205
|
+
var formatContext = function (info) {
|
|
206
|
+
return (0, exports.formatLogContextSuffix)({
|
|
207
|
+
jobId: info.jobId,
|
|
208
|
+
requestId: info.requestId,
|
|
209
|
+
sessionId: info.sessionId,
|
|
210
|
+
terrenoLabels: info.terrenoLabels,
|
|
211
|
+
terrenoLogPrefix: info.terrenoLogPrefix,
|
|
212
|
+
traceId: info.traceId,
|
|
213
|
+
userId: info.userId,
|
|
214
|
+
});
|
|
215
|
+
};
|
|
117
216
|
// Winston doesn't operate like console.log by default, e.g. `logger.error('error',
|
|
118
217
|
// error)` only prints the message and no args. Add handling for all the args,
|
|
119
218
|
// while also supporting splat logging.
|
|
@@ -131,6 +230,43 @@ var printf = function (timestamp) {
|
|
|
131
230
|
return "".concat(info.level, ": ").concat(msg).concat(context, " ").concat(rest);
|
|
132
231
|
};
|
|
133
232
|
};
|
|
233
|
+
var terrenoDevJsonlAttached = false;
|
|
234
|
+
var shouldAttachTerrenoDevJsonl = function () {
|
|
235
|
+
if (process.env.TERRENO_LOG_FILE === "false" || process.env.TERRENO_LOG_FILE === "0") {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (process.env.TERRENO_LOG_FILE === "true" || process.env.TERRENO_LOG_FILE === "1") {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
return process.env.NODE_ENV !== "production";
|
|
242
|
+
};
|
|
243
|
+
var attachTerrenoDevJsonlTransportIfEnabled = function (logger, options) {
|
|
244
|
+
if (options === null || options === void 0 ? void 0 : options.disable) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (!shouldAttachTerrenoDevJsonl()) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (terrenoDevJsonlAttached) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
var logDir = (0, node_path_1.join)(process.cwd(), ".terreno", "logs");
|
|
254
|
+
if (!node_fs_1.default.existsSync(logDir)) {
|
|
255
|
+
node_fs_1.default.mkdirSync(logDir, { recursive: true });
|
|
256
|
+
}
|
|
257
|
+
var maxBytes = 5 * 1024 * 1024;
|
|
258
|
+
logger.add(new winston_1.default.transports.File({
|
|
259
|
+
filename: (0, node_path_1.join)(logDir, "app.log"),
|
|
260
|
+
format: winston_1.default.format.combine(addRequestContextFormat(), winston_1.default.format.timestamp(), winston_1.default.format.json()),
|
|
261
|
+
handleExceptions: true,
|
|
262
|
+
handleRejections: true,
|
|
263
|
+
level: "debug",
|
|
264
|
+
maxFiles: 3,
|
|
265
|
+
maxsize: maxBytes,
|
|
266
|
+
options: { flags: "a", mode: 384 },
|
|
267
|
+
}));
|
|
268
|
+
terrenoDevJsonlAttached = true;
|
|
269
|
+
};
|
|
134
270
|
// Setup a global, default rejection handler.
|
|
135
271
|
winston_1.default.add(new winston_1.default.transports.Console({
|
|
136
272
|
debugStdout: true,
|
|
@@ -153,13 +289,42 @@ exports.winstonLogger = winston_1.default.createLogger({
|
|
|
153
289
|
}),
|
|
154
290
|
],
|
|
155
291
|
});
|
|
292
|
+
var mergeSentryLogAttributes = function (extra) {
|
|
293
|
+
var active = (0, requestContext_1.getCurrentRequestContext)();
|
|
294
|
+
var out = __assign(__assign({}, (0, requestContext_1.getCurrentLogContext)()), (extra !== null && extra !== void 0 ? extra : {}));
|
|
295
|
+
if (active) {
|
|
296
|
+
out.terrenoRequestLog = buildTerrenoRequestLog(active);
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
};
|
|
300
|
+
attachTerrenoDevJsonlTransportIfEnabled(exports.winstonLogger);
|
|
156
301
|
// Helper function to send logs to Sentry if enabled
|
|
157
|
-
var sendToSentry = function (message, level) {
|
|
302
|
+
var sendToSentry = function (message, level, extraAttributes) {
|
|
158
303
|
if (process.env.USE_SENTRY_LOGGING === "true" && Sentry.logger) {
|
|
159
304
|
var logWithContext = Sentry.logger[level];
|
|
160
|
-
logWithContext(message, (
|
|
305
|
+
logWithContext(message, mergeSentryLogAttributes(extraAttributes));
|
|
161
306
|
}
|
|
162
307
|
};
|
|
308
|
+
/**
|
|
309
|
+
* Global application logger. Each method writes through Winston (console/file transports) and, when
|
|
310
|
+
* `USE_SENTRY_LOGGING=true`, mirrors the line to Sentry with the active request context attached.
|
|
311
|
+
*
|
|
312
|
+
* Prefer {@link createScopedLogger} when a workflow spans multiple log lines that should share a
|
|
313
|
+
* prefix or labels.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```typescript
|
|
317
|
+
* import {logger} from "@terreno/api";
|
|
318
|
+
*
|
|
319
|
+
* logger.info("Server started", {port: 4000});
|
|
320
|
+
* logger.warn("Slow query", {ms: 500});
|
|
321
|
+
* logger.error("Failed to process", {error});
|
|
322
|
+
* logger.debug("Request details", {body: req.body});
|
|
323
|
+
*
|
|
324
|
+
* // Convenient `.catch` handler for promises – logs and captures the exception.
|
|
325
|
+
* await chargeCard(id).catch(logger.catch);
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
163
328
|
exports.logger = {
|
|
164
329
|
// simple way to log a caught exception. e.g. promise().catch(logger.catch)
|
|
165
330
|
catch: function (e) {
|
|
@@ -170,7 +335,7 @@ exports.logger = {
|
|
|
170
335
|
Sentry.captureException(e);
|
|
171
336
|
}
|
|
172
337
|
else if (Sentry.logger) {
|
|
173
|
-
Sentry.logger.error(errorMsg);
|
|
338
|
+
Sentry.logger.error(errorMsg, mergeSentryLogAttributes());
|
|
174
339
|
}
|
|
175
340
|
}
|
|
176
341
|
},
|
|
@@ -207,10 +372,261 @@ exports.logger = {
|
|
|
207
372
|
sendToSentry(msg, "warn");
|
|
208
373
|
},
|
|
209
374
|
};
|
|
375
|
+
var normalizeLogLabels = function (labels) {
|
|
376
|
+
var e_2, _a;
|
|
377
|
+
if (!labels) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
var out = {};
|
|
381
|
+
try {
|
|
382
|
+
for (var _b = __values(Object.entries(labels)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
383
|
+
var _d = __read(_c.value, 2), key = _d[0], value = _d[1];
|
|
384
|
+
if (value === undefined || value === null) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
out[key] = String(value);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
391
|
+
finally {
|
|
392
|
+
try {
|
|
393
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
394
|
+
}
|
|
395
|
+
finally { if (e_2) throw e_2.error; }
|
|
396
|
+
}
|
|
397
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
398
|
+
};
|
|
399
|
+
var applyMessagePrefix = function (prefix, msg) {
|
|
400
|
+
var trimmed = prefix === null || prefix === void 0 ? void 0 : prefix.trim();
|
|
401
|
+
if (!trimmed) {
|
|
402
|
+
return msg;
|
|
403
|
+
}
|
|
404
|
+
return "".concat(trimmed, " ").concat(msg);
|
|
405
|
+
};
|
|
406
|
+
var buildScopedLoggerSentryExtras = function (labels, logPrefix) {
|
|
407
|
+
var e_3, _a;
|
|
408
|
+
var out = {};
|
|
409
|
+
if (logPrefix) {
|
|
410
|
+
out.terrenoLogPrefix = logPrefix;
|
|
411
|
+
}
|
|
412
|
+
if (labels) {
|
|
413
|
+
try {
|
|
414
|
+
for (var _b = __values(Object.entries(labels)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
415
|
+
var _d = __read(_c.value, 2), key = _d[0], value = _d[1];
|
|
416
|
+
out[key] = value;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
420
|
+
finally {
|
|
421
|
+
try {
|
|
422
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
423
|
+
}
|
|
424
|
+
finally { if (e_3) throw e_3.error; }
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Creates a {@link ScopedLogger} that prefixes every message and/or attaches stable `labels` to
|
|
431
|
+
* every line, so multi-step workflows are easy to group and search.
|
|
432
|
+
*
|
|
433
|
+
* - `prefix` is prepended to the human-readable message (easy grep / Log Explorer text search) and
|
|
434
|
+
* also stored as the Winston metadata field `terrenoLogPrefix`.
|
|
435
|
+
* - `labels` are normalized to strings and stored as the Winston metadata field `terrenoLabels`.
|
|
436
|
+
* They appear in the plain-text ` key=value` suffix (see {@link formatLogContextSuffix}) and as
|
|
437
|
+
* discrete fields on structured transports such as `@google-cloud/logging-winston`.
|
|
438
|
+
*
|
|
439
|
+
* Both ride on a Winston **child logger**, so they merge with — and never overwrite — the
|
|
440
|
+
* request/job correlation fields that AsyncLocalStorage injects (`requestId`, `userId`,
|
|
441
|
+
* `terrenoRequestLog`, etc.). Avoid label keys that collide with those framework fields:
|
|
442
|
+
* `requestId`, `jobId`, `sessionId`, `userId`, `traceId`, `spanId`, `terrenoLogPrefix`,
|
|
443
|
+
* `terrenoRequestLog`, `terrenoLabels`.
|
|
444
|
+
*
|
|
445
|
+
* If both `prefix` and `labels` are empty, the global {@link logger} is returned unchanged.
|
|
446
|
+
*
|
|
447
|
+
* @param options - Optional `prefix` token and/or `labels` dimensions for this scope.
|
|
448
|
+
* @returns A scoped logger sharing the same methods as the global {@link logger}.
|
|
449
|
+
* @see {@link createFeatureFlaggedLogger} to gate a scoped logger behind a feature flag.
|
|
450
|
+
*
|
|
451
|
+
* @example Reuse one instance for a whole workflow so every line shares identifiers
|
|
452
|
+
* ```typescript
|
|
453
|
+
* import {createScopedLogger} from "@terreno/api";
|
|
454
|
+
*
|
|
455
|
+
* const log = createScopedLogger({
|
|
456
|
+
* prefix: "[InvoicePay]",
|
|
457
|
+
* labels: {invoiceId: invoice._id.toString(), attempt: String(attemptNumber)},
|
|
458
|
+
* });
|
|
459
|
+
*
|
|
460
|
+
* log.info("Starting capture"); // -> "[InvoicePay] Starting capture invoiceId=... attempt=1 requestId=..."
|
|
461
|
+
* log.warn("Stripe rate limited, backing off");
|
|
462
|
+
* await capture(invoice).catch(log.catch);
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
var createScopedLogger = function (options) {
|
|
466
|
+
var _a;
|
|
467
|
+
if (options === void 0) { options = {}; }
|
|
468
|
+
var trimmedPrefix = ((_a = options.prefix) === null || _a === void 0 ? void 0 : _a.trim()) ? options.prefix.trim() : undefined;
|
|
469
|
+
var terrenoLabels = normalizeLogLabels(options.labels);
|
|
470
|
+
if (!trimmedPrefix && !terrenoLabels) {
|
|
471
|
+
return exports.logger;
|
|
472
|
+
}
|
|
473
|
+
var childDefaults = {};
|
|
474
|
+
if (terrenoLabels) {
|
|
475
|
+
childDefaults.terrenoLabels = terrenoLabels;
|
|
476
|
+
}
|
|
477
|
+
if (trimmedPrefix) {
|
|
478
|
+
childDefaults.terrenoLogPrefix = trimmedPrefix;
|
|
479
|
+
}
|
|
480
|
+
var base = exports.winstonLogger.child(childDefaults);
|
|
481
|
+
var sentryExtras = function () {
|
|
482
|
+
return buildScopedLoggerSentryExtras(terrenoLabels, trimmedPrefix);
|
|
483
|
+
};
|
|
484
|
+
return {
|
|
485
|
+
catch: function (e) {
|
|
486
|
+
var errorMsg = applyMessagePrefix(trimmedPrefix, "Caught: ".concat(e === null || e === void 0 ? void 0 : e.message, " ").concat(e === null || e === void 0 ? void 0 : e.stack));
|
|
487
|
+
base.error(errorMsg);
|
|
488
|
+
if (process.env.USE_SENTRY_LOGGING === "true") {
|
|
489
|
+
if (e instanceof Error) {
|
|
490
|
+
Sentry.captureException(e);
|
|
491
|
+
}
|
|
492
|
+
else if (Sentry.logger) {
|
|
493
|
+
Sentry.logger.error(errorMsg, mergeSentryLogAttributes(sentryExtras()));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
debug: function (msg) {
|
|
498
|
+
var args = [];
|
|
499
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
500
|
+
args[_i - 1] = arguments[_i];
|
|
501
|
+
}
|
|
502
|
+
var line = applyMessagePrefix(trimmedPrefix, msg);
|
|
503
|
+
base.debug.apply(base, __spreadArray([line], __read(args), false));
|
|
504
|
+
sendToSentry(line, "debug", sentryExtras());
|
|
505
|
+
},
|
|
506
|
+
error: function (msg) {
|
|
507
|
+
var args = [];
|
|
508
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
509
|
+
args[_i - 1] = arguments[_i];
|
|
510
|
+
}
|
|
511
|
+
var line = applyMessagePrefix(trimmedPrefix, msg);
|
|
512
|
+
base.error.apply(base, __spreadArray([line], __read(args), false));
|
|
513
|
+
sendToSentry(line, "error", sentryExtras());
|
|
514
|
+
},
|
|
515
|
+
info: function (msg) {
|
|
516
|
+
var args = [];
|
|
517
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
518
|
+
args[_i - 1] = arguments[_i];
|
|
519
|
+
}
|
|
520
|
+
var line = applyMessagePrefix(trimmedPrefix, msg);
|
|
521
|
+
base.info.apply(base, __spreadArray([line], __read(args), false));
|
|
522
|
+
sendToSentry(line, "info", sentryExtras());
|
|
523
|
+
},
|
|
524
|
+
warn: function (msg) {
|
|
525
|
+
var args = [];
|
|
526
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
527
|
+
args[_i - 1] = arguments[_i];
|
|
528
|
+
}
|
|
529
|
+
var line = applyMessagePrefix(trimmedPrefix, msg);
|
|
530
|
+
base.warn.apply(base, __spreadArray([line], __read(args), false));
|
|
531
|
+
sendToSentry(line, "warn", sentryExtras());
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
};
|
|
535
|
+
exports.createScopedLogger = createScopedLogger;
|
|
536
|
+
/**
|
|
537
|
+
* Wraps a {@link ScopedLogger} so all `debug` / `info` / `warn` / `error` traffic is dropped while
|
|
538
|
+
* `isEnabled()` returns false. Use it to keep verbose diagnostics in the code but silent until a
|
|
539
|
+
* flag turns them on — no redeploy required.
|
|
540
|
+
*
|
|
541
|
+
* `isEnabled` is evaluated on **every** call, so it can read any feature-flag source: an
|
|
542
|
+
* environment variable, a cached/remote flag map, or a call into `@terreno/feature-flags` from your
|
|
543
|
+
* app. (`@terreno/api` deliberately does not import `@terreno/feature-flags` to avoid a package
|
|
544
|
+
* cycle — you supply the predicate.)
|
|
545
|
+
*
|
|
546
|
+
* @param options - The `isEnabled` predicate plus an optional `target` logger and `gateCatch`.
|
|
547
|
+
* @returns A scoped logger that forwards to `target` only while the flag is enabled.
|
|
548
|
+
* @see {@link createScopedLogger} for the usual `target`.
|
|
549
|
+
*
|
|
550
|
+
* @example Gate a scoped logger behind an env var (flips live, no restart)
|
|
551
|
+
* ```typescript
|
|
552
|
+
* import {createFeatureFlaggedLogger, createScopedLogger} from "@terreno/api";
|
|
553
|
+
*
|
|
554
|
+
* const jobLog = createFeatureFlaggedLogger({
|
|
555
|
+
* isEnabled: () => process.env.JOB_TRACE_LOGS === "true",
|
|
556
|
+
* target: createScopedLogger({prefix: "[Job]", labels: {jobName: "nightly-sync"}}),
|
|
557
|
+
* });
|
|
558
|
+
*
|
|
559
|
+
* jobLog.info("step 1"); // silent unless JOB_TRACE_LOGS=true
|
|
560
|
+
* ```
|
|
561
|
+
*
|
|
562
|
+
* @example Drive it from `@terreno/feature-flags` in app code
|
|
563
|
+
* ```typescript
|
|
564
|
+
* const debugLog = createFeatureFlaggedLogger({
|
|
565
|
+
* isEnabled: () => flags.isEnabled("debug.billing"),
|
|
566
|
+
* target: createScopedLogger({prefix: "[Billing]"}),
|
|
567
|
+
* gateCatch: true, // also silence `catch` while the flag is off (default: false)
|
|
568
|
+
* });
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
var createFeatureFlaggedLogger = function (options) {
|
|
572
|
+
var _a, _b;
|
|
573
|
+
var target = (_a = options.target) !== null && _a !== void 0 ? _a : exports.logger;
|
|
574
|
+
var gateCatch = (_b = options.gateCatch) !== null && _b !== void 0 ? _b : false;
|
|
575
|
+
return {
|
|
576
|
+
catch: function (e) {
|
|
577
|
+
if (gateCatch && !options.isEnabled()) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
target.catch(e);
|
|
581
|
+
},
|
|
582
|
+
debug: function (msg) {
|
|
583
|
+
var args = [];
|
|
584
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
585
|
+
args[_i - 1] = arguments[_i];
|
|
586
|
+
}
|
|
587
|
+
if (!options.isEnabled()) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
target.debug.apply(target, __spreadArray([msg], __read(args), false));
|
|
591
|
+
},
|
|
592
|
+
error: function (msg) {
|
|
593
|
+
var args = [];
|
|
594
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
595
|
+
args[_i - 1] = arguments[_i];
|
|
596
|
+
}
|
|
597
|
+
if (!options.isEnabled()) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
target.error.apply(target, __spreadArray([msg], __read(args), false));
|
|
601
|
+
},
|
|
602
|
+
info: function (msg) {
|
|
603
|
+
var args = [];
|
|
604
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
605
|
+
args[_i - 1] = arguments[_i];
|
|
606
|
+
}
|
|
607
|
+
if (!options.isEnabled()) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
target.info.apply(target, __spreadArray([msg], __read(args), false));
|
|
611
|
+
},
|
|
612
|
+
warn: function (msg) {
|
|
613
|
+
var args = [];
|
|
614
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
615
|
+
args[_i - 1] = arguments[_i];
|
|
616
|
+
}
|
|
617
|
+
if (!options.isEnabled()) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
target.warn.apply(target, __spreadArray([msg], __read(args), false));
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
};
|
|
624
|
+
exports.createFeatureFlaggedLogger = createFeatureFlaggedLogger;
|
|
210
625
|
var setupLogging = function (options) {
|
|
211
|
-
var _a,
|
|
626
|
+
var _a, e_4, _b;
|
|
212
627
|
var _c, _d;
|
|
213
628
|
exports.winstonLogger.clear();
|
|
629
|
+
terrenoDevJsonlAttached = false;
|
|
214
630
|
if (!(options === null || options === void 0 ? void 0 : options.disableConsoleLogging)) {
|
|
215
631
|
var formats = [addRequestContextFormat(), winston_1.default.format.simple()];
|
|
216
632
|
if (!(options === null || options === void 0 ? void 0 : options.disableConsoleColors)) {
|
|
@@ -259,13 +675,16 @@ var setupLogging = function (options) {
|
|
|
259
675
|
exports.winstonLogger.add(transport);
|
|
260
676
|
}
|
|
261
677
|
}
|
|
262
|
-
catch (
|
|
678
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
263
679
|
finally {
|
|
264
680
|
try {
|
|
265
681
|
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
|
|
266
682
|
}
|
|
267
|
-
finally { if (
|
|
683
|
+
finally { if (e_4) throw e_4.error; }
|
|
268
684
|
}
|
|
269
685
|
}
|
|
686
|
+
attachTerrenoDevJsonlTransportIfEnabled(exports.winstonLogger, {
|
|
687
|
+
disable: (options === null || options === void 0 ? void 0 : options.disableTerrenoDevJsonlLog) === true,
|
|
688
|
+
});
|
|
270
689
|
};
|
|
271
690
|
exports.setupLogging = setupLogging;
|