@allthings/cloud-toolkit 5.3.2 → 5.4.0-beta.1
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 +37 -0
- package/dist/dynamodb.cjs.js +1 -1
- package/dist/dynamodb.esm.mjs +1 -1
- package/dist/lib.cjs.js +1 -1
- package/dist/lib.esm.mjs +1 -1
- package/dist/src/tracing/index.d.ts +2 -0
- package/dist/src/tracing/otlpUdpSpanExporter.d.ts +2 -0
- package/dist/src/tracing/tracing.d.ts +13 -0
- package/dist/tracing.cjs.js +173 -0
- package/dist/tracing.esm.mjs +168 -0
- package/dist/utils.cjs.js +1 -1
- package/dist/utils.esm.mjs +1 -1
- package/package.json +39 -15
package/README.md
CHANGED
|
@@ -93,6 +93,43 @@ negative — so leave it off for anything that has to arrive.
|
|
|
93
93
|
|
|
94
94
|
A winston instance will be created with default log levels https://github.com/winstonjs/winston?tab=readme-ov-file#logging
|
|
95
95
|
|
|
96
|
+
### tracing
|
|
97
|
+
|
|
98
|
+
OpenTelemetry tracing for Lambda with the AWS X-Ray backend (replaces the deprecated `aws-xray-sdk-*`). Import it from its own subpath — it is not part of the main entry, so services that don't trace bundle none of `@opentelemetry/*`:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import {
|
|
102
|
+
withInvocationSpan,
|
|
103
|
+
withStageSpan,
|
|
104
|
+
} from '@allthings/cloud-toolkit/tracing'
|
|
105
|
+
|
|
106
|
+
export const handler = (event: APIGatewayProxyEvent) =>
|
|
107
|
+
withInvocationSpan(
|
|
108
|
+
{ 'http.request.method': event.httpMethod, 'url.path': event.path },
|
|
109
|
+
async () => {
|
|
110
|
+
const config = await withStageSpan('load-config', () => fetch(configUrl))
|
|
111
|
+
// ...
|
|
112
|
+
return { statusCode: 200, body: '' }
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Function | Parameters | Description |
|
|
118
|
+
| -------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| `withInvocationSpan` | attributes<br/>run | Runs the invocation in a SERVER span continuing the X-Ray trace, flushes spans (max 2.5 s) before returning; a `statusCode >= 500` result marks it as an error |
|
|
120
|
+
| `withStageSpan` | name<br/>run | Runs a phase of the invocation as a child span |
|
|
121
|
+
| `createTracingSdk` | spanProcessors (optional) | Creates and registers the provider — for tests with an in-memory exporter |
|
|
122
|
+
|
|
123
|
+
Global `fetch` (undici) and `http`/`https` calls are traced automatically.
|
|
124
|
+
|
|
125
|
+
Configuration (environment):
|
|
126
|
+
|
|
127
|
+
- `OTEL_ENABLED=true` — turns tracing on; otherwise every function is a pass-through.
|
|
128
|
+
- Lambda `tracing: Active` — provides `AWS_XRAY_DAEMON_ADDRESS`; spans go there as OTLP over UDP (no layer, no added latency). Without it, spans go via OTLP/HTTP to `OTEL_EXPORTER_OTLP_*` (default `localhost:4318`).
|
|
129
|
+
- `OTEL_SERVICE_NAME` — optional, defaults to `AWS_LAMBDA_FUNCTION_NAME`.
|
|
130
|
+
|
|
131
|
+
The SDK starts when the module is first imported, so import it before modules that load `http`/`https`.
|
|
132
|
+
|
|
96
133
|
## Releasing pre-release versions
|
|
97
134
|
|
|
98
135
|
This project uses [semantic-release](https://semantic-release.gitbook.io/semantic-release/) with support for pre-release channels via dedicated branches.
|
package/dist/dynamodb.cjs.js
CHANGED
|
@@ -8,7 +8,7 @@ const DEFAULT_AWS_CONFIGURATION = {
|
|
|
8
8
|
region: 'eu-central-2',
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }
|
|
11
|
+
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }, (_, count) => list.slice(count * every, count * every + every));
|
|
12
12
|
const until = async (predicate, transformer, initialValue, iterationCount = 0) => {
|
|
13
13
|
const transformed = await transformer(initialValue, iterationCount);
|
|
14
14
|
return (await predicate(transformed, iterationCount))
|
package/dist/dynamodb.esm.mjs
CHANGED
|
@@ -6,7 +6,7 @@ const DEFAULT_AWS_CONFIGURATION = {
|
|
|
6
6
|
region: 'eu-central-2',
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }
|
|
9
|
+
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }, (_, count) => list.slice(count * every, count * every + every));
|
|
10
10
|
const until = async (predicate, transformer, initialValue, iterationCount = 0) => {
|
|
11
11
|
const transformed = await transformer(initialValue, iterationCount);
|
|
12
12
|
return (await predicate(transformed, iterationCount))
|
package/dist/lib.cjs.js
CHANGED
|
@@ -717,7 +717,7 @@ const DEFAULT_AWS_CONFIGURATION = {
|
|
|
717
717
|
region: 'eu-central-2',
|
|
718
718
|
};
|
|
719
719
|
|
|
720
|
-
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }
|
|
720
|
+
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }, (_, count) => list.slice(count * every, count * every + every));
|
|
721
721
|
const until = async (predicate, transformer, initialValue, iterationCount = 0) => {
|
|
722
722
|
const transformed = await transformer(initialValue, iterationCount);
|
|
723
723
|
return (await predicate(transformed, iterationCount))
|
package/dist/lib.esm.mjs
CHANGED
|
@@ -695,7 +695,7 @@ const DEFAULT_AWS_CONFIGURATION = {
|
|
|
695
695
|
region: 'eu-central-2',
|
|
696
696
|
};
|
|
697
697
|
|
|
698
|
-
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }
|
|
698
|
+
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }, (_, count) => list.slice(count * every, count * every + every));
|
|
699
699
|
const until = async (predicate, transformer, initialValue, iterationCount = 0) => {
|
|
700
700
|
const transformed = await transformer(initialValue, iterationCount);
|
|
701
701
|
return (await predicate(transformed, iterationCount))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Tracer } from '@opentelemetry/api';
|
|
2
|
+
import { SpanProcessor } from '@opentelemetry/sdk-trace-node';
|
|
3
|
+
export interface ITracingSdk {
|
|
4
|
+
forceFlush: () => Promise<void>;
|
|
5
|
+
tracer: Tracer;
|
|
6
|
+
}
|
|
7
|
+
export interface ITracingSdkOptions {
|
|
8
|
+
spanProcessors?: SpanProcessor[];
|
|
9
|
+
}
|
|
10
|
+
export type TSpanAttributes = Record<string, number | string | undefined>;
|
|
11
|
+
export declare const createTracingSdk: (options?: ITracingSdkOptions) => ITracingSdk;
|
|
12
|
+
export declare const withInvocationSpan: <Result>(attributes: TSpanAttributes, run: () => Promise<Result>) => Promise<Result>;
|
|
13
|
+
export declare const withStageSpan: <Result>(name: string, run: () => Promise<Result>) => Promise<Result>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var api = require('@opentelemetry/api');
|
|
4
|
+
var core = require('@opentelemetry/core');
|
|
5
|
+
var otlpTransformer = require('@opentelemetry/otlp-transformer');
|
|
6
|
+
var dgram = require('dgram');
|
|
7
|
+
var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
|
|
8
|
+
var idGeneratorAwsXray = require('@opentelemetry/id-generator-aws-xray');
|
|
9
|
+
var instrumentation = require('@opentelemetry/instrumentation');
|
|
10
|
+
var instrumentationHttp = require('@opentelemetry/instrumentation-http');
|
|
11
|
+
var instrumentationUndici = require('@opentelemetry/instrumentation-undici');
|
|
12
|
+
var propagatorAwsXray = require('@opentelemetry/propagator-aws-xray');
|
|
13
|
+
var resources = require('@opentelemetry/resources');
|
|
14
|
+
var sdkTraceNode = require('@opentelemetry/sdk-trace-node');
|
|
15
|
+
var semanticConventions = require('@opentelemetry/semantic-conventions');
|
|
16
|
+
|
|
17
|
+
const DEFAULT_ENDPOINT = '127.0.0.1:2000';
|
|
18
|
+
const PROTOCOL_HEADER = '{"format":"json","version":1}\n';
|
|
19
|
+
const OTEL_TRACES_BINARY_PREFIX = 'T1S';
|
|
20
|
+
const MAX_PORT = 65_535;
|
|
21
|
+
const parseEndpoint = (endpoint) => {
|
|
22
|
+
const [host, port] = endpoint.split(':');
|
|
23
|
+
const parsedPort = Number(port);
|
|
24
|
+
if (!host ||
|
|
25
|
+
!port ||
|
|
26
|
+
!Number.isSafeInteger(parsedPort) ||
|
|
27
|
+
parsedPort < 1 ||
|
|
28
|
+
parsedPort > MAX_PORT) {
|
|
29
|
+
throw new Error(`Invalid X-Ray daemon endpoint: ${endpoint}`);
|
|
30
|
+
}
|
|
31
|
+
return { host, port: parsedPort };
|
|
32
|
+
};
|
|
33
|
+
const reportFailure = (error, resultCallback) => {
|
|
34
|
+
api.diag.error('Error sending OTLP/UDP data: %s', error);
|
|
35
|
+
resultCallback({ code: core.ExportResultCode.FAILED, error });
|
|
36
|
+
};
|
|
37
|
+
const sendMessage = (socket, message, { host, port }, resultCallback) => {
|
|
38
|
+
try {
|
|
39
|
+
socket.send(Buffer.from(message, 'utf8'), port, host, (error) => {
|
|
40
|
+
if (error) {
|
|
41
|
+
reportFailure(error, resultCallback);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
resultCallback({ code: core.ExportResultCode.SUCCESS });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
reportFailure(error instanceof Error ? error : new Error(String(error)), resultCallback);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT) => {
|
|
52
|
+
const { host, port } = parseEndpoint(endpoint);
|
|
53
|
+
const socket = dgram.createSocket('udp4');
|
|
54
|
+
socket.unref();
|
|
55
|
+
return {
|
|
56
|
+
export: (spans, resultCallback) => {
|
|
57
|
+
const serialized = otlpTransformer.ProtobufTraceSerializer.serializeRequest(spans);
|
|
58
|
+
if (serialized == null) {
|
|
59
|
+
resultCallback({ code: core.ExportResultCode.SUCCESS });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const message = `${PROTOCOL_HEADER}${OTEL_TRACES_BINARY_PREFIX}${Buffer.from(serialized).toString('base64')}`;
|
|
63
|
+
sendMessage(socket, message, { host, port }, resultCallback);
|
|
64
|
+
},
|
|
65
|
+
forceFlush: () => Promise.resolve(),
|
|
66
|
+
shutdown: () => new Promise((resolve) => {
|
|
67
|
+
socket.close(() => resolve());
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const OTLP_HTTP_EXPORT_TIMEOUT_MS = 2000;
|
|
73
|
+
const FLUSH_BUDGET_MS = 2500;
|
|
74
|
+
const BATCH_DELAY_MS = 200;
|
|
75
|
+
const getServiceName = () => process.env.OTEL_SERVICE_NAME ??
|
|
76
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME ??
|
|
77
|
+
'lambda';
|
|
78
|
+
const createSpanExporter = () => process.env.AWS_XRAY_DAEMON_ADDRESS
|
|
79
|
+
? createOtlpUdpSpanExporter(process.env.AWS_XRAY_DAEMON_ADDRESS)
|
|
80
|
+
: new exporterTraceOtlpHttp.OTLPTraceExporter({ timeoutMillis: OTLP_HTTP_EXPORT_TIMEOUT_MS });
|
|
81
|
+
const createTracingSdk = (options = {}) => {
|
|
82
|
+
const provider = new sdkTraceNode.NodeTracerProvider({
|
|
83
|
+
idGenerator: new idGeneratorAwsXray.AWSXRayIdGenerator(),
|
|
84
|
+
resource: resources.resourceFromAttributes({ [semanticConventions.ATTR_SERVICE_NAME]: getServiceName() }),
|
|
85
|
+
spanProcessors: options.spanProcessors ?? [
|
|
86
|
+
new sdkTraceNode.BatchSpanProcessor(createSpanExporter(), {
|
|
87
|
+
scheduledDelayMillis: BATCH_DELAY_MS,
|
|
88
|
+
}),
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
provider.register({ propagator: new propagatorAwsXray.AWSXRayPropagator() });
|
|
92
|
+
instrumentation.registerInstrumentations({
|
|
93
|
+
instrumentations: [new instrumentationUndici.UndiciInstrumentation(), new instrumentationHttp.HttpInstrumentation()],
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
forceFlush: () => provider.forceFlush(),
|
|
97
|
+
tracer: api.trace.getTracer('@allthings/cloud-toolkit/tracing'),
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const sdk = process.env.OTEL_ENABLED === 'true' ? createTracingSdk() : undefined;
|
|
101
|
+
const invocationParentContext = () => api.propagation.extract(api.context.active(), {
|
|
102
|
+
'x-amzn-trace-id': process.env._X_AMZN_TRACE_ID ?? '',
|
|
103
|
+
});
|
|
104
|
+
const finishSpan = (span, error) => {
|
|
105
|
+
if (error instanceof Error) {
|
|
106
|
+
span.recordException(error);
|
|
107
|
+
span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
|
|
108
|
+
}
|
|
109
|
+
span.end();
|
|
110
|
+
};
|
|
111
|
+
const logFlushFailure = () => {
|
|
112
|
+
console.warn('OTel span flush failed');
|
|
113
|
+
};
|
|
114
|
+
const flushWithBudget = (flush) => Promise.race([
|
|
115
|
+
Promise.resolve().then(flush).catch(logFlushFailure),
|
|
116
|
+
new Promise((resolve) => {
|
|
117
|
+
setTimeout(resolve, FLUSH_BUDGET_MS).unref();
|
|
118
|
+
}),
|
|
119
|
+
]);
|
|
120
|
+
const getStatusCode = (result) => {
|
|
121
|
+
if (typeof result !== 'object' || result === null) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const { statusCode } = result;
|
|
125
|
+
return typeof statusCode === 'number' ? statusCode : undefined;
|
|
126
|
+
};
|
|
127
|
+
const withInvocationSpan = async (attributes, run) => {
|
|
128
|
+
if (!sdk) {
|
|
129
|
+
return run();
|
|
130
|
+
}
|
|
131
|
+
const parent = invocationParentContext();
|
|
132
|
+
const span = sdk.tracer.startSpan(process.env.AWS_LAMBDA_FUNCTION_NAME ?? 'invocation', { attributes, kind: api.SpanKind.SERVER }, parent);
|
|
133
|
+
try {
|
|
134
|
+
const result = await api.context.with(api.trace.setSpan(parent, span), run);
|
|
135
|
+
const statusCode = getStatusCode(result);
|
|
136
|
+
if (statusCode !== undefined) {
|
|
137
|
+
span.setAttribute('http.response.status_code', statusCode);
|
|
138
|
+
}
|
|
139
|
+
if (statusCode !== undefined && statusCode >= 500) {
|
|
140
|
+
span.setStatus({ code: api.SpanStatusCode.ERROR });
|
|
141
|
+
}
|
|
142
|
+
finishSpan(span);
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
finishSpan(span, error);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
await flushWithBudget(sdk.forceFlush);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const withStageSpan = async (name, run) => {
|
|
154
|
+
if (!sdk) {
|
|
155
|
+
return run();
|
|
156
|
+
}
|
|
157
|
+
return sdk.tracer.startActiveSpan(name, async (span) => {
|
|
158
|
+
try {
|
|
159
|
+
const result = await run();
|
|
160
|
+
finishSpan(span);
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
finishSpan(span, error);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
exports.createOtlpUdpSpanExporter = createOtlpUdpSpanExporter;
|
|
171
|
+
exports.createTracingSdk = createTracingSdk;
|
|
172
|
+
exports.withInvocationSpan = withInvocationSpan;
|
|
173
|
+
exports.withStageSpan = withStageSpan;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { diag, trace, SpanKind, context, SpanStatusCode, propagation } from '@opentelemetry/api';
|
|
2
|
+
import { ExportResultCode } from '@opentelemetry/core';
|
|
3
|
+
import { ProtobufTraceSerializer } from '@opentelemetry/otlp-transformer';
|
|
4
|
+
import dgram from 'dgram';
|
|
5
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
6
|
+
import { AWSXRayIdGenerator } from '@opentelemetry/id-generator-aws-xray';
|
|
7
|
+
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
|
8
|
+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
9
|
+
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
|
|
10
|
+
import { AWSXRayPropagator } from '@opentelemetry/propagator-aws-xray';
|
|
11
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
12
|
+
import { NodeTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-node';
|
|
13
|
+
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_ENDPOINT = '127.0.0.1:2000';
|
|
16
|
+
const PROTOCOL_HEADER = '{"format":"json","version":1}\n';
|
|
17
|
+
const OTEL_TRACES_BINARY_PREFIX = 'T1S';
|
|
18
|
+
const MAX_PORT = 65_535;
|
|
19
|
+
const parseEndpoint = (endpoint) => {
|
|
20
|
+
const [host, port] = endpoint.split(':');
|
|
21
|
+
const parsedPort = Number(port);
|
|
22
|
+
if (!host ||
|
|
23
|
+
!port ||
|
|
24
|
+
!Number.isSafeInteger(parsedPort) ||
|
|
25
|
+
parsedPort < 1 ||
|
|
26
|
+
parsedPort > MAX_PORT) {
|
|
27
|
+
throw new Error(`Invalid X-Ray daemon endpoint: ${endpoint}`);
|
|
28
|
+
}
|
|
29
|
+
return { host, port: parsedPort };
|
|
30
|
+
};
|
|
31
|
+
const reportFailure = (error, resultCallback) => {
|
|
32
|
+
diag.error('Error sending OTLP/UDP data: %s', error);
|
|
33
|
+
resultCallback({ code: ExportResultCode.FAILED, error });
|
|
34
|
+
};
|
|
35
|
+
const sendMessage = (socket, message, { host, port }, resultCallback) => {
|
|
36
|
+
try {
|
|
37
|
+
socket.send(Buffer.from(message, 'utf8'), port, host, (error) => {
|
|
38
|
+
if (error) {
|
|
39
|
+
reportFailure(error, resultCallback);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
reportFailure(error instanceof Error ? error : new Error(String(error)), resultCallback);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT) => {
|
|
50
|
+
const { host, port } = parseEndpoint(endpoint);
|
|
51
|
+
const socket = dgram.createSocket('udp4');
|
|
52
|
+
socket.unref();
|
|
53
|
+
return {
|
|
54
|
+
export: (spans, resultCallback) => {
|
|
55
|
+
const serialized = ProtobufTraceSerializer.serializeRequest(spans);
|
|
56
|
+
if (serialized == null) {
|
|
57
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const message = `${PROTOCOL_HEADER}${OTEL_TRACES_BINARY_PREFIX}${Buffer.from(serialized).toString('base64')}`;
|
|
61
|
+
sendMessage(socket, message, { host, port }, resultCallback);
|
|
62
|
+
},
|
|
63
|
+
forceFlush: () => Promise.resolve(),
|
|
64
|
+
shutdown: () => new Promise((resolve) => {
|
|
65
|
+
socket.close(() => resolve());
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const OTLP_HTTP_EXPORT_TIMEOUT_MS = 2000;
|
|
71
|
+
const FLUSH_BUDGET_MS = 2500;
|
|
72
|
+
const BATCH_DELAY_MS = 200;
|
|
73
|
+
const getServiceName = () => process.env.OTEL_SERVICE_NAME ??
|
|
74
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME ??
|
|
75
|
+
'lambda';
|
|
76
|
+
const createSpanExporter = () => process.env.AWS_XRAY_DAEMON_ADDRESS
|
|
77
|
+
? createOtlpUdpSpanExporter(process.env.AWS_XRAY_DAEMON_ADDRESS)
|
|
78
|
+
: new OTLPTraceExporter({ timeoutMillis: OTLP_HTTP_EXPORT_TIMEOUT_MS });
|
|
79
|
+
const createTracingSdk = (options = {}) => {
|
|
80
|
+
const provider = new NodeTracerProvider({
|
|
81
|
+
idGenerator: new AWSXRayIdGenerator(),
|
|
82
|
+
resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: getServiceName() }),
|
|
83
|
+
spanProcessors: options.spanProcessors ?? [
|
|
84
|
+
new BatchSpanProcessor(createSpanExporter(), {
|
|
85
|
+
scheduledDelayMillis: BATCH_DELAY_MS,
|
|
86
|
+
}),
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
provider.register({ propagator: new AWSXRayPropagator() });
|
|
90
|
+
registerInstrumentations({
|
|
91
|
+
instrumentations: [new UndiciInstrumentation(), new HttpInstrumentation()],
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
forceFlush: () => provider.forceFlush(),
|
|
95
|
+
tracer: trace.getTracer('@allthings/cloud-toolkit/tracing'),
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const sdk = process.env.OTEL_ENABLED === 'true' ? createTracingSdk() : undefined;
|
|
99
|
+
const invocationParentContext = () => propagation.extract(context.active(), {
|
|
100
|
+
'x-amzn-trace-id': process.env._X_AMZN_TRACE_ID ?? '',
|
|
101
|
+
});
|
|
102
|
+
const finishSpan = (span, error) => {
|
|
103
|
+
if (error instanceof Error) {
|
|
104
|
+
span.recordException(error);
|
|
105
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
106
|
+
}
|
|
107
|
+
span.end();
|
|
108
|
+
};
|
|
109
|
+
const logFlushFailure = () => {
|
|
110
|
+
console.warn('OTel span flush failed');
|
|
111
|
+
};
|
|
112
|
+
const flushWithBudget = (flush) => Promise.race([
|
|
113
|
+
Promise.resolve().then(flush).catch(logFlushFailure),
|
|
114
|
+
new Promise((resolve) => {
|
|
115
|
+
setTimeout(resolve, FLUSH_BUDGET_MS).unref();
|
|
116
|
+
}),
|
|
117
|
+
]);
|
|
118
|
+
const getStatusCode = (result) => {
|
|
119
|
+
if (typeof result !== 'object' || result === null) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const { statusCode } = result;
|
|
123
|
+
return typeof statusCode === 'number' ? statusCode : undefined;
|
|
124
|
+
};
|
|
125
|
+
const withInvocationSpan = async (attributes, run) => {
|
|
126
|
+
if (!sdk) {
|
|
127
|
+
return run();
|
|
128
|
+
}
|
|
129
|
+
const parent = invocationParentContext();
|
|
130
|
+
const span = sdk.tracer.startSpan(process.env.AWS_LAMBDA_FUNCTION_NAME ?? 'invocation', { attributes, kind: SpanKind.SERVER }, parent);
|
|
131
|
+
try {
|
|
132
|
+
const result = await context.with(trace.setSpan(parent, span), run);
|
|
133
|
+
const statusCode = getStatusCode(result);
|
|
134
|
+
if (statusCode !== undefined) {
|
|
135
|
+
span.setAttribute('http.response.status_code', statusCode);
|
|
136
|
+
}
|
|
137
|
+
if (statusCode !== undefined && statusCode >= 500) {
|
|
138
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
139
|
+
}
|
|
140
|
+
finishSpan(span);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
finishSpan(span, error);
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await flushWithBudget(sdk.forceFlush);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const withStageSpan = async (name, run) => {
|
|
152
|
+
if (!sdk) {
|
|
153
|
+
return run();
|
|
154
|
+
}
|
|
155
|
+
return sdk.tracer.startActiveSpan(name, async (span) => {
|
|
156
|
+
try {
|
|
157
|
+
const result = await run();
|
|
158
|
+
finishSpan(span);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
finishSpan(span, error);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export { createOtlpUdpSpanExporter, createTracingSdk, withInvocationSpan, withStageSpan };
|
package/dist/utils.cjs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }
|
|
3
|
+
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }, (_, count) => list.slice(count * every, count * every + every));
|
|
4
4
|
const until = async (predicate, transformer, initialValue, iterationCount = 0) => {
|
|
5
5
|
const transformed = await transformer(initialValue, iterationCount);
|
|
6
6
|
return (await predicate(transformed, iterationCount))
|
package/dist/utils.esm.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }
|
|
1
|
+
const splitEvery = (every, list) => Array.from({ length: Math.ceil(list.length / every) }, (_, count) => list.slice(count * every, count * every + every));
|
|
2
2
|
const until = async (predicate, transformer, initialValue, iterationCount = 0) => {
|
|
3
3
|
const transformed = await transformer(initialValue, iterationCount);
|
|
4
4
|
return (await predicate(transformed, iterationCount))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allthings/cloud-toolkit",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0-beta.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "Allthings GmbH",
|
|
6
6
|
"keywords": [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"node": ">=24",
|
|
18
18
|
"npm": ">=10"
|
|
19
19
|
},
|
|
20
|
-
"packageManager": "yarn@4.18.
|
|
20
|
+
"packageManager": "yarn@4.18.1",
|
|
21
21
|
"main": "dist/lib.cjs.js",
|
|
22
22
|
"module": "dist/lib.esm.mjs",
|
|
23
23
|
"types": "dist/src/index.d.ts",
|
|
@@ -67,6 +67,11 @@
|
|
|
67
67
|
"import": "./dist/sqs.esm.mjs",
|
|
68
68
|
"require": "./dist/sqs.cjs.js"
|
|
69
69
|
},
|
|
70
|
+
"./tracing": {
|
|
71
|
+
"types": "./dist/src/tracing/index.d.ts",
|
|
72
|
+
"import": "./dist/tracing.esm.mjs",
|
|
73
|
+
"require": "./dist/tracing.cjs.js"
|
|
74
|
+
},
|
|
70
75
|
"./utils": {
|
|
71
76
|
"types": "./dist/src/utils.d.ts",
|
|
72
77
|
"import": "./dist/utils.esm.mjs",
|
|
@@ -74,7 +79,10 @@
|
|
|
74
79
|
},
|
|
75
80
|
"./package.json": "./package.json"
|
|
76
81
|
},
|
|
77
|
-
"sideEffects":
|
|
82
|
+
"sideEffects": [
|
|
83
|
+
"./dist/tracing.cjs.js",
|
|
84
|
+
"./dist/tracing.esm.mjs"
|
|
85
|
+
],
|
|
78
86
|
"typesVersions": {
|
|
79
87
|
"*": {
|
|
80
88
|
"alagarr": [
|
|
@@ -101,6 +109,9 @@
|
|
|
101
109
|
"sqs": [
|
|
102
110
|
"dist/src/aws/sqs.d.ts"
|
|
103
111
|
],
|
|
112
|
+
"tracing": [
|
|
113
|
+
"dist/src/tracing/index.d.ts"
|
|
114
|
+
],
|
|
104
115
|
"utils": [
|
|
105
116
|
"dist/src/utils.d.ts"
|
|
106
117
|
]
|
|
@@ -146,31 +157,44 @@
|
|
|
146
157
|
"@aws-sdk/client-sqs": "^3.1042.0",
|
|
147
158
|
"@aws-sdk/client-ssm": "^3.1042.0",
|
|
148
159
|
"@aws-sdk/lib-dynamodb": "^3.1042.0",
|
|
160
|
+
"@opentelemetry/api": "^1.9.1",
|
|
161
|
+
"@opentelemetry/core": "^2.12.0",
|
|
162
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.223.0",
|
|
163
|
+
"@opentelemetry/id-generator-aws-xray": "^2.1.0",
|
|
164
|
+
"@opentelemetry/instrumentation": "^0.223.0",
|
|
165
|
+
"@opentelemetry/instrumentation-http": "^0.223.0",
|
|
166
|
+
"@opentelemetry/instrumentation-undici": "^0.33.0",
|
|
167
|
+
"@opentelemetry/otlp-transformer": "^0.223.0",
|
|
168
|
+
"@opentelemetry/propagator-aws-xray": "^2.2.0",
|
|
169
|
+
"@opentelemetry/resources": "^2.12.0",
|
|
170
|
+
"@opentelemetry/sdk-trace-node": "^2.12.0",
|
|
171
|
+
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
149
172
|
"@types/aws-lambda": "^8.10.162",
|
|
150
173
|
"bottleneck": "^2.19.5",
|
|
151
174
|
"nodemailer": "^10.0.0",
|
|
152
175
|
"winston": "^3.19.0"
|
|
153
176
|
},
|
|
154
177
|
"devDependencies": {
|
|
155
|
-
"@allthings/eslint-config": "5.2
|
|
156
|
-
"@commitlint/cli": "21.2.
|
|
157
|
-
"@commitlint/config-conventional": "21.2.
|
|
178
|
+
"@allthings/eslint-config": "5.4.2",
|
|
179
|
+
"@commitlint/cli": "21.2.3",
|
|
180
|
+
"@commitlint/config-conventional": "21.2.3",
|
|
181
|
+
"@opentelemetry/sdk-trace-base": "2.12.0",
|
|
158
182
|
"@rollup/plugin-node-resolve": "16.0.3",
|
|
159
|
-
"@types/node": "24.
|
|
160
|
-
"@types/nodemailer": "8.0.
|
|
161
|
-
"@vitest/coverage-v8": "5.0.
|
|
183
|
+
"@types/node": "24.19.1",
|
|
184
|
+
"@types/nodemailer": "8.0.2",
|
|
185
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
162
186
|
"aws-sdk-client-mock": "4.1.0",
|
|
163
187
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
164
|
-
"eslint": "10.
|
|
188
|
+
"eslint": "10.12.0",
|
|
165
189
|
"husky": "9.1.7",
|
|
166
|
-
"lint-staged": "17.
|
|
167
|
-
"prettier": "3.9.
|
|
190
|
+
"lint-staged": "17.6.0",
|
|
191
|
+
"prettier": "3.9.9",
|
|
168
192
|
"rimraf": "6.1.3",
|
|
169
|
-
"rollup": "4.
|
|
193
|
+
"rollup": "4.64.0",
|
|
170
194
|
"semantic-release": "25.0.9",
|
|
171
195
|
"typescript": "6.0.3",
|
|
172
|
-
"vite": "8.
|
|
173
|
-
"vitest": "5.0.
|
|
196
|
+
"vite": "8.3.2",
|
|
197
|
+
"vitest": "5.0.3"
|
|
174
198
|
},
|
|
175
199
|
"prettier": {
|
|
176
200
|
"printWidth": 80,
|