logixia 2.0.6 → 2.0.8
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-2mbUKhcl.mjs → async-context-COuvmwsS.mjs} +2 -2
- package/dist/{async-context-2mbUKhcl.mjs.map → async-context-COuvmwsS.mjs.map} +1 -1
- package/dist/{async-context-DUFMDYAr.js → async-context-Dv0bmPC-.js} +3 -3
- package/dist/{async-context-DUFMDYAr.js.map → async-context-Dv0bmPC-.js.map} +1 -1
- package/dist/{azure-monitor.transport-C1RWAumT.mjs → azure-monitor.transport-Coz5Gesu.mjs} +5 -5
- package/dist/{azure-monitor.transport-C1RWAumT.mjs.map → azure-monitor.transport-Coz5Gesu.mjs.map} +1 -1
- package/dist/{azure-monitor.transport-Bu4S2I2q.js → azure-monitor.transport-D6ryi8rA.js} +5 -5
- package/dist/{azure-monitor.transport-Bu4S2I2q.js.map → azure-monitor.transport-D6ryi8rA.js.map} +1 -1
- package/dist/azure.js +1 -1
- package/dist/azure.mjs +1 -1
- package/dist/{batch-buffer-C7Am4Mw-.js → batch-buffer-CFzKnuNZ.js} +3 -3
- package/dist/{batch-buffer-C7Am4Mw-.js.map → batch-buffer-CFzKnuNZ.js.map} +1 -1
- package/dist/{batch-buffer-BvXLDPlV.mjs → batch-buffer-CQUWDMYq.mjs} +3 -3
- package/dist/{batch-buffer-BvXLDPlV.mjs.map → batch-buffer-CQUWDMYq.mjs.map} +1 -1
- package/dist/browser.js +25 -1
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +25 -1
- package/dist/browser.mjs.map +1 -1
- package/dist/{builder-yFvJrz7o.js → builder-BTzvPGHN.js} +82 -15
- package/dist/builder-BTzvPGHN.js.map +1 -0
- package/dist/{builder-DhNhvOkt.mjs → builder-DKL7WcIN.mjs} +71 -10
- package/dist/builder-DKL7WcIN.mjs.map +1 -0
- package/dist/cloudwatch.js +1 -1
- package/dist/cloudwatch.mjs +1 -1
- package/dist/{cloudwatch.transport-DZqF2xZO.mjs → cloudwatch.transport-Cr1aeTGj.mjs} +6 -6
- package/dist/{cloudwatch.transport-DZqF2xZO.mjs.map → cloudwatch.transport-Cr1aeTGj.mjs.map} +1 -1
- package/dist/{cloudwatch.transport-Ch1Uae-0.js → cloudwatch.transport-D4G5xYfG.js} +6 -6
- package/dist/{cloudwatch.transport-Ch1Uae-0.js.map → cloudwatch.transport-D4G5xYfG.js.map} +1 -1
- package/dist/correlation.d.mts +6 -0
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +6 -0
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +4 -2
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +4 -2
- package/dist/correlation.mjs.map +1 -1
- package/dist/{entry-fields-BSLkzw18.js → entry-fields-CVwoQplO.js} +3 -3
- package/dist/{entry-fields-BSLkzw18.js.map → entry-fields-CVwoQplO.js.map} +1 -1
- package/dist/{entry-fields-J1OBi24o.mjs → entry-fields-DNTKQxoU.mjs} +2 -2
- package/dist/{entry-fields-J1OBi24o.mjs.map → entry-fields-DNTKQxoU.mjs.map} +1 -1
- package/dist/gcp.js +1 -1
- package/dist/gcp.mjs +1 -1
- package/dist/{gcp.transport-Dy2Lf8FI.js → gcp.transport-CVuRpchl.js} +6 -6
- package/dist/{gcp.transport-Dy2Lf8FI.js.map → gcp.transport-CVuRpchl.js.map} +1 -1
- package/dist/{gcp.transport-DVCmnA3b.mjs → gcp.transport-LLK2bw2-.mjs} +6 -6
- package/dist/{gcp.transport-DVCmnA3b.mjs.map → gcp.transport-LLK2bw2-.mjs.map} +1 -1
- package/dist/{index-BSTtFcQ6.d.mts → index-BAkPHp5D.d.mts} +9 -1
- package/dist/index-BAkPHp5D.d.mts.map +1 -0
- package/dist/{index-CFDGkI8n.d.ts → index-e7LowmAu.d.ts} +9 -1
- package/dist/index-e7LowmAu.d.ts.map +1 -0
- package/dist/index.d.mts +11 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -8
- package/dist/internal-log-Cy5nF1ZL.mjs +63 -0
- package/dist/internal-log-Cy5nF1ZL.mjs.map +1 -0
- package/dist/internal-log-DwqTjhpb.js +80 -0
- package/dist/internal-log-DwqTjhpb.js.map +1 -0
- package/dist/{logitron-logger-D_MYYrQt.d.ts → logitron-logger-Qh3y9-WH.d.ts} +2 -2
- package/dist/{logitron-logger-D_MYYrQt.d.ts.map → logitron-logger-Qh3y9-WH.d.ts.map} +1 -1
- package/dist/{logitron-logger-D22Yb61K.d.mts → logitron-logger-sYuk8fOb.d.mts} +2 -2
- package/dist/{logitron-logger-D22Yb61K.d.mts.map → logitron-logger-sYuk8fOb.d.mts.map} +1 -1
- package/dist/middleware.d.mts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +8 -8
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +3 -3
- package/dist/nest.d.mts +12 -3
- package/dist/nest.d.mts.map +1 -1
- package/dist/nest.d.ts +12 -3
- package/dist/nest.d.ts.map +1 -1
- package/dist/nest.js +128 -20
- package/dist/nest.js.map +1 -1
- package/dist/nest.mjs +128 -20
- package/dist/nest.mjs.map +1 -1
- package/dist/{otel-DyeH6DVR.js → otel-ChnVYRLA.js} +5 -5
- package/dist/{otel-DyeH6DVR.js.map → otel-ChnVYRLA.js.map} +1 -1
- package/dist/{otel-Dt5riXuE.mjs → otel-DOuwGPCd.mjs} +5 -5
- package/dist/{otel-Dt5riXuE.mjs.map → otel-DOuwGPCd.mjs.map} +1 -1
- package/dist/{safe-stringify-B5p5ktFe.mjs → safe-stringify--7gbe95n.mjs} +2 -2
- package/dist/{safe-stringify-B5p5ktFe.mjs.map → safe-stringify--7gbe95n.mjs.map} +1 -1
- package/dist/{safe-stringify-J3sh-7YM.js → safe-stringify-CN7icp8S.js} +5 -5
- package/dist/{safe-stringify-J3sh-7YM.js.map → safe-stringify-CN7icp8S.js.map} +1 -1
- package/dist/{search-DA3ft3X5.d.mts → search-D-8HJADE.d.mts} +2 -2
- package/dist/{search-DA3ft3X5.d.mts.map → search-D-8HJADE.d.mts.map} +1 -1
- package/dist/{search-DEl3Pe47.d.ts → search-DPwTKPve.d.ts} +2 -2
- package/dist/{search-DEl3Pe47.d.ts.map → search-DPwTKPve.d.ts.map} +1 -1
- package/dist/search.d.mts +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/{secret-config-CmK2wVkr.js → secret-config-BvFl2dC8.js} +7 -8
- package/dist/{secret-config-CmK2wVkr.js.map → secret-config-BvFl2dC8.js.map} +1 -1
- package/dist/{secret-config-Dhb6pqTU.mjs → secret-config-ByGq7kJd.mjs} +2 -3
- package/dist/{secret-config-Dhb6pqTU.mjs.map → secret-config-ByGq7kJd.mjs.map} +1 -1
- package/dist/sensitive-names-CHMowujD.js +276 -0
- package/dist/sensitive-names-CHMowujD.js.map +1 -0
- package/dist/sensitive-names-o__1PUUR.mjs +217 -0
- package/dist/sensitive-names-o__1PUUR.mjs.map +1 -0
- package/dist/{slack.transport-BTEwsREY.mjs → slack.transport-CSj1j0_X.mjs} +6 -6
- package/dist/{slack.transport-BTEwsREY.mjs.map → slack.transport-CSj1j0_X.mjs.map} +1 -1
- package/dist/{slack.transport-UbYvOdKx.js → slack.transport-zLQ9MGxS.js} +6 -6
- package/dist/{slack.transport-UbYvOdKx.js.map → slack.transport-zLQ9MGxS.js.map} +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/transports.d.mts +1 -1
- package/dist/transports.d.ts +1 -1
- package/dist/transports.js +6 -6
- package/dist/transports.mjs +6 -6
- package/dist/worker-entry.js +25 -1
- package/dist/worker.js +1 -1
- package/dist/worker.mjs +1 -1
- package/dist/{worker.transport-BmmOKb1-.mjs → worker.transport-D78xhNhB.mjs} +3 -3
- package/dist/{worker.transport-BmmOKb1-.mjs.map → worker.transport-D78xhNhB.mjs.map} +1 -1
- package/dist/{worker.transport-DxxbqQCG.js → worker.transport-DQDv1nAJ.js} +3 -3
- package/dist/{worker.transport-DxxbqQCG.js.map → worker.transport-DQDv1nAJ.js.map} +1 -1
- package/package.json +1 -1
- package/dist/builder-DhNhvOkt.mjs.map +0 -1
- package/dist/builder-yFvJrz7o.js.map +0 -1
- package/dist/index-BSTtFcQ6.d.mts.map +0 -1
- package/dist/index-CFDGkI8n.d.ts.map +0 -1
- package/dist/internal-log-Bg4V1cPg.mjs +0 -170
- package/dist/internal-log-Bg4V1cPg.mjs.map +0 -1
- package/dist/internal-log-By4Govmu.js +0 -223
- package/dist/internal-log-By4Govmu.js.map +0 -1
- package/dist/own-property-BWkibepN.js +0 -40
- package/dist/own-property-BWkibepN.js.map +0 -1
- package/dist/own-property-CjFw9qwZ.mjs +0 -29
- package/dist/own-property-CjFw9qwZ.mjs.map +0 -1
package/dist/{cloudwatch.transport-DZqF2xZO.mjs.map → cloudwatch.transport-Cr1aeTGj.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudwatch.transport-DZqF2xZO.mjs","names":["path"],"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';\nimport { protectSecrets } from './secret-config';\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 protectSecrets(this, ['cached', 'explicit']);\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 /** Secret values this provider may hold, for scrubbing error text. */\n secretValues(): (string | undefined)[] {\n const env = process.env;\n return [\n this.explicit?.secretAccessKey,\n this.explicit?.sessionToken,\n this.cached?.secretAccessKey,\n this.cached?.sessionToken,\n env['AWS_SECRET_ACCESS_KEY'],\n env['AWS_SESSION_TOKEN'],\n ];\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 { createLogger } from 'logixia';\n * import { CloudWatchTransport } from 'logixia/cloudwatch';\n *\n * const logger = createLogger({\n * transports: {\n * custom: [\n * 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 */\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';\nimport { protectSecrets, urlSecrets } from './secret-config';\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 secrets: () => [...this.credentials.secretValues(), ...urlSecrets(this.endpoint)],\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 protectSecrets(this, ['credentials', 'endpoint']);\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 secrets: () => [\n ...this.credentials.secretValues(),\n headers['authorization'],\n headers['x-amz-security-token'],\n ...urlSecrets(this.endpoint),\n ],\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-Cr1aeTGj.mjs","names":["path"],"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';\nimport { protectSecrets } from './secret-config';\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 protectSecrets(this, ['cached', 'explicit']);\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 /** Secret values this provider may hold, for scrubbing error text. */\n secretValues(): (string | undefined)[] {\n const env = process.env;\n return [\n this.explicit?.secretAccessKey,\n this.explicit?.sessionToken,\n this.cached?.secretAccessKey,\n this.cached?.sessionToken,\n env['AWS_SECRET_ACCESS_KEY'],\n env['AWS_SESSION_TOKEN'],\n ];\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 { createLogger } from 'logixia';\n * import { CloudWatchTransport } from 'logixia/cloudwatch';\n *\n * const logger = createLogger({\n * transports: {\n * custom: [\n * 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 */\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';\nimport { protectSecrets, urlSecrets } from './secret-config';\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 secrets: () => [...this.credentials.secretValues(), ...urlSecrets(this.endpoint)],\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 protectSecrets(this, ['credentials', 'endpoint']);\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 secrets: () => [\n ...this.credentials.secretValues(),\n headers['authorization'],\n headers['x-amz-security-token'],\n ...urlSecrets(this.endpoint),\n ],\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const require_secret_config = require("./secret-config-
|
|
2
|
-
const require_internal_log = require("./internal-log-
|
|
3
|
-
const require_safe_stringify = require("./safe-stringify-
|
|
4
|
-
const require_entry_fields = require("./entry-fields-
|
|
5
|
-
const require_batch_buffer = require("./batch-buffer-
|
|
1
|
+
const require_secret_config = require("./secret-config-BvFl2dC8.js");
|
|
2
|
+
const require_internal_log = require("./internal-log-DwqTjhpb.js");
|
|
3
|
+
const require_safe_stringify = require("./safe-stringify-CN7icp8S.js");
|
|
4
|
+
const require_entry_fields = require("./entry-fields-CVwoQplO.js");
|
|
5
|
+
const require_batch_buffer = require("./batch-buffer-CFzKnuNZ.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
node_crypto = require_secret_config.__toESM(node_crypto);
|
|
8
8
|
let node_os = require("node:os");
|
|
@@ -399,4 +399,4 @@ Object.defineProperty(exports, "CloudWatchTransport", {
|
|
|
399
399
|
}
|
|
400
400
|
});
|
|
401
401
|
|
|
402
|
-
//# sourceMappingURL=cloudwatch.transport-
|
|
402
|
+
//# sourceMappingURL=cloudwatch.transport-D4G5xYfG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudwatch.transport-Ch1Uae-0.js","names":["path","os","fs","httpRequest","crypto","HttpError","BatchBuffer","urlSecrets","isPermanentHttpError","stringifyForLog","mergeCoreFields","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';\nimport { protectSecrets } from './secret-config';\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 protectSecrets(this, ['cached', 'explicit']);\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 /** Secret values this provider may hold, for scrubbing error text. */\n secretValues(): (string | undefined)[] {\n const env = process.env;\n return [\n this.explicit?.secretAccessKey,\n this.explicit?.sessionToken,\n this.cached?.secretAccessKey,\n this.cached?.sessionToken,\n env['AWS_SECRET_ACCESS_KEY'],\n env['AWS_SESSION_TOKEN'],\n ];\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 { createLogger } from 'logixia';\n * import { CloudWatchTransport } from 'logixia/cloudwatch';\n *\n * const logger = createLogger({\n * transports: {\n * custom: [\n * 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 */\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';\nimport { protectSecrets, urlSecrets } from './secret-config';\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 secrets: () => [...this.credentials.secretValues(), ...urlSecrets(this.endpoint)],\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 protectSecrets(this, ['credentials', 'endpoint']);\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 secrets: () => [\n ...this.credentials.secretValues(),\n headers['authorization'],\n headers['x-amz-security-token'],\n ...urlSecrets(this.endpoint),\n ],\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cloudwatch.transport-D4G5xYfG.js","names":["path","os","fs","httpRequest","crypto","HttpError","BatchBuffer","urlSecrets","isPermanentHttpError","stringifyForLog","mergeCoreFields","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';\nimport { protectSecrets } from './secret-config';\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 protectSecrets(this, ['cached', 'explicit']);\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 /** Secret values this provider may hold, for scrubbing error text. */\n secretValues(): (string | undefined)[] {\n const env = process.env;\n return [\n this.explicit?.secretAccessKey,\n this.explicit?.sessionToken,\n this.cached?.secretAccessKey,\n this.cached?.sessionToken,\n env['AWS_SECRET_ACCESS_KEY'],\n env['AWS_SESSION_TOKEN'],\n ];\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 { createLogger } from 'logixia';\n * import { CloudWatchTransport } from 'logixia/cloudwatch';\n *\n * const logger = createLogger({\n * transports: {\n * custom: [\n * 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 */\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';\nimport { protectSecrets, urlSecrets } from './secret-config';\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 secrets: () => [...this.credentials.secretValues(), ...urlSecrets(this.endpoint)],\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 protectSecrets(this, ['credentials', 'endpoint']);\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 secrets: () => [\n ...this.credentials.secretValues(),\n headers['authorization'],\n headers['x-amz-security-token'],\n ...urlSecrets(this.endpoint),\n ],\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiBA,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;;AAS5B,SAAgB,SAAS,MAAsD;CAC7E,MAAM,MAA8C,CAAC;CACrD,IAAI;CACJ,KAAK,MAAM,OAAO,KAAK,MAAM,OAAO,GAAG;EACrC,MAAM,OAAO,IAAI,KAAK;EACtB,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG;EAC3D,MAAM,SAAS,aAAa,KAAK,IAAI;EACrC,IAAI,QAAQ;GACV,UAAU,IAAI,OAAO,EAAE,CAAE,KAAK,KAAK,IAAI,OAAO,EAAE,CAAE,KAAK,MAAM,CAAC;GAC9D;EACF;EACA,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,KAAK,KAAK,SAAS,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,KAAK,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK;CACrF;CACA,OAAO;AACT;AAEA,SAAS,UAAsC;CAC7C,MAAM,cAAc,QAAQ,IAAI;CAChC,MAAM,kBAAkB,QAAQ,IAAI;CACpC,IAAI,CAAC,eAAe,CAAC,iBAAiB,OAAO,KAAA;CAC7C,OAAO;EAAE;EAAa;EAAiB,cAAc,QAAQ,IAAI;CAAqB;AACxF;AAEA,SAAS,iBAA6C;CACpD,MAAM,OACJ,QAAQ,IAAI,kCAAkCA,UAAK,KAAKC,QAAG,QAAQ,GAAG,QAAQ,aAAa;CAC7F,MAAM,UAAU,QAAQ,IAAI,kBAAkB;CAC9C,IAAI;CACJ,IAAI;EACF,OAAOC,QAAG,aAAa,MAAM,MAAM;CACrC,QAAQ;EACN;CACF;CACA,MAAM,UAAU,SAAS,IAAI,CAAC,CAAC;CAC/B,MAAM,cAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAc,QAAU;CAC9B,MAAM,kBAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAkB,QAAU;CAClC,IAAI,CAAC,eAAe,CAAC,iBAAiB,OAAO,KAAA;CAC7C,OAAO;EAAE;EAAa;EAAiB,cAAA,YAAA,QAAA,YAAA,KAAA,IAAA,KAAA,IAAc,QAAU;CAAqB;AACtF;AASA,SAAS,aAAa,KAAyC;CAC7D,MAAM,MAAM,KAAK,MAAM,GAAG;CAC1B,IAAI,CAAC,IAAI,eAAe,CAAC,IAAI,iBAAiB,OAAO,KAAA;CACrD,MAAM,YAAY,IAAI,aAAa,KAAK,MAAM,IAAI,UAAU,IAAI;CAChE,OAAO;EACL,aAAa,IAAI;EACjB,iBAAiB,IAAI;EACrB,cAAc,IAAI;EAClB,WAAW,OAAO,MAAM,SAAS,IAAI,KAAA,IAAY;CACnD;AACF;AAEA,MAAM,kCAAkB,IAAI,IAAI;CAE9B;CAEA;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAS,uBAAuB,KAAmB;CACjD,IAAI,IAAI,aAAa,UAAU,OAAO;CACtC,IAAI,IAAI,aAAa,SAAS,OAAO;CACrC,OAAO,gBAAgB,IAAI,IAAI,QAAQ,KAAK,wBAAwB,KAAK,IAAI,QAAQ;AACvF;AAEA,eAAe,gBAAqD;CAClE,MAAM,WAAW,QAAQ,IAAI;CAC7B,MAAM,OAAO,QAAQ,IAAI;CACzB,IAAI,CAAC,YAAY,CAAC,MAAM,OAAO,KAAA;CAC/B,MAAM,MAAM,IAAI,IAAI,WAAW,uBAAuB,aAAc,IAAe;CACnF,IAAI,CAAC,YAAY,CAAC,uBAAuB,GAAG,GAAG;EAC7C,qBAAA,aACE,gDAAgD,IAAI,KAAK,gDAC3D;EACA;CACF;CACA,MAAM,UAAkC,CAAC;CACzC,MAAM,YAAY,QAAQ,IAAI;CAC9B,MAAM,QAAQ,YACVA,QAAG,aAAa,WAAW,MAAM,CAAC,CAAC,KAAK,IACxC,QAAQ,IAAI;CAChB,IAAI,OAAO,QAAQ,mBAAmB;CAQtC,OAAO,cAAa,MAPFC,qBAAAA,YAAY;EAC5B;EACA,QAAQ;EACR;EACA,WAAW;EACX,OAAO;CACT,CAAC,EAAA,CACuB,IAAI;AAC9B;AAEA,eAAe,uBAA4D;;CACzE,IAAI,QAAQ,IAAI,iCAAiC,QAAQ,OAAO,KAAA;CAChE,MAAM,OAAO,QAAQ,IAAI,wCAAwC;CACjE,MAAM,OAAO,GAAW,QAAgB,YACtCA,qBAAAA,YAAY;EACV,KAAK,IAAI,IAAI,GAAG,IAAI;EACpB;EACA;EACA,WAAW;EACX,OAAO;CACT,CAAC;CAIH,MAAM,OAAO,EAAE,6BAA4B,MAHrB,IAAI,qBAAqB,OAAO,EACpD,wCAAwC,QAC1C,CAAC,EAAA,CACkD,KAAK,KAAK,EAAE;CAE/D,MAAM,QAAA,sBAAO,MADO,IAAI,+CAA+C,OAAO,IAAI,EAAA,CAC/D,KAAK,MAAM,IAAI,CAAC,CAAC,QAAA,QAAA,uBAAA,KAAA,IAAA,KAAA,IAAA,mBAAI,KAAK;CAC7C,IAAI,CAAC,MAAM,OAAO,KAAA;CAMlB,OAAO,cAAa,MALA,IAClB,8CAA8C,mBAAmB,IAAI,KACrE,OACA,IACF,EAAA,CAC0B,IAAI;AAChC;AAEA,IAAa,wBAAb,MAAmC;CAKjC,YAAY,QAA6B;EAHtB,KAAA,WAAA;EAIjB,sBAAA,eAAe,MAAM,CAAC,UAAU,UAAU,CAAC;EAC3C,IAAI,OAAO,eAAe,OAAO,iBAC/B,KAAK,WAAW;GACd,aAAa,OAAO;GACpB,iBAAiB,OAAO;GACxB,cAAc,OAAO;EACvB;CAEJ;;CAGA,MAAM,MAA+B;EACnC,IAAI,KAAK,UAAU,OAAO,KAAK;EAC/B,MAAM,MAAM,QAAQ;EACpB,IAAI,KAAK,OAAO;EAChB,IAAI,KAAK,UAAU,CAAC,KAAK,WAAW,KAAK,MAAM,GAAG,OAAO,KAAK;EAC9D,IAAI,KAAK,IAAI,IAAI,KAAK,WAAW,oBAC/B,MAAM,IAAI,MAAM,6CAA6C;EAG/D,IAAI,QAAQ,eAAe;EAC3B,KAAK,MAAM,UAAU,CAAC,eAAe,oBAAoB,GAAG;GAC1D,IAAI,OAAO;GACX,IAAI;IACF,QAAQ,MAAM,OAAO;GACvB,QAAQ,CAER;EACF;EACA,IAAI,CAAC,OAAO;GACV,KAAK,WAAW,KAAK,IAAI;GACzB,MAAM,IAAI,MACR,8JACF;EACF;EACA,KAAK,SAAS;EACd,OAAO;CACT;;CAGA,eAAuC;;EACrC,MAAM,MAAM,QAAQ;EACpB,OAAO;IACL,iBAAA,KAAK,cAAA,QAAA,mBAAA,KAAA,IAAA,KAAA,IAAA,eAAU;IACf,kBAAA,KAAK,cAAA,QAAA,oBAAA,KAAA,IAAA,KAAA,IAAA,gBAAU;IACf,eAAA,KAAK,YAAA,QAAA,iBAAA,KAAA,IAAA,KAAA,IAAA,aAAQ;IACb,gBAAA,KAAK,YAAA,QAAA,kBAAA,KAAA,IAAA,KAAA,IAAA,cAAQ;GACb,IAAI;GACJ,IAAI;EACN;CACF;CAEA,aAAmB;EACjB,KAAK,SAAS,KAAA;EACd,KAAK,WAAW;CAClB;CAEA,WAAmB,GAA4B;EAC7C,OAAO,EAAE,cAAc,KAAA,KAAa,EAAE,YAAY,KAAK,IAAI,IAAI;CACjE;AACF;;;;;ACrMA,MAAM,aAAa,SAAyBC,YAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK;AAEjG,MAAM,QAAQ,KAAsB,QAClCA,YAAO,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO;;AAGtD,SAAgB,OAAO,MAA6C;CAClE,MAAM,EACJ,QACA,MACA,QAAQ,IACR,MACA,QACA,SACA,aACA,SAAS,OACT,SACE;CACJ,MAAM,WAAW,wBAAQ,IAAI,KAAK,EAAA,CAAG,YAAY,CAAC,CAAC,QAAQ,iBAAiB,EAAE;CAC9E,MAAM,YAAY,QAAQ,MAAM,GAAG,CAAC;CAEpC,MAAM,UAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,KAAK,GAAG,QAAQ,EAAE,YAAY,KAAK;CACvE,QAAQ,gBAAgB;CACxB,IAAI,YAAY,cAAc,QAAQ,0BAA0B,YAAY;CAE5E,MAAM,QAAQ,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK;CACxC,MAAM,mBAAmB,MACtB,KAAK,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAE,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG,EAAE,GAAG,CAAC,CAC/D,KAAK,EAAE;CACV,MAAM,gBAAgB,MAAM,KAAK,GAAG;CAEpC,MAAM,mBAAmB;EACvB;EACA;EACA;EACA;EACA;EACA,UAAU,IAAI;CAChB,CAAC,CAAC,KAAK,IAAI;CACX,MAAM,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ;CAChD,MAAM,eAAe;EAAC;EAAoB;EAAS;EAAO,UAAU,gBAAgB;CAAC,CAAC,CAAC,KAAK,IAAI;CAEhG,MAAM,aAAa,KACjB,KAAK,KAAK,KAAK,OAAO,YAAY,mBAAmB,SAAS,GAAG,MAAM,GAAG,OAAO,GACjF,cACF;CACA,MAAM,YAAYA,YAAO,WAAW,UAAU,UAAU,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,OAAO,KAAK;CAE3F,QAAQ,mBACN,+BAA+B,YAAY,YAAY,GAAG,MAAM,kBAC/C,cAAc,cAAc;CAC/C,OAAO;AACT;;ACmBA,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB;;AAGxB,MAAM,kCAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,aAAa,KAAkC;CACtD,IAAI,EAAE,eAAeC,qBAAAA,YAAY,OAAO,KAAA;CACxC,IAAI;EACF,MAAM,OAAQ,KAAK,MAAM,IAAI,IAAI,CAAC,CAAyB;EAC3D,OAAA,SAAA,QAAA,SAAA,KAAA,IAAA,KAAA,IAAO,KAAM,MAAM,GAAG,CAAC,CAAC,IAAI;CAC9B,QAAQ;EACN;CACF;AACF;;;;AAUA,IAAa,sBAAb,MAA4D;CAoB1D,YAAY,QAAmC;EAnBxB,KAAA,OAAA;EAOqB,KAAA,aAAA;EAGtB,KAAA,cAAA;EAUpB,KAAK,SACH,OAAO,UACP,QAAQ,IAAI,iBACZ,QAAQ,IAAI,yBACZ;EACF,KAAK,eAAe,OAAO;EAC3B,KAAK,gBACH,OAAO,iBAAiB,GAAG,QAAQ,IAAI,eAAe,OAAO,GAAG,QAAQ;EAC1E,KAAK,cAAc,IAAI,sBAAsB,MAAM;EACnD,KAAK,WAAW,IAAI,IAAI,OAAO,YAAY,gBAAgB,KAAK,OAAO,eAAe;EACtF,KAAK,YAAY,KAAK,IAAI,OAAO,aAAa,KAAK,kBAAkB;EACrE,KAAK,eAAe,OAAO,gBAAgB;EAC3C,KAAK,gBAAgB,OAAO,mBAAmB;EAC/C,KAAK,MAAM,OAAO,OAAO;EACzB,KAAK,aAAa,OAAO,aAAa,IAAI,IAAI,OAAO,UAAU,IAAI,KAAA;EACnE,KAAK,QAAQ,OAAO;EACpB,KAAK,mBAAmB,OAAO,oBAAA;EAC/B,KAAK,SAAS,IAAIC,qBAAAA,YAAwB;GACxC,MAAM;GACN,WAAW,KAAK;GAChB,eAAe,OAAO,iBAAA;GACtB,OAAO,WAAW,KAAK,aAAa,MAAM;GAC1C,eAAe,CAAC,GAAG,KAAK,YAAY,aAAa,GAAG,GAAGC,sBAAAA,WAAW,KAAK,QAAQ,CAAC;GAChF,cAAc,QACZ,CAACC,qBAAAA,qBAAqB,GAAG,KAAK,gBAAgB,IAAI,aAAa,GAAG,KAAK,EAAE;EAC7E,CAAC;EAED,KAAK,aAAa,kBAAkB;GAClC,KAAK,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;EAC7B,GAAG,KAAK,aAAa;EACrB,IAAI,KAAK,WAAW,OAAO,KAAK,WAAW,MAAM;EACjD,sBAAA,eAAe,MAAM,CAAC,eAAe,UAAU,CAAC;CAClD;CAEA,MAAM,OAAgC;EACpC,MAAM,YAAY,MAAM,YAAY,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI;EACnF,MAAM,UAAU,KAAK,MAAM,KAAK,MAAM,KAAK,IAAIC,uBAAAA,gBAAgB,KAAK;EACpE,KAAK,OAAO,KAAK;GAAE;GAAW;EAAQ,CAAC;EACvC,KAAK,OAAO,YAAY;CAC1B;CAEA,QAAuB;EACrB,OAAO,KAAK,OAAO,MAAM;CAC3B;;;;;;CAOA,MAAM,QAAuB;EAC3B,IAAI,KAAK,YAAY;GACnB,cAAc,KAAK,UAAU;GAC7B,KAAK,aAAa;EACpB;EACA,MAAM,KAAK,OAAO,MAAM;CAC1B;CAEA,MAAc,OAAkC;EAC9C,MAAM,EAAE,OAAO,SAAS,MAAM,SAAS,cAAc;EACrD,MAAM,SAASC,qBAAAA,gBAAgB;GAAE,MAAM,KAAA;GAAW;GAAO;EAAQ,GAAG,OAAO;EAC3E,MAAM,cAAwB,CAAC;EAC/B,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,GAAG;GAC3C,IAAI,MAAM,UAAU,MAAM,WAAW,MAAM,aAAa,OAAO,MAAM,UAAU;GAC/E,IAAI,KAAK,cAAc,CAAC,KAAK,WAAW,IAAI,CAAC,GAAG;GAChD,IAAI,YAAY,SAAS,iBAAiB,YAAY,KAAK,CAAC;EAC9D;EACA,OAAO,UAAU;GACf,WAAW,YAAY,IAAI,KAAK,SAAS,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI;GAChE,mBACE,YAAY,SAAS,IACjB,CACE;IACE,WAAW,KAAK;IAChB,YAAY,CAAC,CAAC,OAAO,CAAC;IACtB,SAAS,YAAY,KAAK,OAAO;KAAE,MAAM;KAAG,MAAM;IAAO,EAAE;GAC7D,CACF,IACA,CAAC;EACT;EACA,OAAOD,uBAAAA,gBAAgB,MAAM;CAC/B;CAEA,MAAc,aAAa,QAAqC;EAC9D,MAAM,KAAK,aAAa;EACxB,MAAM,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;EACtE,IAAI;GACF,MAAM,KAAK,KAAK,gBAAgB;IAC9B,cAAc,KAAK;IACnB,eAAe,KAAK;IACpB;GACF,CAAC;EACH,SAAS,KAAK;GACZ,MAAM,OAAO,aAAa,GAAG;GAC7B,IAAI,SAAS,gCAAgC;GAC7C,IAAI,SAAS,6BAA6B,KAAK,cAAc;GAC7D,IAAI,SAAS,yBAAyB,KAAK,YAAY,WAAW;GAClE,MAAM;EACR;CACF;CAEA,MAAc,eAA8B;EAC1C,IAAI,KAAK,aAAa;EACtB,IAAI;GACF,MAAM,KAAK,KAAK,kBAAkB,EAAE,cAAc,KAAK,aAAa,CAAC;EACvE,SAAS,KAAK;GACZ,IAAI,aAAa,GAAG,MAAM,yBAAyB,KAAK,YAAY,WAAW;GAC/E,IAAI,EAAE,eAAeJ,qBAAAA,YAAY,MAAM;GACvC,IAAI,aAAa,GAAG,MAAM,kCACxB,qBAAA,aAAa,2DAA2D,IAAI,QAAQ,EAAE;EAE1F;EACA,IAAI;GACF,MAAM,KAAK,KAAK,mBAAmB;IACjC,cAAc,KAAK;IACnB,eAAe,KAAK;GACtB,CAAC;EACH,SAAS,KAAK;GACZ,IAAI,aAAa,GAAG,MAAM,kCAAkC,MAAM;EACpE;EACA,KAAK,cAAc;CACrB;CAEA,MAAc,KAAK,QAAgB,SAAiD;EAClF,MAAM,cAAc,MAAM,KAAK,YAAY,IAAI;EAC/C,MAAM,OAAOI,uBAAAA,gBAAgB,OAAO;EACpC,MAAM,EAAE,SAAS,KAAK;EACtB,MAAM,UAAU,OAAO;GACrB,QAAQ;GACR,MAAM;GACN,SAAS;IACP;IACA,gBAAgB;IAChB,gBAAgB,iBAAiB;GACnC;GACA;GACA,QAAQ,KAAK;GACb,SAAS;GACT;EACF,CAAC;EACD,MAAME,qBAAAA,YAAY;GAChB,KAAK,KAAK;GACV;GACA;GACA,WAAW,KAAK;GAChB,OAAO,cAAc;GACrB,eAAe;IACb,GAAG,KAAK,YAAY,aAAa;IACjC,QAAQ;IACR,QAAQ;IACR,GAAGJ,sBAAAA,WAAW,KAAK,QAAQ;GAC7B;EACF,CAAC;CACH;AACF"}
|
package/dist/correlation.d.mts
CHANGED
|
@@ -145,6 +145,12 @@ export interface ChildFromRequestOptions {
|
|
|
145
145
|
* logger gets `child(fields)`.
|
|
146
146
|
*/
|
|
147
147
|
childContext?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Extra query parameter names redacted in the bound `url` field, on top of the
|
|
150
|
+
* defaults (`token`, `code`, `password`, `api_key`, …). A bare logger exposes no
|
|
151
|
+
* `redact.queryParams`, so pass the same list you gave `LoggerConfig.redact`.
|
|
152
|
+
*/
|
|
153
|
+
redactQueryParams?: readonly string[];
|
|
148
154
|
}
|
|
149
155
|
/**
|
|
150
156
|
* Create a child logger enriched with all correlation-relevant identifiers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlation.d.mts","names":[],"sources":["../src/correlation.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"correlation.d.mts","names":[],"sources":["../src/correlation.ts"],"mappings":";;iBA+DiB;EACf;EACA;;EAEA;;GAEC;;iBAGc;;;;;EAKf;;;;;EAKA;;;;;EAKA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;wBAec;;;;;wBAwBA;;;;;;;;;;;;;;wBAkBA,kBAAkB,GAAG,mCAAmC,gBAAgB,IAAI;;;;;;;;;;;;;;wBAoB5E,sBAAsB,UAAS,gCAU3C,KAAK,kBACL,KAAK,mBACL;;;;;;;;;;;wBAkCY,uBAAuB,UAAS,gCAU5C,SAAS,kBACT,OAAO,mBACP;;;;;;;;;;;;;;;;;;wBAyCkB,iBACpB,gBAAgB,MAAM,SACtB,OAAM,aACN;EAAW;IACV,QAAQ;;;;;;;;;;;;;wBAwCK,kCACd;EAAW;KACT,QAAQ,4BAA4B;iBAuBvB;;EAEf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2Bc,iBAAiB;EAAkB,SAAS,gBAAgB;GAC1E,KAAK,kBACL,QAAQ,SACR,UAAS,0BACR;;;;;;;;;;;;;;;;;;;wBAsFa,4BAA4B,SAAS;;;;;;;;;;;;;;wBAyCrC,gCAAgC"}
|
package/dist/correlation.d.ts
CHANGED
|
@@ -145,6 +145,12 @@ export interface ChildFromRequestOptions {
|
|
|
145
145
|
* logger gets `child(fields)`.
|
|
146
146
|
*/
|
|
147
147
|
childContext?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Extra query parameter names redacted in the bound `url` field, on top of the
|
|
150
|
+
* defaults (`token`, `code`, `password`, `api_key`, …). A bare logger exposes no
|
|
151
|
+
* `redact.queryParams`, so pass the same list you gave `LoggerConfig.redact`.
|
|
152
|
+
*/
|
|
153
|
+
redactQueryParams?: readonly string[];
|
|
148
154
|
}
|
|
149
155
|
/**
|
|
150
156
|
* Create a child logger enriched with all correlation-relevant identifiers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlation.d.ts","names":[],"sources":["../src/correlation.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"correlation.d.ts","names":[],"sources":["../src/correlation.ts"],"mappings":";;iBA+DiB;EACf;EACA;;EAEA;;GAEC;;iBAGc;;;;;EAKf;;;;;EAKA;;;;;EAKA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;wBAec;;;;;wBAwBA;;;;;;;;;;;;;;wBAkBA,kBAAkB,GAAG,mCAAmC,gBAAgB,IAAI;;;;;;;;;;;;;;wBAoB5E,sBAAsB,UAAS,gCAU3C,KAAK,kBACL,KAAK,mBACL;;;;;;;;;;;wBAkCY,uBAAuB,UAAS,gCAU5C,SAAS,kBACT,OAAO,mBACP;;;;;;;;;;;;;;;;;;wBAyCkB,iBACpB,gBAAgB,MAAM,SACtB,OAAM,aACN;EAAW;IACV,QAAQ;;;;;;;;;;;;;wBAwCK,kCACd;EAAW;KACT,QAAQ,4BAA4B;iBAuBvB;;EAEf;;;;;;;EAOA;;;;;;;EAOA;;;;;;EAMA;;;;;;;;;;;;;;;;;;;;;;;;;;wBA2Bc,iBAAiB;EAAkB,SAAS,gBAAgB;GAC1E,KAAK,kBACL,QAAQ,SACR,UAAS,0BACR;;;;;;;;;;;;;;;;;;;wBAsFa,4BAA4B,SAAS;;;;;;;;;;;;;;wBAyCrC,gCAAgC"}
|
package/dist/correlation.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_sensitive_names = require("./sensitive-names-CHMowujD.js");
|
|
2
3
|
const require_request_trace = require("./request-trace-ByujsUZk.js");
|
|
3
|
-
const require_async_context = require("./async-context-
|
|
4
|
+
const require_async_context = require("./async-context-Dv0bmPC-.js");
|
|
4
5
|
let node_crypto = require("node:crypto");
|
|
5
6
|
// src/correlation.ts
|
|
6
7
|
/**
|
|
@@ -245,7 +246,8 @@ function childFromRequest(req, logger, options = {}) {
|
|
|
245
246
|
const headers = req.headers ?? {};
|
|
246
247
|
const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
|
|
247
248
|
const method = req.method;
|
|
248
|
-
const
|
|
249
|
+
const rawUrl = req.originalUrl ?? req.url;
|
|
250
|
+
const url = rawUrl === void 0 ? void 0 : require_sensitive_names.redactUrlQuery(rawUrl, require_sensitive_names.buildNameSet(require_sensitive_names.DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams));
|
|
249
251
|
const rawUserAgent = headers["user-agent"];
|
|
250
252
|
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : void 0;
|
|
251
253
|
const ip = resolveClientIp(req, headers, options.trustProxy === true);
|
package/dist/correlation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"correlation.js","names":["randomUUID","sanitizeIncomingId","LogixiaContext"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport type { HttpRequestShape, HttpResponseShape } from './types/http-shapes';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: HttpRequestShape,\n res: HttpResponseShape,\n next: () => void\n ): void {\n const headers = req.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n res.setHeader?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: HttpRequestShape,\n reply: HttpResponseShape,\n done: () => void\n ): void {\n const headers = request.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n reply.header?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: HttpRequestShape,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = req.headers ?? {};\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req.method;\n const url = req.url ?? req.originalUrl;\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: HttpRequestShape,\n headers: Record<string, string | string[] | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req.ip;\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = req.socket?.remoteAddress;\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"correlation.js","names":["randomUUID","sanitizeIncomingId","LogixiaContext","redactUrlQuery","buildNameSet","DEFAULT_REDACT_QUERY_PARAMS"],"sources":["../src/correlation.ts"],"sourcesContent":["/**\n * logixia — Microservices Correlation ID Propagation\n *\n * Provides zero-boilerplate correlation ID generation and propagation for\n * distributed systems. A single correlation ID travels through every service\n * in a request fan-out, making it trivial to join logs across services.\n *\n * Features:\n * - UUID v4 correlation ID generation (via `crypto.randomUUID()`)\n * - Express / Fastify / NestJS incoming-request middleware\n * - `correlationFetch()` — wraps global `fetch`, auto-injects `X-Correlation-ID`\n * - `createCorrelationAxiosInterceptor()` — axios request interceptor\n * - `childFromRequest(req, logger)` — child logger with all request identifiers\n * - Kafka / SQS / RabbitMQ message context helpers\n * - Integration with `LogixiaContext` (AsyncLocalStorage) so every `logger.info()`\n * call automatically includes the correlationId without per-call passing.\n *\n * @example Express middleware\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use(correlationMiddleware());\n *\n * app.get('/orders', (req, res) => {\n * // correlationId is automatically in every log inside this request\n * logger.info('Listing orders');\n * res.json({ ok: true });\n * });\n * ```\n *\n * @example Outbound fetch with correlation\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Reads correlationId from current LogixiaContext and injects it as header\n * const data = await correlationFetch('https://payments.internal/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n *\n * @example Manual correlation context\n * ```ts\n * import { withCorrelationId } from 'logixia/correlation';\n *\n * await withCorrelationId('trace-abc-123', async () => {\n * // All logs and outbound fetch calls in here carry 'trace-abc-123'\n * await processOrder();\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\n\nimport { LogixiaContext } from './context/async-context';\nimport type { HttpRequestShape, HttpResponseShape } from './types/http-shapes';\nimport { buildNameSet, DEFAULT_REDACT_QUERY_PARAMS, redactUrlQuery } from './utils/sensitive-names';\nimport { sanitizeIncomingId } from './utils/trace.utils';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\n/** Fields injected into the log context by the correlation middleware. */\nexport interface CorrelationContext {\n correlationId: string;\n traceId?: string;\n /** Service that originated the request (from `X-Origin-Service` header). */\n originService?: string;\n /** Index signature so CorrelationContext is assignable to LogContext. */\n [key: string]: unknown;\n}\n\nexport interface CorrelationMiddlewareOptions {\n /**\n * HTTP header to read/write the correlation ID from/to.\n * @default 'x-correlation-id'\n */\n header?: string;\n /**\n * HTTP header for the originating service name.\n * @default 'x-origin-service'\n */\n originServiceHeader?: string;\n /**\n * Function to generate a new correlation ID when none is present in the request.\n * @default () => crypto.randomUUID()\n */\n generate?: () => string;\n /**\n * When `false`, the incoming correlation header is ignored and a new ID is\n * always generated. Use this at the edge when client-supplied IDs should not\n * be trusted.\n * @default true\n */\n trustIncoming?: boolean;\n /**\n * When `true`, the response will include the correlation ID header so\n * clients can log it client-side.\n * @default true\n */\n setResponseHeader?: boolean;\n}\n\n// ── ID generation ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a new correlation ID using `crypto.randomUUID()` (available in\n * Node.js 14.17+, all modern browsers, and Edge runtimes).\n *\n * When the Web Crypto global is unavailable (e.g. Node 18 without\n * `--experimental-global-webcrypto`), fall back to `node:crypto`'s\n * `randomUUID`, which returns a proper UUID v4 — matching the shape this\n * function's JSDoc and README promise. `logixia/correlation` is Node-only\n * (it builds on `AsyncLocalStorage`), so importing `node:crypto` is safe here.\n */\nexport function generateCorrelationId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return randomUUID();\n}\n\n/**\n * Return an incoming correlation ID only if it is short and made of safe\n * characters. Anything else (over-long, empty, or containing newlines or other\n * control characters) is dropped so the caller generates a fresh ID instead of\n * writing client-supplied text into every log line.\n */\nfunction validIncomingId(value: unknown): string | undefined {\n if (typeof value === 'string' && value.trim() !== value) return undefined;\n return sanitizeIncomingId(value);\n}\n\n// ── Context helpers ───────────────────────────────────────────────────────────\n\n/**\n * Return the correlation ID from the current `LogixiaContext`, or `undefined`\n * when called outside any context scope.\n */\nexport function getCurrentCorrelationId(): string | undefined {\n const ctx = LogixiaContext.get();\n return ctx?.correlationId as string | undefined;\n}\n\n/**\n * Run `callback` inside a correlation context scope.\n *\n * If a correlation ID is not provided, a new UUID is generated.\n *\n * @example\n * ```ts\n * await withCorrelationId('cid-abc', async () => {\n * logger.info('Processing message'); // → { correlationId: 'cid-abc', ... }\n * await callDownstream(); // correlationFetch() picks up cid-abc\n * });\n * ```\n */\nexport function withCorrelationId<T>(correlationId: string | undefined, callback: () => T): T {\n const id = correlationId ?? generateCorrelationId();\n return LogixiaContext.run({ correlationId: id }, callback);\n}\n\n// ── Express / Connect middleware ──────────────────────────────────────────────\n\n/**\n * Express-compatible middleware that:\n * 1. Reads `X-Correlation-ID` from the incoming request (or generates a new one)\n * 2. Wraps the request in a `LogixiaContext` so all loggers in the handler\n * automatically include `correlationId`\n * 3. Writes the correlation ID back to the response header (opt-out via options)\n *\n * @example\n * ```ts\n * import { correlationMiddleware } from 'logixia/correlation';\n * app.use(correlationMiddleware());\n * ```\n */\nexport function correlationMiddleware(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationMiddleware(\n req: HttpRequestShape,\n res: HttpResponseShape,\n next: () => void\n ): void {\n const headers = req.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n res.setHeader?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, next);\n };\n}\n\n// ── Fastify hook ──────────────────────────────────────────────────────────────\n\n/**\n * Fastify `onRequest` lifecycle hook — same functionality as `correlationMiddleware`\n * but adapted to Fastify's hook API.\n *\n * @example\n * ```ts\n * import { correlationFastifyHook } from 'logixia/correlation';\n * fastify.addHook('onRequest', correlationFastifyHook());\n * ```\n */\nexport function correlationFastifyHook(options: CorrelationMiddlewareOptions = {}) {\n const {\n header = 'x-correlation-id',\n originServiceHeader = 'x-origin-service',\n generate = generateCorrelationId,\n trustIncoming = true,\n setResponseHeader = true,\n } = options;\n\n return function logixiaCorrelationFastifyHook(\n request: HttpRequestShape,\n reply: HttpResponseShape,\n done: () => void\n ): void {\n const headers = request.headers ?? {};\n\n // Node lower-cases incoming header names, so look up with lower-cased keys.\n const incomingId = trustIncoming ? validIncomingId(headers[header.toLowerCase()]) : undefined;\n const correlationId = incomingId ?? generate();\n const originService = validIncomingId(headers[originServiceHeader.toLowerCase()]);\n\n if (setResponseHeader) {\n reply.header?.(header, correlationId);\n }\n\n const ctx: CorrelationContext = {\n correlationId,\n ...(originService !== undefined ? { originService } : {}),\n };\n\n LogixiaContext.run(ctx, done);\n };\n}\n\n// ── Outbound fetch wrapper ────────────────────────────────────────────────────\n\n/**\n * A drop-in replacement for `fetch` that automatically injects the current\n * `correlationId` from `LogixiaContext` as an `X-Correlation-ID` request header.\n *\n * Falls back to the global `fetch` when no correlation context is active.\n *\n * @example\n * ```ts\n * import { correlationFetch } from 'logixia/correlation';\n *\n * // Inside a request handler with correlationMiddleware active:\n * const response = await correlationFetch('https://payments.internal/v1/charge', {\n * method: 'POST',\n * body: JSON.stringify({ amount: 100 }),\n * });\n * ```\n */\nexport async function correlationFetch(\n input: string | URL | Request,\n init: RequestInit = {},\n options: { header?: string } = {}\n): Promise<Response> {\n const header = options.header ?? 'x-correlation-id';\n const correlationId = getCurrentCorrelationId();\n\n // `init.headers` can be a plain object, a `Headers` instance, or an array of\n // `[name, value]` tuples. Spreading it into a plain object loses everything\n // but the object form: a `Headers` instance has no own enumerable properties,\n // and an array spread yields numeric indices. `Headers` accepts all three, is\n // case-insensitive, and is the shape `fetch` expects back.\n //\n // A `Request` carries headers of its own, and `fetch(request, { headers })`\n // replaces them rather than merging them, so they are seeded first and the\n // caller's `init.headers` is applied on top. Without this, accepting a\n // `Request` here would drop its headers, which is the same defect one input\n // shape over.\n const headers = new Headers(input instanceof Request ? input.headers : undefined);\n for (const [name, value] of new Headers(init.headers)) {\n headers.set(name, value);\n }\n if (correlationId && !headers.has(header)) {\n headers.set(header, correlationId);\n }\n\n return globalThis.fetch(input, { ...init, headers });\n}\n\n// ── Axios interceptor factory ─────────────────────────────────────────────────\n\n/**\n * Returns an axios request interceptor that automatically injects the current\n * correlation ID into outgoing axios requests.\n *\n * @example\n * ```ts\n * import axios from 'axios';\n * import { createCorrelationAxiosInterceptor } from 'logixia/correlation';\n *\n * axios.interceptors.request.use(createCorrelationAxiosInterceptor());\n * ```\n */\nexport function createCorrelationAxiosInterceptor(\n options: { header?: string } = {}\n): (config: Record<string, unknown>) => Record<string, unknown> {\n const header = options.header ?? 'x-correlation-id';\n\n return function correlationAxiosInterceptor(\n config: Record<string, unknown>\n ): Record<string, unknown> {\n const correlationId = getCurrentCorrelationId();\n if (!correlationId) return config;\n\n const headers = (config['headers'] ?? {}) as Record<string, unknown>;\n if (!headers[header]) {\n headers[header] = correlationId;\n }\n\n return { ...config, headers };\n };\n}\n\n// ── Child logger from request ─────────────────────────────────────────────────\n\nconst MAX_USER_AGENT_LENGTH = 512;\nconst MAX_IP_LENGTH = 64;\n\nexport interface ChildFromRequestOptions {\n /** Header to read the correlation ID from. @default 'x-correlation-id' */\n correlationHeader?: string;\n /**\n * Take the client IP from the first `X-Forwarded-For` entry. Only enable this\n * when the app sits behind a proxy that overwrites the header; otherwise any\n * client can choose the logged IP.\n * @default false\n */\n trustProxy?: boolean;\n /**\n * Context name passed as the first argument to `logger.child(context, data)`.\n * When omitted, logixia loggers (detected by their `getContext` /\n * `getAvailableTransports` methods) get `child('request', fields)` and any other\n * logger gets `child(fields)`.\n */\n childContext?: string;\n /**\n * Extra query parameter names redacted in the bound `url` field, on top of the\n * defaults (`token`, `code`, `password`, `api_key`, …). A bare logger exposes no\n * `redact.queryParams`, so pass the same list you gave `LoggerConfig.redact`.\n */\n redactQueryParams?: readonly string[];\n}\n\n/**\n * Create a child logger enriched with all correlation-relevant identifiers\n * extracted from the incoming HTTP request.\n *\n * The child logger's every call automatically includes:\n * `correlationId`, `method`, `url`, `userAgent`, `ip`\n *\n * The IP comes from `req.ip` / the socket address unless `trustProxy` is set.\n *\n * @example\n * ```ts\n * import { childFromRequest } from 'logixia/correlation';\n * import { logger } from 'logixia';\n *\n * app.use((req, res, next) => {\n * req.logger = childFromRequest(req, logger);\n * next();\n * });\n *\n * app.get('/orders', (req, res) => {\n * req.logger.info('Listing orders'); // → correlationId, requestId, method, url ...\n * });\n * ```\n */\nexport function childFromRequest<TLogger extends { child(...args: never[]): TLogger }>(\n req: HttpRequestShape,\n logger: TLogger,\n options: ChildFromRequestOptions = {}\n): TLogger {\n const correlationHeader = options.correlationHeader ?? 'x-correlation-id';\n\n const headers = req.headers ?? {};\n // Node lower-cases incoming header names, so look up with a lower-cased key.\n const correlationId =\n validIncomingId(headers[correlationHeader.toLowerCase()]) ??\n getCurrentCorrelationId() ??\n generateCorrelationId();\n\n const method = req.method;\n const rawUrl = req.originalUrl ?? req.url;\n const url =\n rawUrl === undefined\n ? undefined\n : redactUrlQuery(\n rawUrl,\n buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams)\n );\n const rawUserAgent = headers['user-agent'];\n const userAgent =\n typeof rawUserAgent === 'string' ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : undefined;\n const ip = resolveClientIp(req, headers, options.trustProxy === true);\n\n const fields = {\n correlationId,\n ...(method !== undefined ? { method } : {}),\n ...(url !== undefined ? { url } : {}),\n ...(userAgent !== undefined ? { userAgent } : {}),\n ...(ip !== undefined ? { ip } : {}),\n };\n\n const child = logger.child as (...args: unknown[]) => TLogger;\n if (options.childContext !== undefined) {\n return child.call(logger, options.childContext, fields);\n }\n // logixia's ILogger.child(context: string, data?) takes a string namespace;\n // pino/winston-style loggers take a bindings object.\n if (usesStringContext(logger)) {\n return child.call(logger, 'request', fields);\n }\n return child.call(logger, fields);\n}\n\n/** ILogger implementations (LogixiaLogger, LogixiaLoggerService, mock logger) expose these methods. */\nfunction usesStringContext(logger: object): boolean {\n const l = logger as Record<string, unknown>;\n return typeof l['getContext'] === 'function' || typeof l['getAvailableTransports'] === 'function';\n}\n\nfunction resolveClientIp(\n req: HttpRequestShape,\n headers: Record<string, string | string[] | undefined>,\n trustProxy: boolean\n): string | undefined {\n if (trustProxy) {\n const forwarded = headers['x-forwarded-for'];\n const first = typeof forwarded === 'string' ? forwarded.split(',')[0]?.trim() : undefined;\n if (first) return first.slice(0, MAX_IP_LENGTH);\n }\n const fromReq = req.ip;\n if (typeof fromReq === 'string' && fromReq) return fromReq.slice(0, MAX_IP_LENGTH);\n const remote = req.socket?.remoteAddress;\n return typeof remote === 'string' ? remote.slice(0, MAX_IP_LENGTH) : undefined;\n}\n\n// ── Message queue helpers ─────────────────────────────────────────────────────\n\n/**\n * Extract a correlation context from a Kafka / RabbitMQ / SQS message object.\n *\n * Looks for `correlationId`, `correlation_id`, or `x-correlation-id` in\n * `message.headers` (Kafka) or `message.MessageAttributes` (SQS).\n *\n * @example Kafka consumer\n * ```ts\n * import { extractMessageCorrelationId, withCorrelationId } from 'logixia/correlation';\n *\n * consumer.run({\n * eachMessage: async ({ message }) => {\n * const correlationId = extractMessageCorrelationId(message);\n * await withCorrelationId(correlationId, () => processMessage(message));\n * },\n * });\n * ```\n */\nexport function extractMessageCorrelationId(message: Record<string, unknown>): string | undefined {\n // Kafka: message.headers is Record<string, Buffer | string>\n const kafkaHeaders = message['headers'] as Record<string, Buffer | string> | undefined;\n if (kafkaHeaders) {\n const raw =\n kafkaHeaders['x-correlation-id'] ??\n kafkaHeaders['correlationId'] ??\n kafkaHeaders['correlation_id'];\n if (raw !== undefined) {\n return validIncomingId(Buffer.isBuffer(raw) ? raw.toString('utf8', 0, 512) : String(raw));\n }\n }\n\n // SQS: message.MessageAttributes\n const sqsAttrs = message['MessageAttributes'] as\n Record<string, { StringValue?: string }> | undefined;\n if (sqsAttrs) {\n return validIncomingId(\n sqsAttrs['x-correlation-id']?.StringValue ??\n sqsAttrs['correlationId']?.StringValue ??\n sqsAttrs['correlation_id']?.StringValue\n );\n }\n\n // AMQP / generic envelope\n return validIncomingId(message['correlationId'] ?? message['correlation_id']);\n}\n\n/**\n * Build Kafka message headers containing the current correlation context.\n *\n * @example\n * ```ts\n * import { buildKafkaCorrelationHeaders } from 'logixia/correlation';\n *\n * await producer.send({\n * topic: 'orders',\n * messages: [{ value: JSON.stringify(order), headers: buildKafkaCorrelationHeaders() }],\n * });\n * ```\n */\nexport function buildKafkaCorrelationHeaders(): Record<string, string> {\n const ctx = LogixiaContext.get();\n const headers: Record<string, string> = {};\n\n const correlationId = ctx?.correlationId as string | undefined;\n if (correlationId) headers['x-correlation-id'] = correlationId;\n\n const traceId = ctx?.traceId as string | undefined;\n if (traceId) headers['x-trace-id'] = traceId;\n\n return headers;\n}\n"],"mappings":"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"}
|
package/dist/correlation.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { a as buildNameSet, n as DEFAULT_REDACT_QUERY_PARAMS, o as redactUrlQuery } from "./sensitive-names-o__1PUUR.mjs";
|
|
1
2
|
import { p as sanitizeIncomingId } from "./request-trace-BgNykH9M.mjs";
|
|
2
|
-
import { t as LogixiaContext } from "./async-context-
|
|
3
|
+
import { t as LogixiaContext } from "./async-context-COuvmwsS.mjs";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
// src/correlation.ts
|
|
5
6
|
/**
|
|
@@ -244,7 +245,8 @@ function childFromRequest(req, logger, options = {}) {
|
|
|
244
245
|
const headers = req.headers ?? {};
|
|
245
246
|
const correlationId = validIncomingId(headers[correlationHeader.toLowerCase()]) ?? getCurrentCorrelationId() ?? generateCorrelationId();
|
|
246
247
|
const method = req.method;
|
|
247
|
-
const
|
|
248
|
+
const rawUrl = req.originalUrl ?? req.url;
|
|
249
|
+
const url = rawUrl === void 0 ? void 0 : redactUrlQuery(rawUrl, buildNameSet(DEFAULT_REDACT_QUERY_PARAMS, options.redactQueryParams));
|
|
248
250
|
const rawUserAgent = headers["user-agent"];
|
|
249
251
|
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent.slice(0, MAX_USER_AGENT_LENGTH) : void 0;
|
|
250
252
|
const ip = resolveClientIp(req, headers, options.trustProxy === true);
|