@effect/opentelemetry 0.16.0 → 0.18.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.
@@ -50,9 +50,9 @@ const config = config => config;
50
50
  * @since 1.0.0
51
51
  * @category layer
52
52
  */
53
- const layer = config => Layer__namespace.scopedDiscard(Effect__namespace.acquireRelease(Effect__namespace.flatMap(Effect__namespace.all([config, Resource_dist_effectOpentelemetryResource.Resource]), ([config, resource]) => Effect__namespace.sync(() => {
53
+ const layer = config => Layer__namespace.scopedDiscard(Effect__namespace.acquireRelease(Effect__namespace.flatMap(Resource_dist_effectOpentelemetryResource.Resource, resource => Effect__namespace.sync(() => {
54
54
  const sdk = new sdkNode.NodeSDK({
55
- ...config,
55
+ ...config(),
56
56
  resource
57
57
  });
58
58
  sdk.start();
@@ -50,9 +50,9 @@ const config = config => config;
50
50
  * @since 1.0.0
51
51
  * @category layer
52
52
  */
53
- const layer = config => Layer__namespace.scopedDiscard(Effect__namespace.acquireRelease(Effect__namespace.flatMap(Effect__namespace.all([config, Resource_dist_effectOpentelemetryResource.Resource]), ([config, resource]) => Effect__namespace.sync(() => {
53
+ const layer = config => Layer__namespace.scopedDiscard(Effect__namespace.acquireRelease(Effect__namespace.flatMap(Resource_dist_effectOpentelemetryResource.Resource, resource => Effect__namespace.sync(() => {
54
54
  const sdk = new sdkNode.NodeSDK({
55
- ...config,
55
+ ...config(),
56
56
  resource
57
57
  });
58
58
  sdk.start();
@@ -22,9 +22,9 @@ const config = config => config;
22
22
  * @since 1.0.0
23
23
  * @category layer
24
24
  */
25
- const layer = config => Layer.scopedDiscard(Effect.acquireRelease(Effect.flatMap(Effect.all([config, Resource]), ([config, resource]) => Effect.sync(() => {
25
+ const layer = config => Layer.scopedDiscard(Effect.acquireRelease(Effect.flatMap(Resource, resource => Effect.sync(() => {
26
26
  const sdk = new NodeSDK({
27
- ...config,
27
+ ...config(),
28
28
  resource
29
29
  });
30
30
  sdk.start();
@@ -49,17 +49,18 @@ var Tracer__namespace = /*#__PURE__*/_interopNamespace(Tracer);
49
49
  class OtelSpan {
50
50
  _tag = "Span";
51
51
  attributes = new Map();
52
- constructor(contextApi, tracer, name, parent, context, links, startTime) {
52
+ constructor(contextApi, tracer, name, parent, context, links, sampled, startTime) {
53
53
  this.name = name;
54
54
  this.parent = parent;
55
55
  this.context = context;
56
56
  this.links = links;
57
+ this.sampled = sampled;
57
58
  const active = contextApi.active();
58
59
  this.span = tracer.startSpan(name, {
59
60
  startTime: nanosToHrTime(startTime),
60
61
  links: links.length > 0 ? links.map(link => ({
61
62
  context: makeSpanContext(link.span),
62
- attributes: link.attributes
63
+ attributes: recordToAttributes(link.attributes)
63
64
  })) : undefined
64
65
  }, parent._tag === "Some" ? populateContext(active, parent.value, context) : OtelApi__namespace.trace.deleteSpan(active));
65
66
  const spanContext = this.span.spanContext();
@@ -71,7 +72,7 @@ class OtelSpan {
71
72
  };
72
73
  }
73
74
  attribute(key, value) {
74
- this.span.setAttribute(key, value);
75
+ this.span.setAttribute(key, unknownToAttributeValue(value));
75
76
  this.attributes.set(key, value);
76
77
  }
77
78
  end(endTime, exit) {
@@ -103,7 +104,7 @@ class OtelSpan {
103
104
  this.span.end(nanosToHrTime(endTime));
104
105
  }
105
106
  event(name, startTime, attributes) {
106
- this.span.addEvent(name, attributes, nanosToHrTime(startTime));
107
+ this.span.addEvent(name, attributes ? recordToAttributes(attributes) : undefined, nanosToHrTime(startTime));
107
108
  }
108
109
  }
109
110
 
@@ -112,8 +113,8 @@ const OtelTracer$1 = /*#__PURE__*/Context__namespace.Tag("@effect/opentelemetry/
112
113
 
113
114
  /** @internal */
114
115
  const make$1 = /*#__PURE__*/Effect__namespace.map(OtelTracer$1, tracer => Tracer__namespace.make({
115
- span(name, parent, context, links, startTime) {
116
- return new OtelSpan(OtelApi__namespace.context, tracer, name, parent, context, links, startTime);
116
+ span(name, parent, context, links, sampled, startTime) {
117
+ return new OtelSpan(OtelApi__namespace.context, tracer, name, parent, context, links, sampled, startTime);
117
118
  },
118
119
  context(execution, fiber) {
119
120
  const currentSpan = Option__namespace.flatMap(FiberRefs__namespace.get(fiber.getFiberRefs(), FiberRef__namespace.currentTracerSpan), List__namespace.head);
@@ -150,6 +151,7 @@ const makeExternalSpan$1 = options => {
150
151
  _tag: "ExternalSpan",
151
152
  traceId: options.traceId,
152
153
  spanId: options.spanId,
154
+ sampled: options.traceFlags ? options.traceFlags === OtelApi__namespace.TraceFlags.SAMPLED : true,
153
155
  context
154
156
  };
155
157
  };
@@ -161,7 +163,7 @@ const currentOtelSpan$1 = /*#__PURE__*/Effect__namespace.map(Effect__namespace.c
161
163
  const layerOtelTracer$1 = /*#__PURE__*/Layer__namespace.effect(OtelTracer$1, /*#__PURE__*/Effect__namespace.flatMap(Resource_dist_effectOpentelemetryResource.Resource, resource => Effect__namespace.sync(() => OtelApi__namespace.trace.getTracer(resource.attributes["service.name"], resource.attributes["service.version"]))));
162
164
 
163
165
  /** @internal */
164
- const layer$1 = /*#__PURE__*/Layer__namespace.provide(layerOtelTracer$1, /*#__PURE__*/Layer__namespace.unwrapEffect( /*#__PURE__*/Effect__namespace.map(make$1, Effect__namespace.setTracer)));
166
+ const layer$1 = /*#__PURE__*/Layer__namespace.provide(layerOtelTracer$1, /*#__PURE__*/Layer__namespace.unwrapEffect( /*#__PURE__*/Effect__namespace.map(make$1, Layer__namespace.setTracer)));
165
167
 
166
168
  // -------------------------------------------------------------------------------------
167
169
  // utils
@@ -181,6 +183,27 @@ const makeSpanContext = (span, context) => ({
181
183
  traceState: Option__namespace.getOrUndefined(context ? extractTraceTag(span, context, traceStateTag) : Context__namespace.getOption(span.context, traceStateTag))
182
184
  });
183
185
  const extractTraceTag = (parent, context, tag) => Option__namespace.orElse(Context__namespace.getOption(context, tag), () => Context__namespace.getOption(parent.context, tag));
186
+ const recordToAttributes = value => {
187
+ return Object.entries(value).reduce((acc, [key, value]) => {
188
+ acc[key] = unknownToAttributeValue(value);
189
+ return acc;
190
+ }, {});
191
+ };
192
+ const unknownToAttributeValue = value => {
193
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
194
+ return value;
195
+ } else if (typeof value === "bigint") {
196
+ return Number(value);
197
+ }
198
+ return objectToAttribute(value);
199
+ };
200
+ const objectToAttribute = value => {
201
+ try {
202
+ return JSON.stringify(value, null, 2);
203
+ } catch {
204
+ return String(value);
205
+ }
206
+ };
184
207
 
185
208
  /**
186
209
  * @since 1.0.0
@@ -49,17 +49,18 @@ var Tracer__namespace = /*#__PURE__*/_interopNamespace(Tracer);
49
49
  class OtelSpan {
50
50
  _tag = "Span";
51
51
  attributes = new Map();
52
- constructor(contextApi, tracer, name, parent, context, links, startTime) {
52
+ constructor(contextApi, tracer, name, parent, context, links, sampled, startTime) {
53
53
  this.name = name;
54
54
  this.parent = parent;
55
55
  this.context = context;
56
56
  this.links = links;
57
+ this.sampled = sampled;
57
58
  const active = contextApi.active();
58
59
  this.span = tracer.startSpan(name, {
59
60
  startTime: nanosToHrTime(startTime),
60
61
  links: links.length > 0 ? links.map(link => ({
61
62
  context: makeSpanContext(link.span),
62
- attributes: link.attributes
63
+ attributes: recordToAttributes(link.attributes)
63
64
  })) : undefined
64
65
  }, parent._tag === "Some" ? populateContext(active, parent.value, context) : OtelApi__namespace.trace.deleteSpan(active));
65
66
  const spanContext = this.span.spanContext();
@@ -71,7 +72,7 @@ class OtelSpan {
71
72
  };
72
73
  }
73
74
  attribute(key, value) {
74
- this.span.setAttribute(key, value);
75
+ this.span.setAttribute(key, unknownToAttributeValue(value));
75
76
  this.attributes.set(key, value);
76
77
  }
77
78
  end(endTime, exit) {
@@ -103,7 +104,7 @@ class OtelSpan {
103
104
  this.span.end(nanosToHrTime(endTime));
104
105
  }
105
106
  event(name, startTime, attributes) {
106
- this.span.addEvent(name, attributes, nanosToHrTime(startTime));
107
+ this.span.addEvent(name, attributes ? recordToAttributes(attributes) : undefined, nanosToHrTime(startTime));
107
108
  }
108
109
  }
109
110
 
@@ -112,8 +113,8 @@ const OtelTracer$1 = /*#__PURE__*/Context__namespace.Tag("@effect/opentelemetry/
112
113
 
113
114
  /** @internal */
114
115
  const make$1 = /*#__PURE__*/Effect__namespace.map(OtelTracer$1, tracer => Tracer__namespace.make({
115
- span(name, parent, context, links, startTime) {
116
- return new OtelSpan(OtelApi__namespace.context, tracer, name, parent, context, links, startTime);
116
+ span(name, parent, context, links, sampled, startTime) {
117
+ return new OtelSpan(OtelApi__namespace.context, tracer, name, parent, context, links, sampled, startTime);
117
118
  },
118
119
  context(execution, fiber) {
119
120
  const currentSpan = Option__namespace.flatMap(FiberRefs__namespace.get(fiber.getFiberRefs(), FiberRef__namespace.currentTracerSpan), List__namespace.head);
@@ -150,6 +151,7 @@ const makeExternalSpan$1 = options => {
150
151
  _tag: "ExternalSpan",
151
152
  traceId: options.traceId,
152
153
  spanId: options.spanId,
154
+ sampled: options.traceFlags ? options.traceFlags === OtelApi__namespace.TraceFlags.SAMPLED : true,
153
155
  context
154
156
  };
155
157
  };
@@ -161,7 +163,7 @@ const currentOtelSpan$1 = /*#__PURE__*/Effect__namespace.map(Effect__namespace.c
161
163
  const layerOtelTracer$1 = /*#__PURE__*/Layer__namespace.effect(OtelTracer$1, /*#__PURE__*/Effect__namespace.flatMap(Resource_dist_effectOpentelemetryResource.Resource, resource => Effect__namespace.sync(() => OtelApi__namespace.trace.getTracer(resource.attributes["service.name"], resource.attributes["service.version"]))));
162
164
 
163
165
  /** @internal */
164
- const layer$1 = /*#__PURE__*/Layer__namespace.provide(layerOtelTracer$1, /*#__PURE__*/Layer__namespace.unwrapEffect( /*#__PURE__*/Effect__namespace.map(make$1, Effect__namespace.setTracer)));
166
+ const layer$1 = /*#__PURE__*/Layer__namespace.provide(layerOtelTracer$1, /*#__PURE__*/Layer__namespace.unwrapEffect( /*#__PURE__*/Effect__namespace.map(make$1, Layer__namespace.setTracer)));
165
167
 
166
168
  // -------------------------------------------------------------------------------------
167
169
  // utils
@@ -181,6 +183,27 @@ const makeSpanContext = (span, context) => ({
181
183
  traceState: Option__namespace.getOrUndefined(context ? extractTraceTag(span, context, traceStateTag) : Context__namespace.getOption(span.context, traceStateTag))
182
184
  });
183
185
  const extractTraceTag = (parent, context, tag) => Option__namespace.orElse(Context__namespace.getOption(context, tag), () => Context__namespace.getOption(parent.context, tag));
186
+ const recordToAttributes = value => {
187
+ return Object.entries(value).reduce((acc, [key, value]) => {
188
+ acc[key] = unknownToAttributeValue(value);
189
+ return acc;
190
+ }, {});
191
+ };
192
+ const unknownToAttributeValue = value => {
193
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
194
+ return value;
195
+ } else if (typeof value === "bigint") {
196
+ return Number(value);
197
+ }
198
+ return objectToAttribute(value);
199
+ };
200
+ const objectToAttribute = value => {
201
+ try {
202
+ return JSON.stringify(value, null, 2);
203
+ } catch {
204
+ return String(value);
205
+ }
206
+ };
184
207
 
185
208
  /**
186
209
  * @since 1.0.0
@@ -2,7 +2,7 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
  import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node";
5
- import * as Effect from "effect/Effect";
5
+ import type { LazyArg } from "effect/Function";
6
6
  import * as Layer from "effect/Layer";
7
7
  import { Resource } from "./Resource.js";
8
8
  /**
@@ -19,5 +19,5 @@ export declare const config: (config: Configuration) => Configuration;
19
19
  * @since 1.0.0
20
20
  * @category layer
21
21
  */
22
- export declare const layer: <R, E>(config: Effect.Effect<R, E, Partial<Omit<NodeSDKConfiguration, "resource" | "serviceName">>>) => Layer.Layer<Resource | R, E, never>;
22
+ export declare const layer: (config: LazyArg<Configuration>) => Layer.Layer<Resource, never, never>;
23
23
  //# sourceMappingURL=NodeSdk.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeSdk.d.ts","sourceRoot":"../../../src","sources":["NodeSdk.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAEnE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,sBAAkB;AAErC;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,aAAa,CAAC,CAAC,CAAA;AAE3F;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAiD,CAAA;AAEjG;;;GAGG;AACH,eAAO,MAAM,KAAK,6IAcd,CAAA"}
1
+ {"version":3,"file":"NodeSdk.d.ts","sourceRoot":"../../../src","sources":["NodeSdk.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAGnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,sBAAkB;AAErC;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,aAAa,CAAC,CAAC,CAAA;AAE3F;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAiD,CAAA;AAEjG;;;GAGG;AACH,eAAO,MAAM,KAAK,WACR,QAAQ,aAAa,CAAC,KAC7B,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAYjC,CAAA"}
@@ -14,17 +14,18 @@ import { Resource } from '../Resource/dist/effect-opentelemetry-Resource.esm.js'
14
14
  class OtelSpan {
15
15
  _tag = "Span";
16
16
  attributes = new Map();
17
- constructor(contextApi, tracer, name, parent, context, links, startTime) {
17
+ constructor(contextApi, tracer, name, parent, context, links, sampled, startTime) {
18
18
  this.name = name;
19
19
  this.parent = parent;
20
20
  this.context = context;
21
21
  this.links = links;
22
+ this.sampled = sampled;
22
23
  const active = contextApi.active();
23
24
  this.span = tracer.startSpan(name, {
24
25
  startTime: nanosToHrTime(startTime),
25
26
  links: links.length > 0 ? links.map(link => ({
26
27
  context: makeSpanContext(link.span),
27
- attributes: link.attributes
28
+ attributes: recordToAttributes(link.attributes)
28
29
  })) : undefined
29
30
  }, parent._tag === "Some" ? populateContext(active, parent.value, context) : OtelApi.trace.deleteSpan(active));
30
31
  const spanContext = this.span.spanContext();
@@ -36,7 +37,7 @@ class OtelSpan {
36
37
  };
37
38
  }
38
39
  attribute(key, value) {
39
- this.span.setAttribute(key, value);
40
+ this.span.setAttribute(key, unknownToAttributeValue(value));
40
41
  this.attributes.set(key, value);
41
42
  }
42
43
  end(endTime, exit) {
@@ -68,7 +69,7 @@ class OtelSpan {
68
69
  this.span.end(nanosToHrTime(endTime));
69
70
  }
70
71
  event(name, startTime, attributes) {
71
- this.span.addEvent(name, attributes, nanosToHrTime(startTime));
72
+ this.span.addEvent(name, attributes ? recordToAttributes(attributes) : undefined, nanosToHrTime(startTime));
72
73
  }
73
74
  }
74
75
 
@@ -77,8 +78,8 @@ const OtelTracer = /*#__PURE__*/Context.Tag("@effect/opentelemetry/Tracer/OtelTr
77
78
 
78
79
  /** @internal */
79
80
  const make = /*#__PURE__*/Effect.map(OtelTracer, tracer => Tracer.make({
80
- span(name, parent, context, links, startTime) {
81
- return new OtelSpan(OtelApi.context, tracer, name, parent, context, links, startTime);
81
+ span(name, parent, context, links, sampled, startTime) {
82
+ return new OtelSpan(OtelApi.context, tracer, name, parent, context, links, sampled, startTime);
82
83
  },
83
84
  context(execution, fiber) {
84
85
  const currentSpan = Option.flatMap(FiberRefs.get(fiber.getFiberRefs(), FiberRef.currentTracerSpan), List.head);
@@ -115,6 +116,7 @@ const makeExternalSpan = options => {
115
116
  _tag: "ExternalSpan",
116
117
  traceId: options.traceId,
117
118
  spanId: options.spanId,
119
+ sampled: options.traceFlags ? options.traceFlags === OtelApi.TraceFlags.SAMPLED : true,
118
120
  context
119
121
  };
120
122
  };
@@ -126,7 +128,7 @@ const currentOtelSpan = /*#__PURE__*/Effect.map(Effect.currentSpan, span => Opti
126
128
  const layerOtelTracer = /*#__PURE__*/Layer.effect(OtelTracer, /*#__PURE__*/Effect.flatMap(Resource, resource => Effect.sync(() => OtelApi.trace.getTracer(resource.attributes["service.name"], resource.attributes["service.version"]))));
127
129
 
128
130
  /** @internal */
129
- const layer = /*#__PURE__*/Layer.provide(layerOtelTracer, /*#__PURE__*/Layer.unwrapEffect( /*#__PURE__*/Effect.map(make, Effect.setTracer)));
131
+ const layer = /*#__PURE__*/Layer.provide(layerOtelTracer, /*#__PURE__*/Layer.unwrapEffect( /*#__PURE__*/Effect.map(make, Layer.setTracer)));
130
132
 
131
133
  // -------------------------------------------------------------------------------------
132
134
  // utils
@@ -146,5 +148,26 @@ const makeSpanContext = (span, context) => ({
146
148
  traceState: Option.getOrUndefined(context ? extractTraceTag(span, context, traceStateTag) : Context.getOption(span.context, traceStateTag))
147
149
  });
148
150
  const extractTraceTag = (parent, context, tag) => Option.orElse(Context.getOption(context, tag), () => Context.getOption(parent.context, tag));
151
+ const recordToAttributes = value => {
152
+ return Object.entries(value).reduce((acc, [key, value]) => {
153
+ acc[key] = unknownToAttributeValue(value);
154
+ return acc;
155
+ }, {});
156
+ };
157
+ const unknownToAttributeValue = value => {
158
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
159
+ return value;
160
+ } else if (typeof value === "bigint") {
161
+ return Number(value);
162
+ }
163
+ return objectToAttribute(value);
164
+ };
165
+ const objectToAttribute = value => {
166
+ try {
167
+ return JSON.stringify(value, null, 2);
168
+ } catch {
169
+ return String(value);
170
+ }
171
+ };
149
172
 
150
173
  export { OtelSpan, OtelTracer, currentOtelSpan, layer, layerOtelTracer, make, makeExternalSpan, traceFlagsTag, traceStateTag };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/opentelemetry",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@opentelemetry/api": "^1.4",
52
- "effect": "2.0.0-next.46"
52
+ "effect": "2.0.0-next.48"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@babel/core": "^7.23.0",
@@ -57,7 +57,7 @@
57
57
  "@changesets/changelog-github": "^0.4.8",
58
58
  "@changesets/cli": "^2.26.2",
59
59
  "@effect/babel-plugin": "^0.2.0",
60
- "@effect/build-utils": "^0.1.6",
60
+ "@effect/build-utils": "^0.1.9",
61
61
  "@effect/docgen": "^0.1.8",
62
62
  "@effect/eslint-plugin": "^0.1.2",
63
63
  "@effect/language-service": "^0.0.21",
@@ -66,17 +66,17 @@
66
66
  "@opentelemetry/exporter-trace-otlp-http": "^0.43.0",
67
67
  "@opentelemetry/sdk-trace-base": "^1.17.0",
68
68
  "@preconstruct/cli": "^2.8.1",
69
- "@types/chai": "^4.3.6",
70
- "@types/node": "^20.8.2",
71
- "@typescript-eslint/eslint-plugin": "^6.7.4",
72
- "@typescript-eslint/parser": "^6.7.4",
69
+ "@types/chai": "^4.3.7",
70
+ "@types/node": "^20.8.4",
71
+ "@typescript-eslint/eslint-plugin": "^6.7.5",
72
+ "@typescript-eslint/parser": "^6.7.5",
73
73
  "@vitejs/plugin-react": "^4.1.0",
74
74
  "@vitest/coverage-v8": "^0.34.6",
75
75
  "@vitest/expect": "^0.34.6",
76
76
  "babel-plugin-annotate-pure-calls": "^0.4.0",
77
77
  "concurrently": "^8.2.1",
78
- "effect": "2.0.0-next.46",
79
- "eslint": "^8.50.0",
78
+ "effect": "2.0.0-next.48",
79
+ "eslint": "^8.51.0",
80
80
  "eslint-import-resolver-typescript": "^3.6.1",
81
81
  "eslint-plugin-codegen": "0.17.0",
82
82
  "eslint-plugin-deprecation": "^2.0.0",
package/src/NodeSdk.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  import type { NodeSDKConfiguration } from "@opentelemetry/sdk-node"
5
5
  import { NodeSDK } from "@opentelemetry/sdk-node"
6
6
  import * as Effect from "effect/Effect"
7
+ import type { LazyArg } from "effect/Function"
7
8
  import * as Layer from "effect/Layer"
8
9
  import { Resource } from "./Resource"
9
10
 
@@ -23,15 +24,15 @@ export const config: (config: Configuration) => Configuration = (config: Configu
23
24
  * @since 1.0.0
24
25
  * @category layer
25
26
  */
26
- export const layer = <R, E>(
27
- config: Effect.Effect<R, E, Configuration>
28
- ): Layer.Layer<Resource | R, E, never> =>
27
+ export const layer = (
28
+ config: LazyArg<Configuration>
29
+ ): Layer.Layer<Resource, never, never> =>
29
30
  Layer.scopedDiscard(Effect.acquireRelease(
30
31
  Effect.flatMap(
31
- Effect.all([config, Resource]),
32
- ([config, resource]) =>
32
+ Resource,
33
+ (resource) =>
33
34
  Effect.sync(() => {
34
- const sdk = new NodeSDK({ ...config, resource })
35
+ const sdk = new NodeSDK({ ...config(), resource })
35
36
  sdk.start()
36
37
  return sdk
37
38
  })
@@ -18,7 +18,7 @@ export class OtelSpan implements Tracer.Span {
18
18
  readonly span: OtelApi.Span
19
19
  readonly spanId: string
20
20
  readonly traceId: string
21
- readonly attributes = new Map<string, Tracer.AttributeValue>()
21
+ readonly attributes = new Map<string, unknown>()
22
22
  status: Tracer.SpanStatus
23
23
 
24
24
  constructor(
@@ -28,6 +28,7 @@ export class OtelSpan implements Tracer.Span {
28
28
  readonly parent: Option.Option<Tracer.ParentSpan>,
29
29
  readonly context: Context.Context<never>,
30
30
  readonly links: ReadonlyArray<Tracer.SpanLink>,
31
+ readonly sampled: boolean,
31
32
  startTime: bigint
32
33
  ) {
33
34
  const active = contextApi.active()
@@ -38,7 +39,7 @@ export class OtelSpan implements Tracer.Span {
38
39
  links: links.length > 0
39
40
  ? links.map((link) => ({
40
41
  context: makeSpanContext(link.span),
41
- attributes: link.attributes
42
+ attributes: recordToAttributes(link.attributes)
42
43
  }))
43
44
  : undefined
44
45
  },
@@ -55,8 +56,8 @@ export class OtelSpan implements Tracer.Span {
55
56
  }
56
57
  }
57
58
 
58
- attribute(key: string, value: Tracer.AttributeValue) {
59
- this.span.setAttribute(key, value)
59
+ attribute(key: string, value: unknown) {
60
+ this.span.setAttribute(key, unknownToAttributeValue(value))
60
61
  this.attributes.set(key, value)
61
62
  }
62
63
 
@@ -90,8 +91,12 @@ export class OtelSpan implements Tracer.Span {
90
91
  this.span.end(nanosToHrTime(endTime))
91
92
  }
92
93
 
93
- event(name: string, startTime: bigint, attributes?: Record<string, Tracer.AttributeValue>) {
94
- this.span.addEvent(name, attributes, nanosToHrTime(startTime))
94
+ event(name: string, startTime: bigint, attributes?: Record<string, unknown>) {
95
+ this.span.addEvent(
96
+ name,
97
+ attributes ? recordToAttributes(attributes) : undefined,
98
+ nanosToHrTime(startTime)
99
+ )
95
100
  }
96
101
  }
97
102
 
@@ -101,7 +106,7 @@ export const OtelTracer = Context.Tag<OtelApi.Tracer>("@effect/opentelemetry/Tra
101
106
  /** @internal */
102
107
  export const make = Effect.map(OtelTracer, (tracer) =>
103
108
  Tracer.make({
104
- span(name, parent, context, links, startTime) {
109
+ span(name, parent, context, links, sampled, startTime) {
105
110
  return new OtelSpan(
106
111
  OtelApi.context,
107
112
  tracer,
@@ -109,6 +114,7 @@ export const make = Effect.map(OtelTracer, (tracer) =>
109
114
  parent,
110
115
  context,
111
116
  links,
117
+ sampled,
112
118
  startTime
113
119
  )
114
120
  },
@@ -166,6 +172,9 @@ export const makeExternalSpan = (options: {
166
172
  _tag: "ExternalSpan",
167
173
  traceId: options.traceId,
168
174
  spanId: options.spanId,
175
+ sampled: options.traceFlags
176
+ ? options.traceFlags === OtelApi.TraceFlags.SAMPLED
177
+ : true,
169
178
  context
170
179
  }
171
180
  }
@@ -198,7 +207,7 @@ export const layerOtelTracer = Layer.effect(
198
207
  /** @internal */
199
208
  export const layer = Layer.provide(
200
209
  layerOtelTracer,
201
- Layer.unwrapEffect(Effect.map(make, Effect.setTracer))
210
+ Layer.unwrapEffect(Effect.map(make, Layer.setTracer))
202
211
  )
203
212
 
204
213
  // -------------------------------------------------------------------------------------
@@ -247,3 +256,28 @@ const extractTraceTag = <I, S>(
247
256
  Context.getOption(context, tag),
248
257
  () => Context.getOption(parent.context, tag)
249
258
  )
259
+
260
+ const recordToAttributes = (value: Record<string, unknown>): OtelApi.Attributes => {
261
+ return Object.entries(value).reduce((acc, [key, value]) => {
262
+ acc[key] = unknownToAttributeValue(value)
263
+ return acc
264
+ }, {} as OtelApi.Attributes)
265
+ }
266
+
267
+ const unknownToAttributeValue = (value: unknown): OtelApi.AttributeValue => {
268
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
269
+ return value
270
+ } else if (typeof value === "bigint") {
271
+ return Number(value)
272
+ }
273
+
274
+ return objectToAttribute(value)
275
+ }
276
+
277
+ const objectToAttribute = (value: unknown): string => {
278
+ try {
279
+ return JSON.stringify(value, null, 2)
280
+ } catch {
281
+ return String(value)
282
+ }
283
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "compilerOptions": {
3
+ "moduleDetection": "force",
4
+ "downlevelIteration": true,
5
+ "resolveJsonModule": true,
6
+ "esModuleInterop": true,
7
+ "skipLibCheck": true,
8
+ "emitDecoratorMetadata": true,
9
+ "experimentalDecorators": true,
10
+ "preserveSymlinks": true,
11
+ "moduleResolution": "NodeNext",
12
+ "lib": [
13
+ "ES2021",
14
+ "DOM",
15
+ "DOM.Iterable"
16
+ ],
17
+ "sourceMap": true,
18
+ "strict": true,
19
+ "noImplicitReturns": false,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": false,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noEmitOnError": false,
24
+ "allowJs": false,
25
+ "checkJs": false,
26
+ "forceConsistentCasingInFileNames": true,
27
+ "stripInternal": true,
28
+ "noImplicitAny": true,
29
+ "noImplicitThis": true,
30
+ "noUncheckedIndexedAccess": false,
31
+ "strictNullChecks": true,
32
+ "target": "ES2021",
33
+ "module": "NodeNext",
34
+ "incremental": true,
35
+ "removeComments": false,
36
+ "paths": {
37
+ "@effect/opentelemetry": [
38
+ "./index.ts"
39
+ ],
40
+ "@effect/opentelemetry/*": [
41
+ "./*.ts"
42
+ ]
43
+ }
44
+ },
45
+ "include": [
46
+ "**/*"
47
+ ]
48
+ }