logixia 1.13.0 → 1.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- 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-BDFO4F-i.d.ts} +2 -2
- package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-BDFO4F-i.d.ts.map} +1 -1
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D5wQEH04.d.mts} +2 -2
- package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D5wQEH04.d.mts.map} +1 -1
- package/dist/azure.d.mts +2 -2
- package/dist/azure.d.ts +2 -2
- package/dist/cli/index.js +98 -52
- 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-CGHY4buE.d.mts → cloudwatch.transport-B00st6Xv.d.ts} +9 -2
- package/dist/cloudwatch.transport-B00st6Xv.d.ts.map +1 -0
- 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-DK_8cKd3.d.ts → cloudwatch.transport-Blc7Gtxt.d.mts} +9 -2
- package/dist/cloudwatch.transport-Blc7Gtxt.d.mts.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/{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-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-BOhkVKlI.d.ts → gcp.transport-m4fVw0b1.d.ts} +2 -2
- package/dist/gcp.transport-m4fVw0b1.d.ts.map +1 -0
- package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-zzNyIoih.d.mts} +2 -2
- package/dist/gcp.transport-zzNyIoih.d.mts.map +1 -0
- package/dist/{index-BPKwwERo.d.mts → index-BPwEisy4.d.mts} +2 -2
- package/dist/{index-BPKwwERo.d.mts.map → index-BPwEisy4.d.mts.map} +1 -1
- package/dist/{index-C1X-TE5I.d.ts → index-nZEeCr3X.d.ts} +2 -2
- package/dist/{index-C1X-TE5I.d.ts.map → index-nZEeCr3X.d.ts.map} +1 -1
- package/dist/index.d.mts +63 -98
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +63 -98
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -443
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -377
- package/dist/index.mjs.map +1 -1
- 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 +4 -4
- package/dist/nest.d.ts +4 -4
- package/dist/nest.js +20 -11
- package/dist/nest.mjs +7 -5
- package/dist/{logitron-logger.module-BsDZR5BH.js → nestjs-extras-DnJ6vrIC.js} +562 -444
- package/dist/nestjs-extras-DnJ6vrIC.js.map +1 -0
- package/dist/{logitron-logger.module-Bs3XYZgF.mjs → nestjs-extras-ImbuDhs1.mjs} +505 -349
- package/dist/nestjs-extras-ImbuDhs1.mjs.map +1 -0
- package/dist/{logitron-logger.module-CbXy2gfW.d.ts → nestjs-extras-OkC796py.d.mts} +111 -4
- package/dist/nestjs-extras-OkC796py.d.mts.map +1 -0
- package/dist/{logitron-logger.module-BdG1Jnpk.d.mts → nestjs-extras-mKrHE9S6.d.ts} +111 -4
- package/dist/nestjs-extras-mKrHE9S6.d.ts.map +1 -0
- package/dist/{search-njTI2hdo.js → search-B0MH1SJJ.js} +23 -10
- package/dist/search-B0MH1SJJ.js.map +1 -0
- package/dist/{search-DujhR94O.d.ts → search-BfBzDIT9.d.mts} +2 -3
- package/dist/search-BfBzDIT9.d.mts.map +1 -0
- package/dist/{search-CQOo-Iq6.d.mts → search-CHUZPl0h.d.ts} +2 -3
- package/dist/search-CHUZPl0h.d.ts.map +1 -0
- package/dist/{search-CFQRISUv.mjs → search-DjXSn6v5.mjs} +23 -10
- package/dist/search-DjXSn6v5.mjs.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/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-CpdVHEGP.mjs} +365 -86
- package/dist/transport.manager-CpdVHEGP.mjs.map +1 -0
- package/dist/{transport.manager-BivyUTtU.js → transport.manager-Crau3Rle.js} +370 -85
- package/dist/transport.manager-Crau3Rle.js.map +1 -0
- package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-CtNr3D7S.d.mts} +19 -2
- package/dist/transport.types-CtNr3D7S.d.mts.map +1 -0
- package/dist/{transport.types-BuY84TwO.d.ts → transport.types-DqVLZTyf.d.ts} +19 -2
- package/dist/transport.types-DqVLZTyf.d.ts.map +1 -0
- package/dist/transports.d.mts +38 -13
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +38 -13
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +6 -5
- package/dist/transports.mjs +6 -5
- 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-BP8Q28Z2.d.mts → worker.transport-Bgv9heDI.d.mts} +2 -2
- package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-Bgv9heDI.d.mts.map} +1 -1
- package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-Bj-i0P9V.d.ts} +2 -2
- package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-Bj-i0P9V.d.ts.map} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js → worker.transport-JP8YHBuU.js} +1 -1
- package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-JP8YHBuU.js.map} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-otwhJ_Et.mjs} +1 -1
- package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-otwhJ_Et.mjs.map} +1 -1
- package/package.json +10 -6
- 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.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.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
|
@@ -2,6 +2,7 @@ const require_chunk = require('./chunk-nOFOJqeH.js');
|
|
|
2
2
|
const require_internal_log = require('./internal-log-BUJYMZbE.js');
|
|
3
3
|
const require_safe_stringify = require('./safe-stringify-DWo3aZp_.js');
|
|
4
4
|
const require_batch_buffer = require('./batch-buffer-BY-mRWIK.js');
|
|
5
|
+
const require_entry_fields = require('./entry-fields-BvOLFlJ4.js');
|
|
5
6
|
let node_fs = require("node:fs");
|
|
6
7
|
node_fs = require_chunk.__toESM(node_fs);
|
|
7
8
|
let node_os = require("node:os");
|
|
@@ -202,6 +203,7 @@ function signV4(opts) {
|
|
|
202
203
|
//#endregion
|
|
203
204
|
//#region src/transports/cloudwatch.transport.ts
|
|
204
205
|
const MAX_EVENTS_PER_PUT = 1e4;
|
|
206
|
+
const EMF_MAX_METRICS = 100;
|
|
205
207
|
/** Error types worth retrying even though CloudWatch reports them as HTTP 400. */
|
|
206
208
|
const RETRYABLE_TYPES = new Set([
|
|
207
209
|
"ThrottlingException",
|
|
@@ -237,6 +239,7 @@ var CloudWatchTransport = class {
|
|
|
237
239
|
this.emfNamespace = config.emfNamespace ?? "Logixia";
|
|
238
240
|
this.flushInterval = config.flushIntervalMs ?? 5e3;
|
|
239
241
|
this.emf = config.emf ?? false;
|
|
242
|
+
this.emfMetrics = config.emfMetrics ? new Set(config.emfMetrics) : void 0;
|
|
240
243
|
this.level = config.level;
|
|
241
244
|
this.requestTimeoutMs = config.requestTimeoutMs ?? require_batch_buffer.DEFAULT_REQUEST_TIMEOUT_MS;
|
|
242
245
|
this.buffer = new require_batch_buffer.BatchBuffer({
|
|
@@ -277,25 +280,29 @@ var CloudWatchTransport = class {
|
|
|
277
280
|
}
|
|
278
281
|
toEmf(entry) {
|
|
279
282
|
const { level, message, data: payload, timestamp } = entry;
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
return require_safe_stringify.stringifyForLog({
|
|
283
|
-
_aws: {
|
|
284
|
-
Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),
|
|
285
|
-
CloudWatchMetrics: Object.keys(numericFields).length > 0 ? [{
|
|
286
|
-
Namespace: this.emfNamespace,
|
|
287
|
-
Dimensions: [["level"]],
|
|
288
|
-
Metrics: Object.keys(numericFields).map((n) => ({
|
|
289
|
-
Name: n,
|
|
290
|
-
Unit: "None"
|
|
291
|
-
}))
|
|
292
|
-
}] : []
|
|
293
|
-
},
|
|
283
|
+
const fields = require_entry_fields.mergeCoreFields({
|
|
284
|
+
_aws: void 0,
|
|
294
285
|
level,
|
|
295
|
-
message
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
286
|
+
message
|
|
287
|
+
}, payload);
|
|
288
|
+
const metricNames = [];
|
|
289
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
290
|
+
if (k === "_aws" || k === "level" || k === "message" || typeof v !== "number") continue;
|
|
291
|
+
if (this.emfMetrics && !this.emfMetrics.has(k)) continue;
|
|
292
|
+
if (metricNames.length < EMF_MAX_METRICS) metricNames.push(k);
|
|
293
|
+
}
|
|
294
|
+
fields["_aws"] = {
|
|
295
|
+
Timestamp: timestamp ? new Date(timestamp).getTime() : Date.now(),
|
|
296
|
+
CloudWatchMetrics: metricNames.length > 0 ? [{
|
|
297
|
+
Namespace: this.emfNamespace,
|
|
298
|
+
Dimensions: [["level"]],
|
|
299
|
+
Metrics: metricNames.map((n) => ({
|
|
300
|
+
Name: n,
|
|
301
|
+
Unit: "None"
|
|
302
|
+
}))
|
|
303
|
+
}] : []
|
|
304
|
+
};
|
|
305
|
+
return require_safe_stringify.stringifyForLog(fields);
|
|
299
306
|
}
|
|
300
307
|
async putLogEvents(events) {
|
|
301
308
|
await this.ensureStream();
|
|
@@ -367,4 +374,4 @@ Object.defineProperty(exports, 'CloudWatchTransport', {
|
|
|
367
374
|
return CloudWatchTransport;
|
|
368
375
|
}
|
|
369
376
|
});
|
|
370
|
-
//# sourceMappingURL=cloudwatch.transport-
|
|
377
|
+
//# sourceMappingURL=cloudwatch.transport-BO1ahxw-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-BO1ahxw-.js","names":["out: Record<string, Record<string, string>>","section: Record<string, string> | undefined","path","os","text: string","fs","headers: Record<string, string>","httpRequest","crypto","headers: Record<string, string>","HttpError","DEFAULT_REQUEST_TIMEOUT_MS","BatchBuffer","DEFAULT_MAX_BUFFER_SIZE","isPermanentHttpError","stringifyForLog","mergeCoreFields","metricNames: string[]","httpRequest"],"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":"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"}
|
|
@@ -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-CtNr3D7S.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-Blc7Gtxt.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-Blc7Gtxt.d.mts","names":[],"sources":["../src/transports/cloudwatch.transport.ts"],"sourcesContent":[],"mappings":";;;;UA+CiB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+EJ,mBAAA,YAA+B;;;;;;;;;;;;;;;;;sBAoBtB;eAiCP;WAOJ;;;;;;WASM"}
|
|
@@ -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":"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"}
|
|
@@ -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"}
|