@effect/opentelemetry 0.8.2 → 0.9.1
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/LICENSE +21 -0
- package/Metrics.d.ts +47 -0
- package/Metrics.d.ts.map +1 -0
- package/Metrics.js +52 -0
- package/Metrics.js.map +1 -0
- package/Resource.js.map +1 -1
- package/internal/metrics.d.ts +2 -0
- package/internal/metrics.d.ts.map +1 -0
- package/internal/metrics.js +268 -0
- package/internal/metrics.js.map +1 -0
- package/internal/tracer.js.map +1 -1
- package/mjs/Metrics.mjs +37 -0
- package/mjs/Metrics.mjs.map +1 -0
- package/mjs/Resource.mjs.map +1 -1
- package/mjs/internal/metrics.mjs +252 -0
- package/mjs/internal/metrics.mjs.map +1 -0
- package/mjs/internal/tracer.mjs.map +1 -1
- package/package.json +15 -10
- package/src/Metrics.ts +58 -0
- package/src/Resource.ts +3 -3
- package/src/internal/metrics.ts +310 -0
- package/src/internal/tracer.ts +9 -14
|
@@ -0,0 +1,310 @@
|
|
|
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/Metric/Key"
|
|
8
|
+
import * as MetricKeyType from "@effect/io/Metric/KeyType"
|
|
9
|
+
import * as MetricLabel from "@effect/io/Metric/Label"
|
|
10
|
+
import * as MetricState from "@effect/io/Metric/State"
|
|
11
|
+
import * as Resource from "@effect/opentelemetry/Resource"
|
|
12
|
+
import type { HrTime } from "@opentelemetry/api"
|
|
13
|
+
import { ValueType } from "@opentelemetry/api"
|
|
14
|
+
import type * as Resources from "@opentelemetry/resources"
|
|
15
|
+
import type {
|
|
16
|
+
CollectionResult,
|
|
17
|
+
DataPoint,
|
|
18
|
+
InstrumentDescriptor,
|
|
19
|
+
MetricData,
|
|
20
|
+
MetricReader
|
|
21
|
+
} from "@opentelemetry/sdk-metrics"
|
|
22
|
+
import { AggregationTemporality, DataPointType, InstrumentType } from "@opentelemetry/sdk-metrics"
|
|
23
|
+
import type { MetricCollectOptions, MetricProducer } from "@opentelemetry/sdk-metrics/build/src/export/MetricProducer"
|
|
24
|
+
|
|
25
|
+
const sdkName = "@effect/opentelemetry/Metrics"
|
|
26
|
+
|
|
27
|
+
const aggregationTemporalityLabelKey = `${sdkName}/aggregationTemporality`
|
|
28
|
+
|
|
29
|
+
/** @internal */
|
|
30
|
+
export const aggregationTemporalityLabel = (value: "delta" | "cumulative") =>
|
|
31
|
+
MetricLabel.make(
|
|
32
|
+
aggregationTemporalityLabelKey,
|
|
33
|
+
value
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const incrementalOnlyLabelKey = `${sdkName}/isIncrementalOnly`
|
|
37
|
+
|
|
38
|
+
/** @internal */
|
|
39
|
+
export const incrementalOnlyLabel = MetricLabel.make(
|
|
40
|
+
incrementalOnlyLabelKey,
|
|
41
|
+
"true"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const integerLabelKey = `${sdkName}/isInteger`
|
|
45
|
+
|
|
46
|
+
/** @internal */
|
|
47
|
+
export const integerLabel = MetricLabel.make(
|
|
48
|
+
integerLabelKey,
|
|
49
|
+
"true"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const unitLabelKey = `${sdkName}/unit`
|
|
53
|
+
|
|
54
|
+
/** @internal */
|
|
55
|
+
export const unitLabel = (unit: string) =>
|
|
56
|
+
MetricLabel.make(
|
|
57
|
+
unitLabelKey,
|
|
58
|
+
unit
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/** @internal */
|
|
62
|
+
export class MetricProducerImpl implements MetricProducer {
|
|
63
|
+
constructor(readonly resource: Resources.Resource) {
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
collect(_options?: MetricCollectOptions): Promise<CollectionResult> {
|
|
67
|
+
const snapshot = Metric.unsafeSnapshot()
|
|
68
|
+
const hrTimeNow = currentHrTime()
|
|
69
|
+
const metricData: Array<MetricData> = []
|
|
70
|
+
|
|
71
|
+
for (const { metricKey, metricState } of snapshot) {
|
|
72
|
+
const tags = HashSet.reduce(metricKey.tags, {}, (acc: Record<string, string>, label) => {
|
|
73
|
+
acc[label.key] = label.value
|
|
74
|
+
return acc
|
|
75
|
+
})
|
|
76
|
+
const attributes = Object.fromEntries(
|
|
77
|
+
Object.entries(tags).filter(([key]) => !key.startsWith(sdkName))
|
|
78
|
+
)
|
|
79
|
+
const isDelta = tags[aggregationTemporalityLabelKey] === "delta"
|
|
80
|
+
const descriptor = descriptorFromKey(metricKey, tags)
|
|
81
|
+
|
|
82
|
+
if (MetricState.isCounterState(metricState)) {
|
|
83
|
+
metricData.push({
|
|
84
|
+
dataPointType: DataPointType.SUM,
|
|
85
|
+
descriptor,
|
|
86
|
+
isMonotonic: descriptor.type === InstrumentType.COUNTER,
|
|
87
|
+
aggregationTemporality: isDelta ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE,
|
|
88
|
+
dataPoints: [{
|
|
89
|
+
startTime: hrTimeNow,
|
|
90
|
+
endTime: hrTimeNow,
|
|
91
|
+
attributes,
|
|
92
|
+
value: metricState.count
|
|
93
|
+
}]
|
|
94
|
+
})
|
|
95
|
+
} else if (MetricState.isGaugeState(metricState)) {
|
|
96
|
+
metricData.push({
|
|
97
|
+
dataPointType: DataPointType.GAUGE,
|
|
98
|
+
descriptor,
|
|
99
|
+
aggregationTemporality: isDelta ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE,
|
|
100
|
+
dataPoints: [{
|
|
101
|
+
startTime: hrTimeNow,
|
|
102
|
+
endTime: hrTimeNow,
|
|
103
|
+
attributes,
|
|
104
|
+
value: metricState.value
|
|
105
|
+
}]
|
|
106
|
+
})
|
|
107
|
+
} else if (MetricState.isHistogramState(metricState)) {
|
|
108
|
+
const size = metricState.buckets.length
|
|
109
|
+
const buckets = {
|
|
110
|
+
boundaries: Array<number>(size - 1),
|
|
111
|
+
counts: Array<number>(size)
|
|
112
|
+
}
|
|
113
|
+
let i = 0
|
|
114
|
+
let prev = 0
|
|
115
|
+
for (const [boundary, value] of metricState.buckets) {
|
|
116
|
+
if (i < size - 1) {
|
|
117
|
+
buckets.boundaries[i] = boundary
|
|
118
|
+
}
|
|
119
|
+
buckets.counts[i] = value - prev
|
|
120
|
+
prev = value
|
|
121
|
+
i++
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
metricData.push({
|
|
125
|
+
dataPointType: DataPointType.HISTOGRAM,
|
|
126
|
+
descriptor,
|
|
127
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
128
|
+
dataPoints: [{
|
|
129
|
+
startTime: hrTimeNow,
|
|
130
|
+
endTime: hrTimeNow,
|
|
131
|
+
attributes,
|
|
132
|
+
value: {
|
|
133
|
+
buckets,
|
|
134
|
+
count: metricState.count,
|
|
135
|
+
min: metricState.min,
|
|
136
|
+
max: metricState.max,
|
|
137
|
+
sum: metricState.sum
|
|
138
|
+
}
|
|
139
|
+
}]
|
|
140
|
+
})
|
|
141
|
+
} else if (MetricState.isFrequencyState(metricState)) {
|
|
142
|
+
const dataPoints: Array<DataPoint<number>> = []
|
|
143
|
+
for (const [freqKey, value] of metricState.occurrences) {
|
|
144
|
+
dataPoints.push({
|
|
145
|
+
startTime: hrTimeNow,
|
|
146
|
+
endTime: hrTimeNow,
|
|
147
|
+
attributes: {
|
|
148
|
+
...attributes,
|
|
149
|
+
key: freqKey
|
|
150
|
+
},
|
|
151
|
+
value
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
metricData.push({
|
|
155
|
+
dataPointType: DataPointType.SUM,
|
|
156
|
+
descriptor: descriptorFromKey(metricKey, tags),
|
|
157
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
158
|
+
isMonotonic: true,
|
|
159
|
+
dataPoints
|
|
160
|
+
})
|
|
161
|
+
} else if (MetricState.isSummaryState(metricState)) {
|
|
162
|
+
const dataPoints: Array<DataPoint<number>> = [{
|
|
163
|
+
startTime: hrTimeNow,
|
|
164
|
+
endTime: hrTimeNow,
|
|
165
|
+
attributes: { ...attributes, quantile: "min" },
|
|
166
|
+
value: metricState.min
|
|
167
|
+
}]
|
|
168
|
+
for (const [quantile, value] of metricState.quantiles) {
|
|
169
|
+
dataPoints.push({
|
|
170
|
+
startTime: hrTimeNow,
|
|
171
|
+
endTime: hrTimeNow,
|
|
172
|
+
attributes: { ...attributes, quantile: quantile.toString() },
|
|
173
|
+
value: value._tag === "Some" ? value.value : 0
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
dataPoints.push({
|
|
177
|
+
startTime: hrTimeNow,
|
|
178
|
+
endTime: hrTimeNow,
|
|
179
|
+
attributes: { ...attributes, quantile: "max" },
|
|
180
|
+
value: metricState.max
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
metricData.push({
|
|
184
|
+
dataPointType: DataPointType.SUM,
|
|
185
|
+
descriptor: descriptorFromKey(metricKey, tags, "quantiles"),
|
|
186
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
187
|
+
isMonotonic: false,
|
|
188
|
+
dataPoints
|
|
189
|
+
})
|
|
190
|
+
metricData.push({
|
|
191
|
+
dataPointType: DataPointType.SUM,
|
|
192
|
+
descriptor: {
|
|
193
|
+
...descriptorMeta(metricKey, "count"),
|
|
194
|
+
unit: "1",
|
|
195
|
+
type: InstrumentType.COUNTER,
|
|
196
|
+
valueType: ValueType.INT
|
|
197
|
+
},
|
|
198
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
199
|
+
isMonotonic: true,
|
|
200
|
+
dataPoints: [{
|
|
201
|
+
startTime: hrTimeNow,
|
|
202
|
+
endTime: hrTimeNow,
|
|
203
|
+
attributes,
|
|
204
|
+
value: metricState.count
|
|
205
|
+
}]
|
|
206
|
+
})
|
|
207
|
+
metricData.push({
|
|
208
|
+
dataPointType: DataPointType.SUM,
|
|
209
|
+
descriptor: {
|
|
210
|
+
...descriptorMeta(metricKey, "sum"),
|
|
211
|
+
unit: "1",
|
|
212
|
+
type: InstrumentType.COUNTER,
|
|
213
|
+
valueType: ValueType.DOUBLE
|
|
214
|
+
},
|
|
215
|
+
aggregationTemporality: AggregationTemporality.CUMULATIVE,
|
|
216
|
+
isMonotonic: true,
|
|
217
|
+
dataPoints: [{
|
|
218
|
+
startTime: hrTimeNow,
|
|
219
|
+
endTime: hrTimeNow,
|
|
220
|
+
attributes,
|
|
221
|
+
value: metricState.sum
|
|
222
|
+
}]
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return Promise.resolve({
|
|
228
|
+
resourceMetrics: {
|
|
229
|
+
resource: this.resource,
|
|
230
|
+
scopeMetrics: [{
|
|
231
|
+
scope: { name: sdkName },
|
|
232
|
+
metrics: metricData
|
|
233
|
+
}]
|
|
234
|
+
},
|
|
235
|
+
errors: []
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const descriptorMeta = (
|
|
241
|
+
metricKey: MetricKey.MetricKey.Untyped,
|
|
242
|
+
suffix?: string
|
|
243
|
+
) => ({
|
|
244
|
+
name: suffix ? `${metricKey.name}_${suffix}` : metricKey.name,
|
|
245
|
+
description: Option.getOrElse(metricKey.description, () => "")
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const descriptorFromKey = (
|
|
249
|
+
metricKey: MetricKey.MetricKey.Untyped,
|
|
250
|
+
tags: Record<string, string>,
|
|
251
|
+
suffix?: string
|
|
252
|
+
): InstrumentDescriptor => ({
|
|
253
|
+
...descriptorMeta(metricKey, suffix),
|
|
254
|
+
unit: tags[unitLabelKey] ?? tags.unit ?? tags.time_unit ?? "1",
|
|
255
|
+
type: instrumentTypeFromKey(metricKey, tags),
|
|
256
|
+
valueType: valueTypeFromKey(metricKey, tags)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const instrumentTypeFromKey = (key: MetricKey.MetricKey.Untyped, tags: Record<string, string>): InstrumentType => {
|
|
260
|
+
if (MetricKeyType.isHistogramKey(key.keyType)) {
|
|
261
|
+
return InstrumentType.HISTOGRAM
|
|
262
|
+
} else if (MetricKeyType.isGaugeKey(key.keyType)) {
|
|
263
|
+
return InstrumentType.OBSERVABLE_GAUGE
|
|
264
|
+
} else if (MetricKeyType.isSummaryKey(key.keyType)) {
|
|
265
|
+
return InstrumentType.UP_DOWN_COUNTER
|
|
266
|
+
} else if (MetricKeyType.isFrequencyKey(key.keyType)) {
|
|
267
|
+
return InstrumentType.COUNTER
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return tags[incrementalOnlyLabelKey] ?
|
|
271
|
+
InstrumentType.COUNTER :
|
|
272
|
+
InstrumentType.UP_DOWN_COUNTER
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const valueTypeFromKey = (_key: MetricKey.MetricKey.Untyped, tags: Record<string, string>): ValueType => {
|
|
276
|
+
if (tags[integerLabelKey]) {
|
|
277
|
+
return ValueType.INT
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return ValueType.DOUBLE
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const currentHrTime = (): HrTime => {
|
|
284
|
+
const now = Date.now()
|
|
285
|
+
return [Math.floor(now / 1000), (now % 1000) * 1000000]
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** @internal */
|
|
289
|
+
export const makeProducer = Effect.map(
|
|
290
|
+
Resource.Resource,
|
|
291
|
+
(resource): MetricProducer => new MetricProducerImpl(resource)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
/** @internal */
|
|
295
|
+
export const registerProducer = (self: MetricProducer, metricReader: LazyArg<MetricReader>) =>
|
|
296
|
+
Effect.acquireRelease(
|
|
297
|
+
Effect.sync(() => {
|
|
298
|
+
const reader = metricReader()
|
|
299
|
+
reader.setMetricProducer(self)
|
|
300
|
+
return reader
|
|
301
|
+
}),
|
|
302
|
+
(reader) => Effect.promise(() => reader.shutdown())
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
/** @internal */
|
|
306
|
+
export const layer = (evaluate: LazyArg<MetricReader>) =>
|
|
307
|
+
Layer.scopedDiscard(Effect.flatMap(
|
|
308
|
+
makeProducer,
|
|
309
|
+
(producer) => registerProducer(producer, evaluate)
|
|
310
|
+
))
|
package/src/internal/tracer.ts
CHANGED
|
@@ -34,23 +34,18 @@ export class OtelSpan implements Tracer.Span {
|
|
|
34
34
|
startTime: bigint
|
|
35
35
|
) {
|
|
36
36
|
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
|
-
)
|
|
37
|
+
this.span = tracer.startSpan(name, {
|
|
38
|
+
startTime: nanosToHrTime(startTime),
|
|
39
|
+
links: links.length > 0
|
|
40
|
+
? links.map((link) => ({
|
|
41
|
+
context: makeSpanContext(link.span),
|
|
42
|
+
attributes: link.attributes
|
|
43
|
+
}))
|
|
44
|
+
: undefined
|
|
45
|
+
}, parent._tag === "Some" ? populateContext(active, parent.value, context) : active)
|
|
50
46
|
const spanContext = this.span.spanContext()
|
|
51
47
|
this.spanId = spanContext.spanId
|
|
52
48
|
this.traceId = spanContext.traceId
|
|
53
|
-
|
|
54
49
|
this.status = {
|
|
55
50
|
_tag: "Started",
|
|
56
51
|
startTime
|