@allthings/cloud-toolkit 5.4.0-beta.1 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/tracing/index.d.ts +1 -1
- package/dist/src/tracing/otlpUdpSpanExporter.d.ts +4 -1
- package/dist/tracing.cjs.js +66 -35
- package/dist/tracing.esm.mjs +66 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ Global `fetch` (undici) and `http`/`https` calls are traced automatically.
|
|
|
125
125
|
Configuration (environment):
|
|
126
126
|
|
|
127
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,
|
|
128
|
+
- Lambda `tracing: Active` — provides `AWS_XRAY_DAEMON_ADDRESS`; spans go there as OTLP over UDP (no layer; a send only waits for the local socket write, and `withInvocationSpan` waits at most 2.5 s for the flush before returning). Without it, spans go via OTLP/HTTP to `OTEL_EXPORTER_OTLP_*` (default `localhost:4318`).
|
|
129
129
|
- `OTEL_SERVICE_NAME` — optional, defaults to `AWS_LAMBDA_FUNCTION_NAME`.
|
|
130
130
|
|
|
131
131
|
The SDK starts when the module is first imported, so import it before modules that load `http`/`https`.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createOtlpUdpSpanExporter } from './otlpUdpSpanExporter';
|
|
1
|
+
export { createOtlpUdpSpanExporter, IOtlpUdpSpanExporterOptions, } from './otlpUdpSpanExporter';
|
|
2
2
|
export { createTracingSdk, ITracingSdk, ITracingSdkOptions, TSpanAttributes, withInvocationSpan, withStageSpan, } from './tracing';
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
import { SpanExporter } from '@opentelemetry/sdk-trace-node';
|
|
2
|
-
export
|
|
2
|
+
export interface IOtlpUdpSpanExporterOptions {
|
|
3
|
+
maxPayloadBytes?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare const createOtlpUdpSpanExporter: (endpoint?: string, { maxPayloadBytes }?: IOtlpUdpSpanExporterOptions) => SpanExporter;
|
package/dist/tracing.cjs.js
CHANGED
|
@@ -18,6 +18,7 @@ const DEFAULT_ENDPOINT = '127.0.0.1:2000';
|
|
|
18
18
|
const PROTOCOL_HEADER = '{"format":"json","version":1}\n';
|
|
19
19
|
const OTEL_TRACES_BINARY_PREFIX = 'T1S';
|
|
20
20
|
const MAX_PORT = 65_535;
|
|
21
|
+
const MAX_UDP_PAYLOAD_BYTES = 65_507;
|
|
21
22
|
const parseEndpoint = (endpoint) => {
|
|
22
23
|
const [host, port] = endpoint.split(':');
|
|
23
24
|
const parsedPort = Number(port);
|
|
@@ -30,37 +31,63 @@ const parseEndpoint = (endpoint) => {
|
|
|
30
31
|
}
|
|
31
32
|
return { host, port: parsedPort };
|
|
32
33
|
};
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const encode = (spans) => {
|
|
35
|
+
const serialized = otlpTransformer.ProtobufTraceSerializer.serializeRequest(spans);
|
|
36
|
+
return serialized == null
|
|
37
|
+
? undefined
|
|
38
|
+
: Buffer.from(`${PROTOCOL_HEADER}${OTEL_TRACES_BINARY_PREFIX}${Buffer.from(serialized).toString('base64')}`, 'utf8');
|
|
36
39
|
};
|
|
37
|
-
const
|
|
40
|
+
const encodeBatches = (spans, maxPayloadBytes) => {
|
|
41
|
+
if (spans.length === 0) {
|
|
42
|
+
return { droppedSpans: 0, messages: [] };
|
|
43
|
+
}
|
|
44
|
+
const message = encode(spans);
|
|
45
|
+
if (!message) {
|
|
46
|
+
return { droppedSpans: 0, messages: [] };
|
|
47
|
+
}
|
|
48
|
+
if (message.length <= maxPayloadBytes) {
|
|
49
|
+
return { droppedSpans: 0, messages: [message] };
|
|
50
|
+
}
|
|
51
|
+
if (spans.length === 1) {
|
|
52
|
+
return { droppedSpans: 1, messages: [] };
|
|
53
|
+
}
|
|
54
|
+
const middle = Math.ceil(spans.length / 2);
|
|
55
|
+
const head = encodeBatches(spans.slice(0, middle), maxPayloadBytes);
|
|
56
|
+
const tail = encodeBatches(spans.slice(middle), maxPayloadBytes);
|
|
57
|
+
return {
|
|
58
|
+
droppedSpans: head.droppedSpans + tail.droppedSpans,
|
|
59
|
+
messages: [...head.messages, ...tail.messages],
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const send = (socket, message, { host, port }) => new Promise((resolve) => {
|
|
38
63
|
try {
|
|
39
|
-
socket.send(
|
|
40
|
-
|
|
41
|
-
reportFailure(error, resultCallback);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
resultCallback({ code: core.ExportResultCode.SUCCESS });
|
|
64
|
+
socket.send(message, port, host, (error) => {
|
|
65
|
+
resolve(error ?? undefined);
|
|
45
66
|
});
|
|
46
67
|
}
|
|
47
68
|
catch (error) {
|
|
48
|
-
|
|
69
|
+
resolve(error instanceof Error ? error : new Error(String(error)));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
const exportBatches = async (socket, endpoint, { droppedSpans, messages }) => {
|
|
73
|
+
const errors = await Promise.all(messages.map((message) => send(socket, message, endpoint)));
|
|
74
|
+
const error = errors.find((sendError) => sendError !== undefined) ??
|
|
75
|
+
(droppedSpans > 0
|
|
76
|
+
? new Error(`${droppedSpans} span(s) exceed the UDP payload limit`)
|
|
77
|
+
: undefined);
|
|
78
|
+
if (error) {
|
|
79
|
+
api.diag.error('Error sending OTLP/UDP data: %s', error);
|
|
80
|
+
return { code: core.ExportResultCode.FAILED, error };
|
|
49
81
|
}
|
|
82
|
+
return { code: core.ExportResultCode.SUCCESS };
|
|
50
83
|
};
|
|
51
|
-
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT) => {
|
|
52
|
-
const
|
|
84
|
+
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT, { maxPayloadBytes = MAX_UDP_PAYLOAD_BYTES } = {}) => {
|
|
85
|
+
const parsedEndpoint = parseEndpoint(endpoint);
|
|
53
86
|
const socket = dgram.createSocket('udp4');
|
|
54
87
|
socket.unref();
|
|
55
88
|
return {
|
|
56
89
|
export: (spans, resultCallback) => {
|
|
57
|
-
|
|
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);
|
|
90
|
+
void exportBatches(socket, parsedEndpoint, encodeBatches(spans, Math.min(maxPayloadBytes, MAX_UDP_PAYLOAD_BYTES))).then(resultCallback);
|
|
64
91
|
},
|
|
65
92
|
forceFlush: () => Promise.resolve(),
|
|
66
93
|
shutdown: () => new Promise((resolve) => {
|
|
@@ -101,22 +128,26 @@ const sdk = process.env.OTEL_ENABLED === 'true' ? createTracingSdk() : undefined
|
|
|
101
128
|
const invocationParentContext = () => api.propagation.extract(api.context.active(), {
|
|
102
129
|
'x-amzn-trace-id': process.env._X_AMZN_TRACE_ID ?? '',
|
|
103
130
|
});
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
131
|
+
const failSpan = (span, error) => {
|
|
132
|
+
const exception = error instanceof Error ? error : new Error(String(error));
|
|
133
|
+
span.recordException(exception);
|
|
134
|
+
span.setStatus({ code: api.SpanStatusCode.ERROR, message: exception.message });
|
|
109
135
|
span.end();
|
|
110
136
|
};
|
|
111
137
|
const logFlushFailure = () => {
|
|
112
138
|
console.warn('OTel span flush failed');
|
|
113
139
|
};
|
|
114
|
-
const flushWithBudget = (flush) => Promise
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
const flushWithBudget = (flush) => new Promise((resolve) => {
|
|
141
|
+
const budget = setTimeout(resolve, FLUSH_BUDGET_MS);
|
|
142
|
+
budget.unref();
|
|
143
|
+
void Promise.resolve()
|
|
144
|
+
.then(flush)
|
|
145
|
+
.catch(logFlushFailure)
|
|
146
|
+
.finally(() => {
|
|
147
|
+
clearTimeout(budget);
|
|
148
|
+
resolve();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
120
151
|
const getStatusCode = (result) => {
|
|
121
152
|
if (typeof result !== 'object' || result === null) {
|
|
122
153
|
return undefined;
|
|
@@ -139,11 +170,11 @@ const withInvocationSpan = async (attributes, run) => {
|
|
|
139
170
|
if (statusCode !== undefined && statusCode >= 500) {
|
|
140
171
|
span.setStatus({ code: api.SpanStatusCode.ERROR });
|
|
141
172
|
}
|
|
142
|
-
|
|
173
|
+
span.end();
|
|
143
174
|
return result;
|
|
144
175
|
}
|
|
145
176
|
catch (error) {
|
|
146
|
-
|
|
177
|
+
failSpan(span, error);
|
|
147
178
|
throw error;
|
|
148
179
|
}
|
|
149
180
|
finally {
|
|
@@ -157,11 +188,11 @@ const withStageSpan = async (name, run) => {
|
|
|
157
188
|
return sdk.tracer.startActiveSpan(name, async (span) => {
|
|
158
189
|
try {
|
|
159
190
|
const result = await run();
|
|
160
|
-
|
|
191
|
+
span.end();
|
|
161
192
|
return result;
|
|
162
193
|
}
|
|
163
194
|
catch (error) {
|
|
164
|
-
|
|
195
|
+
failSpan(span, error);
|
|
165
196
|
throw error;
|
|
166
197
|
}
|
|
167
198
|
});
|
package/dist/tracing.esm.mjs
CHANGED
|
@@ -16,6 +16,7 @@ const DEFAULT_ENDPOINT = '127.0.0.1:2000';
|
|
|
16
16
|
const PROTOCOL_HEADER = '{"format":"json","version":1}\n';
|
|
17
17
|
const OTEL_TRACES_BINARY_PREFIX = 'T1S';
|
|
18
18
|
const MAX_PORT = 65_535;
|
|
19
|
+
const MAX_UDP_PAYLOAD_BYTES = 65_507;
|
|
19
20
|
const parseEndpoint = (endpoint) => {
|
|
20
21
|
const [host, port] = endpoint.split(':');
|
|
21
22
|
const parsedPort = Number(port);
|
|
@@ -28,37 +29,63 @@ const parseEndpoint = (endpoint) => {
|
|
|
28
29
|
}
|
|
29
30
|
return { host, port: parsedPort };
|
|
30
31
|
};
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const encode = (spans) => {
|
|
33
|
+
const serialized = ProtobufTraceSerializer.serializeRequest(spans);
|
|
34
|
+
return serialized == null
|
|
35
|
+
? undefined
|
|
36
|
+
: Buffer.from(`${PROTOCOL_HEADER}${OTEL_TRACES_BINARY_PREFIX}${Buffer.from(serialized).toString('base64')}`, 'utf8');
|
|
34
37
|
};
|
|
35
|
-
const
|
|
38
|
+
const encodeBatches = (spans, maxPayloadBytes) => {
|
|
39
|
+
if (spans.length === 0) {
|
|
40
|
+
return { droppedSpans: 0, messages: [] };
|
|
41
|
+
}
|
|
42
|
+
const message = encode(spans);
|
|
43
|
+
if (!message) {
|
|
44
|
+
return { droppedSpans: 0, messages: [] };
|
|
45
|
+
}
|
|
46
|
+
if (message.length <= maxPayloadBytes) {
|
|
47
|
+
return { droppedSpans: 0, messages: [message] };
|
|
48
|
+
}
|
|
49
|
+
if (spans.length === 1) {
|
|
50
|
+
return { droppedSpans: 1, messages: [] };
|
|
51
|
+
}
|
|
52
|
+
const middle = Math.ceil(spans.length / 2);
|
|
53
|
+
const head = encodeBatches(spans.slice(0, middle), maxPayloadBytes);
|
|
54
|
+
const tail = encodeBatches(spans.slice(middle), maxPayloadBytes);
|
|
55
|
+
return {
|
|
56
|
+
droppedSpans: head.droppedSpans + tail.droppedSpans,
|
|
57
|
+
messages: [...head.messages, ...tail.messages],
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const send = (socket, message, { host, port }) => new Promise((resolve) => {
|
|
36
61
|
try {
|
|
37
|
-
socket.send(
|
|
38
|
-
|
|
39
|
-
reportFailure(error, resultCallback);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
62
|
+
socket.send(message, port, host, (error) => {
|
|
63
|
+
resolve(error ?? undefined);
|
|
43
64
|
});
|
|
44
65
|
}
|
|
45
66
|
catch (error) {
|
|
46
|
-
|
|
67
|
+
resolve(error instanceof Error ? error : new Error(String(error)));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const exportBatches = async (socket, endpoint, { droppedSpans, messages }) => {
|
|
71
|
+
const errors = await Promise.all(messages.map((message) => send(socket, message, endpoint)));
|
|
72
|
+
const error = errors.find((sendError) => sendError !== undefined) ??
|
|
73
|
+
(droppedSpans > 0
|
|
74
|
+
? new Error(`${droppedSpans} span(s) exceed the UDP payload limit`)
|
|
75
|
+
: undefined);
|
|
76
|
+
if (error) {
|
|
77
|
+
diag.error('Error sending OTLP/UDP data: %s', error);
|
|
78
|
+
return { code: ExportResultCode.FAILED, error };
|
|
47
79
|
}
|
|
80
|
+
return { code: ExportResultCode.SUCCESS };
|
|
48
81
|
};
|
|
49
|
-
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT) => {
|
|
50
|
-
const
|
|
82
|
+
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT, { maxPayloadBytes = MAX_UDP_PAYLOAD_BYTES } = {}) => {
|
|
83
|
+
const parsedEndpoint = parseEndpoint(endpoint);
|
|
51
84
|
const socket = dgram.createSocket('udp4');
|
|
52
85
|
socket.unref();
|
|
53
86
|
return {
|
|
54
87
|
export: (spans, resultCallback) => {
|
|
55
|
-
|
|
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);
|
|
88
|
+
void exportBatches(socket, parsedEndpoint, encodeBatches(spans, Math.min(maxPayloadBytes, MAX_UDP_PAYLOAD_BYTES))).then(resultCallback);
|
|
62
89
|
},
|
|
63
90
|
forceFlush: () => Promise.resolve(),
|
|
64
91
|
shutdown: () => new Promise((resolve) => {
|
|
@@ -99,22 +126,26 @@ const sdk = process.env.OTEL_ENABLED === 'true' ? createTracingSdk() : undefined
|
|
|
99
126
|
const invocationParentContext = () => propagation.extract(context.active(), {
|
|
100
127
|
'x-amzn-trace-id': process.env._X_AMZN_TRACE_ID ?? '',
|
|
101
128
|
});
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
129
|
+
const failSpan = (span, error) => {
|
|
130
|
+
const exception = error instanceof Error ? error : new Error(String(error));
|
|
131
|
+
span.recordException(exception);
|
|
132
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: exception.message });
|
|
107
133
|
span.end();
|
|
108
134
|
};
|
|
109
135
|
const logFlushFailure = () => {
|
|
110
136
|
console.warn('OTel span flush failed');
|
|
111
137
|
};
|
|
112
|
-
const flushWithBudget = (flush) => Promise
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
138
|
+
const flushWithBudget = (flush) => new Promise((resolve) => {
|
|
139
|
+
const budget = setTimeout(resolve, FLUSH_BUDGET_MS);
|
|
140
|
+
budget.unref();
|
|
141
|
+
void Promise.resolve()
|
|
142
|
+
.then(flush)
|
|
143
|
+
.catch(logFlushFailure)
|
|
144
|
+
.finally(() => {
|
|
145
|
+
clearTimeout(budget);
|
|
146
|
+
resolve();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
118
149
|
const getStatusCode = (result) => {
|
|
119
150
|
if (typeof result !== 'object' || result === null) {
|
|
120
151
|
return undefined;
|
|
@@ -137,11 +168,11 @@ const withInvocationSpan = async (attributes, run) => {
|
|
|
137
168
|
if (statusCode !== undefined && statusCode >= 500) {
|
|
138
169
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
139
170
|
}
|
|
140
|
-
|
|
171
|
+
span.end();
|
|
141
172
|
return result;
|
|
142
173
|
}
|
|
143
174
|
catch (error) {
|
|
144
|
-
|
|
175
|
+
failSpan(span, error);
|
|
145
176
|
throw error;
|
|
146
177
|
}
|
|
147
178
|
finally {
|
|
@@ -155,11 +186,11 @@ const withStageSpan = async (name, run) => {
|
|
|
155
186
|
return sdk.tracer.startActiveSpan(name, async (span) => {
|
|
156
187
|
try {
|
|
157
188
|
const result = await run();
|
|
158
|
-
|
|
189
|
+
span.end();
|
|
159
190
|
return result;
|
|
160
191
|
}
|
|
161
192
|
catch (error) {
|
|
162
|
-
|
|
193
|
+
failSpan(span, error);
|
|
163
194
|
throw error;
|
|
164
195
|
}
|
|
165
196
|
});
|