@effect/opentelemetry 0.46.4 → 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.
- package/Otlp/package.json +6 -0
- package/OtlpLogger/package.json +6 -0
- package/OtlpMetrics/package.json +6 -0
- package/OtlpResource/package.json +6 -0
- package/dist/cjs/Otlp.js +38 -0
- package/dist/cjs/Otlp.js.map +1 -0
- package/dist/cjs/OtlpLogger.js +153 -0
- package/dist/cjs/OtlpLogger.js.map +1 -0
- package/dist/cjs/OtlpMetrics.js +354 -0
- package/dist/cjs/OtlpMetrics.js.map +1 -0
- package/dist/cjs/OtlpResource.js +93 -0
- package/dist/cjs/OtlpResource.js.map +1 -0
- package/dist/cjs/OtlpTracer.js +37 -124
- package/dist/cjs/OtlpTracer.js.map +1 -1
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/internal/otlpExporter.js +81 -0
- package/dist/cjs/internal/otlpExporter.js.map +1 -0
- package/dist/dts/Otlp.d.ts +29 -0
- package/dist/dts/Otlp.d.ts.map +1 -0
- package/dist/dts/OtlpLogger.d.ts +39 -0
- package/dist/dts/OtlpLogger.d.ts.map +1 -0
- package/dist/dts/OtlpMetrics.d.ts +38 -0
- package/dist/dts/OtlpMetrics.d.ts.map +1 -0
- package/dist/dts/OtlpResource.d.ts +89 -0
- package/dist/dts/OtlpResource.d.ts.map +1 -0
- package/dist/dts/OtlpTracer.d.ts +6 -3
- package/dist/dts/OtlpTracer.d.ts.map +1 -1
- package/dist/dts/index.d.ts +17 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/otlpExporter.d.ts +2 -0
- package/dist/dts/internal/otlpExporter.d.ts.map +1 -0
- package/dist/esm/Otlp.js +29 -0
- package/dist/esm/Otlp.js.map +1 -0
- package/dist/esm/OtlpLogger.js +144 -0
- package/dist/esm/OtlpLogger.js.map +1 -0
- package/dist/esm/OtlpMetrics.js +345 -0
- package/dist/esm/OtlpMetrics.js.map +1 -0
- package/dist/esm/OtlpResource.js +81 -0
- package/dist/esm/OtlpResource.js.map +1 -0
- package/dist/esm/OtlpTracer.js +34 -120
- package/dist/esm/OtlpTracer.js.map +1 -1
- package/dist/esm/index.js +17 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/otlpExporter.js +73 -0
- package/dist/esm/internal/otlpExporter.js.map +1 -0
- package/package.json +35 -3
- package/src/Otlp.ts +56 -0
- package/src/OtlpLogger.ts +243 -0
- package/src/OtlpMetrics.ts +568 -0
- package/src/OtlpResource.ts +168 -0
- package/src/OtlpTracer.ts +47 -178
- package/src/index.ts +21 -0
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
82
|
+
export(span) {
|
|
83
|
+
exporter.push(makeOtlpSpan(span))
|
|
84
|
+
}
|
|
159
85
|
})
|
|
160
86
|
},
|
|
161
|
-
context
|
|
162
|
-
|
|
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>
|
|
@@ -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:
|
|
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<
|
|
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<
|
|
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<
|
|
300
|
+
readonly attributes: Array<KeyValue>
|
|
432
301
|
readonly spanId: string
|
|
433
302
|
readonly traceId: string
|
|
434
303
|
readonly droppedAttributesCount: number
|
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
|
+
})
|