@effect/opentelemetry 0.46.3 → 0.46.5

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.
Files changed (53) hide show
  1. package/Otlp/package.json +6 -0
  2. package/OtlpLogger/package.json +6 -0
  3. package/OtlpMetrics/package.json +6 -0
  4. package/OtlpResource/package.json +6 -0
  5. package/dist/cjs/Otlp.js +38 -0
  6. package/dist/cjs/Otlp.js.map +1 -0
  7. package/dist/cjs/OtlpLogger.js +153 -0
  8. package/dist/cjs/OtlpLogger.js.map +1 -0
  9. package/dist/cjs/OtlpMetrics.js +354 -0
  10. package/dist/cjs/OtlpMetrics.js.map +1 -0
  11. package/dist/cjs/OtlpResource.js +93 -0
  12. package/dist/cjs/OtlpResource.js.map +1 -0
  13. package/dist/cjs/OtlpTracer.js +45 -130
  14. package/dist/cjs/OtlpTracer.js.map +1 -1
  15. package/dist/cjs/index.js +9 -1
  16. package/dist/cjs/internal/otlpExporter.js +81 -0
  17. package/dist/cjs/internal/otlpExporter.js.map +1 -0
  18. package/dist/dts/Otlp.d.ts +29 -0
  19. package/dist/dts/Otlp.d.ts.map +1 -0
  20. package/dist/dts/OtlpLogger.d.ts +39 -0
  21. package/dist/dts/OtlpLogger.d.ts.map +1 -0
  22. package/dist/dts/OtlpMetrics.d.ts +38 -0
  23. package/dist/dts/OtlpMetrics.d.ts.map +1 -0
  24. package/dist/dts/OtlpResource.d.ts +89 -0
  25. package/dist/dts/OtlpResource.d.ts.map +1 -0
  26. package/dist/dts/OtlpTracer.d.ts +6 -3
  27. package/dist/dts/OtlpTracer.d.ts.map +1 -1
  28. package/dist/dts/index.d.ts +17 -0
  29. package/dist/dts/index.d.ts.map +1 -1
  30. package/dist/dts/internal/otlpExporter.d.ts +2 -0
  31. package/dist/dts/internal/otlpExporter.d.ts.map +1 -0
  32. package/dist/esm/Otlp.js +29 -0
  33. package/dist/esm/Otlp.js.map +1 -0
  34. package/dist/esm/OtlpLogger.js +144 -0
  35. package/dist/esm/OtlpLogger.js.map +1 -0
  36. package/dist/esm/OtlpMetrics.js +345 -0
  37. package/dist/esm/OtlpMetrics.js.map +1 -0
  38. package/dist/esm/OtlpResource.js +81 -0
  39. package/dist/esm/OtlpResource.js.map +1 -0
  40. package/dist/esm/OtlpTracer.js +42 -126
  41. package/dist/esm/OtlpTracer.js.map +1 -1
  42. package/dist/esm/index.js +17 -0
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/internal/otlpExporter.js +73 -0
  45. package/dist/esm/internal/otlpExporter.js.map +1 -0
  46. package/package.json +35 -3
  47. package/src/Otlp.ts +56 -0
  48. package/src/OtlpLogger.ts +243 -0
  49. package/src/OtlpMetrics.ts +568 -0
  50. package/src/OtlpResource.ts +168 -0
  51. package/src/OtlpTracer.ts +54 -185
  52. package/src/index.ts +21 -0
  53. package/src/internal/otlpExporter.ts +114 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Inspectable from "effect/Inspectable"
