@kids-reporter/logger 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.js +101 -101
- package/dist/utils.js.map +1 -1
- package/package.json +13 -4
- package/eslint.config.mjs +0 -22
- package/src/index.ts +0 -2
- package/src/types.ts +0 -27
- package/src/utils.ts +0 -180
- package/tsconfig.json +0 -17
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export * from './types.js'\nexport * from './utils.js'\n"],"mappings":"AAAA,cAAc,YAAY;AAC1B,cAAc,YAAY","ignoreList":[]}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["export type LogSeverity =\n | 'DEBUG'\n | 'INFO'\n | 'NOTICE'\n | 'WARNING'\n | 'ERROR'\n | 'ALERT'\n | 'CRITICAL'\n\nexport type StructuredLogPayload = {\n severity: LogSeverity\n message?: string\n} & Record<string, unknown>\n\n/** Supports Express req.headers where values can be string | string[]. */\nexport type TraceHeaderInput =\n | Headers\n | Record<string, string | string[] | undefined | unknown>\n | undefined\n\n/** Trace context for GCP Cloud Logging correlation. Only traceId is propagated. */\nexport type NormalizedTraceContext = {\n traceId: string\n traceHeaders: {\n 'X-Cloud-Trace-Context': string\n }\n}\n"],"mappings":"","ignoreList":[]}
|
package/dist/utils.js
CHANGED
|
@@ -1,129 +1,129 @@
|
|
|
1
1
|
const XCLOUD_TRACE_HEADER = 'x-cloud-trace-context';
|
|
2
2
|
const TRACE_ID_REGEX = /^[a-f0-9]{32}$/i;
|
|
3
3
|
const CONSOLE_BY_SEVERITY = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
ALERT: 'error',
|
|
5
|
+
CRITICAL: 'error',
|
|
6
|
+
ERROR: 'error',
|
|
7
|
+
WARNING: 'warn',
|
|
8
|
+
NOTICE: 'log',
|
|
9
|
+
INFO: 'log',
|
|
10
|
+
DEBUG: 'log'
|
|
11
11
|
};
|
|
12
12
|
function normalizeHeaderValue(value) {
|
|
13
|
-
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
if (typeof value === 'string') {
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
if (Array.isArray(value) &&
|
|
20
|
-
value.length > 0 &&
|
|
21
|
-
typeof value[0] === 'string') {
|
|
22
|
-
return value[0];
|
|
23
|
-
}
|
|
13
|
+
if (value === undefined || value === null) {
|
|
24
14
|
return undefined;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'string') {
|
|
20
|
+
return value[0];
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
25
23
|
}
|
|
26
24
|
function getHeaderValue(input, headerName) {
|
|
27
|
-
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
if (typeof Headers !== 'undefined' && input instanceof Headers) {
|
|
31
|
-
return input.get(headerName) || undefined;
|
|
32
|
-
}
|
|
33
|
-
const lowered = headerName.toLowerCase();
|
|
34
|
-
for (const [key, value] of Object.entries(input)) {
|
|
35
|
-
if (key.toLowerCase() === lowered) {
|
|
36
|
-
return normalizeHeaderValue(value);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
25
|
+
if (!input) {
|
|
39
26
|
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (typeof Headers !== 'undefined' && input instanceof Headers) {
|
|
29
|
+
return input.get(headerName) || undefined;
|
|
30
|
+
}
|
|
31
|
+
const lowered = headerName.toLowerCase();
|
|
32
|
+
for (const [key, value] of Object.entries(input)) {
|
|
33
|
+
if (key.toLowerCase() === lowered) {
|
|
34
|
+
return normalizeHeaderValue(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
40
38
|
}
|
|
41
39
|
function getRandomHex(length) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const bytes = new Uint8Array(Math.ceil(length / 2));
|
|
41
|
+
if (globalThis.crypto && typeof globalThis.crypto.getRandomValues === 'function') {
|
|
42
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
43
|
+
} else {
|
|
44
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
45
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
bytes[i] = Math.floor(Math.random() * 256);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return Array.from(bytes, (value) => value.toString(16).padStart(2, '0'))
|
|
53
|
-
.join('')
|
|
54
|
-
.slice(0, length);
|
|
47
|
+
}
|
|
48
|
+
return Array.from(bytes, value => value.toString(16).padStart(2, '0')).join('').slice(0, length);
|
|
55
49
|
}
|
|
50
|
+
|
|
56
51
|
/**
|
|
57
52
|
* Parses traceId from GCP X-Cloud-Trace-Context.
|
|
58
53
|
* Accepts "traceId;o=1" or "traceId/spanId;o=1" (spanId is ignored).
|
|
59
54
|
*/
|
|
60
55
|
function parseXCloudTraceContext(xCloudTraceContext) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
if (!xCloudTraceContext) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const [traceAndSpan] = xCloudTraceContext.trim().split(';');
|
|
60
|
+
const traceId = traceAndSpan.split('/')[0]?.trim();
|
|
61
|
+
if (!traceId || !TRACE_ID_REGEX.test(traceId)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return traceId.toLowerCase();
|
|
70
65
|
}
|
|
66
|
+
|
|
67
|
+
// Overloads: when generateIfMissing is true, return is always NormalizedTraceContext
|
|
68
|
+
/* eslint-disable no-redeclare -- overload signatures + implementation */
|
|
69
|
+
|
|
71
70
|
export function normalizeTraceContext(headersInput, options = {}) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
/* eslint-enable no-redeclare */
|
|
72
|
+
const shouldGenerate = options.generateIfMissing !== false;
|
|
73
|
+
const xCloudTraceContext = getHeaderValue(headersInput, XCLOUD_TRACE_HEADER);
|
|
74
|
+
const traceId = parseXCloudTraceContext(xCloudTraceContext);
|
|
75
|
+
if (!traceId && !shouldGenerate) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const resolvedTraceId = traceId ?? getRandomHex(32);
|
|
79
|
+
const xCloudValue = `${resolvedTraceId};o=1`;
|
|
80
|
+
return {
|
|
81
|
+
traceId: resolvedTraceId,
|
|
82
|
+
traceHeaders: {
|
|
83
|
+
'X-Cloud-Trace-Context': xCloudValue
|
|
78
84
|
}
|
|
79
|
-
|
|
80
|
-
const xCloudValue = `${resolvedTraceId};o=1`;
|
|
81
|
-
return {
|
|
82
|
-
traceId: resolvedTraceId,
|
|
83
|
-
traceHeaders: {
|
|
84
|
-
'X-Cloud-Trace-Context': xCloudValue,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
85
|
+
};
|
|
87
86
|
}
|
|
88
|
-
export function getGcpTraceField({
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
export function getGcpTraceField({
|
|
88
|
+
projectId,
|
|
89
|
+
traceId
|
|
90
|
+
}) {
|
|
91
|
+
if (!projectId || !traceId) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return `projects/${projectId}/traces/${traceId}`;
|
|
93
95
|
}
|
|
94
96
|
export function getTraceLogFields(headersInput, options = {}) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
97
|
+
const traceContext = normalizeTraceContext(headersInput, {
|
|
98
|
+
generateIfMissing: options.generateIfMissing ?? false
|
|
99
|
+
});
|
|
100
|
+
if (!traceContext) {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
const projectId = options.projectId || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCP_PROJECT;
|
|
104
|
+
const traceField = getGcpTraceField({
|
|
105
|
+
projectId,
|
|
106
|
+
traceId: traceContext.traceId
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
...(traceField ? {
|
|
110
|
+
'logging.googleapis.com/trace': traceField
|
|
111
|
+
} : {}),
|
|
112
|
+
traceId: traceContext.traceId
|
|
113
|
+
};
|
|
112
114
|
}
|
|
113
115
|
export function emitStructured(payload) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
console.log(message);
|
|
116
|
+
const severity = typeof payload?.severity === 'string' ? payload.severity.toUpperCase() : 'INFO';
|
|
117
|
+
const method = CONSOLE_BY_SEVERITY[severity] || 'log';
|
|
118
|
+
const message = JSON.stringify(payload);
|
|
119
|
+
if (method === 'error') {
|
|
120
|
+
console.error(message);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (method === 'warn') {
|
|
124
|
+
console.warn(message);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
console.log(message);
|
|
128
128
|
}
|
|
129
129
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","
|
|
1
|
+
{"version":3,"file":"utils.js","names":["XCLOUD_TRACE_HEADER","TRACE_ID_REGEX","CONSOLE_BY_SEVERITY","ALERT","CRITICAL","ERROR","WARNING","NOTICE","INFO","DEBUG","normalizeHeaderValue","value","undefined","Array","isArray","length","getHeaderValue","input","headerName","Headers","get","lowered","toLowerCase","key","Object","entries","getRandomHex","bytes","Uint8Array","Math","ceil","globalThis","crypto","getRandomValues","i","floor","random","from","toString","padStart","join","slice","parseXCloudTraceContext","xCloudTraceContext","traceAndSpan","trim","split","traceId","test","normalizeTraceContext","headersInput","options","shouldGenerate","generateIfMissing","resolvedTraceId","xCloudValue","traceHeaders","getGcpTraceField","projectId","getTraceLogFields","traceContext","process","env","GOOGLE_CLOUD_PROJECT","GCP_PROJECT","traceField","emitStructured","payload","severity","toUpperCase","method","message","JSON","stringify","console","error","warn","log"],"sources":["../src/utils.ts"],"sourcesContent":["import {\n NormalizedTraceContext,\n StructuredLogPayload,\n TraceHeaderInput,\n} from './types.js'\n\nconst XCLOUD_TRACE_HEADER = 'x-cloud-trace-context'\nconst TRACE_ID_REGEX = /^[a-f0-9]{32}$/i\n\nconst CONSOLE_BY_SEVERITY: Record<string, 'log' | 'warn' | 'error'> = {\n ALERT: 'error',\n CRITICAL: 'error',\n ERROR: 'error',\n WARNING: 'warn',\n NOTICE: 'log',\n INFO: 'log',\n DEBUG: 'log',\n}\n\nfunction normalizeHeaderValue(\n value: string | string[] | undefined | unknown\n): string | undefined {\n if (value === undefined || value === null) {\n return undefined\n }\n if (typeof value === 'string') {\n return value\n }\n if (\n Array.isArray(value) &&\n value.length > 0 &&\n typeof value[0] === 'string'\n ) {\n return value[0]\n }\n return undefined\n}\n\nfunction getHeaderValue(input: TraceHeaderInput, headerName: string) {\n if (!input) {\n return undefined\n }\n if (typeof Headers !== 'undefined' && input instanceof Headers) {\n return input.get(headerName) || undefined\n }\n const lowered = headerName.toLowerCase()\n for (const [key, value] of Object.entries(input)) {\n if (key.toLowerCase() === lowered) {\n return normalizeHeaderValue(value)\n }\n }\n return undefined\n}\n\nfunction getRandomHex(length: number) {\n const bytes = new Uint8Array(Math.ceil(length / 2))\n if (\n globalThis.crypto &&\n typeof globalThis.crypto.getRandomValues === 'function'\n ) {\n globalThis.crypto.getRandomValues(bytes)\n } else {\n for (let i = 0; i < bytes.length; i += 1) {\n bytes[i] = Math.floor(Math.random() * 256)\n }\n }\n return Array.from(bytes, (value) => value.toString(16).padStart(2, '0'))\n .join('')\n .slice(0, length)\n}\n\n/**\n * Parses traceId from GCP X-Cloud-Trace-Context.\n * Accepts \"traceId;o=1\" or \"traceId/spanId;o=1\" (spanId is ignored).\n */\nfunction parseXCloudTraceContext(\n xCloudTraceContext?: string\n): string | undefined {\n if (!xCloudTraceContext) {\n return undefined\n }\n const [traceAndSpan] = xCloudTraceContext.trim().split(';')\n const traceId = traceAndSpan.split('/')[0]?.trim()\n if (!traceId || !TRACE_ID_REGEX.test(traceId)) {\n return undefined\n }\n return traceId.toLowerCase()\n}\n\n// Overloads: when generateIfMissing is true, return is always NormalizedTraceContext\n/* eslint-disable no-redeclare -- overload signatures + implementation */\nexport function normalizeTraceContext(\n headersInput: TraceHeaderInput | undefined,\n options: { generateIfMissing: true }\n): NormalizedTraceContext\nexport function normalizeTraceContext(\n headersInput?: TraceHeaderInput,\n options?: { generateIfMissing?: boolean }\n): NormalizedTraceContext | undefined\nexport function normalizeTraceContext(\n headersInput?: TraceHeaderInput,\n options: { generateIfMissing?: boolean } = {}\n) {\n /* eslint-enable no-redeclare */\n const shouldGenerate = options.generateIfMissing !== false\n const xCloudTraceContext = getHeaderValue(headersInput, XCLOUD_TRACE_HEADER)\n const traceId = parseXCloudTraceContext(xCloudTraceContext)\n\n if (!traceId && !shouldGenerate) {\n return undefined\n }\n\n const resolvedTraceId = traceId ?? getRandomHex(32)\n const xCloudValue = `${resolvedTraceId};o=1`\n\n return {\n traceId: resolvedTraceId,\n traceHeaders: {\n 'X-Cloud-Trace-Context': xCloudValue,\n },\n }\n}\n\nexport function getGcpTraceField({\n projectId,\n traceId,\n}: {\n projectId?: string\n traceId: string\n}) {\n if (!projectId || !traceId) {\n return undefined\n }\n return `projects/${projectId}/traces/${traceId}`\n}\n\nexport function getTraceLogFields(\n headersInput?: TraceHeaderInput,\n options: { projectId?: string; generateIfMissing?: boolean } = {}\n) {\n const traceContext = normalizeTraceContext(headersInput, {\n generateIfMissing: options.generateIfMissing ?? false,\n })\n if (!traceContext) {\n return {}\n }\n\n const projectId =\n options.projectId ||\n process.env.GOOGLE_CLOUD_PROJECT ||\n process.env.GCP_PROJECT\n const traceField = getGcpTraceField({\n projectId,\n traceId: traceContext.traceId,\n })\n\n return {\n ...(traceField ? { 'logging.googleapis.com/trace': traceField } : {}),\n traceId: traceContext.traceId,\n }\n}\n\nexport function emitStructured(payload: StructuredLogPayload) {\n const severity =\n typeof payload?.severity === 'string'\n ? payload.severity.toUpperCase()\n : 'INFO'\n const method = CONSOLE_BY_SEVERITY[severity] || 'log'\n const message = JSON.stringify(payload)\n\n if (method === 'error') {\n console.error(message)\n return\n }\n if (method === 'warn') {\n console.warn(message)\n return\n }\n console.log(message)\n}\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kids-reporter/logger",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Shared structured logger for Kids Reporter services",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
7
10
|
"main": "./dist/index.js",
|
|
8
11
|
"types": "./dist/index.d.ts",
|
|
9
12
|
"exports": {
|
|
@@ -14,13 +17,19 @@
|
|
|
14
17
|
}
|
|
15
18
|
},
|
|
16
19
|
"scripts": {
|
|
17
|
-
"build": "
|
|
18
|
-
"postinstall": "yarn build",
|
|
20
|
+
"build": "./scripts/build.sh",
|
|
19
21
|
"lint:check": "eslint src --config ./eslint.config.mjs --ext .ts",
|
|
20
22
|
"type-check": "tsc --noEmit -p tsconfig.json"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
|
-
"
|
|
25
|
+
"@babel/cli": "^7.18.5",
|
|
26
|
+
"@babel/core": "^7.18.5",
|
|
27
|
+
"@babel/plugin-transform-runtime": "^7.18.5",
|
|
28
|
+
"@babel/preset-env": "^7.18.2",
|
|
29
|
+
"@babel/preset-typescript": "^7.17.12",
|
|
24
30
|
"typescript": "^5.9.2"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@babel/runtime": "^7.18.5"
|
|
25
34
|
}
|
|
26
35
|
}
|
package/eslint.config.mjs
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import baseConfig, {
|
|
2
|
-
nodeConfig,
|
|
3
|
-
typescriptConfig,
|
|
4
|
-
} from '../../eslint.base.config.mjs'
|
|
5
|
-
|
|
6
|
-
export default [
|
|
7
|
-
...baseConfig,
|
|
8
|
-
{
|
|
9
|
-
...typescriptConfig,
|
|
10
|
-
files: ['src/**/*.ts'],
|
|
11
|
-
rules: {
|
|
12
|
-
...typescriptConfig.rules,
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
...nodeConfig,
|
|
17
|
-
files: ['**/*.config.{js,mjs,cjs}'],
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
ignores: ['node_modules/**', 'dist/**'],
|
|
21
|
-
},
|
|
22
|
-
]
|
package/src/index.ts
DELETED
package/src/types.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export type LogSeverity =
|
|
2
|
-
| 'DEBUG'
|
|
3
|
-
| 'INFO'
|
|
4
|
-
| 'NOTICE'
|
|
5
|
-
| 'WARNING'
|
|
6
|
-
| 'ERROR'
|
|
7
|
-
| 'ALERT'
|
|
8
|
-
| 'CRITICAL'
|
|
9
|
-
|
|
10
|
-
export type StructuredLogPayload = {
|
|
11
|
-
severity: LogSeverity
|
|
12
|
-
message?: string
|
|
13
|
-
} & Record<string, unknown>
|
|
14
|
-
|
|
15
|
-
/** Supports Express req.headers where values can be string | string[]. */
|
|
16
|
-
export type TraceHeaderInput =
|
|
17
|
-
| Headers
|
|
18
|
-
| Record<string, string | string[] | undefined | unknown>
|
|
19
|
-
| undefined
|
|
20
|
-
|
|
21
|
-
/** Trace context for GCP Cloud Logging correlation. Only traceId is propagated. */
|
|
22
|
-
export type NormalizedTraceContext = {
|
|
23
|
-
traceId: string
|
|
24
|
-
traceHeaders: {
|
|
25
|
-
'X-Cloud-Trace-Context': string
|
|
26
|
-
}
|
|
27
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NormalizedTraceContext,
|
|
3
|
-
StructuredLogPayload,
|
|
4
|
-
TraceHeaderInput,
|
|
5
|
-
} from './types.js'
|
|
6
|
-
|
|
7
|
-
const XCLOUD_TRACE_HEADER = 'x-cloud-trace-context'
|
|
8
|
-
const TRACE_ID_REGEX = /^[a-f0-9]{32}$/i
|
|
9
|
-
|
|
10
|
-
const CONSOLE_BY_SEVERITY: Record<string, 'log' | 'warn' | 'error'> = {
|
|
11
|
-
ALERT: 'error',
|
|
12
|
-
CRITICAL: 'error',
|
|
13
|
-
ERROR: 'error',
|
|
14
|
-
WARNING: 'warn',
|
|
15
|
-
NOTICE: 'log',
|
|
16
|
-
INFO: 'log',
|
|
17
|
-
DEBUG: 'log',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function normalizeHeaderValue(
|
|
21
|
-
value: string | string[] | undefined | unknown
|
|
22
|
-
): string | undefined {
|
|
23
|
-
if (value === undefined || value === null) {
|
|
24
|
-
return undefined
|
|
25
|
-
}
|
|
26
|
-
if (typeof value === 'string') {
|
|
27
|
-
return value
|
|
28
|
-
}
|
|
29
|
-
if (
|
|
30
|
-
Array.isArray(value) &&
|
|
31
|
-
value.length > 0 &&
|
|
32
|
-
typeof value[0] === 'string'
|
|
33
|
-
) {
|
|
34
|
-
return value[0]
|
|
35
|
-
}
|
|
36
|
-
return undefined
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getHeaderValue(input: TraceHeaderInput, headerName: string) {
|
|
40
|
-
if (!input) {
|
|
41
|
-
return undefined
|
|
42
|
-
}
|
|
43
|
-
if (typeof Headers !== 'undefined' && input instanceof Headers) {
|
|
44
|
-
return input.get(headerName) || undefined
|
|
45
|
-
}
|
|
46
|
-
const lowered = headerName.toLowerCase()
|
|
47
|
-
for (const [key, value] of Object.entries(input)) {
|
|
48
|
-
if (key.toLowerCase() === lowered) {
|
|
49
|
-
return normalizeHeaderValue(value)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return undefined
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getRandomHex(length: number) {
|
|
56
|
-
const bytes = new Uint8Array(Math.ceil(length / 2))
|
|
57
|
-
if (
|
|
58
|
-
globalThis.crypto &&
|
|
59
|
-
typeof globalThis.crypto.getRandomValues === 'function'
|
|
60
|
-
) {
|
|
61
|
-
globalThis.crypto.getRandomValues(bytes)
|
|
62
|
-
} else {
|
|
63
|
-
for (let i = 0; i < bytes.length; i += 1) {
|
|
64
|
-
bytes[i] = Math.floor(Math.random() * 256)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return Array.from(bytes, (value) => value.toString(16).padStart(2, '0'))
|
|
68
|
-
.join('')
|
|
69
|
-
.slice(0, length)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Parses traceId from GCP X-Cloud-Trace-Context.
|
|
74
|
-
* Accepts "traceId;o=1" or "traceId/spanId;o=1" (spanId is ignored).
|
|
75
|
-
*/
|
|
76
|
-
function parseXCloudTraceContext(
|
|
77
|
-
xCloudTraceContext?: string
|
|
78
|
-
): string | undefined {
|
|
79
|
-
if (!xCloudTraceContext) {
|
|
80
|
-
return undefined
|
|
81
|
-
}
|
|
82
|
-
const [traceAndSpan] = xCloudTraceContext.trim().split(';')
|
|
83
|
-
const traceId = traceAndSpan.split('/')[0]?.trim()
|
|
84
|
-
if (!traceId || !TRACE_ID_REGEX.test(traceId)) {
|
|
85
|
-
return undefined
|
|
86
|
-
}
|
|
87
|
-
return traceId.toLowerCase()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Overloads: when generateIfMissing is true, return is always NormalizedTraceContext
|
|
91
|
-
/* eslint-disable no-redeclare -- overload signatures + implementation */
|
|
92
|
-
export function normalizeTraceContext(
|
|
93
|
-
headersInput: TraceHeaderInput | undefined,
|
|
94
|
-
options: { generateIfMissing: true }
|
|
95
|
-
): NormalizedTraceContext
|
|
96
|
-
export function normalizeTraceContext(
|
|
97
|
-
headersInput?: TraceHeaderInput,
|
|
98
|
-
options?: { generateIfMissing?: boolean }
|
|
99
|
-
): NormalizedTraceContext | undefined
|
|
100
|
-
export function normalizeTraceContext(
|
|
101
|
-
headersInput?: TraceHeaderInput,
|
|
102
|
-
options: { generateIfMissing?: boolean } = {}
|
|
103
|
-
) {
|
|
104
|
-
/* eslint-enable no-redeclare */
|
|
105
|
-
const shouldGenerate = options.generateIfMissing !== false
|
|
106
|
-
const xCloudTraceContext = getHeaderValue(headersInput, XCLOUD_TRACE_HEADER)
|
|
107
|
-
const traceId = parseXCloudTraceContext(xCloudTraceContext)
|
|
108
|
-
|
|
109
|
-
if (!traceId && !shouldGenerate) {
|
|
110
|
-
return undefined
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const resolvedTraceId = traceId ?? getRandomHex(32)
|
|
114
|
-
const xCloudValue = `${resolvedTraceId};o=1`
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
traceId: resolvedTraceId,
|
|
118
|
-
traceHeaders: {
|
|
119
|
-
'X-Cloud-Trace-Context': xCloudValue,
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function getGcpTraceField({
|
|
125
|
-
projectId,
|
|
126
|
-
traceId,
|
|
127
|
-
}: {
|
|
128
|
-
projectId?: string
|
|
129
|
-
traceId: string
|
|
130
|
-
}) {
|
|
131
|
-
if (!projectId || !traceId) {
|
|
132
|
-
return undefined
|
|
133
|
-
}
|
|
134
|
-
return `projects/${projectId}/traces/${traceId}`
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function getTraceLogFields(
|
|
138
|
-
headersInput?: TraceHeaderInput,
|
|
139
|
-
options: { projectId?: string; generateIfMissing?: boolean } = {}
|
|
140
|
-
) {
|
|
141
|
-
const traceContext = normalizeTraceContext(headersInput, {
|
|
142
|
-
generateIfMissing: options.generateIfMissing ?? false,
|
|
143
|
-
})
|
|
144
|
-
if (!traceContext) {
|
|
145
|
-
return {}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const projectId =
|
|
149
|
-
options.projectId ||
|
|
150
|
-
process.env.GOOGLE_CLOUD_PROJECT ||
|
|
151
|
-
process.env.GCP_PROJECT
|
|
152
|
-
const traceField = getGcpTraceField({
|
|
153
|
-
projectId,
|
|
154
|
-
traceId: traceContext.traceId,
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
...(traceField ? { 'logging.googleapis.com/trace': traceField } : {}),
|
|
159
|
-
traceId: traceContext.traceId,
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function emitStructured(payload: StructuredLogPayload) {
|
|
164
|
-
const severity =
|
|
165
|
-
typeof payload?.severity === 'string'
|
|
166
|
-
? payload.severity.toUpperCase()
|
|
167
|
-
: 'INFO'
|
|
168
|
-
const method = CONSOLE_BY_SEVERITY[severity] || 'log'
|
|
169
|
-
const message = JSON.stringify(payload)
|
|
170
|
-
|
|
171
|
-
if (method === 'error') {
|
|
172
|
-
console.error(message)
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
if (method === 'warn') {
|
|
176
|
-
console.warn(message)
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
console.log(message)
|
|
180
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"rootDir": "./src",
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"declaration": true,
|
|
9
|
-
"declarationMap": true,
|
|
10
|
-
"sourceMap": true,
|
|
11
|
-
"noEmit": false,
|
|
12
|
-
"strict": true,
|
|
13
|
-
"skipLibCheck": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*.ts"],
|
|
16
|
-
"exclude": ["node_modules", "dist"]
|
|
17
|
-
}
|