@quicore/tracelet-otel 0.0.0-stage → 0.5.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/LICENSE +21 -0
- package/README.md +163 -2
- package/dist/index.js +303 -0
- package/dist/metrics.js +104 -0
- package/dist/runtime.js +83 -0
- package/package.json +36 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Carnival Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,164 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @quicore/tracelet-otel 0.5.0
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Optional OpenTelemetry adapter: native spans, W3C propagation, bounded metrics and runtime cache export. Core and runtime remain dependency-free. The application supplies a tracer and optional meter; the adapter never registers global providers/context managers, starts samplers/exporters, installs signal handlers, or flushes/shuts down providers.
|
|
4
|
+
|
|
5
|
+
## Installation and compatibility
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @quicore/tracelet-otel @opentelemetry/api@1.9.0
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires API peer `@opentelemetry/api@1.9.0`; depends on `@quicore/tracelet@0.5.0` (internal bridge coupling) and `@opentelemetry/core@2.12.0` (W3C propagator). SDKs, exporters, context managers and the runtime sampler are application dependencies. The pinned test/example SDK is 2.12.0. API 1.10 and SDK 3 are **not** supported by this release's dependency contract.
|
|
12
|
+
|
|
13
|
+
Engine range: Node ^18.19.0 or >=20.6.0. Windows tests passed on 18.20.8, 20.20.0, 22.22.0 and 24.13.0. Compatibility tests do not endorse EOL runtimes; Linux and other dependency combinations remain unverified. CJS and ESM named imports work. The npm package contains precompiled JavaScript; no build step is needed.
|
|
14
|
+
|
|
15
|
+
## Basic use
|
|
16
|
+
|
|
17
|
+
For this runnable local example, install the application's SDK dependencies:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install @opentelemetry/sdk-trace@2.12.0 @opentelemetry/sdk-metrics@2.12.0
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The console exporter below is for local inspection. Configure your application's
|
|
24
|
+
exporter, resources, sampling and bounded batch processing before deployment.
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { createTraceletAdapter } from '@quicore/tracelet-otel';
|
|
28
|
+
import { TracerProvider, SimpleSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace';
|
|
29
|
+
import { MeterProvider } from '@opentelemetry/sdk-metrics';
|
|
30
|
+
|
|
31
|
+
const tracerProvider = new TracerProvider({
|
|
32
|
+
spanProcessors: [new SimpleSpanProcessor({ exporter: new ConsoleSpanExporter() })],
|
|
33
|
+
});
|
|
34
|
+
const meterProvider = new MeterProvider(); // Add a reader/exporter for metric delivery.
|
|
35
|
+
const adapter = createTraceletAdapter({
|
|
36
|
+
tracer: tracerProvider.getTracer('my-service.tracelet', '0.5.0'),
|
|
37
|
+
meter: meterProvider.getMeter('my-service.tracelet', '0.5.0'),
|
|
38
|
+
operationNames: ['syncOrders'],
|
|
39
|
+
resourceTypes: ['system'],
|
|
40
|
+
businessMetrics: [{ name: 'orders', type: 'upDownCounter' }],
|
|
41
|
+
tagKeys: ['region'],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const work = adapter.create({ operationName: 'syncOrders' });
|
|
45
|
+
try {
|
|
46
|
+
work.start().tag('region', 'west');
|
|
47
|
+
// Replace these example observations with measured application values.
|
|
48
|
+
work.recordIO({ status: 200, durationMs: 12, bytesIn: 120 });
|
|
49
|
+
work.increment('orders', 1).complete();
|
|
50
|
+
console.log(adapter.otelContext(work));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
work.fail(error);
|
|
53
|
+
throw error;
|
|
54
|
+
} finally {
|
|
55
|
+
// In a service, do this once at shutdown after draining all work.
|
|
56
|
+
adapter.dispose();
|
|
57
|
+
await Promise.all([tracerProvider.shutdown(), meterProvider.shutdown()]);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
CommonJS: `const { createTraceletAdapter } = require('@quicore/tracelet-otel')`.
|
|
62
|
+
To export runtime data, install `@quicore/tracelet-runtime` and pass an application-started
|
|
63
|
+
sampler as `runtimeSampler`; the application also owns its shutdown.
|
|
64
|
+
|
|
65
|
+
`withContext` requires an application-installed OTel context manager (for Node, AsyncLocalStorageContextManager). Without it the API's no-op manager cannot propagate active context through async work. Explicit parents and `createChildSpan()` do not need a global manager.
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
| API | Behavior |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `create(coreOptions, spanOptions?)` | Returns a normal Tracelet; no late attach to existing contexts |
|
|
72
|
+
| `fromHeaders(headers, coreOptions, spanOptions?)` | Explicit W3C extraction; plain objects or native Headers |
|
|
73
|
+
| `inject(tracelet, carrier?, {legacy?: boolean})` | Mutates/returns carrier; clears old W3C fields first; optional legacy trace/span IDs only |
|
|
74
|
+
| `otelContext(tracelet)` | Detached native traceId/spanId/traceFlags; null for no-op provider, start failure or capacity shedding |
|
|
75
|
+
| `withContext(tracelet, callback, ...args)` | Uses app context manager; returns callback value/promise and restores prior context |
|
|
76
|
+
| `toDiagnostics()` | Detached fixed counters plus activeSpans and disposed |
|
|
77
|
+
| `dispose()` | Idempotent; removes observable callbacks, ends active SDK spans as abandoned, rejects future creates/children |
|
|
78
|
+
|
|
79
|
+
`spanOptions`: explicit `parentContext` wins, otherwise `useActiveContext: true` requests the active context; default is a root. `kind` is a native SpanKind, default INTERNAL. `fromHeaders` uses an explicit parent or extracted remote parent, otherwise root; `trustRemoteParent: false` discards remote ancestry. Inbound extraction has no implicit active-context fallback.
|
|
80
|
+
|
|
81
|
+
| Factory option | Default / limits |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| tracer / meter | Required tracer / optional meter; injected app-owned objects |
|
|
84
|
+
| operationNames / resourceTypes | Empty static registries, max 100 each; unmatched metric values become __other__ |
|
|
85
|
+
| businessMetrics | Up to 100 unique {name, type, unit?}; type counter, upDownCounter, or gauge |
|
|
86
|
+
| tagKeys | Empty allowlist, max 32 |
|
|
87
|
+
| includeIdentity | false; true includes tenant/integration as trace attributes only |
|
|
88
|
+
| sanitizeError | Optional synchronous function returning approved name/message/stack strings; no exception text/type/stack by default |
|
|
89
|
+
| maxActiveSpans | 10000; configurable 1..100000 |
|
|
90
|
+
| maxDimensionSets | 1000 per synchronous instrument; configurable 1..10000 |
|
|
91
|
+
| runtimeSampler | Optional schema-1 sampler; requires meter; never started/stopped by adapter |
|
|
92
|
+
|
|
93
|
+
Registration names match `[a-zA-Z][a-zA-Z0-9_.-]{0,127}` and must be unique/static. Register once at startup. Use one adapter per instrumentation scope/service lifetime: recreating adapters resets adapter budgets without clearing SDK aggregation, defeating a lifetime cardinality bound.
|
|
94
|
+
|
|
95
|
+
## Lifecycle, identity and ownership
|
|
96
|
+
|
|
97
|
+
Native spans start at construction, including PENDING time. Start adds an event; complete/fail/skip end once. Fail marks ERROR without a message; complete/skip retain UNSET. Failed I/O does not automatically fail the execution. End timestamps use the core epoch anchor plus monotonic elapsed time.
|
|
98
|
+
|
|
99
|
+
Existing UUID/custom IDs and snapshot schema 3 remain unchanged. Legacy IDs become trace correlation attributes, never fabricated native IDs or metric dimensions. The SDK owns native identity/sampling. Children inherit native ancestry, can outlive parents, and can be created after parent completion. Parenting into an existing HTTP server span creates an INTERNAL business child by default.
|
|
100
|
+
|
|
101
|
+
Per-context state is weakly keyed. Completed contexts retain native SpanContext for late children/correlation, not SDK Span objects or ancestor context trees. The bounded active-span registry drops excess native spans while retaining core behavior/metrics. Shed contexts have no native propagation context; a later child can become a new root if capacity returns. Unfinished spans occupy capacity until terminal/disposal; there is no automatic timeout policy.
|
|
102
|
+
|
|
103
|
+
Disposal ends active SDK spans as abandoned without changing core lifecycle or fabricating terminal execution metrics. Existing contexts continue locally with telemetry disabled. Callback failures are diagnosed and contained. Disposal does not flush or guarantee delivery; exporter queues, retries, transport and shutdown order belong to the app/Collector.
|
|
104
|
+
|
|
105
|
+
## Propagation and data policy
|
|
106
|
+
|
|
107
|
+
W3C syntax validation uses OTel's propagator. Invalid/duplicate/ambiguous traceparent becomes a root and increments invalidParents. Valid tracestate survives. Baggage is never imported or injected; unrelated fields already in an outgoing carrier are untouched.
|
|
108
|
+
|
|
109
|
+
Legacy-only incoming correlation starts a native root. Ambiguous custom headers still throw. Incoming tenant/integration headers are ignored: provide authenticated identity in coreOptions. Optional legacy dual-write sends correlation IDs only, unlike core's existing toTraceHeaders(), which can include identity.
|
|
110
|
+
|
|
111
|
+
Tags export only allowlisted scalars or homogeneous scalar arrays (max 32); strings are capped at 1024 characters. Nested objects are omitted. Registered per-context gauges become span attributes, not global gauges. Exception fields need an explicit sanitizer, with 1024 characters per field. Async sanitizers are unsupported/diagnosed; native promise rejections are consumed. No raw errors are logged on failure.
|
|
112
|
+
|
|
113
|
+
Operation names, resourceType and legacy IDs still reach trace attributes and may themselves contain sensitive values. Use static operation names and opaque identifiers. This is not automatic PHI/secret redaction or identity verification. SDK attribute limits may drop additional attributes.
|
|
114
|
+
|
|
115
|
+
## Metrics
|
|
116
|
+
|
|
117
|
+
Names are Tracelet-owned, not a claim of full semantic-convention compliance. Configure service/instance identity on application SDK resources. No tenant, integration, trace/span ID, URL or error text dimensions are used.
|
|
118
|
+
|
|
119
|
+
| Instrument | Type / unit | Meaning |
|
|
120
|
+
| --- | --- | --- |
|
|
121
|
+
| tracelet.executions | Counter / {execution} | Accepted terminal transitions; operation, resource, outcome |
|
|
122
|
+
| tracelet.execution.duration | Histogram / s | Construction-to-terminal time, same dimensions |
|
|
123
|
+
| tracelet.io.attempts | Counter / {attempt} | Accepted attempts; operation, resource, success, coarse 1xx..5xx/other status |
|
|
124
|
+
| tracelet.io.duration | Histogram / s | Valid individual durations, including zero |
|
|
125
|
+
| tracelet.io.bytes.in / .out | Counter / By | Known byte observations |
|
|
126
|
+
| tracelet.io.measurement.unavailable | Counter / {measurement} | Missing/invalid by field/reason plus I/O dimensions |
|
|
127
|
+
| tracelet.business.NAME | Counter or UpDownCounter / configured unit | Registered deltas; Counter drops negative deltas, UpDownCounter preserves them |
|
|
128
|
+
| tracelet.adapter.events | ObservableCounter / {event} | Fixed diagnostic reason counters |
|
|
129
|
+
|
|
130
|
+
Unknown business names/types are diagnosed and omitted. Gauge registrations affect traces only; application-wide business gauges should use the application's meter directly. Snapshot reads/resetIO never replay/reset exported counters. Metrics are independent of trace sampling. Histograms receive individual observations, never local percentiles. Configure bucket boundaries and SDK cardinality limits in app-owned views/readers.
|
|
131
|
+
|
|
132
|
+
Each synchronous instrument independently caps dimension sets and drops excess new sets. A bounded lifetime sum guard drops values that would make an instrument/dimension aggregate nonfinite; this is conservative for delta readers. It does not provide arbitrary precision or prevent downstream overflow when SDK views merge dimensions or services aggregate. SDKs may apply additional limits.
|
|
133
|
+
|
|
134
|
+
### Runtime
|
|
135
|
+
|
|
136
|
+
Cache export includes CPU user/system cumulative seconds, core-equivalent CPU utilization, RSS, uptime, isolate heap/external/array-buffer bytes, event-loop utilization and mean/max/p50/p95/p99 delay **gauges** in seconds. CPU time is an ObservableCounter; other measurements are gauges. Delay sample count and sampler interval are also gauges. Workers omit process metrics; isolate series include tracelet.thread.id. Array-buffer bytes are included in external memory; do not sum them.
|
|
137
|
+
|
|
138
|
+
| Runtime instrument | Unit / dimensions |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| tracelet.process.cpu.time | s; cpu.mode=user/system |
|
|
141
|
+
| tracelet.process.cpu.utilization | 1; core-equivalents, can exceed 1 |
|
|
142
|
+
| tracelet.process.memory.rss | By |
|
|
143
|
+
| tracelet.process.uptime | s |
|
|
144
|
+
| tracelet.node.heap.used / .total | By; thread |
|
|
145
|
+
| tracelet.node.memory.external / .array_buffers | By; thread |
|
|
146
|
+
| tracelet.node.event_loop.utilization | 1; thread |
|
|
147
|
+
| tracelet.node.event_loop.delay | s; thread, statistic=mean/max/p50/p95/p99 |
|
|
148
|
+
| tracelet.node.event_loop.delay.samples | {sample}; thread, interval count (not cumulative) |
|
|
149
|
+
| tracelet.runtime.sample.interval | s; thread |
|
|
150
|
+
| tracelet.runtime.sample.age | s; thread |
|
|
151
|
+
| tracelet.runtime.sample.valid | 1; no dimensions |
|
|
152
|
+
| tracelet.runtime.sample.errors | {error}; thread, fixed reason |
|
|
153
|
+
|
|
154
|
+
`tracelet.runtime.sample.age` reports seconds since last successful sample. `tracelet.runtime.sample.valid` is 1 only when running, status ok, and cache age <= three configured intervals; otherwise 0. `tracelet.runtime.sample.errors` is an ObservableCounter by fixed reason. Initial deltas and invalid groups are not observed. Multiple readers never collect/reset the sampler.
|
|
155
|
+
|
|
156
|
+
**SDK history caveat:** the tested cumulative SDK retains prior values even when later callbacks omit them or are removed. Use sample.valid=1 and freshness when interpreting runtime data; omission does not guarantee remote series disappearance. After disposal even validity can remain in SDK history. Shut down/export app-owned providers deliberately; disposal is not remote series deletion. Partial samples conservatively mark the whole sample invalid.
|
|
157
|
+
|
|
158
|
+
## Deployment limitations
|
|
159
|
+
|
|
160
|
+
Package installation is separate from production-readiness validation. Collector/OTLP transport, host metrics, Linux, sustained retention/outage tests, SDK 3 migration and LogicMonitor ingestion remain separate work.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT. See [LICENSE](LICENSE).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createTraceletAdapter = createTraceletAdapter;
|
|
7
|
+
var _api = require("@opentelemetry/api");
|
|
8
|
+
var _core = require("@opentelemetry/core");
|
|
9
|
+
var _tracelet = require("@quicore/tracelet");
|
|
10
|
+
var _observe = require("@quicore/tracelet/internal/observe");
|
|
11
|
+
var _metrics = require("./metrics.js");
|
|
12
|
+
var _runtime = require("./runtime.js");
|
|
13
|
+
var _nodeUtil = require("node:util");
|
|
14
|
+
const timestamp = ms => [Math.floor(ms / 1000), Math.round(ms % 1000 * 1e6)];
|
|
15
|
+
const DIAGNOSTICS = ['sdkErrors', 'spanLimitDrops', 'dimensionDrops', 'numericOverflowDrops', 'unregisteredMetrics', 'negativeCounterDrops', 'metricTypeDrops', 'invalidParents', 'runtimeErrors', 'sanitizerErrors'];
|
|
16
|
+
const getter = {
|
|
17
|
+
keys: carrier => Object.keys(carrier),
|
|
18
|
+
get: (carrier, key) => carrier[key]
|
|
19
|
+
};
|
|
20
|
+
const setter = {
|
|
21
|
+
set: (carrier, key, value) => {
|
|
22
|
+
if (typeof carrier.set === 'function') carrier.set(key, value);else carrier[key] = value;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function carrierHeaders(headers) {
|
|
26
|
+
const result = Object.create(null);
|
|
27
|
+
const entries = typeof headers?.entries === 'function' ? headers.entries() : Object.entries(headers ?? {});
|
|
28
|
+
for (const [key, value] of entries) {
|
|
29
|
+
const k = key.toLowerCase();
|
|
30
|
+
if (k !== 'traceparent' && k !== 'tracestate') continue;
|
|
31
|
+
// Reject ambiguous parents before delegating syntax validation to W3C.
|
|
32
|
+
if (Object.hasOwn(result, k)) result[k] = '';else result[k] = Array.isArray(value) ? value.length === 1 ? value[0] : '' : value;
|
|
33
|
+
if (typeof result[k] !== 'string' || result[k].length > 512) result[k] = '';
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
function attribute(value) {
|
|
38
|
+
if (typeof value === 'string') return value.slice(0, 1024);
|
|
39
|
+
if (typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)) return value;
|
|
40
|
+
if (Array.isArray(value) && value.length <= 32 && value.every(v => typeof v === typeof value[0] && ['string', 'boolean', 'number'].includes(typeof v) && (typeof v !== 'number' || Number.isFinite(v)))) return value.map(v => typeof v === 'string' ? v.slice(0, 1024) : v);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Explicit adapter: never registers globals, starts timers or owns providers. */
|
|
45
|
+
function createTraceletAdapter(options = {}) {
|
|
46
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('options must be an object');
|
|
47
|
+
const {
|
|
48
|
+
tracer,
|
|
49
|
+
meter,
|
|
50
|
+
runtimeSampler,
|
|
51
|
+
sanitizeError
|
|
52
|
+
} = options;
|
|
53
|
+
if (!tracer || typeof tracer.startSpan !== 'function') throw new TypeError('An application-owned tracer is required');
|
|
54
|
+
if (sanitizeError !== undefined && typeof sanitizeError !== 'function') throw new TypeError('sanitizeError must be a synchronous function');
|
|
55
|
+
if (options.includeIdentity !== undefined && typeof options.includeIdentity !== 'boolean') throw new TypeError('includeIdentity must be boolean');
|
|
56
|
+
const tags = (0, _metrics.names)(options.tagKeys ?? [], 'tagKeys', 32);
|
|
57
|
+
const maxActive = options.maxActiveSpans ?? 10000;
|
|
58
|
+
if (!Number.isInteger(maxActive) || maxActive < 1 || maxActive > 100000) throw new TypeError('maxActiveSpans must be 1..100000');
|
|
59
|
+
const diagnostics = Object.fromEntries(DIAGNOSTICS.map(key => [key, 0]));
|
|
60
|
+
const diagnostic = key => {
|
|
61
|
+
diagnostics[key] = Math.min(Number.MAX_SAFE_INTEGER, diagnostics[key] + 1);
|
|
62
|
+
};
|
|
63
|
+
const metrics = (0, _metrics.createMetrics)(meter, options, diagnostic);
|
|
64
|
+
const states = new WeakMap();
|
|
65
|
+
const active = new Set();
|
|
66
|
+
const propagator = new _core.W3CTraceContextPropagator();
|
|
67
|
+
let disposed = false;
|
|
68
|
+
const detachRuntime = (0, _runtime.attachRuntime)(meter, runtimeSampler, diagnostic);
|
|
69
|
+
let diagnosticInstrument;
|
|
70
|
+
const diagnosticCallback = result => {
|
|
71
|
+
try {
|
|
72
|
+
for (const reason of DIAGNOSTICS) result.observe(diagnostics[reason], {
|
|
73
|
+
'tracelet.reason': reason
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
diagnostic('sdkErrors');
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
diagnosticInstrument = meter?.createObservableCounter('tracelet.adapter.events', {
|
|
81
|
+
unit: '{event}'
|
|
82
|
+
});
|
|
83
|
+
diagnosticInstrument?.addCallback(diagnosticCallback);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
detachRuntime();
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
const nativeContext = state => state.sc ? _api.trace.setSpanContext(_api.ROOT_CONTEXT, state.sc) : _api.ROOT_CONTEXT;
|
|
89
|
+
const lookup = value => {
|
|
90
|
+
const state = states.get((0, _observe.observationHandlerFor)(value));
|
|
91
|
+
if (!state) throw new TypeError('Context does not belong to this adapter');
|
|
92
|
+
return state;
|
|
93
|
+
};
|
|
94
|
+
function finish(state, endTime) {
|
|
95
|
+
const span = state.span;
|
|
96
|
+
state.span = null;
|
|
97
|
+
active.delete(state);
|
|
98
|
+
if (span) {
|
|
99
|
+
try {
|
|
100
|
+
span.end(endTime);
|
|
101
|
+
} catch {
|
|
102
|
+
diagnostic('sdkErrors');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function observe(state, event) {
|
|
107
|
+
if (disposed) return;
|
|
108
|
+
const terminal = event.type === 'lifecycle' && event.isFinal;
|
|
109
|
+
const time = timestamp(event.timing.startTimeEpochMs + event.timing.elapsedMs);
|
|
110
|
+
if (event.type === 'create') {
|
|
111
|
+
try {
|
|
112
|
+
if (active.size >= maxActive) {
|
|
113
|
+
diagnostic('spanLimitDrops');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const id = event.identity;
|
|
117
|
+
const attrs = {
|
|
118
|
+
'tracelet.trace_id': id.traceId,
|
|
119
|
+
'tracelet.span_id': id.spanId,
|
|
120
|
+
'tracelet.resource_type': id.resourceType
|
|
121
|
+
};
|
|
122
|
+
if (id.parentSpanId) attrs['tracelet.parent_span_id'] = id.parentSpanId;
|
|
123
|
+
if (options.includeIdentity) {
|
|
124
|
+
if (id.tenantId) attrs['tracelet.tenant_id'] = id.tenantId;
|
|
125
|
+
if (id.integrationId) attrs['tracelet.integration_id'] = id.integrationId;
|
|
126
|
+
}
|
|
127
|
+
state.span = tracer.startSpan(id.operationName, {
|
|
128
|
+
kind: state.kind,
|
|
129
|
+
startTime: timestamp(event.timing.startTimeEpochMs),
|
|
130
|
+
attributes: attrs
|
|
131
|
+
}, state.parent);
|
|
132
|
+
if (disposed) {
|
|
133
|
+
finish(state, time);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
active.add(state);
|
|
137
|
+
const sc = state.span.spanContext();
|
|
138
|
+
state.sc = (0, _api.isSpanContextValid)(sc) ? Object.freeze({
|
|
139
|
+
...sc,
|
|
140
|
+
isRemote: false
|
|
141
|
+
}) : null;
|
|
142
|
+
} catch {
|
|
143
|
+
diagnostic('sdkErrors');
|
|
144
|
+
finish(state, time);
|
|
145
|
+
} finally {
|
|
146
|
+
state.parent = null;
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Metrics are independent of sampling and SDK span failures.
|
|
151
|
+
try {
|
|
152
|
+
metrics.observe(event, nativeContext(state), state.span);
|
|
153
|
+
} catch {
|
|
154
|
+
diagnostic('sdkErrors');
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const span = state.span;
|
|
158
|
+
if (!span) return;
|
|
159
|
+
if (event.type === 'tag' && tags.has(event.key)) {
|
|
160
|
+
const value = attribute(event.value);
|
|
161
|
+
if (value !== undefined) span.setAttribute(`tracelet.tag.${event.key}`, value);
|
|
162
|
+
} else if (event.type === 'lifecycle') {
|
|
163
|
+
if (terminal) {
|
|
164
|
+
span.setAttribute('tracelet.outcome', event.state.toLowerCase());
|
|
165
|
+
if (event.state === 'FAILED') {
|
|
166
|
+
span.setStatus({
|
|
167
|
+
code: _api.SpanStatusCode.ERROR
|
|
168
|
+
});
|
|
169
|
+
if (sanitizeError) {
|
|
170
|
+
try {
|
|
171
|
+
const safe = sanitizeError(event.error);
|
|
172
|
+
// Only explicit, bounded exception fields are accepted.
|
|
173
|
+
if (safe && typeof safe.then === 'function') {
|
|
174
|
+
if (_nodeUtil.types.isPromise(safe)) Promise.prototype.then.call(safe, undefined, () => {});
|
|
175
|
+
diagnostic('sanitizerErrors');
|
|
176
|
+
} else if (safe && typeof safe === 'object') {
|
|
177
|
+
const exception = {};
|
|
178
|
+
for (const key of ['name', 'message', 'stack']) if (typeof safe[key] === 'string') exception[key] = safe[key].slice(0, 1024);
|
|
179
|
+
if (Object.keys(exception).length) span.recordException(exception, time);
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
diagnostic('sanitizerErrors');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else span.addEvent('tracelet.running', {}, time);
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
diagnostic('sdkErrors');
|
|
190
|
+
} finally {
|
|
191
|
+
if (terminal) finish(state, time);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function create(traceletOptions, spanOptions = {}) {
|
|
195
|
+
if (disposed) throw new Error('Adapter is disposed');
|
|
196
|
+
if (!spanOptions || typeof spanOptions !== 'object') throw new TypeError('spanOptions must be an object');
|
|
197
|
+
const kind = spanOptions.kind ?? _api.SpanKind.INTERNAL;
|
|
198
|
+
if (![0, 1, 2, 3, 4].includes(kind)) throw new TypeError('Invalid span kind');
|
|
199
|
+
let rootParent = spanOptions.parentContext ?? (spanOptions.useActiveContext ? _api.context.active() : _api.ROOT_CONTEXT);
|
|
200
|
+
if (!rootParent || typeof rootParent.getValue !== 'function') throw new TypeError('Invalid parentContext');
|
|
201
|
+
function factory(parentHandler) {
|
|
202
|
+
if (disposed) throw new Error('Adapter is disposed');
|
|
203
|
+
const state = {
|
|
204
|
+
span: null,
|
|
205
|
+
sc: null,
|
|
206
|
+
parent: parentHandler ? nativeContext(states.get(parentHandler)) : rootParent,
|
|
207
|
+
kind: parentHandler ? _api.SpanKind.INTERNAL : kind
|
|
208
|
+
};
|
|
209
|
+
const handler = event => {
|
|
210
|
+
observe(state, event);
|
|
211
|
+
};
|
|
212
|
+
states.set(handler, state);
|
|
213
|
+
return handler;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
return (0, _observe.createObservationTree)(traceletOptions, factory);
|
|
217
|
+
} finally {
|
|
218
|
+
rootParent = null;
|
|
219
|
+
} // Never retain an application's full context tree.
|
|
220
|
+
}
|
|
221
|
+
return Object.freeze({
|
|
222
|
+
create,
|
|
223
|
+
fromHeaders(headers, traceletOptions, spanOptions = {}) {
|
|
224
|
+
const legacy = _tracelet.Tracelet._normalizeHeaders(headers);
|
|
225
|
+
const carrier = carrierHeaders(headers);
|
|
226
|
+
let parent = _api.ROOT_CONTEXT;
|
|
227
|
+
if (spanOptions.trustRemoteParent !== false) {
|
|
228
|
+
parent = propagator.extract(_api.ROOT_CONTEXT, carrier, getter);
|
|
229
|
+
if (Object.hasOwn(carrier, 'traceparent') && !_api.trace.getSpanContext(parent)) diagnostic('invalidParents');
|
|
230
|
+
}
|
|
231
|
+
// Incoming tenant/integration values are not authenticated identity.
|
|
232
|
+
return create({
|
|
233
|
+
traceId: legacy['x-trace-id'] || undefined,
|
|
234
|
+
parentSpanId: legacy['x-span-id'] || null,
|
|
235
|
+
...traceletOptions
|
|
236
|
+
}, {
|
|
237
|
+
...spanOptions,
|
|
238
|
+
parentContext: spanOptions.parentContext ?? parent
|
|
239
|
+
});
|
|
240
|
+
},
|
|
241
|
+
inject(value, carrier = {}, {
|
|
242
|
+
legacy = false
|
|
243
|
+
} = {}) {
|
|
244
|
+
const state = lookup(value);
|
|
245
|
+
if (typeof carrier.delete === 'function' && typeof carrier.set === 'function') {
|
|
246
|
+
carrier.delete('traceparent');
|
|
247
|
+
carrier.delete('tracestate');
|
|
248
|
+
} else {
|
|
249
|
+
for (const key of Object.keys(carrier)) if (['traceparent', 'tracestate'].includes(key.toLowerCase())) delete carrier[key];
|
|
250
|
+
}
|
|
251
|
+
propagator.inject(nativeContext(state), carrier, setter);
|
|
252
|
+
if (legacy) {
|
|
253
|
+
// Opt-in legacy correlation only; identity must not cross trust boundaries implicitly.
|
|
254
|
+
setter.set(carrier, 'x-trace-id', value.traceId);
|
|
255
|
+
setter.set(carrier, 'x-span-id', value.spanId);
|
|
256
|
+
}
|
|
257
|
+
return carrier;
|
|
258
|
+
},
|
|
259
|
+
otelContext(value) {
|
|
260
|
+
const sc = lookup(value).sc;
|
|
261
|
+
return sc ? {
|
|
262
|
+
traceId: sc.traceId,
|
|
263
|
+
spanId: sc.spanId,
|
|
264
|
+
traceFlags: sc.traceFlags
|
|
265
|
+
} : null;
|
|
266
|
+
},
|
|
267
|
+
withContext(value, callback, ...args) {
|
|
268
|
+
const state = lookup(value);
|
|
269
|
+
const base = _api.trace.deleteSpan(_api.context.active());
|
|
270
|
+
const ctx = state.span ? _api.trace.setSpan(base, state.span) : state.sc ? _api.trace.setSpanContext(base, state.sc) : base;
|
|
271
|
+
return _api.context.with(ctx, callback, undefined, ...args);
|
|
272
|
+
},
|
|
273
|
+
toDiagnostics() {
|
|
274
|
+
return {
|
|
275
|
+
...diagnostics,
|
|
276
|
+
activeSpans: active.size,
|
|
277
|
+
disposed
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
dispose() {
|
|
281
|
+
if (disposed) return;
|
|
282
|
+
disposed = true;
|
|
283
|
+
try {
|
|
284
|
+
detachRuntime();
|
|
285
|
+
} catch {
|
|
286
|
+
diagnostic('sdkErrors');
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
diagnosticInstrument?.removeCallback(diagnosticCallback);
|
|
290
|
+
} catch {
|
|
291
|
+
diagnostic('sdkErrors');
|
|
292
|
+
}
|
|
293
|
+
for (const state of active) {
|
|
294
|
+
try {
|
|
295
|
+
state.span.setAttribute('tracelet.outcome', 'abandoned');
|
|
296
|
+
} catch {
|
|
297
|
+
diagnostic('sdkErrors');
|
|
298
|
+
}
|
|
299
|
+
finish(state); // SDK monotonic end time; no fabricated core terminal event.
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createMetrics = createMetrics;
|
|
7
|
+
exports.names = names;
|
|
8
|
+
var _api = require("@opentelemetry/api");
|
|
9
|
+
function names(values, label, limit = 100) {
|
|
10
|
+
if (!Array.isArray(values) || values.length > limit || values.some(v => typeof v !== 'string' || !/^[a-zA-Z][a-zA-Z0-9_.-]{0,127}$/.test(v)) || new Set(values).size !== values.length) {
|
|
11
|
+
throw new TypeError(`${label} must contain at most ${limit} unique static names`);
|
|
12
|
+
}
|
|
13
|
+
return new Set(values);
|
|
14
|
+
}
|
|
15
|
+
function createMetrics(meter, options, diagnostic) {
|
|
16
|
+
const operations = names(options.operationNames ?? [], 'operationNames');
|
|
17
|
+
const resources = names(options.resourceTypes ?? [], 'resourceTypes');
|
|
18
|
+
const definitions = options.businessMetrics ?? [];
|
|
19
|
+
if (!Array.isArray(definitions) || definitions.length > 100) throw new TypeError('businessMetrics must be an array of at most 100 registrations');
|
|
20
|
+
names(definitions.map(d => d?.name), 'business metric names');
|
|
21
|
+
for (const d of definitions) {
|
|
22
|
+
if (!['counter', 'upDownCounter', 'gauge'].includes(d.type) || d.unit !== undefined && (typeof d.unit !== 'string' || d.unit.length > 32)) throw new TypeError('Invalid business metric registration');
|
|
23
|
+
}
|
|
24
|
+
const max = options.maxDimensionSets ?? 1000;
|
|
25
|
+
if (!Number.isInteger(max) || max < 1 || max > 10000) throw new TypeError('maxDimensionSets must be 1..10000');
|
|
26
|
+
// Each synchronous instrument has an independent, lifetime-bounded label set.
|
|
27
|
+
// Overflow is dropped and counted, never silently merged with real labels.
|
|
28
|
+
function instrument(method, name, unit, recordMethod) {
|
|
29
|
+
const target = meter?.[method](name, {
|
|
30
|
+
unit
|
|
31
|
+
});
|
|
32
|
+
const seen = new Map();
|
|
33
|
+
return (value, attributes, ctx = _api.ROOT_CONTEXT) => {
|
|
34
|
+
if (!target) return;
|
|
35
|
+
const key = JSON.stringify(attributes);
|
|
36
|
+
if (!seen.has(key)) {
|
|
37
|
+
if (seen.size >= max) {
|
|
38
|
+
diagnostic('dimensionDrops');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
seen.set(key, 0);
|
|
42
|
+
}
|
|
43
|
+
const sum = seen.get(key) + value;
|
|
44
|
+
if (!Number.isFinite(sum)) {
|
|
45
|
+
diagnostic('numericOverflowDrops');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
seen.set(key, sum);
|
|
49
|
+
try {
|
|
50
|
+
target[recordMethod](value, attributes, ctx);
|
|
51
|
+
} catch {
|
|
52
|
+
diagnostic('sdkErrors');
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const executions = instrument('createCounter', 'tracelet.executions', '{execution}', 'add');
|
|
57
|
+
const duration = instrument('createHistogram', 'tracelet.execution.duration', 's', 'record');
|
|
58
|
+
const attempts = instrument('createCounter', 'tracelet.io.attempts', '{attempt}', 'add');
|
|
59
|
+
const latency = instrument('createHistogram', 'tracelet.io.duration', 's', 'record');
|
|
60
|
+
const bytesIn = instrument('createCounter', 'tracelet.io.bytes.in', 'By', 'add');
|
|
61
|
+
const bytesOut = instrument('createCounter', 'tracelet.io.bytes.out', 'By', 'add');
|
|
62
|
+
const unavailable = instrument('createCounter', 'tracelet.io.measurement.unavailable', '{measurement}', 'add');
|
|
63
|
+
const business = new Map(definitions.map(d => [d.name, {
|
|
64
|
+
type: d.type,
|
|
65
|
+
write: d.type === 'gauge' ? null : instrument(d.type === 'counter' ? 'createCounter' : 'createUpDownCounter', `tracelet.business.${d.name}`, d.unit ?? '1', 'add')
|
|
66
|
+
}]));
|
|
67
|
+
const attributes = e => ({
|
|
68
|
+
'tracelet.operation': operations.has(e.identity.operationName) ? e.identity.operationName : '__other__',
|
|
69
|
+
'tracelet.resource': resources.has(e.identity.resourceType) ? e.identity.resourceType : '__other__'
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
observe(e, ctx, span) {
|
|
73
|
+
const attrs = attributes(e);
|
|
74
|
+
if (e.type === 'lifecycle' && e.isFinal) {
|
|
75
|
+
attrs['tracelet.outcome'] = e.state.toLowerCase();
|
|
76
|
+
executions(1, attrs, ctx);
|
|
77
|
+
duration(e.timing.elapsedMs / 1000, attrs, ctx);
|
|
78
|
+
} else if (e.type === 'io') {
|
|
79
|
+
attrs['tracelet.io.success'] = e.isSuccess;
|
|
80
|
+
attrs['tracelet.io.status_class'] = /^[1-5][0-9]{2}$/.test(String(e.status)) ? `${String(e.status)[0]}xx` : 'other';
|
|
81
|
+
attempts(1, attrs, ctx);
|
|
82
|
+
for (const [field, writer, scale] of [['durationMs', latency, 1000], ['bytesIn', bytesIn, 1], ['bytesOut', bytesOut, 1]]) {
|
|
83
|
+
const observation = e.measurements[field];
|
|
84
|
+
if (observation.validity === 'valid') writer(observation.value / scale, attrs, ctx);else unavailable(1, {
|
|
85
|
+
...attrs,
|
|
86
|
+
'tracelet.measurement': field,
|
|
87
|
+
'tracelet.reason': observation.validity
|
|
88
|
+
}, ctx);
|
|
89
|
+
}
|
|
90
|
+
} else if (e.type === 'counter' || e.type === 'gauge') {
|
|
91
|
+
const registration = business.get(e.name);
|
|
92
|
+
if (!registration) {
|
|
93
|
+
diagnostic('unregisteredMetrics');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (e.type === 'gauge' && registration.type === 'gauge') {
|
|
97
|
+
span?.setAttribute(`tracelet.gauge.${e.name}`, e.value);
|
|
98
|
+
} else if (e.type === 'counter' && registration.type !== 'gauge') {
|
|
99
|
+
if (registration.type === 'counter' && e.delta < 0) diagnostic('negativeCounterDrops');else registration.write(e.delta, attrs, ctx);
|
|
100
|
+
} else diagnostic('metricTypeDrops');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.attachRuntime = attachRuntime;
|
|
7
|
+
// No sampler ownership: readers only inspect the cache, never collect or reset.
|
|
8
|
+
function attachRuntime(meter, sampler, diagnostic) {
|
|
9
|
+
if (!sampler) return () => {};
|
|
10
|
+
if (!meter || typeof sampler.toSnapshot !== 'function') throw new TypeError('runtimeSampler requires a meter and toSnapshot()');
|
|
11
|
+
const instruments = [];
|
|
12
|
+
const make = (name, unit, counter = false) => {
|
|
13
|
+
const result = meter[counter ? 'createObservableCounter' : 'createObservableGauge'](name, {
|
|
14
|
+
unit
|
|
15
|
+
});
|
|
16
|
+
instruments.push(result);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
const cpu = make('tracelet.process.cpu.time', 's', true);
|
|
20
|
+
const utilization = make('tracelet.process.cpu.utilization', '1');
|
|
21
|
+
const rss = make('tracelet.process.memory.rss', 'By');
|
|
22
|
+
const uptime = make('tracelet.process.uptime', 's');
|
|
23
|
+
const memory = Object.fromEntries(['heapUsedBytes', 'heapTotalBytes', 'externalBytes', 'arrayBuffersBytes'].map((key, i) => [key, make(['tracelet.node.heap.used', 'tracelet.node.heap.total', 'tracelet.node.memory.external', 'tracelet.node.memory.array_buffers'][i], 'By')]));
|
|
24
|
+
const elu = make('tracelet.node.event_loop.utilization', '1');
|
|
25
|
+
const delay = make('tracelet.node.event_loop.delay', 's');
|
|
26
|
+
const count = make('tracelet.node.event_loop.delay.samples', '{sample}');
|
|
27
|
+
const interval = make('tracelet.runtime.sample.interval', 's');
|
|
28
|
+
const age = make('tracelet.runtime.sample.age', 's');
|
|
29
|
+
const valid = make('tracelet.runtime.sample.valid', '1');
|
|
30
|
+
const errors = make('tracelet.runtime.sample.errors', '{error}', true);
|
|
31
|
+
const reasons = ['clock', 'cpu', 'memory', 'uptime', 'eventLoopUtilization', 'eventLoopDelay', 'monitor', 'timer'];
|
|
32
|
+
const callback = result => {
|
|
33
|
+
let scope = {};
|
|
34
|
+
try {
|
|
35
|
+
const s = sampler.toSnapshot();
|
|
36
|
+
if (s.schemaVersion !== 1) throw new Error('Unsupported runtime schema');
|
|
37
|
+
// Stopped/stale caches must not look like fresh process measurements.
|
|
38
|
+
scope = {
|
|
39
|
+
'tracelet.thread.id': s.scope.threadId
|
|
40
|
+
};
|
|
41
|
+
const observe = (instrument, value, attrs = scope) => {
|
|
42
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0) result.observe(instrument, value, attrs);
|
|
43
|
+
};
|
|
44
|
+
observe(age, s.lastSuccessfulSampleAgeSeconds);
|
|
45
|
+
const fresh = s.running && s.sampleAgeSeconds !== null && s.sampleAgeSeconds <= s.config.intervalMs / 1000 * 3;
|
|
46
|
+
observe(valid, fresh && s.status === 'ok' ? 1 : 0, {});
|
|
47
|
+
for (const reason of reasons) observe(errors, s.diagnostics.errors[reason], {
|
|
48
|
+
...scope,
|
|
49
|
+
'tracelet.reason': reason
|
|
50
|
+
});
|
|
51
|
+
if (!fresh) return;
|
|
52
|
+
observe(interval, s.intervalSeconds);
|
|
53
|
+
if (s.scope.processMetricsEnabled && s.process) {
|
|
54
|
+
const processScope = {}; // Process resource identity belongs to the SDK resource.
|
|
55
|
+
observe(cpu, s.process.cpu?.userSeconds, {
|
|
56
|
+
'cpu.mode': 'user'
|
|
57
|
+
});
|
|
58
|
+
observe(cpu, s.process.cpu?.systemSeconds, {
|
|
59
|
+
'cpu.mode': 'system'
|
|
60
|
+
});
|
|
61
|
+
observe(utilization, s.process.cpu?.utilization, processScope);
|
|
62
|
+
observe(rss, s.process.rssBytes, processScope);
|
|
63
|
+
observe(uptime, s.process.uptimeSeconds, processScope);
|
|
64
|
+
}
|
|
65
|
+
for (const [key, instrument] of Object.entries(memory)) observe(instrument, s.node.memory?.[key]);
|
|
66
|
+
observe(elu, s.node.eventLoop?.utilization);
|
|
67
|
+
observe(count, s.node.eventLoop?.delay?.sampleCount);
|
|
68
|
+
for (const statistic of ['mean', 'max', 'p50', 'p95', 'p99']) observe(delay, s.node.eventLoop?.delay?.[`${statistic}Seconds`], {
|
|
69
|
+
...scope,
|
|
70
|
+
'tracelet.statistic': statistic
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
diagnostic('runtimeErrors');
|
|
74
|
+
try {
|
|
75
|
+
result.observe(valid, 0, {});
|
|
76
|
+
} catch {
|
|
77
|
+
diagnostic('sdkErrors');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
meter.addBatchObservableCallback(callback, instruments);
|
|
82
|
+
return () => meter.removeBatchObservableCallback(callback, instruments);
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quicore/tracelet-otel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Explicit Tracelet adapter for application-owned OpenTelemetry providers",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/**/*.js",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": "^18.19.0 || >=20.6.0"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@opentelemetry/api": ">=1.9.0 <1.10.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@quicore/tracelet": "0.5.0",
|
|
26
|
+
"@opentelemetry/core": "2.12.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"tracelet",
|
|
33
|
+
"opentelemetry",
|
|
34
|
+
"tracing",
|
|
35
|
+
"metrics",
|
|
36
|
+
"w3c"
|
|
37
|
+
]
|
|
38
|
+
}
|