5
+
6
+ /**
7
+ * @since 1.0.0
8
+ * @category Models
9
+ */
10
+ export interface Resource {
11
+ /** Resource attributes */
12
+ attributes: Array<KeyValue>
13
+ /** Resource droppedAttributesCount */
14
+ droppedAttributesCount: number
15
+ }
16
+
17
+ /**
18
+ * @since 1.0.0
19
+ * @category Constructors
20
+ */
21
+ export const make = (options: {
22
+ readonly serviceName: string
23
+ readonly serviceVersion?: string | undefined
24
+ readonly attributes?: Record<string, unknown> | undefined
25
+ }): Resource => {
26
+ const resourceAttributes = options.attributes
27
+ ? entriesToAttributes(Object.entries(options.attributes))
28
+ : []
29
+ resourceAttributes.push({
30
+ key: "service.name",
31
+ value: {
32
+ stringValue: options.serviceName
33
+ }
34
+ })
35
+ if (options.serviceVersion) {
36
+ resourceAttributes.push({
37
+ key: "service.version",
38
+ value: {
39
+ stringValue: options.serviceVersion
40
+ }
41
+ })
42
+ }
43
+
44
+ return {
45
+ attributes: resourceAttributes,
46
+ droppedAttributesCount: 0
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @since 1.0.0
52
+ * @category Attributes
53
+ */
54
+ export const entriesToAttributes = (entries: Iterable<[string, unknown]>): Array<KeyValue> => {
55
+ const attributes: Array<KeyValue> = []
56
+ for (const [key, value] of entries) {
57
+ attributes.push({
58
+ key,
59
+ value: unknownToAttributeValue(value)
60
+ })
61
+ }
62
+ return attributes
63
+ }
64
+
65
+ /**
66
+ * @since 1.0.0
67
+ * @category Attributes
68
+ */
69
+ export const unknownToAttributeValue = (value: unknown): AnyValue => {
70
+ if (Array.isArray(value)) {
71
+ return {
72
+ arrayValue: {
73
+ values: value.map(unknownToAttributeValue)
74
+ }
75
+ }
76
+ }
77
+ switch (typeof value) {
78
+ case "string":
79
+ return {
80
+ stringValue: value
81
+ }
82
+ case "bigint":
83
+ return {
84
+ intValue: Number(value)
85
+ }
86
+ case "number":
87
+ return Number.isInteger(value)
88
+ ? {
89
+ intValue: value
90
+ }
91
+ : {
92
+ doubleValue: value
93
+ }
94
+ case "boolean":
95
+ return {
96
+ boolValue: value
97
+ }
98
+ default:
99
+ return {
100
+ stringValue: Inspectable.toStringUnknown(value)
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @since 1.0.0
107
+ * @category Models
108
+ */
109
+ export interface KeyValue {
110
+ /** KeyValue key */
111
+ key: string
112
+ /** KeyValue value */
113
+ value: AnyValue
114
+ }
115
+
116
+ /**
117
+ * @since 1.0.0
118
+ * @category Models
119
+ */
120
+ export interface AnyValue {
121
+ /** AnyValue stringValue */
122
+ stringValue?: string | null
123
+ /** AnyValue boolValue */
124
+ boolValue?: boolean | null
125
+ /** AnyValue intValue */
126
+ intValue?: number | null
127
+ /** AnyValue doubleValue */
128
+ doubleValue?: number | null
129
+ /** AnyValue arrayValue */
130
+ arrayValue?: ArrayValue
131
+ /** AnyValue kvlistValue */
132
+ kvlistValue?: KeyValueList
133
+ /** AnyValue bytesValue */
134
+ bytesValue?: Uint8Array
135
+ }
136
+
137
+ /**
138
+ * @since 1.0.0
139
+ * @category Models
140
+ */
141
+ export interface ArrayValue {
142
+ /** ArrayValue values */
143
+ values: Array<AnyValue>
144
+ }
145
+
146
+ /**
147
+ * @since 1.0.0
148
+ * @category Models
149
+ */
150
+ export interface KeyValueList {
151
+ /** KeyValueList values */
152
+ values: Array<KeyValue>
153
+ }
154
+
155
+ /**
156
+ * @since 1.0.0
157
+ * @category Models
158
+ */
159
+ export interface LongBits {
160
+ low: number
161
+ high: number
162
+ }
163
+
164
+ /**
165
+ * @since 1.0.0
166
+ * @category Models
167
+ */
168
+ export type Fixed64 = LongBits | string | number
package/src/OtlpTracer.ts CHANGED
@@ -1,23 +1,22 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import * as Headers from "@effect/platform/Headers"
5
- import * as HttpBody from "@effect/platform/HttpBody"
6
- import * as HttpClient from "@effect/platform/HttpClient"
7
- import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
4
+ import type * as Headers from "@effect/platform/Headers"
5
+ import type * as HttpClient from "@effect/platform/HttpClient"
8
6
  import * as Cause from "effect/Cause"
9
7
  import type * as Context from "effect/Context"
10
8
  import * as Duration from "effect/Duration"
11
9
  import * as Effect from "effect/Effect"
12
10
  import type * as Exit from "effect/Exit"
13
- import * as FiberSet from "effect/FiberSet"
14
- import * as Inspectable from "effect/Inspectable"
15
11
  import * as Layer from "effect/Layer"
16
12
  import * as Option from "effect/Option"
17
- import * as Schedule from "effect/Schedule"
18
- import * as Scope from "effect/Scope"
13
+ import type * as Scope from "effect/Scope"
19
14
  import * as Tracer from "effect/Tracer"
20
15
  import type { ExtractTag } from "effect/Types"
16
+ import * as Exporter from "./internal/otlpExporter.js"
17
+ import type { KeyValue, Resource } from "./OtlpResource.js"
18
+ import { entriesToAttributes } from "./OtlpResource.js"
19
+ import * as OtlpResource from "./OtlpResource.js"
21
20
 
22
21
  /**
23
22
  * @since 1.0.0
@@ -34,112 +33,37 @@ export const make: (
34
33
  readonly headers?: Headers.Input | undefined
35
34
  readonly exportInterval?: Duration.DurationInput | undefined
36
35
  readonly maxBatchSize?: number | undefined
36
+ readonly context?: (<X>(f: () => X, span: Tracer.AnySpan) => X) | undefined
37
37
  }
38
38
  ) => Effect.Effect<
39
39
  Tracer.Tracer,
40
40
  never,
41
41
  HttpClient.HttpClient | Scope.Scope
42
42
  > = Effect.fnUntraced(function*(options) {
43
- const exporterScope = yield* Effect.scope
44
- const exportInterval = options.exportInterval ? Duration.decode(options.exportInterval) : Duration.seconds(5)
45
- const maxBatchSize = options.maxBatchSize ?? 1000
46
-
47
- const client = HttpClient.filterStatusOk(yield* HttpClient.HttpClient).pipe(
48
- HttpClient.tapError((error) => {
49
- if (error._tag !== "ResponseError" || error.response.status !== 429) {
50
- return Effect.void
51
- }
52
- const retryAfter = error.response.headers["retry-after"]
53
- const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : 5
54
- return Effect.sleep(Duration.seconds(retryAfterSeconds))
55
- }),
56
- HttpClient.retryTransient({
57
- schedule: Schedule.spaced(1000)
58
- })
59
- )
60
-
61
- let headers = Headers.unsafeFromRecord({
62
- "user-agent": "@effect-opentelemetry-OtlpExporter"
63
- })
64
- if (options.headers) {
65
- headers = Headers.merge(Headers.fromInput(options.headers), headers)
66
- }
67
-
68
- const resourceAttributes = options.resource.attributes
69
- ? entriesToAttributes(Object.entries(options.resource.attributes))
70
- : []
71
- resourceAttributes.push({
72
- key: "service.name",
73
- value: {
74
- stringValue: options.resource.serviceName
75
- }
76
- })
77
- if (options.resource.serviceVersion) {
78
- resourceAttributes.push({
79
- key: "service.version",
80
- value: {
81
- stringValue: options.resource.serviceVersion
82
- }
83
- })
84
- }
85
-
86
- const otelResource: OtlpResource = {
87
- attributes: resourceAttributes,
88
- droppedAttributesCount: 0
89
- }
43
+ const otelResource = OtlpResource.make(options.resource)
90
44
  const scope: Scope = {
91
45
  name: options.resource.serviceName
92
46
  }
93
47
 
94
- let spanBuffer: Array<OtlpSpan> = []
95
-
96
- const runExport = Effect.suspend(() => {
97
- if (spanBuffer.length === 0) {
98
- return Effect.void
99
- }
100
- const spans = spanBuffer
101
- spanBuffer = []
102
- const data: TraceData = {
103
- resourceSpans: [{
104
- resource: otelResource,
105
- scopeSpans: [{
106
- scope,
107
- spans
48
+ const exporter = yield* Exporter.make({
49
+ label: "OtlpTracer",
50
+ url: options.url,
51
+ headers: options.headers,
52
+ exportInterval: options.exportInterval ?? Duration.seconds(5),
53
+ maxBatchSize: options.maxBatchSize ?? 1000,
54
+ body(spans) {
55
+ const data: TraceData = {
56
+ resourceSpans: [{
57
+ resource: otelResource,
58
+ scopeSpans: [{
59
+ scope,
60
+ spans
61
+ }]
108
62
  }]
109
- }]
110
- }
111
- return Effect.asVoid(client.execute(
112
- HttpClientRequest.post(options.url, {
113
- body: HttpBody.unsafeJson(data),
114
- headers
115
- })
116
- ))
117
- }).pipe(
118
- Effect.catchAllCause((cause) => Effect.logWarning("Failed to export spans", cause)),
119
- Effect.annotateLogs({
120
- package: "@effect/opentelemetry",
121
- module: "OtlpExporter"
122
- })
123
- )
124
-
125
- yield* Scope.addFinalizer(exporterScope, runExport)
126
-
127
- yield* Effect.sleep(exportInterval).pipe(
128
- Effect.zipRight(runExport),
129
- Effect.forever,
130
- Effect.forkScoped,
131
- Effect.interruptible
132
- )
133
-
134
- const runFork = yield* FiberSet.makeRuntime().pipe(
135
- Effect.interruptible
136
- )
137
- const addSpan = (span: SpanImpl) => {
138
- spanBuffer.push(makeOtlpSpan(span))
139
- if (spanBuffer.length >= maxBatchSize) {
140
- runFork(runExport)
63
+ }
64
+ return data
141
65
  }
142
- }
66
+ })
143
67
 
144
68
  return Tracer.make({
145
69
  span(name, parent, context, links, startTime, kind) {
@@ -155,12 +79,19 @@ export const make: (
155
79
  links,
156
80
  sampled: true,
157
81
  kind,
158
- export: addSpan
82
+ export(span) {
83
+ exporter.push(makeOtlpSpan(span))
84
+ }
159
85
  })
160
86
  },
161
- context(f, _fiber) {
162
- return f()
163
- }
87
+ context: options.context ?
88
+ function(f, fiber) {
89
+ if (fiber.currentSpan === undefined) {
90
+ return f()
91
+ }
92
+ return options.context!(f, fiber.currentSpan)
93
+ } :
94
+ defaultContext
164
95
  })
165
96
  })
166
97
 
@@ -177,10 +108,16 @@ export const layer = (options: {
177
108
  }
178
109
  readonly headers?: Headers.Input | undefined
179
110
  readonly exportInterval?: Duration.DurationInput | undefined
111
+ readonly maxBatchSize?: number | undefined
112
+ readonly context?: (<X>(f: () => X, span: Tracer.AnySpan) => X) | undefined
180
113
  }): Layer.Layer<never, never, HttpClient.HttpClient> => Layer.unwrapScoped(Effect.map(make(options), Layer.setTracer))
181
114
 
182
115
  // internal
183
116
 
117
+ function defaultContext<X>(f: () => X, _: any): X {
118
+ return f()
119
+ }
120
+
184
121
  interface SpanImpl extends Tracer.Span {
185
122
  readonly export: (span: SpanImpl) => void
186
123
  readonly attributes: Map<string, unknown>
@@ -262,10 +199,10 @@ const makeOtlpSpan = (self: SpanImpl): OtlpSpan => {
262
199
  const errors = Cause.prettyErrors(status.exit.cause)
263
200
  const firstError = errors[0]
264
201
  otelStatus = {
265
- code: StatusCode.Error,
266
- message: firstError && firstError.message
202
+ code: StatusCode.Error
267
203
  }
268
- for (const error of errors) {
204
+ if (firstError) {
205
+ otelStatus.message = firstError.message
269
206
  events.push({
270
207
  name: "exception",
271
208
  timeUnixNano: String(status.endTime),
@@ -274,19 +211,19 @@ const makeOtlpSpan = (self: SpanImpl): OtlpSpan => {
274
211
  {
275
212
  "key": "exception.type",
276
213
  "value": {
277
- "stringValue": error.name
214
+ "stringValue": firstError.name
278
215
  }
279
216
  },
280
217
  {
281
218
  "key": "exception.message",
282
219
  "value": {
283
- "stringValue": error.message
220
+ "stringValue": firstError.message
284
221
  }
285
222
  },
286
223
  {
287
224
  "key": "exception.stacktrace",
288
225
  "value": {
289
- "stringValue": error.stack ?? ""
226
+ "stringValue": Cause.pretty(status.exit.cause, { renderErrorCause: true })
290
227
  }
291
228
  }
292
229
  ]
@@ -317,83 +254,15 @@ const makeOtlpSpan = (self: SpanImpl): OtlpSpan => {
317
254
  }
318
255
  }
319
256
 
320
- const unknownToAttributeValue = (value: unknown): AttributeValue => {
321
- switch (typeof value) {
322
- case "string":
323
- return {
324
- stringValue: value
325
- }
326
- case "bigint":
327
- return {
328
- intValue: Number(value)
329
- }
330
- case "number":
331
- return Number.isInteger(value)
332
- ? {
333
- intValue: value
334
- }
335
- : {
336
- doubleValue: value
337
- }
338
- case "boolean":
339
- return {
340
- boolValue: value
341
- }
342
- default:
343
- return {
344
- stringValue: Inspectable.toStringUnknown(value)
345
- }
346
- }
347
- }
348
-
349
- const entriesToAttributes = (entries: Iterable<[string, unknown]>): Array<Attribute> => {
350
- const attributes: Array<Attribute> = []
351
- for (const [key, value] of entries) {
352
- attributes.push({
353
- key,
354
- value: unknownToAttributeValue(value)
355
- })
356
- }
357
- return attributes
358
- }
359
-
360
257
  interface TraceData {
361
258
  readonly resourceSpans: Array<ResourceSpan>
362
259
  }
363
260
 
364
261
  interface ResourceSpan {
365
- readonly resource: OtlpResource
262
+ readonly resource: Resource
366
263
  readonly scopeSpans: Array<ScopeSpan>
367
264
  }
368
265
 
369
- interface OtlpResource {
370
- readonly attributes: Array<Attribute>
371
- readonly droppedAttributesCount: number
372
- }
373
-
374
- interface Attribute {
375
- readonly key: string
376
- readonly value: AttributeValue
377
- }
378
-
379
- type AttributeValue = IntValue | DoubleValue | BoolValue | StringValue
380
-
381
- interface IntValue {
382
- readonly intValue: number
383
- }
384
-
385
- interface DoubleValue {
386
- readonly doubleValue: number
387
- }
388
-
389
- interface BoolValue {
390
- readonly boolValue: boolean
391
- }
392
-
393
- interface StringValue {
394
- readonly stringValue: string
395
- }
396
-
397
266
  interface ScopeSpan {
398
267
  readonly scope: Scope
399
268
  readonly spans: Array<OtlpSpan>
@@ -411,7 +280,7 @@ interface OtlpSpan {
411
280
  readonly kind: number
412
281
  readonly startTimeUnixNano: string
413
282
  readonly endTimeUnixNano: string
414
- readonly attributes: Array<Attribute>
283
+ readonly attributes: Array<KeyValue>
415
284
  readonly droppedAttributesCount: number
416
285
  readonly events: Array<Event>
417
286
  readonly droppedEventsCount: number
@@ -421,14 +290,14 @@ interface OtlpSpan {
421
290
  }
422
291
 
423
292
  interface Event {
424
- readonly attributes: Array<Attribute>
293
+ readonly attributes: Array<KeyValue>
425
294
  readonly name: string
426
295
  readonly timeUnixNano: string
427
296
  readonly droppedAttributesCount: number
428
297
  }
429
298
 
430
299
  interface Link {
431
- readonly attributes: Array<Attribute>
300
+ readonly attributes: Array<KeyValue>
432
301
  readonly spanId: string
433
302
  readonly traceId: string
434
303
  readonly droppedAttributesCount: number
@@ -436,7 +305,7 @@ interface Link {
436
305
 
437
306
  interface Status {
438
307
  readonly code: StatusCode
439
- readonly message?: string
308
+ message?: string
440
309
  }
441
310
 
442
311
  const enum StatusCode {
package/src/index.ts CHANGED
@@ -13,6 +13,27 @@ export * as Metrics from "./Metrics.js"
13
13
  */
14
14
  export * as NodeSdk from "./NodeSdk.js"
15
15
 
16
+ /**
17
+ * @since 1.0.0
18
+ */
19
+ export * as Otlp from "./Otlp.js"
20
+
21
+ /**
22
+ * @since 1.0.0
23
+ * @category Constructors
24
+ */
25
+ export * as OtlpLogger from "./OtlpLogger.js"
26
+
27
+ /**
28
+ * @since 1.0.0
29
+ */
30
+ export * as OtlpMetrics from "./OtlpMetrics.js"
31
+
32
+ /**
33
+ * @since 1.0.0
34
+ */
35
+ export * as OtlpResource from "./OtlpResource.js"
36
+
16
37
  /**
17
38
  * @since 1.0.0
18
39
  */
@@ -0,0 +1,114 @@
1
+ import * as Headers from "@effect/platform/Headers"
2
+ import * as HttpClient from "@effect/platform/HttpClient"
3
+ import * as HttpClientError from "@effect/platform/HttpClientError"
4
+ import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
5
+ import * as Duration from "effect/Duration"
6
+ import * as Effect from "effect/Effect"
7
+ import * as FiberSet from "effect/FiberSet"
8
+ import * as Num from "effect/Number"
9
+ import * as Option from "effect/Option"
10
+ import * as Schedule from "effect/Schedule"
11
+ import * as Scope from "effect/Scope"
12
+
13
+ const policy = Schedule.forever.pipe(
14
+ Schedule.passthrough,
15
+ Schedule.addDelay((error) => {
16
+ if (
17
+ HttpClientError.isHttpClientError(error)
18
+ && error._tag === "ResponseError"
19
+ && error.response.status === 429
20
+ ) {
21
+ const retryAfter = Option.fromNullable(error.response.headers["retry-after"]).pipe(
22
+ Option.flatMap(Num.parse),
23
+ Option.getOrElse(() => 5)
24
+ )
25
+ return Duration.seconds(retryAfter)
26
+ }
27
+ return Duration.seconds(1)
28
+ })
29
+ )
30
+
31
+ /** @internal */
32
+ export const make: (
33
+ options: {
34
+ readonly url: string
35
+ readonly headers: Headers.Input | undefined
36
+ readonly label: string
37
+ readonly exportInterval: Duration.DurationInput
38
+ readonly maxBatchSize: number | "disabled"
39
+ readonly body: (data: Array<any>) => unknown
40
+ }
41
+ ) => Effect.Effect<
42
+ { readonly push: (data: unknown) => void },
43
+ never,
44
+ HttpClient.HttpClient | Scope.Scope
45
+ > = Effect.fnUntraced(function*(options) {
46
+ const scope = yield* Effect.scope
47
+ const exportInterval = Duration.decode(options.exportInterval)
48
+
49
+ const client = HttpClient.filterStatusOk(yield* HttpClient.HttpClient).pipe(
50
+ HttpClient.tapError((error) => {
51
+ if (error._tag !== "ResponseError" || error.response.status !== 429) {
52
+ return Effect.void
53
+ }
54
+ const retryAfter = error.response.headers["retry-after"]
55
+ const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : 5
56
+ return Effect.sleep(Duration.seconds(retryAfterSeconds))
57
+ }),
58
+ HttpClient.retryTransient({ schedule: policy })
59
+ )
60
+
61
+ let headers = Headers.unsafeFromRecord({
62
+ "user-agent": `effect-opentelemetry-${options.label}/0.0.0`
63
+ })
64
+ if (options.headers) {
65
+ headers = Headers.merge(Headers.fromInput(options.headers), headers)
66
+ }
67
+
68
+ const request = HttpClientRequest.post(options.url, { headers })
69
+ let buffer: Array<any> = []
70
+ const runExport = Effect.suspend(() => {
71
+ const items = buffer
72
+ if (options.maxBatchSize !== "disabled") {
73
+ if (buffer.length === 0) {
74
+ return Effect.void
75
+ }
76
+ buffer = []
77
+ }
78
+ return Effect.asVoid(client.execute(
79
+ HttpClientRequest.bodyUnsafeJson(request, options.body(items))
80
+ ))
81
+ })
82
+
83
+ yield* Scope.addFinalizer(scope, Effect.ignore(runExport))
84
+
85
+ let disabled = false
86
+
87
+ yield* Effect.sleep(exportInterval).pipe(
88
+ Effect.zipRight(runExport),
89
+ Effect.forever,
90
+ Effect.catchAllCause((cause) => {
91
+ disabled = true
92
+ return Effect.logDebug("Failed to export", cause)
93
+ }),
94
+ Effect.annotateLogs({
95
+ package: "@effect/opentelemetry",
96
+ module: options.label
97
+ }),
98
+ Effect.forkIn(scope),
99
+ Effect.interruptible
100
+ )
101
+
102
+ const runFork = yield* FiberSet.makeRuntime().pipe(
103
+ Effect.interruptible
104
+ )
105
+ return {
106
+ push(data) {
107
+ if (disabled) return
108
+ buffer.push(data)
109
+ if (options.maxBatchSize !== "disabled" && buffer.length >= options.maxBatchSize) {
110
+ runFork(runExport)
111
+ }
112
+ }
113
+ }
114
+ })