autotel-effect 4.0.1 → 5.0.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 +40 -3
- package/dist/index.cjs +20 -0
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +20 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -73,9 +73,42 @@ In a server, merge the layer where you assemble services:
|
|
|
73
73
|
appLayer.pipe(Layer.provideMerge(layer({ serviceName: 'my-api' })));
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
### 3. Join the surrounding trace with `withAutotel`
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Effect takes a span's parent from its own `Tracer.ParentSpan`, not from the
|
|
79
|
+
ambient OpenTelemetry context, and marks a span as a root when it has none. So a
|
|
80
|
+
bare `Effect.runPromise` inside an instrumented HTTP handler opens a **new
|
|
81
|
+
trace** — the handler's span and the Effect's span end up in two separate
|
|
82
|
+
traces, with nothing to say they belong together. `withAutotel` hands Effect the
|
|
83
|
+
active autotel span as the parent:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { withAutotel } from 'autotel-effect';
|
|
87
|
+
|
|
88
|
+
app.get('/api/todos', async () => {
|
|
89
|
+
// inside autotel's `node:http` span
|
|
90
|
+
return Effect.runPromise(withAutotel(program));
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
It reads the active span when the effect **runs**, so a program assembled once
|
|
95
|
+
at startup still joins the request it is run inside. That is also why this is
|
|
96
|
+
not part of `layer()`: a layer is built once for the whole application, long
|
|
97
|
+
before any request span exists.
|
|
98
|
+
|
|
99
|
+
With it, HTTP spans from autotel's `node:http` instrumentation become parents of
|
|
100
|
+
domain spans from `Effect.withSpan`.
|
|
101
|
+
|
|
102
|
+
### Don't reach for `trace()` here
|
|
103
|
+
|
|
104
|
+
autotel's `trace(name, fn)` wraps a function and ends its span when that
|
|
105
|
+
function **returns**. A function that returns an `Effect` returns a description
|
|
106
|
+
of work, not the work, so the span closes before anything runs and measures the
|
|
107
|
+
construction rather than the effect. Use Effect's own instrumentation instead —
|
|
108
|
+
`Effect.fn('name')(...)` or `Effect.withSpan('name')` — which is what `layer()`
|
|
109
|
+
routes through autotel.
|
|
110
|
+
|
|
111
|
+
### 4. Logs come with it
|
|
79
112
|
|
|
80
113
|
One `layer()` call bridges both signals. `Effect.log*` becomes an OpenTelemetry
|
|
81
114
|
**log record** (so it reaches any OTLP log backend, including autotel-devtools)
|
|
@@ -113,7 +146,7 @@ allows it); `pretty: true` swaps JSON for human-readable stdout; `console: false
|
|
|
113
146
|
drops the stdout line and emits only the log record — use it when
|
|
114
147
|
`captureConsole()` is on, or every Effect log is reported twice.
|
|
115
148
|
|
|
116
|
-
###
|
|
149
|
+
### 5. Unit tests: provide nothing
|
|
117
150
|
|
|
118
151
|
Effect's default `Tracer` is already an in-memory native tracer, so handler tests
|
|
119
152
|
need no layer at all — `Effect.withSpan` runs and exports nowhere:
|
|
@@ -127,12 +160,16 @@ that assert on exported spans.
|
|
|
127
160
|
|
|
128
161
|
## Trace shape
|
|
129
162
|
|
|
163
|
+
With `Effect.runPromise(withAutotel(program))`:
|
|
164
|
+
|
|
130
165
|
```text
|
|
131
166
|
HTTP GET /api/todos (autotel node:http)
|
|
132
167
|
└── todo.list (Effect.withSpan)
|
|
133
168
|
└── db.query (another withSpan or instrumented client)
|
|
134
169
|
```
|
|
135
170
|
|
|
171
|
+
Without `withAutotel`, `todo.list` is the root of a second, unrelated trace.
|
|
172
|
+
|
|
136
173
|
## Example
|
|
137
174
|
|
|
138
175
|
See [`apps/example-effect`](../../apps/example-effect) in the autotel monorepo.
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,8 @@ let autotel = require("autotel");
|
|
|
31
31
|
let autotel_logger = require("autotel/logger");
|
|
32
32
|
let effect_Cause = require("effect/Cause");
|
|
33
33
|
effect_Cause = __toESM(effect_Cause, 1);
|
|
34
|
+
let effect_Effect = require("effect/Effect");
|
|
35
|
+
effect_Effect = __toESM(effect_Effect, 1);
|
|
34
36
|
let effect_Layer = require("effect/Layer");
|
|
35
37
|
effect_Layer = __toESM(effect_Layer, 1);
|
|
36
38
|
let effect_Logger = require("effect/Logger");
|
|
@@ -58,6 +60,23 @@ function layer(options) {
|
|
|
58
60
|
}));
|
|
59
61
|
}
|
|
60
62
|
/**
|
|
63
|
+
* Runs `self` as a child of the autotel span active around it, so an
|
|
64
|
+
* `Effect.withSpan` inside an instrumented handler lands in that request's
|
|
65
|
+
* trace: `Effect.runPromise(withAutotel(program))`.
|
|
66
|
+
*
|
|
67
|
+
* Effect takes a span's parent from its own `Tracer.ParentSpan`, which the
|
|
68
|
+
* ambient OpenTelemetry context does not supply. The active span is read when
|
|
69
|
+
* the effect runs rather than when it is built, so a program assembled once at
|
|
70
|
+
* startup still joins the request it runs inside - and why this is not part of
|
|
71
|
+
* `layer()`, which is built once for the whole application.
|
|
72
|
+
*/
|
|
73
|
+
function withAutotel(self) {
|
|
74
|
+
return effect_Effect.suspend(() => {
|
|
75
|
+
const spanContext = (0, autotel.getActiveSpan)()?.spanContext();
|
|
76
|
+
return spanContext && autotel.otelTrace.isSpanContextValid(spanContext) ? _effect_opentelemetry_OtelTracer.withSpanContext(self, spanContext) : self;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
61
80
|
* Routes Effect's `Effect.log*` through autotel instead of Effect's console
|
|
62
81
|
* logger, emitting each log as an OpenTelemetry log record (so it reaches any
|
|
63
82
|
* OTLP log backend, autotel-devtools included) and, unless disabled, a
|
|
@@ -113,3 +132,4 @@ function formatPart(part) {
|
|
|
113
132
|
//#endregion
|
|
114
133
|
exports.layer = layer;
|
|
115
134
|
exports.loggerLayer = loggerLayer;
|
|
135
|
+
exports.withAutotel = withAutotel;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BuiltinLoggerOptions } from "autotel/logger";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Layer from "effect/Layer";
|
|
3
4
|
//#region src/index.d.ts
|
|
4
5
|
interface AutotelEffectLoggerOptions {
|
|
@@ -36,6 +37,18 @@ interface AutotelEffectLayerOptions {
|
|
|
36
37
|
* instrumentation module with `node --import` or `tsx --import`.
|
|
37
38
|
*/
|
|
38
39
|
declare function layer(options: AutotelEffectLayerOptions): Layer.Layer<never>;
|
|
40
|
+
/**
|
|
41
|
+
* Runs `self` as a child of the autotel span active around it, so an
|
|
42
|
+
* `Effect.withSpan` inside an instrumented handler lands in that request's
|
|
43
|
+
* trace: `Effect.runPromise(withAutotel(program))`.
|
|
44
|
+
*
|
|
45
|
+
* Effect takes a span's parent from its own `Tracer.ParentSpan`, which the
|
|
46
|
+
* ambient OpenTelemetry context does not supply. The active span is read when
|
|
47
|
+
* the effect runs rather than when it is built, so a program assembled once at
|
|
48
|
+
* startup still joins the request it runs inside - and why this is not part of
|
|
49
|
+
* `layer()`, which is built once for the whole application.
|
|
50
|
+
*/
|
|
51
|
+
declare function withAutotel<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
|
|
39
52
|
/**
|
|
40
53
|
* Routes Effect's `Effect.log*` through autotel instead of Effect's console
|
|
41
54
|
* logger, emitting each log as an OpenTelemetry log record (so it reaches any
|
|
@@ -47,4 +60,4 @@ declare function layer(options: AutotelEffectLayerOptions): Layer.Layer<never>;
|
|
|
47
60
|
*/
|
|
48
61
|
declare function loggerLayer(options: Omit<AutotelEffectLayerOptions, 'logs'> & AutotelEffectLoggerOptions): Layer.Layer<never, never, never>;
|
|
49
62
|
//#endregion
|
|
50
|
-
export { AutotelEffectLayerOptions, AutotelEffectLoggerOptions, layer, loggerLayer };
|
|
63
|
+
export { AutotelEffectLayerOptions, AutotelEffectLoggerOptions, layer, loggerLayer, withAutotel };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BuiltinLoggerOptions } from "autotel/logger";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Layer from "effect/Layer";
|
|
3
4
|
//#region src/index.d.ts
|
|
4
5
|
interface AutotelEffectLoggerOptions {
|
|
@@ -36,6 +37,18 @@ interface AutotelEffectLayerOptions {
|
|
|
36
37
|
* instrumentation module with `node --import` or `tsx --import`.
|
|
37
38
|
*/
|
|
38
39
|
declare function layer(options: AutotelEffectLayerOptions): Layer.Layer<never>;
|
|
40
|
+
/**
|
|
41
|
+
* Runs `self` as a child of the autotel span active around it, so an
|
|
42
|
+
* `Effect.withSpan` inside an instrumented handler lands in that request's
|
|
43
|
+
* trace: `Effect.runPromise(withAutotel(program))`.
|
|
44
|
+
*
|
|
45
|
+
* Effect takes a span's parent from its own `Tracer.ParentSpan`, which the
|
|
46
|
+
* ambient OpenTelemetry context does not supply. The active span is read when
|
|
47
|
+
* the effect runs rather than when it is built, so a program assembled once at
|
|
48
|
+
* startup still joins the request it runs inside - and why this is not part of
|
|
49
|
+
* `layer()`, which is built once for the whole application.
|
|
50
|
+
*/
|
|
51
|
+
declare function withAutotel<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
|
|
39
52
|
/**
|
|
40
53
|
* Routes Effect's `Effect.log*` through autotel instead of Effect's console
|
|
41
54
|
* logger, emitting each log as an OpenTelemetry log record (so it reaches any
|
|
@@ -47,4 +60,4 @@ declare function layer(options: AutotelEffectLayerOptions): Layer.Layer<never>;
|
|
|
47
60
|
*/
|
|
48
61
|
declare function loggerLayer(options: Omit<AutotelEffectLayerOptions, 'logs'> & AutotelEffectLoggerOptions): Layer.Layer<never, never, never>;
|
|
49
62
|
//#endregion
|
|
50
|
-
export { AutotelEffectLayerOptions, AutotelEffectLoggerOptions, layer, loggerLayer };
|
|
63
|
+
export { AutotelEffectLayerOptions, AutotelEffectLoggerOptions, layer, loggerLayer, withAutotel };
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,10 @@ import { logLevelToSeverityNumber } from "@effect/opentelemetry/OtelLogger";
|
|
|
2
2
|
import * as OtelTracer from "@effect/opentelemetry/OtelTracer";
|
|
3
3
|
import * as Resource from "@effect/opentelemetry/Resource";
|
|
4
4
|
import { logs } from "@opentelemetry/api-logs";
|
|
5
|
-
import { flattenToAttributes } from "autotel";
|
|
5
|
+
import { flattenToAttributes, getActiveSpan, otelTrace } from "autotel";
|
|
6
6
|
import { createBuiltinLogger } from "autotel/logger";
|
|
7
7
|
import * as Cause from "effect/Cause";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
8
9
|
import * as Layer from "effect/Layer";
|
|
9
10
|
import * as Logger from "effect/Logger";
|
|
10
11
|
import * as References from "effect/References";
|
|
@@ -29,6 +30,23 @@ function layer(options) {
|
|
|
29
30
|
}));
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
33
|
+
* Runs `self` as a child of the autotel span active around it, so an
|
|
34
|
+
* `Effect.withSpan` inside an instrumented handler lands in that request's
|
|
35
|
+
* trace: `Effect.runPromise(withAutotel(program))`.
|
|
36
|
+
*
|
|
37
|
+
* Effect takes a span's parent from its own `Tracer.ParentSpan`, which the
|
|
38
|
+
* ambient OpenTelemetry context does not supply. The active span is read when
|
|
39
|
+
* the effect runs rather than when it is built, so a program assembled once at
|
|
40
|
+
* startup still joins the request it runs inside - and why this is not part of
|
|
41
|
+
* `layer()`, which is built once for the whole application.
|
|
42
|
+
*/
|
|
43
|
+
function withAutotel(self) {
|
|
44
|
+
return Effect.suspend(() => {
|
|
45
|
+
const spanContext = getActiveSpan()?.spanContext();
|
|
46
|
+
return spanContext && otelTrace.isSpanContextValid(spanContext) ? OtelTracer.withSpanContext(self, spanContext) : self;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
32
50
|
* Routes Effect's `Effect.log*` through autotel instead of Effect's console
|
|
33
51
|
* logger, emitting each log as an OpenTelemetry log record (so it reaches any
|
|
34
52
|
* OTLP log backend, autotel-devtools included) and, unless disabled, a
|
|
@@ -82,4 +100,4 @@ function formatPart(part) {
|
|
|
82
100
|
}
|
|
83
101
|
}
|
|
84
102
|
//#endregion
|
|
85
|
-
export { layer, loggerLayer };
|
|
103
|
+
export { layer, loggerLayer, withAutotel };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-effect",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Bridge autotel and Effect v4: Effect.withSpan spans export through autotel's global TracerProvider",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@opentelemetry/api-logs": "^0.222.0",
|
|
56
56
|
"@opentelemetry/sdk-logs": "^0.222.0",
|
|
57
57
|
"@types/node": "^26.1.2",
|
|
58
|
-
"autotel": "7.
|
|
58
|
+
"autotel": "7.7.0",
|
|
59
59
|
"effect": "4.0.0-rc.112",
|
|
60
60
|
"rimraf": "^6.1.3",
|
|
61
61
|
"tsdown": "^0.22.14",
|