logixia 1.13.0 → 2.0.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/README.md +81 -16
- package/dist/{async-context-CwBNMm2E.mjs → async-context-BzJrbfQs.mjs} +8 -4
- package/dist/async-context-BzJrbfQs.mjs.map +1 -0
- package/dist/{async-context-CSHWoHMB.js → async-context-DyQQYq3I.js} +8 -4
- package/dist/async-context-DyQQYq3I.js.map +1 -0
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-Cn_3Usvu.d.ts} +2 -2
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-Cn_3Usvu.d.ts.map} +1 -1
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D7Da4RAb.d.mts} +2 -2
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D7Da4RAb.d.mts.map} +1 -1
- package/dist/azure.d.mts +2 -2
- package/dist/azure.d.ts +2 -2
- package/dist/browser.d.mts +9 -0
- package/dist/browser.d.mts.map +1 -1
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +43 -12
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +43 -12
- package/dist/browser.mjs.map +1 -1
- package/dist/{logitron-logger.module-Bs3XYZgF.mjs → builder-0gEzRopq.mjs} +383 -1021
- package/dist/builder-0gEzRopq.mjs.map +1 -0
- package/dist/{logitron-logger.module-BsDZR5BH.js → builder-D44QktIo.js} +411 -1155
- package/dist/builder-D44QktIo.js.map +1 -0
- package/dist/cli/index.js +130 -65
- package/dist/cloudwatch.d.mts +2 -2
- package/dist/cloudwatch.d.ts +2 -2
- package/dist/cloudwatch.js +2 -1
- package/dist/cloudwatch.mjs +2 -1
- package/dist/{cloudwatch.transport-pNBNWJLt.js → cloudwatch.transport-BO1ahxw-.js} +26 -19
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +1 -0
- package/dist/{cloudwatch.transport-yBwm4OeO.mjs → cloudwatch.transport-DZ6ies7g.mjs} +26 -19
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +1 -0
- package/dist/{cloudwatch.transport-CGHY4buE.d.mts → cloudwatch.transport-DsEq633o.d.ts} +9 -2
- package/dist/cloudwatch.transport-DsEq633o.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-DK_8cKd3.d.ts → cloudwatch.transport-UlyZFWMn.d.mts} +9 -2
- package/dist/cloudwatch.transport-UlyZFWMn.d.mts.map +1 -0
- package/dist/{coerce.utils-BN980Y9V.js → coerce.utils-B-LilukN.js} +1 -1
- package/dist/{coerce.utils-BN980Y9V.js.map → coerce.utils-B-LilukN.js.map} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs → coerce.utils-Cu6IUTgX.mjs} +1 -1
- package/dist/{coerce.utils-Dxv4_ikC.mjs.map → coerce.utils-Cu6IUTgX.mjs.map} +1 -1
- package/dist/correlation.d.mts +24 -6
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +24 -6
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +41 -16
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +41 -16
- package/dist/correlation.mjs.map +1 -1
- package/dist/entry-fields-BJkuPnIg.mjs +30 -0
- package/dist/entry-fields-BJkuPnIg.mjs.map +1 -0
- package/dist/entry-fields-BvOLFlJ4.js +36 -0
- package/dist/entry-fields-BvOLFlJ4.js.map +1 -0
- package/dist/gcp.d.mts +2 -2
- package/dist/gcp.d.ts +2 -2
- package/dist/gcp.js +2 -1
- package/dist/gcp.mjs +2 -1
- package/dist/{gcp.transport-BOhkVKlI.d.ts → gcp.transport-B7oc97sG.d.ts} +2 -2
- package/dist/gcp.transport-B7oc97sG.d.ts.map +1 -0
- package/dist/{gcp.transport-SHdtc1n0.js → gcp.transport-CHV8MGgz.js} +5 -5
- package/dist/gcp.transport-CHV8MGgz.js.map +1 -0
- package/dist/{gcp.transport-BJuNRFe_.mjs → gcp.transport-DBX5TPIy.mjs} +5 -5
- package/dist/gcp.transport-DBX5TPIy.mjs.map +1 -0
- package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-GrJurKo8.d.mts} +2 -2
- package/dist/gcp.transport-GrJurKo8.d.mts.map +1 -0
- package/dist/{index-BPKwwERo.d.mts → index-3mw5gaCw.d.mts} +2 -2
- package/dist/{index-BPKwwERo.d.mts.map → index-3mw5gaCw.d.mts.map} +1 -1
- package/dist/{index-C1X-TE5I.d.ts → index-DhIoploR.d.ts} +2 -2
- package/dist/{index-C1X-TE5I.d.ts.map → index-DhIoploR.d.ts.map} +1 -1
- package/dist/index.d.mts +99 -137
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +99 -137
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -510
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +99 -431
- package/dist/index.mjs.map +1 -1
- package/dist/logitron-logger-BFDSO8Io.d.mts +257 -0
- package/dist/logitron-logger-BFDSO8Io.d.mts.map +1 -0
- package/dist/logitron-logger-UVeN-mQm.d.ts +257 -0
- package/dist/logitron-logger-UVeN-mQm.d.ts.map +1 -0
- package/dist/middleware.d.mts +11 -4
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +11 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +148 -24
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +148 -24
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +481 -6
- package/dist/nest.d.mts.map +1 -1
- package/dist/nest.d.ts +481 -6
- package/dist/nest.d.ts.map +1 -1
- package/dist/nest.js +884 -13
- package/dist/nest.js.map +1 -0
- package/dist/nest.mjs +863 -6
- package/dist/nest.mjs.map +1 -0
- package/dist/{search-CFQRISUv.mjs → search-BFbu_oVn.mjs} +35 -12
- package/dist/search-BFbu_oVn.mjs.map +1 -0
- package/dist/{search-njTI2hdo.js → search-C4V0OMEn.js} +35 -12
- package/dist/search-C4V0OMEn.js.map +1 -0
- package/dist/{search-DujhR94O.d.ts → search-CNh9uVwR.d.ts} +2 -3
- package/dist/search-CNh9uVwR.d.ts.map +1 -0
- package/dist/{search-CQOo-Iq6.d.mts → search-DXANsQ2n.d.mts} +2 -3
- package/dist/search-DXANsQ2n.d.mts.map +1 -0
- package/dist/search.d.mts +3 -3
- package/dist/search.d.ts +3 -3
- package/dist/search.js +2 -2
- package/dist/search.mjs +2 -2
- package/dist/slack.transport-BZzoXDPn.d.mts +112 -0
- package/dist/slack.transport-BZzoXDPn.d.mts.map +1 -0
- package/dist/slack.transport-BfJRouB2.js +654 -0
- package/dist/slack.transport-BfJRouB2.js.map +1 -0
- package/dist/slack.transport-Bzw7sucI.mjs +643 -0
- package/dist/slack.transport-Bzw7sucI.mjs.map +1 -0
- package/dist/slack.transport-CXjQYOes.d.ts +112 -0
- package/dist/slack.transport-CXjQYOes.d.ts.map +1 -0
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/trace.utils-CvXgaiQT.mjs +278 -0
- package/dist/trace.utils-CvXgaiQT.mjs.map +1 -0
- package/dist/trace.utils-DpBOLDtq.js +358 -0
- package/dist/trace.utils-DpBOLDtq.js.map +1 -0
- package/dist/{transport.manager-D6KL3t9V.mjs → transport.manager-BiuCAJd2.mjs} +526 -160
- package/dist/transport.manager-BiuCAJd2.mjs.map +1 -0
- package/dist/{transport.manager-BivyUTtU.js → transport.manager-CXbKKdVE.js} +537 -159
- package/dist/transport.manager-CXbKKdVE.js.map +1 -0
- package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-BMnurPcc.d.mts} +23 -3
- package/dist/transport.types-BMnurPcc.d.mts.map +1 -0
- package/dist/{transport.types-BuY84TwO.d.ts → transport.types-CGoHpWQR.d.ts} +23 -3
- package/dist/transport.types-CGoHpWQR.d.ts.map +1 -0
- package/dist/transports.d.mts +50 -20
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +50 -20
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +9 -5
- package/dist/transports.mjs +8 -6
- package/dist/worker-entry.js +307 -55
- package/dist/worker.d.mts +2 -2
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +1 -1
- package/dist/worker.mjs +1 -1
- package/dist/{worker.transport-CfH6z37Z.js → worker.transport-BdPxu3PN.js} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-BdPxu3PN.js.map} +1 -1
- package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-CdzSZJmw.d.ts} +2 -2
- package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-CdzSZJmw.d.ts.map} +1 -1
- package/dist/{worker.transport-BP8Q28Z2.d.mts → worker.transport-JvGPog3C.d.mts} +2 -2
- package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-JvGPog3C.d.mts.map} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-lPxO2MVL.mjs} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-lPxO2MVL.mjs.map} +1 -1
- package/package.json +27 -14
- package/dist/.tsbuildinfo +0 -1
- package/dist/async-context-CSHWoHMB.js.map +0 -1
- package/dist/async-context-CwBNMm2E.mjs.map +0 -1
- package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +0 -1
- package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +0 -1
- package/dist/cloudwatch.transport-pNBNWJLt.js.map +0 -1
- package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +0 -1
- package/dist/gcp.transport-BJuNRFe_.mjs.map +0 -1
- package/dist/gcp.transport-BOhkVKlI.d.ts.map +0 -1
- package/dist/gcp.transport-Bi6CUqZR.d.mts.map +0 -1
- package/dist/gcp.transport-SHdtc1n0.js.map +0 -1
- package/dist/logitron-logger.module-BdG1Jnpk.d.mts +0 -618
- package/dist/logitron-logger.module-BdG1Jnpk.d.mts.map +0 -1
- package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +0 -1
- package/dist/logitron-logger.module-BsDZR5BH.js.map +0 -1
- package/dist/logitron-logger.module-CbXy2gfW.d.ts +0 -618
- package/dist/logitron-logger.module-CbXy2gfW.d.ts.map +0 -1
- package/dist/search-CFQRISUv.mjs.map +0 -1
- package/dist/search-CQOo-Iq6.d.mts.map +0 -1
- package/dist/search-DujhR94O.d.ts.map +0 -1
- package/dist/search-njTI2hdo.js.map +0 -1
- package/dist/transport.manager-BivyUTtU.js.map +0 -1
- package/dist/transport.manager-D6KL3t9V.mjs.map +0 -1
- package/dist/transport.types-B5vcyv2k.d.mts.map +0 -1
- package/dist/transport.types-BuY84TwO.d.ts.map +0 -1
- package/scripts/postinstall.js +0 -61
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { r as internalWarn } from "./internal-log-Cm8SOWSV.mjs";
|
|
2
2
|
import { i as stringifyForLog } from "./safe-stringify-DJ66tW5I.mjs";
|
|
3
3
|
import { a as httpRequest, i as HttpError, n as DEFAULT_MAX_BUFFER_SIZE, o as isPermanentHttpError, r as DEFAULT_REQUEST_TIMEOUT_MS, t as BatchBuffer } from "./batch-buffer-PGvSgbmd.mjs";
|
|
4
|
+
import { t as mergeCoreFields } from "./entry-fields-BJkuPnIg.mjs";
|
|
4
5
|
import * as fs from "node:fs";
|
|
5
6
|
import * as os from "node:os";
|
|
6
7
|
import * as path$1 from "node:path";
|
|
@@ -197,6 +198,7 @@ function signV4(opts) {
|
|
|
197
198
|
//#endregion
|
|
198
199
|
//#region src/transports/cloudwatch.transport.ts
|
|
199
200
|
const MAX_EVENTS_PER_PUT = 1e4;
|
|
201
|
+
const EMF_MAX_METRICS = 100;
|
|
200
202
|
/** Error types worth retrying even though CloudWatch reports them as HTTP 400. */
|
|
201
203
|
const RETRYABLE_TYPES = new Set([
|
|
202
204
|
"ThrottlingException",
|
|
@@ -232,6 +234,7 @@ var CloudWatchTransport = class {
|
|
|
232
234
|
this.emfNamespace = config.emfNamespace ?? "Logixia";
|
|
233
235
|
this.flushInterval = config.flushIntervalMs ?? 5e3;
|
|
234
236
|
this.emf = config.emf ?? false;
|
|
237
|
+
this.emfMetrics = config.emfMetrics ? new Set(config.emfMetrics) : void 0;
|
|
235
238
|
this.level = config.level;
|
|
236
239
|
this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
237
240
|
this.buffer = new BatchBuffer({
|
|
@@ -272,25 +275,29 @@ var CloudWatchTransport = class {
|
|
|
272
275
|
}
|
|
273
276
|
toEmf(entry) {
|
|
274
277
|
const { level, message, data: payload, timestamp } = entry;
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
return stringifyForLog({
|
|
278
|
-
_aws: {
|
|
279
|
-
Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),
|
|
280
|
-
CloudWatchMetrics: Object.keys(numericFields).length > 0 ? [{
|
|
281
|
-
Namespace: this.emfNamespace,
|
|
282
|
-
Dimensions: [["level"]],
|
|
283
|
-
Metrics: Object.keys(numericFields).map((n) => ({
|
|
284
|
-
Name: n,
|
|
285
|
-
Unit: "None"
|
|
286
|
-
}))
|
|
287
|
-
}] : []
|
|
288
|
-
},
|
|
278
|
+
const fields = mergeCoreFields({
|
|
279
|
+
_aws: void 0,
|
|
289
280
|
level,
|
|
290
|
-
message
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
281
|
+
message
|
|
282
|
+
}, payload);
|
|
283
|
+
const metricNames = [];
|
|
284
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
285
|
+
if (k === "_aws" || k === "level" || k === "message" || typeof v !== "number") continue;
|
|
286
|
+
if (this.emfMetrics && !this.emfMetrics.has(k)) continue;
|
|
287
|
+
if (metricNames.length < EMF_MAX_METRICS) metricNames.push(k);
|
|
288
|
+
}
|
|
289
|
+
fields["_aws"] = {
|
|
290
|
+
Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),
|
|
291
|
+
CloudWatchMetrics: metricNames.length > 0 ? [{
|
|
292
|
+
Namespace: this.emfNamespace,
|
|
293
|
+
Dimensions: [["level"]],
|
|
294
|
+
Metrics: metricNames.map((n) => ({
|
|
295
|
+
Name: n,
|
|
296
|
+
Unit: "None"
|
|
297
|
+
}))
|
|
298
|
+
}] : []
|
|
299
|
+
};
|
|
300
|
+
return stringifyForLog(fields);
|
|
294
301
|
}
|
|
295
302
|
async putLogEvents(events) {
|
|
296
303
|
await this.ensureStream();
|
|
@@ -357,4 +364,4 @@ var CloudWatchTransport = class {
|
|
|
357
364
|
|
|
358
365
|
//#endregion
|
|
359
366
|
export { CloudWatchTransport as t };
|
|
360
|
-
//# sourceMappingURL=cloudwatch.transport-
|
|
367
|
+
//# sourceMappingURL=cloudwatch.transport-DZ6ies7g.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-DZ6ies7g.mjs","names":["out: Record<string, Record<string, string>>","section: Record<string, string> | undefined","path","text: string","headers: Record<string, string>","headers: Record<string, string>","path","metricNames: string[]"],"sources":["../src/transports/aws-credentials.ts","../src/transports/aws-sigv4.ts","../src/transports/cloudwatch.transport.ts"],"sourcesContent":["/**\n * AWS credential resolution without the SDK.\n *\n * Order: explicit config, environment variables, shared credentials file,\n * ECS/container credentials, EC2 instance metadata (IMDSv2).\n * Not supported: SSO, assume-role profiles, credential_process, web identity.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nimport { internalWarn } from '../utils/internal-log';\nimport { type AwsCredentials } from './aws-sigv4';\nimport { httpRequest } from './batch-buffer';\n\nconst REFRESH_MARGIN_MS = 5 * 60 * 1000;\nconst FAILURE_BACKOFF_MS = 30 * 1000;\nconst METADATA_TIMEOUT_MS = 2000;\n\nexport interface AwsCredentialConfig {\n accessKeyId?: string | undefined;\n secretAccessKey?: string | undefined;\n sessionToken?: string | undefined;\n}\n\n/** Parse the sections of an AWS ini file into `{ section: { key: value } }`. */\nexport function parseIni(text: string): Record<string, Record<string, string>> {\n const out: Record<string, Record<string, string>> = {};\n let section: Record<string, string> | undefined;\n for (const raw of text.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (!line || line.startsWith('#') || line.startsWith(';')) continue;\n const header = /^\\[(.+)\\]$/.exec(line);\n if (header) {\n section = out[header[1]!.trim()] = out[header[1]!.trim()] ?? {};\n continue;\n }\n const eq = line.indexOf('=');\n if (eq > 0 && section) section[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();\n }\n return out;\n}\n\nfunction fromEnv(): AwsCredentials | undefined {\n const accessKeyId = process.env['AWS_ACCESS_KEY_ID'];\n const secretAccessKey = process.env['AWS_SECRET_ACCESS_KEY'];\n if (!accessKeyId || !secretAccessKey) return undefined;\n return { accessKeyId, secretAccessKey, sessionToken: process.env['AWS_SESSION_TOKEN'] };\n}\n\nfunction fromSharedFile(): AwsCredentials | undefined {\n const file =\n process.env['AWS_SHARED_CREDENTIALS_FILE'] ?? path.join(os.homedir(), '.aws', 'credentials');\n const profile = process.env['AWS_PROFILE'] ?? 'default';\n let text: string;\n try {\n text = fs.readFileSync(file, 'utf8');\n } catch {\n return undefined;\n }\n const section = parseIni(text)[profile];\n const accessKeyId = section?.['aws_access_key_id'];\n const secretAccessKey = section?.['aws_secret_access_key'];\n if (!accessKeyId || !secretAccessKey) return undefined;\n return { accessKeyId, secretAccessKey, sessionToken: section?.['aws_session_token'] };\n}\n\ninterface CredentialDocument {\n AccessKeyId?: string;\n SecretAccessKey?: string;\n Token?: string;\n Expiration?: string;\n}\n\nfunction fromDocument(raw: string): AwsCredentials | undefined {\n const doc = JSON.parse(raw) as CredentialDocument;\n if (!doc.AccessKeyId || !doc.SecretAccessKey) return undefined;\n const expiresAt = doc.Expiration ? Date.parse(doc.Expiration) : Number.NaN;\n return {\n accessKeyId: doc.AccessKeyId,\n secretAccessKey: doc.SecretAccessKey,\n sessionToken: doc.Token,\n expiresAt: Number.isNaN(expiresAt) ? undefined : expiresAt,\n };\n}\n\nconst CONTAINER_HOSTS = new Set([\n // eslint-disable-next-line sonarjs/no-hardcoded-ip -- fixed ECS credentials endpoint\n '169.254.170.2',\n // eslint-disable-next-line sonarjs/no-hardcoded-ip -- fixed EKS pod identity endpoint\n '169.254.170.23',\n '[fd00:ec2::23]',\n 'localhost',\n '[::1]',\n]);\n\n/** Same rule as the AWS SDKs: never send the container token to an arbitrary http host. */\nfunction isAllowedContainerHost(url: URL): boolean {\n if (url.protocol === 'https:') return true;\n if (url.protocol !== 'http:') return false;\n return CONTAINER_HOSTS.has(url.hostname) || /^127(?:\\.\\d{1,3}){3}$/.test(url.hostname);\n}\n\nasync function fromContainer(): Promise<AwsCredentials | undefined> {\n const relative = process.env['AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'];\n const full = process.env['AWS_CONTAINER_CREDENTIALS_FULL_URI'];\n if (!relative && !full) return undefined;\n const url = new URL(relative ? `http://169.254.170.2${relative}` : (full as string));\n if (!relative && !isAllowedContainerHost(url)) {\n internalWarn(\n `ignoring AWS_CONTAINER_CREDENTIALS_FULL_URI: ${url.host} is not https or a loopback / ECS / EKS address`\n );\n return undefined;\n }\n const headers: Record<string, string> = {};\n const tokenFile = process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE'];\n const token = tokenFile\n ? fs.readFileSync(tokenFile, 'utf8').trim()\n : process.env['AWS_CONTAINER_AUTHORIZATION_TOKEN'];\n if (token) headers['Authorization'] = token;\n const res = await httpRequest({\n url,\n method: 'GET',\n headers,\n timeoutMs: METADATA_TIMEOUT_MS,\n label: 'AWS container credentials',\n });\n return fromDocument(res.body);\n}\n\nasync function fromInstanceMetadata(): Promise<AwsCredentials | undefined> {\n if (process.env['AWS_EC2_METADATA_DISABLED'] === 'true') return undefined;\n const base = process.env['AWS_EC2_METADATA_SERVICE_ENDPOINT'] ?? 'http://169.254.169.254';\n const get = (p: string, method: string, headers: Record<string, string>) =>\n httpRequest({\n url: new URL(p, base),\n method,\n headers,\n timeoutMs: METADATA_TIMEOUT_MS,\n label: 'EC2 instance metadata',\n });\n const session = await get('/latest/api/token', 'PUT', {\n 'X-aws-ec2-metadata-token-ttl-seconds': '21600',\n });\n const auth = { 'X-aws-ec2-metadata-token': session.body.trim() };\n const roles = await get('/latest/meta-data/iam/security-credentials/', 'GET', auth);\n const role = roles.body.split('\\n')[0]?.trim();\n if (!role) return undefined;\n const creds = await get(\n `/latest/meta-data/iam/security-credentials/${encodeURIComponent(role)}`,\n 'GET',\n auth\n );\n return fromDocument(creds.body);\n}\n\nexport class AwsCredentialProvider {\n private cached: AwsCredentials | undefined;\n private failedAt = 0;\n private readonly explicit: AwsCredentials | undefined;\n\n constructor(config: AwsCredentialConfig) {\n if (config.accessKeyId && config.secretAccessKey) {\n this.explicit = {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n sessionToken: config.sessionToken,\n };\n }\n }\n\n /** Throws when no source yields credentials, so the caller keeps its buffer. */\n async get(): Promise<AwsCredentials> {\n if (this.explicit) return this.explicit;\n const env = fromEnv();\n if (env) return env;\n if (this.cached && !this.isExpiring(this.cached)) return this.cached;\n if (Date.now() - this.failedAt < FAILURE_BACKOFF_MS) {\n throw new Error('no AWS credentials found (retrying shortly)');\n }\n\n let found = fromSharedFile();\n for (const source of [fromContainer, fromInstanceMetadata]) {\n if (found) break;\n try {\n found = await source();\n } catch {\n /* source not available here; try the next */\n }\n }\n if (!found) {\n this.failedAt = Date.now();\n throw new Error(\n 'no AWS credentials found: set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, a shared credentials file, container credentials, or run on EC2 with an instance role'\n );\n }\n this.cached = found;\n return found;\n }\n\n invalidate(): void {\n this.cached = undefined;\n this.failedAt = 0;\n }\n\n private isExpiring(c: AwsCredentials): boolean {\n return c.expiresAt !== undefined && c.expiresAt - Date.now() < REFRESH_MARGIN_MS;\n }\n}\n","/**\n * AWS Signature Version 4 request signing, dependency-free.\n */\n\nimport * as crypto from 'node:crypto';\n\nexport interface AwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string | undefined;\n /** Epoch ms after which the credentials are no longer valid. */\n expiresAt?: number | undefined;\n}\n\nexport interface SignV4Options {\n method: string;\n path: string;\n query?: string;\n /** Must include `host`. Names are lowercased before signing. */\n headers: Record<string, string>;\n body: string;\n region: string;\n service: string;\n credentials: AwsCredentials;\n date?: Date;\n}\n\nconst sha256Hex = (data: string): string => crypto.createHash('sha256').update(data).digest('hex');\n\nconst hmac = (key: Buffer | string, msg: string): Buffer =>\n crypto.createHmac('sha256', key).update(msg).digest();\n\n/** Returns the request headers (lowercase names) with `x-amz-date` and `authorization` added. */\nexport function signV4(opts: SignV4Options): Record<string, string> {\n const {\n method,\n path,\n query = '',\n body,\n region,\n service,\n credentials,\n headers: input,\n date,\n } = opts;\n const amzDate = (date ?? new Date()).toISOString().replace(/[:-]|\\.\\d{3}/g, '');\n const dateStamp = amzDate.slice(0, 8);\n\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(input)) headers[k.toLowerCase()] = v;\n headers['x-amz-date'] = amzDate;\n if (credentials.sessionToken) headers['x-amz-security-token'] = credentials.sessionToken;\n\n const names = Object.keys(headers).sort();\n const canonicalHeaders = names\n .map((n) => `${n}:${headers[n]!.trim().replace(/\\s+/g, ' ')}\\n`)\n .join('');\n const signedHeaders = names.join(';');\n\n const canonicalRequest = [\n method,\n path,\n query,\n canonicalHeaders,\n signedHeaders,\n sha256Hex(body),\n ].join('\\n');\n const scope = `${dateStamp}/${region}/${service}/aws4_request`;\n const stringToSign = ['AWS4-HMAC-SHA256', amzDate, scope, sha256Hex(canonicalRequest)].join('\\n');\n\n const signingKey = hmac(\n hmac(hmac(hmac(`AWS4${credentials.secretAccessKey}`, dateStamp), region), service),\n 'aws4_request'\n );\n const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex');\n\n headers['authorization'] =\n `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${scope}, ` +\n `SignedHeaders=${signedHeaders}, Signature=${signature}`;\n return headers;\n}\n","/**\n * logixia — AWS CloudWatch Logs transport\n *\n * Sends log entries to AWS CloudWatch Logs via the PutLogEvents API.\n * Supports EMF (Embedded Metric Format) for automatic metric extraction.\n *\n * Auth, in order: explicit config, environment variables, the shared\n * credentials file (AWS_PROFILE, AWS_SHARED_CREDENTIALS_FILE), ECS container\n * credentials, then EC2 instance metadata (IMDSv2). SSO, assume-role profiles\n * and credential_process are not supported. No SDK dependency — raw HTTPS with\n * AWS Signature Version 4.\n *\n * The log group and stream are created on first use if they do not exist.\n *\n * @example\n * ```ts\n * import { CloudWatchTransport } from 'logixia';\n *\n * const logger = createLogger({\n * transports: {\n * cloudwatch: new CloudWatchTransport({\n * region: 'us-east-1',\n * logGroupName: '/myapp/production',\n * logStreamName: `api-${process.env.HOSTNAME}`,\n * batchSize: 100,\n * flushIntervalMs: 5000,\n * }),\n * },\n * });\n * ```\n */\n\nimport type { IAsyncTransport, TransportLogEntry } from '../types/transport.types';\nimport { internalWarn } from '../utils/internal-log';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport { AwsCredentialProvider } from './aws-credentials';\nimport { signV4 } from './aws-sigv4';\nimport {\n BatchBuffer,\n DEFAULT_MAX_BUFFER_SIZE,\n DEFAULT_REQUEST_TIMEOUT_MS,\n HttpError,\n httpRequest,\n isPermanentHttpError,\n} from './batch-buffer';\nimport { mergeCoreFields } from './entry-fields';\n\nexport interface CloudWatchTransportConfig {\n /** AWS region, e.g. `'us-east-1'`. Falls back to `AWS_REGION` env var. */\n region?: string;\n /** CloudWatch Logs group name. Created automatically if it does not exist. */\n logGroupName: string;\n /** CloudWatch Logs stream name. Defaults to hostname + process PID. */\n logStreamName?: string;\n /** Override the Logs endpoint, e.g. for LocalStack. Default: `https://logs.<region>.amazonaws.com`. */\n endpoint?: string;\n /** AWS access key ID. Falls back to the credential chain described above. */\n accessKeyId?: string;\n /** AWS secret access key. Used together with `accessKeyId`. */\n secretAccessKey?: string;\n /** AWS session token for temporary credentials. Used together with `accessKeyId`. */\n sessionToken?: string;\n /**\n * Number of log events to batch before flushing.\n * CloudWatch allows max 10,000 events per PutLogEvents call; larger values are capped.\n * @default 100\n */\n batchSize?: number;\n /**\n * Max ms between flushes even if batchSize is not reached.\n * @default 5000\n */\n flushIntervalMs?: number;\n /**\n * Enable EMF (Embedded Metric Format) — wraps log entries as CloudWatch\n * metric namespaces so numeric fields become CloudWatch Metrics automatically.\n * @default false\n */\n emf?: boolean;\n /** CloudWatch Metrics namespace used by EMF. @default 'Logixia' */\n emfNamespace?: string;\n /**\n * Payload fields published as EMF metrics. When set, only these numeric\n * fields become metrics; when unset every numeric payload field does (up to\n * the EMF limit of 100 per entry).\n */\n emfMetrics?: string[];\n /** Abort a request that has not finished after this many ms. @default 10000 */\n requestTimeoutMs?: number;\n /** Max buffered events; the oldest are dropped past this. @default 10000 */\n maxBufferSize?: number;\n /** Minimum log level to forward. */\n level?: string;\n}\n\nconst MAX_EVENTS_PER_PUT = 10_000;\nconst EMF_MAX_METRICS = 100;\n\n/** Error types worth retrying even though CloudWatch reports them as HTTP 400. */\nconst RETRYABLE_TYPES = new Set([\n 'ThrottlingException',\n 'ThrottledException',\n 'LimitExceededException',\n 'ServiceUnavailableException',\n 'ExpiredTokenException',\n 'ResourceNotFoundException',\n]);\n\nfunction awsErrorType(err: unknown): string | undefined {\n if (!(err instanceof HttpError)) return undefined;\n try {\n const type = (JSON.parse(err.body) as { __type?: string }).__type;\n return type?.split('#').pop();\n } catch {\n return undefined;\n }\n}\n\ninterface CwLogEvent {\n timestamp: number;\n message: string;\n}\n\n/**\n * AWS CloudWatch Logs transport with EMF support and batched PutLogEvents calls.\n */\nexport class CloudWatchTransport implements IAsyncTransport {\n public readonly name = 'cloudwatch';\n public readonly batchSize: number;\n public readonly flushInterval: number;\n public readonly level: string | undefined;\n\n private readonly buffer: BatchBuffer<CwLogEvent>;\n private readonly requestTimeoutMs: number;\n private flushTimer: NodeJS.Timeout | null = null;\n private readonly credentials: AwsCredentialProvider;\n private readonly endpoint: URL;\n private streamReady = false;\n\n private readonly region: string;\n private readonly logGroupName: string;\n private readonly logStreamName: string;\n private readonly emf: boolean;\n private readonly emfNamespace: string;\n private readonly emfMetrics: ReadonlySet<string> | undefined;\n\n constructor(config: CloudWatchTransportConfig) {\n this.region =\n config.region ??\n process.env['AWS_REGION'] ??\n process.env['AWS_DEFAULT_REGION'] ??\n 'us-east-1';\n this.logGroupName = config.logGroupName;\n this.logStreamName =\n config.logStreamName ?? `${process.env['HOSTNAME'] ?? 'node'}-${process.pid}`;\n this.credentials = new AwsCredentialProvider(config);\n this.endpoint = new URL(config.endpoint ?? `https://logs.${this.region}.amazonaws.com`);\n this.batchSize = Math.min(config.batchSize ?? 100, MAX_EVENTS_PER_PUT);\n this.emfNamespace = config.emfNamespace ?? 'Logixia';\n this.flushInterval = config.flushIntervalMs ?? 5000;\n this.emf = config.emf ?? false;\n this.emfMetrics = config.emfMetrics ? new Set(config.emfMetrics) : undefined;\n this.level = config.level;\n this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n this.buffer = new BatchBuffer<CwLogEvent>({\n name: 'CloudWatchTransport',\n batchSize: this.batchSize,\n maxBufferSize: config.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE,\n send: (events) => this.putLogEvents(events),\n shouldRetry: (err) =>\n !isPermanentHttpError(err) || RETRYABLE_TYPES.has(awsErrorType(err) ?? ''),\n });\n\n this.flushTimer = setInterval(() => {\n this.flush().catch(() => {});\n }, this.flushInterval);\n if (this.flushTimer.unref) this.flushTimer.unref();\n }\n\n write(entry: TransportLogEntry): void {\n const timestamp = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();\n const message = this.emf ? this.toEmf(entry) : stringifyForLog(entry);\n this.buffer.push({ timestamp, message });\n this.buffer.flushIfFull();\n }\n\n flush(): Promise<void> {\n return this.buffer.flush();\n }\n\n /**\n * Flush remaining events and stop the interval timer on shutdown. Without this\n * the manager's close() skips the transport (close was undefined) and any\n * buffered events were lost on deploy. Waits for any in-flight send first.\n */\n async close(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n await this.buffer.close();\n }\n\n private toEmf(entry: TransportLogEntry): string {\n const { level, message, data: payload, timestamp } = entry;\n const fields = mergeCoreFields({ _aws: undefined, level, message }, payload);\n const metricNames: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (k === '_aws' || k === 'level' || k === 'message' || typeof v !== 'number') continue;\n if (this.emfMetrics && !this.emfMetrics.has(k)) continue;\n if (metricNames.length < EMF_MAX_METRICS) metricNames.push(k);\n }\n fields['_aws'] = {\n Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),\n CloudWatchMetrics:\n metricNames.length > 0\n ? [\n {\n Namespace: this.emfNamespace,\n Dimensions: [['level']],\n Metrics: metricNames.map((n) => ({ Name: n, Unit: 'None' })),\n },\n ]\n : [],\n };\n return stringifyForLog(fields);\n }\n\n private async putLogEvents(events: CwLogEvent[]): Promise<void> {\n await this.ensureStream();\n const logEvents = [...events].sort((a, b) => a.timestamp - b.timestamp);\n try {\n await this.call('PutLogEvents', {\n logGroupName: this.logGroupName,\n logStreamName: this.logStreamName,\n logEvents,\n });\n } catch (err) {\n const type = awsErrorType(err);\n if (type === 'DataAlreadyAcceptedException') return;\n if (type === 'ResourceNotFoundException') this.streamReady = false;\n if (type === 'ExpiredTokenException') this.credentials.invalidate();\n throw err;\n }\n }\n\n private async ensureStream(): Promise<void> {\n if (this.streamReady) return;\n try {\n await this.call('CreateLogGroup', { logGroupName: this.logGroupName });\n } catch (err) {\n if (awsErrorType(err) === 'ExpiredTokenException') this.credentials.invalidate();\n if (!(err instanceof HttpError)) throw err;\n if (awsErrorType(err) !== 'ResourceAlreadyExistsException') {\n internalWarn(`CloudWatchTransport: CreateLogGroup failed, continuing (${err.message})`);\n }\n }\n try {\n await this.call('CreateLogStream', {\n logGroupName: this.logGroupName,\n logStreamName: this.logStreamName,\n });\n } catch (err) {\n if (awsErrorType(err) !== 'ResourceAlreadyExistsException') throw err;\n }\n this.streamReady = true;\n }\n\n private async call(action: string, payload: Record<string, unknown>): Promise<void> {\n const credentials = await this.credentials.get();\n const body = stringifyForLog(payload);\n const { host } = this.endpoint;\n const headers = signV4({\n method: 'POST',\n path: '/',\n headers: {\n host,\n 'content-type': 'application/x-amz-json-1.1',\n 'x-amz-target': `Logs_20140328.${action}`,\n },\n body,\n region: this.region,\n service: 'logs',\n credentials,\n });\n await httpRequest({\n url: this.endpoint,\n headers,\n body,\n timeoutMs: this.requestTimeoutMs,\n label: `CloudWatch ${action}`,\n });\n }\n}\n"],"mappings":";;;;;;;;;;AAgBA,MAAM,oBAAoB,MAAS;AACnC,MAAM,qBAAqB,KAAK;AAChC,MAAM,sBAAsB;;AAS5B,SAAgB,SAAS,MAAsD;CAC7E,MAAMA,MAA8C,EAAE;CACtD,IAAIC;AACJ,MAAK,MAAM,OAAO,KAAK,MAAM,QAAQ,EAAE;EACrC,MAAM,OAAO,IAAI,MAAM;AACvB,MAAI,CAAC,QAAQ,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,CAAE;EAC3D,MAAM,SAAS,aAAa,KAAK,KAAK;AACtC,MAAI,QAAQ;AACV,aAAU,IAAI,OAAO,GAAI,MAAM,IAAI,IAAI,OAAO,GAAI,MAAM,KAAK,EAAE;AAC/D;;EAEF,MAAM,KAAK,KAAK,QAAQ,IAAI;AAC5B,MAAI,KAAK,KAAK,QAAS,SAAQ,KAAK,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,MAAM,KAAK,EAAE,CAAC,MAAM;;AAEtF,QAAO;;AAGT,SAAS,UAAsC;CAC7C,MAAM,cAAc,QAAQ,IAAI;CAChC,MAAM,kBAAkB,QAAQ,IAAI;AACpC,KAAI,CAAC,eAAe,CAAC,gBAAiB,QAAO;AAC7C,QAAO;EAAE;EAAa;EAAiB,cAAc,QAAQ,IAAI;EAAsB;;AAGzF,SAAS,iBAA6C;CACpD,MAAM,OACJ,QAAQ,IAAI,kCAAkCC,OAAK,KAAK,GAAG,SAAS,EAAE,QAAQ,cAAc;CAC9F,MAAM,UAAU,QAAQ,IAAI,kBAAkB;CAC9C,IAAIC;AACJ,KAAI;AACF,SAAO,GAAG,aAAa,MAAM,OAAO;SAC9B;AACN;;CAEF,MAAM,UAAU,SAAS,KAAK,CAAC;CAC/B,MAAM,gEAAc,QAAU;CAC9B,MAAM,oEAAkB,QAAU;AAClC,KAAI,CAAC,eAAe,CAAC,gBAAiB,QAAO;AAC7C,QAAO;EAAE;EAAa;EAAiB,gEAAc,QAAU;EAAsB;;AAUvF,SAAS,aAAa,KAAyC;CAC7D,MAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,KAAI,CAAC,IAAI,eAAe,CAAC,IAAI,gBAAiB,QAAO;CACrD,MAAM,YAAY,IAAI,aAAa,KAAK,MAAM,IAAI,WAAW,GAAG;AAChE,QAAO;EACL,aAAa,IAAI;EACjB,iBAAiB,IAAI;EACrB,cAAc,IAAI;EAClB,WAAW,OAAO,MAAM,UAAU,GAAG,SAAY;EAClD;;AAGH,MAAM,kBAAkB,IAAI,IAAI;CAE9B;CAEA;CACA;CACA;CACA;CACD,CAAC;;AAGF,SAAS,uBAAuB,KAAmB;AACjD,KAAI,IAAI,aAAa,SAAU,QAAO;AACtC,KAAI,IAAI,aAAa,QAAS,QAAO;AACrC,QAAO,gBAAgB,IAAI,IAAI,SAAS,IAAI,wBAAwB,KAAK,IAAI,SAAS;;AAGxF,eAAe,gBAAqD;CAClE,MAAM,WAAW,QAAQ,IAAI;CAC7B,MAAM,OAAO,QAAQ,IAAI;AACzB,KAAI,CAAC,YAAY,CAAC,KAAM,QAAO;CAC/B,MAAM,MAAM,IAAI,IAAI,WAAW,uBAAuB,aAAc,KAAgB;AACpF,KAAI,CAAC,YAAY,CAAC,uBAAuB,IAAI,EAAE;AAC7C,eACE,gDAAgD,IAAI,KAAK,iDAC1D;AACD;;CAEF,MAAMC,UAAkC,EAAE;CAC1C,MAAM,YAAY,QAAQ,IAAI;CAC9B,MAAM,QAAQ,YACV,GAAG,aAAa,WAAW,OAAO,CAAC,MAAM,GACzC,QAAQ,IAAI;AAChB,KAAI,MAAO,SAAQ,mBAAmB;AAQtC,QAAO,cAPK,MAAM,YAAY;EAC5B;EACA,QAAQ;EACR;EACA,WAAW;EACX,OAAO;EACR,CAAC,EACsB,KAAK;;AAG/B,eAAe,uBAA4D;;AACzE,KAAI,QAAQ,IAAI,iCAAiC,OAAQ,QAAO;CAChE,MAAM,OAAO,QAAQ,IAAI,wCAAwC;CACjE,MAAM,OAAO,GAAW,QAAgB,YACtC,YAAY;EACV,KAAK,IAAI,IAAI,GAAG,KAAK;EACrB;EACA;EACA,WAAW;EACX,OAAO;EACR,CAAC;CACJ,MAAM,UAAU,MAAM,IAAI,qBAAqB,OAAO,EACpD,wCAAwC,SACzC,CAAC;CACF,MAAM,OAAO,EAAE,4BAA4B,QAAQ,KAAK,MAAM,EAAE;CAEhE,MAAM,8BADQ,MAAM,IAAI,+CAA+C,OAAO,KAAK,EAChE,KAAK,MAAM,KAAK,CAAC,4EAAI,MAAM;AAC9C,KAAI,CAAC,KAAM,QAAO;AAMlB,QAAO,cALO,MAAM,IAClB,8CAA8C,mBAAmB,KAAK,IACtE,OACA,KACD,EACyB,KAAK;;AAGjC,IAAa,wBAAb,MAAmC;CAKjC,YAAY,QAA6B;kBAHtB;AAIjB,MAAI,OAAO,eAAe,OAAO,gBAC/B,MAAK,WAAW;GACd,aAAa,OAAO;GACpB,iBAAiB,OAAO;GACxB,cAAc,OAAO;GACtB;;;CAKL,MAAM,MAA+B;AACnC,MAAI,KAAK,SAAU,QAAO,KAAK;EAC/B,MAAM,MAAM,SAAS;AACrB,MAAI,IAAK,QAAO;AAChB,MAAI,KAAK,UAAU,CAAC,KAAK,WAAW,KAAK,OAAO,CAAE,QAAO,KAAK;AAC9D,MAAI,KAAK,KAAK,GAAG,KAAK,WAAW,mBAC/B,OAAM,IAAI,MAAM,8CAA8C;EAGhE,IAAI,QAAQ,gBAAgB;AAC5B,OAAK,MAAM,UAAU,CAAC,eAAe,qBAAqB,EAAE;AAC1D,OAAI,MAAO;AACX,OAAI;AACF,YAAQ,MAAM,QAAQ;WAChB;;AAIV,MAAI,CAAC,OAAO;AACV,QAAK,WAAW,KAAK,KAAK;AAC1B,SAAM,IAAI,MACR,+JACD;;AAEH,OAAK,SAAS;AACd,SAAO;;CAGT,aAAmB;AACjB,OAAK,SAAS;AACd,OAAK,WAAW;;CAGlB,AAAQ,WAAW,GAA4B;AAC7C,SAAO,EAAE,cAAc,UAAa,EAAE,YAAY,KAAK,KAAK,GAAG;;;;;;ACpLnE,MAAM,aAAa,SAAyB,OAAO,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;AAElG,MAAM,QAAQ,KAAsB,QAClC,OAAO,WAAW,UAAU,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ;;AAGvD,SAAgB,OAAO,MAA6C;CAClE,MAAM,EACJ,QACA,cACA,QAAQ,IACR,MACA,QACA,SACA,aACA,SAAS,OACT,SACE;CACJ,MAAM,WAAW,wBAAQ,IAAI,MAAM,EAAE,aAAa,CAAC,QAAQ,iBAAiB,GAAG;CAC/E,MAAM,YAAY,QAAQ,MAAM,GAAG,EAAE;CAErC,MAAMC,UAAkC,EAAE;AAC1C,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CAAE,SAAQ,EAAE,aAAa,IAAI;AACvE,SAAQ,gBAAgB;AACxB,KAAI,YAAY,aAAc,SAAQ,0BAA0B,YAAY;CAE5E,MAAM,QAAQ,OAAO,KAAK,QAAQ,CAAC,MAAM;CACzC,MAAM,mBAAmB,MACtB,KAAK,MAAM,GAAG,EAAE,GAAG,QAAQ,GAAI,MAAM,CAAC,QAAQ,QAAQ,IAAI,CAAC,IAAI,CAC/D,KAAK,GAAG;CACX,MAAM,gBAAgB,MAAM,KAAK,IAAI;CAErC,MAAM,mBAAmB;EACvB;EACAC;EACA;EACA;EACA;EACA,UAAU,KAAK;EAChB,CAAC,KAAK,KAAK;CACZ,MAAM,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ;CAChD,MAAM,eAAe;EAAC;EAAoB;EAAS;EAAO,UAAU,iBAAiB;EAAC,CAAC,KAAK,KAAK;CAEjG,MAAM,aAAa,KACjB,KAAK,KAAK,KAAK,OAAO,YAAY,mBAAmB,UAAU,EAAE,OAAO,EAAE,QAAQ,EAClF,eACD;CACD,MAAM,YAAY,OAAO,WAAW,UAAU,WAAW,CAAC,OAAO,aAAa,CAAC,OAAO,MAAM;AAE5F,SAAQ,mBACN,+BAA+B,YAAY,YAAY,GAAG,MAAM,kBAC/C,cAAc,cAAc;AAC/C,QAAO;;;;;ACgBT,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB;;AAGxB,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,aAAa,KAAkC;AACtD,KAAI,EAAE,eAAe,WAAY,QAAO;AACxC,KAAI;EACF,MAAM,OAAQ,KAAK,MAAM,IAAI,KAAK,CAAyB;AAC3D,qDAAO,KAAM,MAAM,IAAI,CAAC,KAAK;SACvB;AACN;;;;;;AAYJ,IAAa,sBAAb,MAA4D;CAoB1D,YAAY,QAAmC;cAnBxB;oBAOqB;qBAGtB;AAUpB,OAAK,SACH,OAAO,UACP,QAAQ,IAAI,iBACZ,QAAQ,IAAI,yBACZ;AACF,OAAK,eAAe,OAAO;AAC3B,OAAK,gBACH,OAAO,iBAAiB,GAAG,QAAQ,IAAI,eAAe,OAAO,GAAG,QAAQ;AAC1E,OAAK,cAAc,IAAI,sBAAsB,OAAO;AACpD,OAAK,WAAW,IAAI,IAAI,OAAO,YAAY,gBAAgB,KAAK,OAAO,gBAAgB;AACvF,OAAK,YAAY,KAAK,IAAI,OAAO,aAAa,KAAK,mBAAmB;AACtE,OAAK,eAAe,OAAO,gBAAgB;AAC3C,OAAK,gBAAgB,OAAO,mBAAmB;AAC/C,OAAK,MAAM,OAAO,OAAO;AACzB,OAAK,aAAa,OAAO,aAAa,IAAI,IAAI,OAAO,WAAW,GAAG;AACnE,OAAK,QAAQ,OAAO;AACpB,OAAK,mBAAmB,OAAO,oBAAoB;AACnD,OAAK,SAAS,IAAI,YAAwB;GACxC,MAAM;GACN,WAAW,KAAK;GAChB,eAAe,OAAO,iBAAiB;GACvC,OAAO,WAAW,KAAK,aAAa,OAAO;GAC3C,cAAc,QACZ,CAAC,qBAAqB,IAAI,IAAI,gBAAgB,IAAI,aAAa,IAAI,IAAI,GAAG;GAC7E,CAAC;AAEF,OAAK,aAAa,kBAAkB;AAClC,QAAK,OAAO,CAAC,YAAY,GAAG;KAC3B,KAAK,cAAc;AACtB,MAAI,KAAK,WAAW,MAAO,MAAK,WAAW,OAAO;;CAGpD,MAAM,OAAgC;EACpC,MAAM,YAAY,MAAM,YAAY,IAAI,KAAK,MAAM,UAAU,CAAC,SAAS,GAAG,KAAK,KAAK;EACpF,MAAM,UAAU,KAAK,MAAM,KAAK,MAAM,MAAM,GAAG,gBAAgB,MAAM;AACrE,OAAK,OAAO,KAAK;GAAE;GAAW;GAAS,CAAC;AACxC,OAAK,OAAO,aAAa;;CAG3B,QAAuB;AACrB,SAAO,KAAK,OAAO,OAAO;;;;;;;CAQ5B,MAAM,QAAuB;AAC3B,MAAI,KAAK,YAAY;AACnB,iBAAc,KAAK,WAAW;AAC9B,QAAK,aAAa;;AAEpB,QAAM,KAAK,OAAO,OAAO;;CAG3B,AAAQ,MAAM,OAAkC;EAC9C,MAAM,EAAE,OAAO,SAAS,MAAM,SAAS,cAAc;EACrD,MAAM,SAAS,gBAAgB;GAAE,MAAM;GAAW;GAAO;GAAS,EAAE,QAAQ;EAC5E,MAAMC,cAAwB,EAAE;AAChC,OAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,OAAO,EAAE;AAC3C,OAAI,MAAM,UAAU,MAAM,WAAW,MAAM,aAAa,OAAO,MAAM,SAAU;AAC/E,OAAI,KAAK,cAAc,CAAC,KAAK,WAAW,IAAI,EAAE,CAAE;AAChD,OAAI,YAAY,SAAS,gBAAiB,aAAY,KAAK,EAAE;;AAE/D,SAAO,UAAU;GACf,WAAW,YAAY,IAAI,KAAK,UAAU,CAAC,SAAS,GAAG,KAAK,KAAK;GACjE,mBACE,YAAY,SAAS,IACjB,CACE;IACE,WAAW,KAAK;IAChB,YAAY,CAAC,CAAC,QAAQ,CAAC;IACvB,SAAS,YAAY,KAAK,OAAO;KAAE,MAAM;KAAG,MAAM;KAAQ,EAAE;IAC7D,CACF,GACD,EAAE;GACT;AACD,SAAO,gBAAgB,OAAO;;CAGhC,MAAc,aAAa,QAAqC;AAC9D,QAAM,KAAK,cAAc;EACzB,MAAM,YAAY,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU;AACvE,MAAI;AACF,SAAM,KAAK,KAAK,gBAAgB;IAC9B,cAAc,KAAK;IACnB,eAAe,KAAK;IACpB;IACD,CAAC;WACK,KAAK;GACZ,MAAM,OAAO,aAAa,IAAI;AAC9B,OAAI,SAAS,+BAAgC;AAC7C,OAAI,SAAS,4BAA6B,MAAK,cAAc;AAC7D,OAAI,SAAS,wBAAyB,MAAK,YAAY,YAAY;AACnE,SAAM;;;CAIV,MAAc,eAA8B;AAC1C,MAAI,KAAK,YAAa;AACtB,MAAI;AACF,SAAM,KAAK,KAAK,kBAAkB,EAAE,cAAc,KAAK,cAAc,CAAC;WAC/D,KAAK;AACZ,OAAI,aAAa,IAAI,KAAK,wBAAyB,MAAK,YAAY,YAAY;AAChF,OAAI,EAAE,eAAe,WAAY,OAAM;AACvC,OAAI,aAAa,IAAI,KAAK,iCACxB,cAAa,2DAA2D,IAAI,QAAQ,GAAG;;AAG3F,MAAI;AACF,SAAM,KAAK,KAAK,mBAAmB;IACjC,cAAc,KAAK;IACnB,eAAe,KAAK;IACrB,CAAC;WACK,KAAK;AACZ,OAAI,aAAa,IAAI,KAAK,iCAAkC,OAAM;;AAEpE,OAAK,cAAc;;CAGrB,MAAc,KAAK,QAAgB,SAAiD;EAClF,MAAM,cAAc,MAAM,KAAK,YAAY,KAAK;EAChD,MAAM,OAAO,gBAAgB,QAAQ;EACrC,MAAM,EAAE,SAAS,KAAK;EACtB,MAAM,UAAU,OAAO;GACrB,QAAQ;GACR,MAAM;GACN,SAAS;IACP;IACA,gBAAgB;IAChB,gBAAgB,iBAAiB;IAClC;GACD;GACA,QAAQ,KAAK;GACb,SAAS;GACT;GACD,CAAC;AACF,QAAM,YAAY;GAChB,KAAK,KAAK;GACV;GACA;GACA,WAAW,KAAK;GAChB,OAAO,cAAc;GACtB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-
|
|
1
|
+
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-CGoHpWQR.js";
|
|
2
2
|
|
|
3
3
|
//#region src/transports/cloudwatch.transport.d.ts
|
|
4
4
|
|
|
@@ -36,6 +36,12 @@ interface CloudWatchTransportConfig {
|
|
|
36
36
|
emf?: boolean;
|
|
37
37
|
/** CloudWatch Metrics namespace used by EMF. @default 'Logixia' */
|
|
38
38
|
emfNamespace?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Payload fields published as EMF metrics. When set, only these numeric
|
|
41
|
+
* fields become metrics; when unset every numeric payload field does (up to
|
|
42
|
+
* the EMF limit of 100 per entry).
|
|
43
|
+
*/
|
|
44
|
+
emfMetrics?: string[];
|
|
39
45
|
/** Abort a request that has not finished after this many ms. @default 10000 */
|
|
40
46
|
requestTimeoutMs?: number;
|
|
41
47
|
/** Max buffered events; the oldest are dropped past this. @default 10000 */
|
|
@@ -62,6 +68,7 @@ declare class CloudWatchTransport implements IAsyncTransport {
|
|
|
62
68
|
private readonly logStreamName;
|
|
63
69
|
private readonly emf;
|
|
64
70
|
private readonly emfNamespace;
|
|
71
|
+
private readonly emfMetrics;
|
|
65
72
|
constructor(config: CloudWatchTransportConfig);
|
|
66
73
|
write(entry: TransportLogEntry): void;
|
|
67
74
|
flush(): Promise<void>;
|
|
@@ -78,4 +85,4 @@ declare class CloudWatchTransport implements IAsyncTransport {
|
|
|
78
85
|
}
|
|
79
86
|
//#endregion
|
|
80
87
|
export { CloudWatchTransportConfig as n, CloudWatchTransport as t };
|
|
81
|
-
//# sourceMappingURL=cloudwatch.transport-
|
|
88
|
+
//# sourceMappingURL=cloudwatch.transport-DsEq633o.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-DsEq633o.d.ts","names":[],"sources":["../src/transports/cloudwatch.transport.ts"],"sourcesContent":[],"mappings":";;;;UA+CiB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+EJ,mBAAA,YAA+B;;;;;;;;;;;;;;;;;sBAoBtB;eAiCP;WAOJ;;;;;;WASM"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-
|
|
1
|
+
import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-BMnurPcc.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/transports/cloudwatch.transport.d.ts
|
|
4
4
|
|
|
@@ -36,6 +36,12 @@ interface CloudWatchTransportConfig {
|
|
|
36
36
|
emf?: boolean;
|
|
37
37
|
/** CloudWatch Metrics namespace used by EMF. @default 'Logixia' */
|
|
38
38
|
emfNamespace?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Payload fields published as EMF metrics. When set, only these numeric
|
|
41
|
+
* fields become metrics; when unset every numeric payload field does (up to
|
|
42
|
+
* the EMF limit of 100 per entry).
|
|
43
|
+
*/
|
|
44
|
+
emfMetrics?: string[];
|
|
39
45
|
/** Abort a request that has not finished after this many ms. @default 10000 */
|
|
40
46
|
requestTimeoutMs?: number;
|
|
41
47
|
/** Max buffered events; the oldest are dropped past this. @default 10000 */
|
|
@@ -62,6 +68,7 @@ declare class CloudWatchTransport implements IAsyncTransport {
|
|
|
62
68
|
private readonly logStreamName;
|
|
63
69
|
private readonly emf;
|
|
64
70
|
private readonly emfNamespace;
|
|
71
|
+
private readonly emfMetrics;
|
|
65
72
|
constructor(config: CloudWatchTransportConfig);
|
|
66
73
|
write(entry: TransportLogEntry): void;
|
|
67
74
|
flush(): Promise<void>;
|
|
@@ -78,4 +85,4 @@ declare class CloudWatchTransport implements IAsyncTransport {
|
|
|
78
85
|
}
|
|
79
86
|
//#endregion
|
|
80
87
|
export { CloudWatchTransportConfig as n, CloudWatchTransport as t };
|
|
81
|
-
//# sourceMappingURL=cloudwatch.transport-
|
|
88
|
+
//# sourceMappingURL=cloudwatch.transport-UlyZFWMn.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-UlyZFWMn.d.mts","names":[],"sources":["../src/transports/cloudwatch.transport.ts"],"sourcesContent":[],"mappings":";;;;UA+CiB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+EJ,mBAAA,YAA+B;;;;;;;;;;;;;;;;;sBAoBtB;eAiCP;WAOJ;;;;;;WASM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coerce.utils-
|
|
1
|
+
{"version":3,"file":"coerce.utils-B-LilukN.js","names":[],"sources":["../src/utils/coerce.utils.ts"],"sourcesContent":["/**\n * Defensive value coercion helpers for Logixia.\n *\n * Logger inputs come from third-party code (NestJS framework internals,\n * user payloads, error objects) where the runtime type is not always what\n * the TypeScript signature promises. The formatter / transport hot paths\n * call `.replace()` on string fields and crash if the field is anything\n * else. Use these helpers anywhere a downstream consumer assumes a string.\n */\n\n/**\n * Coerce any value to a string without throwing.\n *\n * - `undefined` / `null` → `''` (so the field is rendered as empty rather\n * than the literal string \"undefined\")\n * - strings → returned as-is (zero allocation on the hot path)\n * - Errors → `error.message` (matches what users would expect to see)\n * - everything else → `JSON.stringify` if possible, falling back to\n * `String(value)` for circular refs / values without a stringifier.\n */\nexport function safeToString(value: unknown): string {\n if (typeof value === 'string') return value;\n if (value === undefined || value === null) return '';\n if (value instanceof Error) return value.message;\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'symbol') return value.toString();\n if (typeof value === 'function') {\n return `[Function: ${(value as { name?: string }).name || 'anonymous'}]`;\n }\n // value is now narrowed to object — JSON-stringify, falling back to a\n // constructor-name tag for circular refs / throwing toJSON.\n try {\n const json = JSON.stringify(value);\n // JSON.stringify returns undefined for values it can't represent (e.g. an\n // object whose toJSON() returns undefined). Returning that would defeat the\n // whole point of this helper — a downstream .replace() on `undefined` throws.\n if (json !== undefined) return json;\n } catch {\n /* fall through to the constructor-name tag below */\n }\n const ctor = (value as { constructor?: { name?: string } }).constructor?.name;\n return `[${ctor ?? 'object'}]`;\n}\n\n/**\n * String-replace on a value that may not be a string.\n * Coerces non-strings before delegating to `String.prototype.replace`.\n */\nexport function safeReplace(value: unknown, pattern: string | RegExp, replacement: string): string {\n return safeToString(value).replace(pattern, replacement);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aAAa,OAAwB;;AACnD,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,KAAI,iBAAiB,MAAO,QAAO,MAAM;AACzC,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,UAC7E,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,OAAO,UAAU,WACnB,QAAO,cAAe,MAA4B,QAAQ,YAAY;AAIxE,KAAI;EACF,MAAM,OAAO,KAAK,UAAU,MAAM;AAIlC,MAAI,SAAS,OAAW,QAAO;SACzB;AAIR,QAAO,qBADO,MAA8C,yEAAa,SACtD,SAAS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coerce.utils-
|
|
1
|
+
{"version":3,"file":"coerce.utils-Cu6IUTgX.mjs","names":[],"sources":["../src/utils/coerce.utils.ts"],"sourcesContent":["/**\n * Defensive value coercion helpers for Logixia.\n *\n * Logger inputs come from third-party code (NestJS framework internals,\n * user payloads, error objects) where the runtime type is not always what\n * the TypeScript signature promises. The formatter / transport hot paths\n * call `.replace()` on string fields and crash if the field is anything\n * else. Use these helpers anywhere a downstream consumer assumes a string.\n */\n\n/**\n * Coerce any value to a string without throwing.\n *\n * - `undefined` / `null` → `''` (so the field is rendered as empty rather\n * than the literal string \"undefined\")\n * - strings → returned as-is (zero allocation on the hot path)\n * - Errors → `error.message` (matches what users would expect to see)\n * - everything else → `JSON.stringify` if possible, falling back to\n * `String(value)` for circular refs / values without a stringifier.\n */\nexport function safeToString(value: unknown): string {\n if (typeof value === 'string') return value;\n if (value === undefined || value === null) return '';\n if (value instanceof Error) return value.message;\n if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'symbol') return value.toString();\n if (typeof value === 'function') {\n return `[Function: ${(value as { name?: string }).name || 'anonymous'}]`;\n }\n // value is now narrowed to object — JSON-stringify, falling back to a\n // constructor-name tag for circular refs / throwing toJSON.\n try {\n const json = JSON.stringify(value);\n // JSON.stringify returns undefined for values it can't represent (e.g. an\n // object whose toJSON() returns undefined). Returning that would defeat the\n // whole point of this helper — a downstream .replace() on `undefined` throws.\n if (json !== undefined) return json;\n } catch {\n /* fall through to the constructor-name tag below */\n }\n const ctor = (value as { constructor?: { name?: string } }).constructor?.name;\n return `[${ctor ?? 'object'}]`;\n}\n\n/**\n * String-replace on a value that may not be a string.\n * Coerces non-strings before delegating to `String.prototype.replace`.\n */\nexport function safeReplace(value: unknown, pattern: string | RegExp, replacement: string): string {\n return safeToString(value).replace(pattern, replacement);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aAAa,OAAwB;;AACnD,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,KAAI,iBAAiB,MAAO,QAAO,MAAM;AACzC,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,UAC7E,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,OAAO,UAAU,WACnB,QAAO,cAAe,MAA4B,QAAQ,YAAY;AAIxE,KAAI;EACF,MAAM,OAAO,KAAK,UAAU,MAAM;AAIlC,MAAI,SAAS,OAAW,QAAO;SACzB;AAIR,QAAO,qBADO,MAA8C,yEAAa,SACtD,SAAS"}
|
package/dist/correlation.d.mts
CHANGED
|
@@ -180,12 +180,32 @@ declare function correlationFetch(input: string | URL | Request, init?: RequestI
|
|
|
180
180
|
declare function createCorrelationAxiosInterceptor(options?: {
|
|
181
181
|
header?: string;
|
|
182
182
|
}): (config: Record<string, unknown>) => Record<string, unknown>;
|
|
183
|
+
interface ChildFromRequestOptions {
|
|
184
|
+
/** Header to read the correlation ID from. @default 'x-correlation-id' */
|
|
185
|
+
correlationHeader?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Take the client IP from the first `X-Forwarded-For` entry. Only enable this
|
|
188
|
+
* when the app sits behind a proxy that overwrites the header; otherwise any
|
|
189
|
+
* client can choose the logged IP.
|
|
190
|
+
* @default false
|
|
191
|
+
*/
|
|
192
|
+
trustProxy?: boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Context name passed as the first argument to `logger.child(context, data)`.
|
|
195
|
+
* When omitted, logixia loggers (detected by their `getContext` /
|
|
196
|
+
* `getAvailableTransports` methods) get `child('request', fields)` and any other
|
|
197
|
+
* logger gets `child(fields)`.
|
|
198
|
+
*/
|
|
199
|
+
childContext?: string;
|
|
200
|
+
}
|
|
183
201
|
/**
|
|
184
202
|
* Create a child logger enriched with all correlation-relevant identifiers
|
|
185
203
|
* extracted from the incoming HTTP request.
|
|
186
204
|
*
|
|
187
205
|
* The child logger's every call automatically includes:
|
|
188
|
-
* `correlationId`, `
|
|
206
|
+
* `correlationId`, `method`, `url`, `userAgent`, `ip`
|
|
207
|
+
*
|
|
208
|
+
* The IP comes from `req.ip` / the socket address unless `trustProxy` is set.
|
|
189
209
|
*
|
|
190
210
|
* @example
|
|
191
211
|
* ```ts
|
|
@@ -203,10 +223,8 @@ declare function createCorrelationAxiosInterceptor(options?: {
|
|
|
203
223
|
* ```
|
|
204
224
|
*/
|
|
205
225
|
declare function childFromRequest<TLogger extends {
|
|
206
|
-
child(
|
|
207
|
-
}>(req: Record<string, unknown>, logger: TLogger, options?:
|
|
208
|
-
correlationHeader?: string;
|
|
209
|
-
}): TLogger;
|
|
226
|
+
child(...args: never[]): TLogger;
|
|
227
|
+
}>(req: Record<string, unknown>, logger: TLogger, options?: ChildFromRequestOptions): TLogger;
|
|
210
228
|
/**
|
|
211
229
|
* Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
|
|
212
230
|
*
|
|
@@ -241,5 +259,5 @@ declare function extractMessageCorrelationId(message: Record<string, unknown>):
|
|
|
241
259
|
*/
|
|
242
260
|
declare function buildKafkaCorrelationHeaders(): Record<string, string>;
|
|
243
261
|
//#endregion
|
|
244
|
-
export { CorrelationContext, CorrelationMiddlewareOptions, buildKafkaCorrelationHeaders, childFromRequest, correlationFastifyHook, correlationFetch, correlationMiddleware, createCorrelationAxiosInterceptor, extractMessageCorrelationId, generateCorrelationId, getCurrentCorrelationId, withCorrelationId };
|
|
262
|
+
export { ChildFromRequestOptions, CorrelationContext, CorrelationMiddlewareOptions, buildKafkaCorrelationHeaders, childFromRequest, correlationFastifyHook, correlationFetch, correlationMiddleware, createCorrelationAxiosInterceptor, extractMessageCorrelationId, generateCorrelationId, getCurrentCorrelationId, withCorrelationId };
|
|
245
263
|
//# sourceMappingURL=correlation.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlation.d.mts","names":[],"sources":["../src/correlation.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"correlation.d.mts","names":[],"sources":["../src/correlation.ts"],"sourcesContent":[],"mappings":";;AA6DA;AASA;AA2CA;AAwBA;AAkBA;AAoBA;;;;;AAiDA;;;;;AAwDA;;;;;;;AA4CA;AAyBA;AA2CA;;;;;;;AAmFA;AAyCA;;;;;;;;;;;;;;;;;;;;UAvciB,kBAAA;;;;;;;;UASA,4BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CD,qBAAA,CAAA;;;;;iBAwBA,uBAAA,CAAA;;;;;;;;;;;;;;iBAkBA,wEAAwE,IAAI;;;;;;;;;;;;;;iBAoB5E,qBAAA,WAA+B,qCAUtC,8BACA;;;;;;;;;;;iBAsCO,sBAAA,WAAgC,yCAUnC,gCACF;;;;;;;;;;;;;;;;;;iBA6CW,gBAAA,iBACJ,MAAM,gBAChB;;IAEL,QAAQ;;;;;;;;;;;;;iBAwCK,iCAAA;;aAEJ,4BAA4B;UAuBvB,uBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CD;2BAA4D;QACrE,iCACG,mBACC,0BACR;;;;;;;;;;;;;;;;;;;iBA+Ea,2BAAA,UAAqC;;;;;;;;;;;;;;iBAyCrC,4BAAA,CAAA,GAAgC"}
|
package/dist/correlation.d.ts
CHANGED
|
@@ -180,12 +180,32 @@ declare function correlationFetch(input: string | URL | Request, init?: RequestI
|
|
|
180
180
|
declare function createCorrelationAxiosInterceptor(options?: {
|
|
181
181
|
header?: string;
|
|
182
182
|
}): (config: Record<string, unknown>) => Record<string, unknown>;
|
|
183
|
+
interface ChildFromRequestOptions {
|
|
184
|
+
/** Header to read the correlation ID from. @default 'x-correlation-id' */
|
|
185
|
+
correlationHeader?: string;
|
|
186
|
+
/**
|
|
187
|
+
* Take the client IP from the first `X-Forwarded-For` entry. Only enable this
|
|
188
|
+
* when the app sits behind a proxy that overwrites the header; otherwise any
|
|
189
|
+
* client can choose the logged IP.
|
|
190
|
+
* @default false
|
|
191
|
+
*/
|
|
192
|
+
trustProxy?: boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Context name passed as the first argument to `logger.child(context, data)`.
|
|
195
|
+
* When omitted, logixia loggers (detected by their `getContext` /
|
|
196
|
+
* `getAvailableTransports` methods) get `child('request', fields)` and any other
|
|
197
|
+
* logger gets `child(fields)`.
|
|
198
|
+
*/
|
|
199
|
+
childContext?: string;
|
|
200
|
+
}
|
|
183
201
|
/**
|
|
184
202
|
* Create a child logger enriched with all correlation-relevant identifiers
|
|
185
203
|
* extracted from the incoming HTTP request.
|
|
186
204
|
*
|
|
187
205
|
* The child logger's every call automatically includes:
|
|
188
|
-
* `correlationId`, `
|
|
206
|
+
* `correlationId`, `method`, `url`, `userAgent`, `ip`
|
|
207
|
+
*
|
|
208
|
+
* The IP comes from `req.ip` / the socket address unless `trustProxy` is set.
|
|
189
209
|
*
|
|
190
210
|
* @example
|
|
191
211
|
* ```ts
|
|
@@ -203,10 +223,8 @@ declare function createCorrelationAxiosInterceptor(options?: {
|
|
|
203
223
|
* ```
|
|
204
224
|
*/
|
|
205
225
|
declare function childFromRequest<TLogger extends {
|
|
206
|
-
child(
|
|
207
|
-
}>(req: Record<string, unknown>, logger: TLogger, options?:
|
|
208
|
-
correlationHeader?: string;
|
|
209
|
-
}): TLogger;
|
|
226
|
+
child(...args: never[]): TLogger;
|
|
227
|
+
}>(req: Record<string, unknown>, logger: TLogger, options?: ChildFromRequestOptions): TLogger;
|
|
210
228
|
/**
|
|
211
229
|
* Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
|
|
212
230
|
*
|
|
@@ -241,5 +259,5 @@ declare function extractMessageCorrelationId(message: Record<string, unknown>):
|
|
|
241
259
|
*/
|
|
242
260
|
declare function buildKafkaCorrelationHeaders(): Record<string, string>;
|
|
243
261
|
//#endregion
|
|
244
|
-
export { CorrelationContext, CorrelationMiddlewareOptions, buildKafkaCorrelationHeaders, childFromRequest, correlationFastifyHook, correlationFetch, correlationMiddleware, createCorrelationAxiosInterceptor, extractMessageCorrelationId, generateCorrelationId, getCurrentCorrelationId, withCorrelationId };
|
|
262
|
+
export { ChildFromRequestOptions, CorrelationContext, CorrelationMiddlewareOptions, buildKafkaCorrelationHeaders, childFromRequest, correlationFastifyHook, correlationFetch, correlationMiddleware, createCorrelationAxiosInterceptor, extractMessageCorrelationId, generateCorrelationId, getCurrentCorrelationId, withCorrelationId };
|
|
245
263
|
//# sourceMappingURL=correlation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlation.d.ts","names":[],"sources":["../src/correlation.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"correlation.d.ts","names":[],"sources":["../src/correlation.ts"],"sourcesContent":[],"mappings":";;AA6DA;AASA;AA2CA;AAwBA;AAkBA;AAoBA;;;;;AAiDA;;;;;AAwDA;;;;;;;AA4CA;AAyBA;AA2CA;;;;;;;AAmFA;AAyCA;;;;;;;;;;;;;;;;;;;;UAvciB,kBAAA;;;;;;;;UASA,4BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CD,qBAAA,CAAA;;;;;iBAwBA,uBAAA,CAAA;;;;;;;;;;;;;;iBAkBA,wEAAwE,IAAI;;;;;;;;;;;;;;iBAoB5E,qBAAA,WAA+B,qCAUtC,8BACA;;;;;;;;;;;iBAsCO,sBAAA,WAAgC,yCAUnC,gCACF;;;;;;;;;;;;;;;;;;iBA6CW,gBAAA,iBACJ,MAAM,gBAChB;;IAEL,QAAQ;;;;;;;;;;;;;iBAwCK,iCAAA;;aAEJ,4BAA4B;UAuBvB,uBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CD;2BAA4D;QACrE,iCACG,mBACC,0BACR;;;;;;;;;;;;;;;;;;;iBA+Ea,2BAAA,UAAqC;;;;;;;;;;;;;;iBAyCrC,4BAAA,CAAA,GAAgC"}
|
package/dist/correlation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-nOFOJqeH.js');
|
|
2
|
-
const
|
|
2
|
+
const require_trace_utils = require('./trace.utils-DpBOLDtq.js');
|
|
3
|
+
const require_async_context = require('./async-context-DyQQYq3I.js');
|
|
3
4
|
let node_crypto = require("node:crypto");
|
|
4
5
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
5
6
|
|
|
@@ -18,8 +19,6 @@ function generateCorrelationId() {
|
|
|
18
19
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
19
20
|
return (0, node_crypto.randomUUID)();
|
|
20
21
|
}
|
|
21
|
-
const MAX_INCOMING_ID_LENGTH = 128;
|
|
22
|
-
const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
|
|
23
22
|
/**
|
|
24
23
|
* Return an incoming correlation ID only if it is short and made of safe
|
|
25
24
|
* characters. Anything else (over-long, empty, or containing newlines or other
|
|
@@ -27,8 +26,8 @@ const VALID_INCOMING_ID = /^[A-Za-z0-9._:-]+$/;
|
|
|
27
26
|
* writing client-supplied text into every log line.
|
|
28
27
|
*/
|
|
29
28
|
function validIncomingId(value) {
|
|
30
|
-
if (value ===
|
|
31
|
-
return
|
|
29
|
+
if (typeof value === "string" && value.trim() !== value) return void 0;
|
|
30
|
+
return require_trace_utils.sanitizeIncomingId(value);
|
|
32
31
|
}
|
|
33
32
|
/**
|
|
34
33
|
* Return the correlation ID from the current `LogixiaContext`, or `undefined`
|
|
@@ -73,7 +72,7 @@ function correlationMiddleware(options = {}) {
|
|
|
73
72
|
return function logixiaCorrelationMiddleware(req, res, next) {
|
|
74
73
|
const headers = req["headers"] ?? {};
|
|
75
74
|
const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
|
|
76
|
-
const originService = headers[originServiceHeader.toLowerCase()];
|
|
75
|
+
const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
|
|
77
76
|
if (setResponseHeader) {
|
|
78
77
|
const setHeader = res["setHeader"];
|
|
79
78
|
if (typeof setHeader === "function") setHeader.call(res, header, correlationId);
|
|
@@ -100,7 +99,7 @@ function correlationFastifyHook(options = {}) {
|
|
|
100
99
|
return function logixiaCorrelationFastifyHook(request, reply, done) {
|
|
101
100
|
const headers = request["headers"] ?? {};
|
|
102
101
|
const correlationId = (trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : void 0) ?? generate();
|
|
103
|
-
const originService = headers[originServiceHeader.toLowerCase()];
|
|
102
|
+
const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);
|
|
104
103
|
if (setResponseHeader) {
|
|
105
104
|
const replyHeader = reply["header"];
|
|
106
105
|
if (typeof replyHeader === "function") replyHeader.call(reply, header, correlationId);
|
|
@@ -165,12 +164,16 @@ function createCorrelationAxiosInterceptor(options = {}) {
|
|
|
165
164
|
};
|
|
166
165
|
};
|
|
167
166
|
}
|
|
167
|
+
const MAX_USER_AGENT_LENGTH = 512;
|
|
168
|
+
const MAX_IP_LENGTH = 64;
|
|
168
169
|
/**
|
|
169
170
|
* Create a child logger enriched with all correlation-relevant identifiers
|
|
170
171
|
* extracted from the incoming HTTP request.
|
|
171
172
|
*
|
|
172
173
|
* The child logger's every call automatically includes:
|
|
173
|
-
* `correlationId`, `
|
|
174
|
+
* `correlationId`, `method`, `url`, `userAgent`, `ip`
|
|
175
|
+
*
|
|
176
|
+
* The IP comes from `req.ip` / the socket address unless `trustProxy` is set.
|
|
174
177
|
*
|
|
175
178
|
* @example
|
|
176
179
|
* ```ts
|
|
@@ -188,21 +191,43 @@ function createCorrelationAxiosInterceptor(options = {}) {
|
|
|
188
191
|
* ```
|
|
189
192
|
*/
|
|
190
193
|
function childFromRequest(req, logger, options = {}) {
|
|
191
|
-
var _headers$xForwarded, _req$socket;
|
|
192
194
|
const correlationHeader = options.correlationHeader ?? "x-correlation-id";
|
|
193
195
|
const headers = req["headers"] ?? {};
|
|
194
196
|
const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
|
|
195
197
|
const method = req["method"];
|
|
196
198
|
const url = req["url"] ?? req["originalUrl"];
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
|
|
199
|
+
const rawUserAgent = headers["user-agent"];
|
|
200
|
+
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : void 0;
|
|
201
|
+
const ip = resolveClientIp(req, headers, options.trustProxy === true);
|
|
202
|
+
const fields = {
|
|
200
203
|
correlationId,
|
|
201
204
|
...method !== void 0 ? { method } : {},
|
|
202
205
|
...url !== void 0 ? { url } : {},
|
|
203
206
|
...userAgent !== void 0 ? { userAgent } : {},
|
|
204
207
|
...ip !== void 0 ? { ip } : {}
|
|
205
|
-
}
|
|
208
|
+
};
|
|
209
|
+
const child = logger.child;
|
|
210
|
+
if (options.childContext !== void 0) return child.call(logger, options.childContext, fields);
|
|
211
|
+
if (usesStringContext(logger)) return child.call(logger, "request", fields);
|
|
212
|
+
return child.call(logger, fields);
|
|
213
|
+
}
|
|
214
|
+
/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */
|
|
215
|
+
function usesStringContext(logger) {
|
|
216
|
+
const l = logger;
|
|
217
|
+
return typeof l["getContext"] === "function" || typeof l["getAvailableTransports"] === "function";
|
|
218
|
+
}
|
|
219
|
+
function resolveClientIp(req, headers, trustProxy) {
|
|
220
|
+
var _req$socket;
|
|
221
|
+
if (trustProxy) {
|
|
222
|
+
var _forwarded$split$;
|
|
223
|
+
const forwarded = headers["x-forwarded-for"];
|
|
224
|
+
const first = typeof forwarded === "string" ? (_forwarded$split$ = forwarded.split(",")[0]) === null || _forwarded$split$ === void 0 ? void 0 : _forwarded$split$.trim() : void 0;
|
|
225
|
+
if (first) return first.slice(0, MAX_IP_LENGTH);
|
|
226
|
+
}
|
|
227
|
+
const fromReq = req["ip"];
|
|
228
|
+
if (typeof fromReq === "string" && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);
|
|
229
|
+
const remote = (_req$socket = req["socket"]) === null || _req$socket === void 0 ? void 0 : _req$socket["remoteAddress"];
|
|
230
|
+
return typeof remote === "string" ? remote.slice(0, MAX_IP_LENGTH) : void 0;
|
|
206
231
|
}
|
|
207
232
|
/**
|
|
208
233
|
* Extract a correlation context from a Kafka / RabbitMQ / SQS message object.
|
|
@@ -226,14 +251,14 @@ function extractMessageCorrelationId(message) {
|
|
|
226
251
|
const kafkaHeaders = message["headers"];
|
|
227
252
|
if (kafkaHeaders) {
|
|
228
253
|
const raw = kafkaHeaders["x-correlation-id"] ?? kafkaHeaders["correlationId"] ?? kafkaHeaders["correlation_id"];
|
|
229
|
-
if (raw !== void 0) return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
|
|
254
|
+
if (raw !== void 0) return validIncomingId(Buffer.isBuffer(raw) ? raw.toString("utf8", 0, 512) : String(raw));
|
|
230
255
|
}
|
|
231
256
|
const sqsAttrs = message["MessageAttributes"];
|
|
232
257
|
if (sqsAttrs) {
|
|
233
258
|
var _sqsAttrs$xCorrelati, _sqsAttrs$correlation, _sqsAttrs$correlation2;
|
|
234
|
-
return ((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue);
|
|
259
|
+
return validIncomingId(((_sqsAttrs$xCorrelati = sqsAttrs["x-correlation-id"]) === null || _sqsAttrs$xCorrelati === void 0 ? void 0 : _sqsAttrs$xCorrelati.StringValue) ?? ((_sqsAttrs$correlation = sqsAttrs["correlationId"]) === null || _sqsAttrs$correlation === void 0 ? void 0 : _sqsAttrs$correlation.StringValue) ?? ((_sqsAttrs$correlation2 = sqsAttrs["correlation_id"]) === null || _sqsAttrs$correlation2 === void 0 ? void 0 : _sqsAttrs$correlation2.StringValue));
|
|
235
260
|
}
|
|
236
|
-
return message["correlationId"] ?? message["correlation_id"];
|
|
261
|
+
return validIncomingId(message["correlationId"] ?? message["correlation_id"]);
|
|
237
262
|
}
|
|
238
263
|
/**
|
|
239
264
|
* Build Kafka message headers containing the current correlation context.
|