@effect/opentelemetry 0.9.0 → 0.10.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/Metrics.d.ts +26 -0
- package/Metrics.d.ts.map +1 -0
- package/Metrics.js +28 -0
- package/Metrics.js.map +1 -0
- package/Resource.js.map +1 -1
- package/Tracer.d.ts +0 -9
- package/Tracer.d.ts.map +1 -1
- package/Tracer.js +1 -7
- package/Tracer.js.map +1 -1
- package/internal/metrics.d.ts +2 -0
- package/internal/metrics.d.ts.map +1 -0
- package/internal/metrics.js +243 -0
- package/internal/metrics.js.map +1 -0
- package/internal/tracer.js +9 -20
- package/internal/tracer.js.map +1 -1
- package/mjs/Metrics.mjs +17 -0
- package/mjs/Metrics.mjs.map +1 -0
- package/mjs/Resource.mjs.map +1 -1
- package/mjs/Tracer.mjs +0 -9
- package/mjs/Tracer.mjs.map +1 -1
- package/mjs/internal/metrics.mjs +231 -0
- package/mjs/internal/metrics.mjs.map +1 -0
- package/mjs/internal/tracer.mjs +8 -17
- package/mjs/internal/tracer.mjs.map +1 -1
- package/package.json +7 -6
- package/src/Metrics.ts +32 -0
- package/src/Resource.ts +3 -3
- package/src/Tracer.ts +0 -11
- package/src/internal/metrics.ts +261 -0
- package/src/internal/tracer.ts +28 -49
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { LazyArg } from "@effect/data/Function"
|
|
2
|
+
import * as HashSet from "@effect/data/HashSet"
|
|
3
|
+
import * as Option from "@effect/data/Option"
|
|
4
|
+
import * as Effect from "@effect/io/Effect"
|
|
5
|
+
import * as Layer from "@effect/io/Layer"
|
|
6
|
+
import * as Metric from "@effect/io/Metric"
|
|
7
|
+
import type * as MetricKey from "@effect/io/MetricKey"
|
|
8
|
+
import * as MetricKeyType from "@effect/io/MetricKeyType"
|
|
9
|
+
import * as MetricState from "@effect/io/MetricState"
|
|
10
|
+
import * as Resource from "@effect/opentelemetry/Resource"
|
|
11
|
+
import type { HrTime } from "@opentelemetry/api"
|
|
12
|
+
import { ValueType } from "@opentelemetry/api"
|
|
13
|
+
import type * as Resources from "@opentelemetry/resources"
|
|
14
|
+
import type {
|
|
15
|
+
CollectionResult,
|
|
16
|
+
DataPoint,
|
|
17
|
+
InstrumentDescriptor,
|
|
18
|
+
MetricData,
|
|
19
|
+
MetricReader
|
|
20
|
+
} from "@opentelemetry/sdk-metrics"
|
|
21
|
+
import { AggregationTemporality, DataPointType, InstrumentType } from "@opentelemetry/sdk-metrics"
|
|
22
|
+
import type { MetricCollectOptions, MetricProducer } from "@opentelemetry/sdk-metrics/build/src/export/MetricProducer"
|
|
23
|
+
|
|
24
|
+
const sdkName = "@effect/opentelemetry/Metrics"
|
|
25
|
+
|
|
26
|
+
/** @internal */
|
|
27
|
+
export class MetricProducerImpl implements MetricProducer {
|
|
28
|
+
constructor(readonly resource: Resources.Resource) {
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
collect(_options?: MetricCollectOptions): Promise<CollectionResult> {
|
|
32
|
+
const snapshot = Metric.unsafeSnapshot()
|
|
33
|
+
const hrTimeNow = currentHrTime()
|
|
34
|
+
const metricData: Array<MetricData> = []
|
|
35
|
+
|
|
36
|
+
for (const { metricKey, metricState } of snapshot) {
|
|
37
|
+
const attributes = HashSet.reduce(metricKey.tags, {}, (acc: Record<string, string>, label) => {
|
|
38
|
+
acc[label.key] = label.value
|
|
39
|
+
return acc
|
|
40
|
+
})
|
|
41
|
+
const descriptor = descriptorFromKey(metricKey, attributes)
|
|
42
|
+
|
|
43
|
+
if (MetricState.isCounterState(metricState)) {
|
|
44
|
+
metricData.push({
|
|
45
|
+
dataPointType: DataPointType.SUM,
|
|
46
|
+
descriptor,
|
|
47
|
+
isMonotonic: descriptor.type === InstrumentType.COUNTER,
|
|
48
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
49
|
+
dataPoints: [{
|
|
50
|
+
startTime: hrTimeNow,
|
|
51
|
+
endTime: hrTimeNow,
|
|
52
|
+
attributes,
|
|
53
|
+
value: metricState.count
|
|
54
|
+
}]
|
|
55
|
+
})
|
|
56
|
+
} else if (MetricState.isGaugeState(metricState)) {
|
|
57
|
+
metricData.push({
|
|
58
|
+
dataPointType: DataPointType.GAUGE,
|
|
59
|
+
descriptor,
|
|
60
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
61
|
+
dataPoints: [{
|
|
62
|
+
startTime: hrTimeNow,
|
|
63
|
+
endTime: hrTimeNow,
|
|
64
|
+
attributes,
|
|
65
|
+
value: metricState.value
|
|
66
|
+
}]
|
|
67
|
+
})
|
|
68
|
+
} else if (MetricState.isHistogramState(metricState)) {
|
|
69
|
+
const size = metricState.buckets.length
|
|
70
|
+
const buckets = {
|
|
71
|
+
boundaries: Array<number>(size - 1),
|
|
72
|
+
counts: Array<number>(size)
|
|
73
|
+
}
|
|
74
|
+
let i = 0
|
|
75
|
+
let prev = 0
|
|
76
|
+
for (const [boundary, value] of metricState.buckets) {
|
|
77
|
+
if (i < size - 1) {
|
|
78
|
+
buckets.boundaries[i] = boundary
|
|
79
|
+
}
|
|
80
|
+
buckets.counts[i] = value - prev
|
|
81
|
+
prev = value
|
|
82
|
+
i++
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
metricData.push({
|
|
86
|
+
dataPointType: DataPointType.HISTOGRAM,
|
|
87
|
+
descriptor,
|
|
88
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
89
|
+
dataPoints: [{
|
|
90
|
+
startTime: hrTimeNow,
|
|
91
|
+
endTime: hrTimeNow,
|
|
92
|
+
attributes,
|
|
93
|
+
value: {
|
|
94
|
+
buckets,
|
|
95
|
+
count: metricState.count,
|
|
96
|
+
min: metricState.min,
|
|
97
|
+
max: metricState.max,
|
|
98
|
+
sum: metricState.sum
|
|
99
|
+
}
|
|
100
|
+
}]
|
|
101
|
+
})
|
|
102
|
+
} else if (MetricState.isFrequencyState(metricState)) {
|
|
103
|
+
const dataPoints: Array<DataPoint<number>> = []
|
|
104
|
+
for (const [freqKey, value] of metricState.occurrences) {
|
|
105
|
+
dataPoints.push({
|
|
106
|
+
startTime: hrTimeNow,
|
|
107
|
+
endTime: hrTimeNow,
|
|
108
|
+
attributes: {
|
|
109
|
+
...attributes,
|
|
110
|
+
key: freqKey
|
|
111
|
+
},
|
|
112
|
+
value
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
metricData.push({
|
|
116
|
+
dataPointType: DataPointType.SUM,
|
|
117
|
+
descriptor: descriptorFromKey(metricKey, attributes),
|
|
118
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
119
|
+
isMonotonic: true,
|
|
120
|
+
dataPoints
|
|
121
|
+
})
|
|
122
|
+
} else if (MetricState.isSummaryState(metricState)) {
|
|
123
|
+
const dataPoints: Array<DataPoint<number>> = [{
|
|
124
|
+
startTime: hrTimeNow,
|
|
125
|
+
endTime: hrTimeNow,
|
|
126
|
+
attributes: { ...attributes, quantile: "min" },
|
|
127
|
+
value: metricState.min
|
|
128
|
+
}]
|
|
129
|
+
for (const [quantile, value] of metricState.quantiles) {
|
|
130
|
+
dataPoints.push({
|
|
131
|
+
startTime: hrTimeNow,
|
|
132
|
+
endTime: hrTimeNow,
|
|
133
|
+
attributes: { ...attributes, quantile: quantile.toString() },
|
|
134
|
+
value: value._tag === "Some" ? value.value : 0
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
dataPoints.push({
|
|
138
|
+
startTime: hrTimeNow,
|
|
139
|
+
endTime: hrTimeNow,
|
|
140
|
+
attributes: { ...attributes, quantile: "max" },
|
|
141
|
+
value: metricState.max
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
metricData.push({
|
|
145
|
+
dataPointType: DataPointType.SUM,
|
|
146
|
+
descriptor: descriptorFromKey(metricKey, attributes, "quantiles"),
|
|
147
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
148
|
+
isMonotonic: false,
|
|
149
|
+
dataPoints
|
|
150
|
+
})
|
|
151
|
+
metricData.push({
|
|
152
|
+
dataPointType: DataPointType.SUM,
|
|
153
|
+
descriptor: {
|
|
154
|
+
...descriptorMeta(metricKey, "count"),
|
|
155
|
+
unit: "1",
|
|
156
|
+
type: InstrumentType.COUNTER,
|
|
157
|
+
valueType: ValueType.INT
|
|
158
|
+
},
|
|
159
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
160
|
+
isMonotonic: true,
|
|
161
|
+
dataPoints: [{
|
|
162
|
+
startTime: hrTimeNow,
|
|
163
|
+
endTime: hrTimeNow,
|
|
164
|
+
attributes,
|
|
165
|
+
value: metricState.count
|
|
166
|
+
}]
|
|
167
|
+
})
|
|
168
|
+
metricData.push({
|
|
169
|
+
dataPointType: DataPointType.SUM,
|
|
170
|
+
descriptor: {
|
|
171
|
+
...descriptorMeta(metricKey, "sum"),
|
|
172
|
+
unit: "1",
|
|
173
|
+
type: InstrumentType.COUNTER,
|
|
174
|
+
valueType: ValueType.DOUBLE
|
|
175
|
+
},
|
|
176
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
177
|
+
isMonotonic: true,
|
|
178
|
+
dataPoints: [{
|
|
179
|
+
startTime: hrTimeNow,
|
|
180
|
+
endTime: hrTimeNow,
|
|
181
|
+
attributes,
|
|
182
|
+
value: metricState.sum
|
|
183
|
+
}]
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return Promise.resolve({
|
|
189
|
+
resourceMetrics: {
|
|
190
|
+
resource: this.resource,
|
|
191
|
+
scopeMetrics: [{
|
|
192
|
+
scope: { name: sdkName },
|
|
193
|
+
metrics: metricData
|
|
194
|
+
}]
|
|
195
|
+
},
|
|
196
|
+
errors: []
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const descriptorMeta = (
|
|
202
|
+
metricKey: MetricKey.MetricKey.Untyped,
|
|
203
|
+
suffix?: string
|
|
204
|
+
) => ({
|
|
205
|
+
name: suffix ? `${metricKey.name}_${suffix}` : metricKey.name,
|
|
206
|
+
description: Option.getOrElse(metricKey.description, () => "")
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const descriptorFromKey = (
|
|
210
|
+
metricKey: MetricKey.MetricKey.Untyped,
|
|
211
|
+
tags: Record<string, string>,
|
|
212
|
+
suffix?: string
|
|
213
|
+
): InstrumentDescriptor => ({
|
|
214
|
+
...descriptorMeta(metricKey, suffix),
|
|
215
|
+
unit: tags.unit ?? tags.time_unit ?? "1",
|
|
216
|
+
type: instrumentTypeFromKey(metricKey),
|
|
217
|
+
valueType: ValueType.DOUBLE
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const instrumentTypeFromKey = (key: MetricKey.MetricKey.Untyped): InstrumentType => {
|
|
221
|
+
if (MetricKeyType.isHistogramKey(key.keyType)) {
|
|
222
|
+
return InstrumentType.HISTOGRAM
|
|
223
|
+
} else if (MetricKeyType.isGaugeKey(key.keyType)) {
|
|
224
|
+
return InstrumentType.OBSERVABLE_GAUGE
|
|
225
|
+
} else if (MetricKeyType.isSummaryKey(key.keyType)) {
|
|
226
|
+
return InstrumentType.UP_DOWN_COUNTER
|
|
227
|
+
} else if (MetricKeyType.isFrequencyKey(key.keyType)) {
|
|
228
|
+
return InstrumentType.COUNTER
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return InstrumentType.UP_DOWN_COUNTER
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const currentHrTime = (): HrTime => {
|
|
235
|
+
const now = Date.now()
|
|
236
|
+
return [Math.floor(now / 1000), (now % 1000) * 1000000]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** @internal */
|
|
240
|
+
export const makeProducer = Effect.map(
|
|
241
|
+
Resource.Resource,
|
|
242
|
+
(resource): MetricProducer => new MetricProducerImpl(resource)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
/** @internal */
|
|
246
|
+
export const registerProducer = (self: MetricProducer, metricReader: LazyArg<MetricReader>) =>
|
|
247
|
+
Effect.acquireRelease(
|
|
248
|
+
Effect.sync(() => {
|
|
249
|
+
const reader = metricReader()
|
|
250
|
+
reader.setMetricProducer(self)
|
|
251
|
+
return reader
|
|
252
|
+
}),
|
|
253
|
+
(reader) => Effect.promise(() => reader.shutdown())
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
/** @internal */
|
|
257
|
+
export const layer = (evaluate: LazyArg<MetricReader>) =>
|
|
258
|
+
Layer.scopedDiscard(Effect.flatMap(
|
|
259
|
+
makeProducer,
|
|
260
|
+
(producer) => registerProducer(producer, evaluate)
|
|
261
|
+
))
|
package/src/internal/tracer.ts
CHANGED
|
@@ -5,11 +5,9 @@ import * as Option from "@effect/data/Option"
|
|
|
5
5
|
import * as Cause from "@effect/io/Cause"
|
|
6
6
|
import * as Effect from "@effect/io/Effect"
|
|
7
7
|
import type { Exit } from "@effect/io/Exit"
|
|
8
|
-
import type { RuntimeFiber } from "@effect/io/Fiber"
|
|
9
8
|
import * as FiberRef from "@effect/io/FiberRef"
|
|
10
9
|
import * as FiberRefs from "@effect/io/FiberRefs"
|
|
11
10
|
import * as Layer from "@effect/io/Layer"
|
|
12
|
-
import * as Supervisor from "@effect/io/Supervisor"
|
|
13
11
|
import * as Tracer from "@effect/io/Tracer"
|
|
14
12
|
import { Resource } from "@effect/opentelemetry/Resource"
|
|
15
13
|
import * as OtelApi from "@opentelemetry/api"
|
|
@@ -34,23 +32,18 @@ export class OtelSpan implements Tracer.Span {
|
|
|
34
32
|
startTime: bigint
|
|
35
33
|
) {
|
|
36
34
|
const active = contextApi.active()
|
|
37
|
-
this.span = tracer.startSpan(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
undefined
|
|
47
|
-
},
|
|
48
|
-
parent._tag === "Some" ? populateContext(active, parent.value, context) : active
|
|
49
|
-
)
|
|
35
|
+
this.span = tracer.startSpan(name, {
|
|
36
|
+
startTime: nanosToHrTime(startTime),
|
|
37
|
+
links: links.length > 0
|
|
38
|
+
? links.map((link) => ({
|
|
39
|
+
context: makeSpanContext(link.span),
|
|
40
|
+
attributes: link.attributes
|
|
41
|
+
}))
|
|
42
|
+
: undefined
|
|
43
|
+
}, parent._tag === "Some" ? populateContext(active, parent.value, context) : active)
|
|
50
44
|
const spanContext = this.span.spanContext()
|
|
51
45
|
this.spanId = spanContext.spanId
|
|
52
46
|
this.traceId = spanContext.traceId
|
|
53
|
-
|
|
54
47
|
this.status = {
|
|
55
48
|
_tag: "Started",
|
|
56
49
|
startTime
|
|
@@ -120,6 +113,24 @@ export const make = pipe(
|
|
|
120
113
|
links,
|
|
121
114
|
startTime
|
|
122
115
|
)
|
|
116
|
+
},
|
|
117
|
+
context(execution, fiber) {
|
|
118
|
+
const currentSpan = Option.flatMap(
|
|
119
|
+
FiberRefs.get(
|
|
120
|
+
fiber.unsafeGetFiberRefs(),
|
|
121
|
+
FiberRef.currentTracerSpan
|
|
122
|
+
),
|
|
123
|
+
List.head
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if (currentSpan._tag === "None") {
|
|
127
|
+
return execution()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return OtelApi.context.with(
|
|
131
|
+
populateContext(OtelApi.context.active(), currentSpan.value),
|
|
132
|
+
execution
|
|
133
|
+
)
|
|
123
134
|
}
|
|
124
135
|
})
|
|
125
136
|
)
|
|
@@ -160,39 +171,7 @@ export const makeExternalSpan = (options: {
|
|
|
160
171
|
}
|
|
161
172
|
|
|
162
173
|
/** @internal */
|
|
163
|
-
export
|
|
164
|
-
value(): Effect.Effect<never, never, void> {
|
|
165
|
-
return Effect.unit
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
onRun<E, A, X>(execution: () => X, fiber: RuntimeFiber<E, A>): X {
|
|
169
|
-
const currentSpan = Option.flatMap(
|
|
170
|
-
FiberRefs.get(
|
|
171
|
-
fiber.unsafeGetFiberRefs(),
|
|
172
|
-
FiberRef.currentTracerSpan
|
|
173
|
-
),
|
|
174
|
-
List.head
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
if (currentSpan._tag === "None") {
|
|
178
|
-
return execution()
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return OtelApi.context.with(
|
|
182
|
-
populateContext(OtelApi.context.active(), currentSpan.value),
|
|
183
|
-
execution
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** @internal */
|
|
189
|
-
export const supervisor = new OtelSupervisor()
|
|
190
|
-
|
|
191
|
-
/** @internal */
|
|
192
|
-
export const layer = Layer.merge(
|
|
193
|
-
Layer.unwrapEffect(Effect.map(make, Effect.setTracer)),
|
|
194
|
-
Supervisor.addSupervisor(supervisor)
|
|
195
|
-
)
|
|
174
|
+
export const layer = Layer.unwrapEffect(Effect.map(make, Effect.setTracer))
|
|
196
175
|
|
|
197
176
|
// -------------------------------------------------------------------------------------
|
|
198
177
|
// utils
|