@allthings/cloud-toolkit 5.4.0-beta.1 → 5.4.0-beta.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/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 +63 -35
- package/dist/tracing.esm.mjs +63 -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,60 @@ 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
|
+
const message = encode(spans);
|
|
42
|
+
if (!message) {
|
|
43
|
+
return { droppedSpans: 0, messages: [] };
|
|
44
|
+
}
|
|
45
|
+
if (message.length <= maxPayloadBytes) {
|
|
46
|
+
return { droppedSpans: 0, messages: [message] };
|
|
47
|
+
}
|
|
48
|
+
if (spans.length === 1) {
|
|
49
|
+
return { droppedSpans: 1, messages: [] };
|
|
50
|
+
}
|
|
51
|
+
const middle = Math.ceil(spans.length / 2);
|
|
52
|
+
const head = encodeBatches(spans.slice(0, middle), maxPayloadBytes);
|
|
53
|
+
const tail = encodeBatches(spans.slice(middle), maxPayloadBytes);
|
|
54
|
+
return {
|
|
55
|
+
droppedSpans: head.droppedSpans + tail.droppedSpans,
|
|
56
|
+
messages: [...head.messages, ...tail.messages],
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
const send = (socket, message, { host, port }) => new Promise((resolve) => {
|
|
38
60
|
try {
|
|
39
|
-
socket.send(
|
|
40
|
-
|
|
41
|
-
reportFailure(error, resultCallback);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
resultCallback({ code: core.ExportResultCode.SUCCESS });
|
|
61
|
+
socket.send(message, port, host, (error) => {
|
|
62
|
+
resolve(error ?? undefined);
|
|
45
63
|
});
|
|
46
64
|
}
|
|
47
65
|
catch (error) {
|
|
48
|
-
|
|
66
|
+
resolve(error instanceof Error ? error : new Error(String(error)));
|
|
49
67
|
}
|
|
68
|
+
});
|
|
69
|
+
const exportBatches = async (socket, endpoint, { droppedSpans, messages }) => {
|
|
70
|
+
const errors = await Promise.all(messages.map((message) => send(socket, message, endpoint)));
|
|
71
|
+
const error = errors.find((sendError) => sendError !== undefined) ??
|
|
72
|
+
(droppedSpans > 0
|
|
73
|
+
? new Error(`${droppedSpans} span(s) exceed the UDP payload limit`)
|
|
74
|
+
: undefined);
|
|
75
|
+
if (error) {
|
|
76
|
+
api.diag.error('Error sending OTLP/UDP data: %s', error);
|
|
77
|
+
return { code: core.ExportResultCode.FAILED, error };
|
|
78
|
+
}
|
|
79
|
+
return { code: core.ExportResultCode.SUCCESS };
|
|
50
80
|
};
|
|
51
|
-
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT) => {
|
|
52
|
-
const
|
|
81
|
+
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT, { maxPayloadBytes = MAX_UDP_PAYLOAD_BYTES } = {}) => {
|
|
82
|
+
const parsedEndpoint = parseEndpoint(endpoint);
|
|
53
83
|
const socket = dgram.createSocket('udp4');
|
|
54
84
|
socket.unref();
|
|
55
85
|
return {
|
|
56
86
|
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);
|
|
87
|
+
void exportBatches(socket, parsedEndpoint, encodeBatches(spans, maxPayloadBytes)).then(resultCallback);
|
|
64
88
|
},
|
|
65
89
|
forceFlush: () => Promise.resolve(),
|
|
66
90
|
shutdown: () => new Promise((resolve) => {
|
|
@@ -101,22 +125,26 @@ const sdk = process.env.OTEL_ENABLED === 'true' ? createTracingSdk() : undefined
|
|
|
101
125
|
const invocationParentContext = () => api.propagation.extract(api.context.active(), {
|
|
102
126
|
'x-amzn-trace-id': process.env._X_AMZN_TRACE_ID ?? '',
|
|
103
127
|
});
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
128
|
+
const failSpan = (span, error) => {
|
|
129
|
+
const exception = error instanceof Error ? error : new Error(String(error));
|
|
130
|
+
span.recordException(exception);
|
|
131
|
+
span.setStatus({ code: api.SpanStatusCode.ERROR, message: exception.message });
|
|
109
132
|
span.end();
|
|
110
133
|
};
|
|
111
134
|
const logFlushFailure = () => {
|
|
112
135
|
console.warn('OTel span flush failed');
|
|
113
136
|
};
|
|
114
|
-
const flushWithBudget = (flush) => Promise
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
137
|
+
const flushWithBudget = (flush) => new Promise((resolve) => {
|
|
138
|
+
const budget = setTimeout(resolve, FLUSH_BUDGET_MS);
|
|
139
|
+
budget.unref();
|
|
140
|
+
void Promise.resolve()
|
|
141
|
+
.then(flush)
|
|
142
|
+
.catch(logFlushFailure)
|
|
143
|
+
.finally(() => {
|
|
144
|
+
clearTimeout(budget);
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
120
148
|
const getStatusCode = (result) => {
|
|
121
149
|
if (typeof result !== 'object' || result === null) {
|
|
122
150
|
return undefined;
|
|
@@ -139,11 +167,11 @@ const withInvocationSpan = async (attributes, run) => {
|
|
|
139
167
|
if (statusCode !== undefined && statusCode >= 500) {
|
|
140
168
|
span.setStatus({ code: api.SpanStatusCode.ERROR });
|
|
141
169
|
}
|
|
142
|
-
|
|
170
|
+
span.end();
|
|
143
171
|
return result;
|
|
144
172
|
}
|
|
145
173
|
catch (error) {
|
|
146
|
-
|
|
174
|
+
failSpan(span, error);
|
|
147
175
|
throw error;
|
|
148
176
|
}
|
|
149
177
|
finally {
|
|
@@ -157,11 +185,11 @@ const withStageSpan = async (name, run) => {
|
|
|
157
185
|
return sdk.tracer.startActiveSpan(name, async (span) => {
|
|
158
186
|
try {
|
|
159
187
|
const result = await run();
|
|
160
|
-
|
|
188
|
+
span.end();
|
|
161
189
|
return result;
|
|
162
190
|
}
|
|
163
191
|
catch (error) {
|
|
164
|
-
|
|
192
|
+
failSpan(span, error);
|
|
165
193
|
throw error;
|
|
166
194
|
}
|
|
167
195
|
});
|
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,60 @@ 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
|
+
const message = encode(spans);
|
|
40
|
+
if (!message) {
|
|
41
|
+
return { droppedSpans: 0, messages: [] };
|
|
42
|
+
}
|
|
43
|
+
if (message.length <= maxPayloadBytes) {
|
|
44
|
+
return { droppedSpans: 0, messages: [message] };
|
|
45
|
+
}
|
|
46
|
+
if (spans.length === 1) {
|
|
47
|
+
return { droppedSpans: 1, messages: [] };
|
|
48
|
+
}
|
|
49
|
+
const middle = Math.ceil(spans.length / 2);
|
|
50
|
+
const head = encodeBatches(spans.slice(0, middle), maxPayloadBytes);
|
|
51
|
+
const tail = encodeBatches(spans.slice(middle), maxPayloadBytes);
|
|
52
|
+
return {
|
|
53
|
+
droppedSpans: head.droppedSpans + tail.droppedSpans,
|
|
54
|
+
messages: [...head.messages, ...tail.messages],
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const send = (socket, message, { host, port }) => new Promise((resolve) => {
|
|
36
58
|
try {
|
|
37
|
-
socket.send(
|
|
38
|
-
|
|
39
|
-
reportFailure(error, resultCallback);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
59
|
+
socket.send(message, port, host, (error) => {
|
|
60
|
+
resolve(error ?? undefined);
|
|
43
61
|
});
|
|
44
62
|
}
|
|
45
63
|
catch (error) {
|
|
46
|
-
|
|
64
|
+
resolve(error instanceof Error ? error : new Error(String(error)));
|
|
47
65
|
}
|
|
66
|
+
});
|
|
67
|
+
const exportBatches = async (socket, endpoint, { droppedSpans, messages }) => {
|
|
68
|
+
const errors = await Promise.all(messages.map((message) => send(socket, message, endpoint)));
|
|
69
|
+
const error = errors.find((sendError) => sendError !== undefined) ??
|
|
70
|
+
(droppedSpans > 0
|
|
71
|
+
? new Error(`${droppedSpans} span(s) exceed the UDP payload limit`)
|
|
72
|
+
: undefined);
|
|
73
|
+
if (error) {
|
|
74
|
+
diag.error('Error sending OTLP/UDP data: %s', error);
|
|
75
|
+
return { code: ExportResultCode.FAILED, error };
|
|
76
|
+
}
|
|
77
|
+
return { code: ExportResultCode.SUCCESS };
|
|
48
78
|
};
|
|
49
|
-
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT) => {
|
|
50
|
-
const
|
|
79
|
+
const createOtlpUdpSpanExporter = (endpoint = DEFAULT_ENDPOINT, { maxPayloadBytes = MAX_UDP_PAYLOAD_BYTES } = {}) => {
|
|
80
|
+
const parsedEndpoint = parseEndpoint(endpoint);
|
|
51
81
|
const socket = dgram.createSocket('udp4');
|
|
52
82
|
socket.unref();
|
|
53
83
|
return {
|
|
54
84
|
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);
|
|
85
|
+
void exportBatches(socket, parsedEndpoint, encodeBatches(spans, maxPayloadBytes)).then(resultCallback);
|
|
62
86
|
},
|
|
63
87
|
forceFlush: () => Promise.resolve(),
|
|
64
88
|
shutdown: () => new Promise((resolve) => {
|
|
@@ -99,22 +123,26 @@ const sdk = process.env.OTEL_ENABLED === 'true' ? createTracingSdk() : undefined
|
|
|
99
123
|
const invocationParentContext = () => propagation.extract(context.active(), {
|
|
100
124
|
'x-amzn-trace-id': process.env._X_AMZN_TRACE_ID ?? '',
|
|
101
125
|
});
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
126
|
+
const failSpan = (span, error) => {
|
|
127
|
+
const exception = error instanceof Error ? error : new Error(String(error));
|
|
128
|
+
span.recordException(exception);
|
|
129
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: exception.message });
|
|
107
130
|
span.end();
|
|
108
131
|
};
|
|
109
132
|
const logFlushFailure = () => {
|
|
110
133
|
console.warn('OTel span flush failed');
|
|
111
134
|
};
|
|
112
|
-
const flushWithBudget = (flush) => Promise
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
const flushWithBudget = (flush) => new Promise((resolve) => {
|
|
136
|
+
const budget = setTimeout(resolve, FLUSH_BUDGET_MS);
|
|
137
|
+
budget.unref();
|
|
138
|
+
void Promise.resolve()
|
|
139
|
+
.then(flush)
|
|
140
|
+
.catch(logFlushFailure)
|
|
141
|
+
.finally(() => {
|
|
142
|
+
clearTimeout(budget);
|
|
143
|
+
resolve();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
118
146
|
const getStatusCode = (result) => {
|
|
119
147
|
if (typeof result !== 'object' || result === null) {
|
|
120
148
|
return undefined;
|
|
@@ -137,11 +165,11 @@ const withInvocationSpan = async (attributes, run) => {
|
|
|
137
165
|
if (statusCode !== undefined && statusCode >= 500) {
|
|
138
166
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
139
167
|
}
|
|
140
|
-
|
|
168
|
+
span.end();
|
|
141
169
|
return result;
|
|
142
170
|
}
|
|
143
171
|
catch (error) {
|
|
144
|
-
|
|
172
|
+
failSpan(span, error);
|
|
145
173
|
throw error;
|
|
146
174
|
}
|
|
147
175
|
finally {
|
|
@@ -155,11 +183,11 @@ const withStageSpan = async (name, run) => {
|
|
|
155
183
|
return sdk.tracer.startActiveSpan(name, async (span) => {
|
|
156
184
|
try {
|
|
157
185
|
const result = await run();
|
|
158
|
-
|
|
186
|
+
span.end();
|
|
159
187
|
return result;
|
|
160
188
|
}
|
|
161
189
|
catch (error) {
|
|
162
|
-
|
|
190
|
+
failSpan(span, error);
|
|
163
191
|
throw error;
|
|
164
192
|
}
|
|
165
193
|
});
|