Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'
This commit is contained in:
122
repos/effect/packages/opentelemetry/test/OtelLogger.test.ts
Normal file
122
repos/effect/packages/opentelemetry/test/OtelLogger.test.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import * as NodeSdk from "@effect/opentelemetry/NodeSdk"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { SeverityNumber } from "@opentelemetry/api-logs"
|
||||
import { InMemoryLogRecordExporter, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs"
|
||||
import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"
|
||||
import * as Clock from "effect/Clock"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as References from "effect/References"
|
||||
|
||||
describe("Logger", () => {
|
||||
describe("provided", () => {
|
||||
const exporter = new InMemoryLogRecordExporter()
|
||||
|
||||
const TracingLive = NodeSdk.layer(Effect.sync(() => ({
|
||||
resource: {
|
||||
serviceName: "test"
|
||||
},
|
||||
logRecordProcessor: [new SimpleLogRecordProcessor({ exporter })]
|
||||
})))
|
||||
|
||||
it.effect("emits log records", () =>
|
||||
Effect.gen(function*() {
|
||||
yield* Effect.log("test").pipe(
|
||||
Effect.repeat({ times: 9 })
|
||||
)
|
||||
assert.lengthOf(exporter.getFinishedLogRecords(), 10)
|
||||
}).pipe(Effect.provide(TracingLive)))
|
||||
|
||||
it.effect("maps Effect LogLevel to OTel SeverityNumber spec values", () => {
|
||||
const severityExporter = new InMemoryLogRecordExporter()
|
||||
const SeverityLayer = NodeSdk.layer(Effect.sync(() => ({
|
||||
resource: { serviceName: "test" },
|
||||
logRecordProcessor: [new SimpleLogRecordProcessor({ exporter: severityExporter })]
|
||||
})))
|
||||
|
||||
return Effect.gen(function*() {
|
||||
yield* Effect.logTrace("trace")
|
||||
yield* Effect.logDebug("debug")
|
||||
yield* Effect.logInfo("info")
|
||||
yield* Effect.logWarning("warn")
|
||||
yield* Effect.logError("error")
|
||||
yield* Effect.logFatal("fatal")
|
||||
|
||||
const records = severityExporter.getFinishedLogRecords()
|
||||
const byText = Object.fromEntries(records.map((r) => [r.severityText, r.severityNumber]))
|
||||
|
||||
assert.strictEqual(byText.Trace, SeverityNumber.TRACE)
|
||||
assert.strictEqual(byText.Debug, SeverityNumber.DEBUG)
|
||||
assert.strictEqual(byText.Info, SeverityNumber.INFO)
|
||||
assert.strictEqual(byText.Warn, SeverityNumber.WARN)
|
||||
assert.strictEqual(byText.Error, SeverityNumber.ERROR)
|
||||
assert.strictEqual(byText.Fatal, SeverityNumber.FATAL)
|
||||
}).pipe(
|
||||
Effect.provide(SeverityLayer.pipe(Layer.provideMerge(Layer.succeed(References.MinimumLogLevel, "Trace"))))
|
||||
)
|
||||
})
|
||||
|
||||
it.effect("uses monotonic clock timestamps and keeps them aligned with spans", () => {
|
||||
const logExporter = new InMemoryLogRecordExporter()
|
||||
const spanExporter = new InMemorySpanExporter()
|
||||
const timeNanos = 1_735_689_600_123_456_789n
|
||||
const expectedTime: readonly [number, number] = [
|
||||
Number(timeNanos / BigInt(1_000_000_000)),
|
||||
Number(timeNanos % BigInt(1_000_000_000))
|
||||
]
|
||||
const skewedClock: Clock.Clock = {
|
||||
currentTimeMillisUnsafe: () => 1,
|
||||
currentTimeMillis: Effect.succeed(1),
|
||||
currentTimeNanosUnsafe: () => timeNanos,
|
||||
currentTimeNanos: Effect.succeed(timeNanos),
|
||||
sleep: () => Effect.void
|
||||
}
|
||||
|
||||
const TracingLive = NodeSdk.layer(Effect.sync(() => ({
|
||||
resource: {
|
||||
serviceName: "test"
|
||||
},
|
||||
spanProcessor: [new SimpleSpanProcessor(spanExporter)],
|
||||
logRecordProcessor: [new SimpleLogRecordProcessor({ exporter: logExporter })]
|
||||
})))
|
||||
|
||||
return Effect.gen(function*() {
|
||||
yield* Effect.log("test").pipe(
|
||||
Effect.withSpan("parent")
|
||||
)
|
||||
|
||||
const logs = logExporter.getFinishedLogRecords()
|
||||
const spans = spanExporter.getFinishedSpans()
|
||||
assert.lengthOf(logs, 1)
|
||||
assert.lengthOf(spans, 1)
|
||||
const log = logs[0]!
|
||||
const span = spans[0]!
|
||||
|
||||
assert.deepStrictEqual(log.hrTime, expectedTime)
|
||||
assert.deepStrictEqual(log.hrTimeObserved, expectedTime)
|
||||
assert.deepStrictEqual(log.hrTime, span.startTime)
|
||||
assert.strictEqual(log.attributes.spanId, span.spanContext().spanId)
|
||||
assert.strictEqual(log.attributes.traceId, span.spanContext().traceId)
|
||||
}).pipe(
|
||||
Effect.provide(TracingLive),
|
||||
Effect.provideService(Clock.Clock, skewedClock)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("not provided", () => {
|
||||
const exporter = new InMemoryLogRecordExporter()
|
||||
|
||||
const TracingLive = NodeSdk.layer(Effect.sync(() => ({
|
||||
resource: {
|
||||
serviceName: "test"
|
||||
}
|
||||
})))
|
||||
|
||||
it.effect("withSpan", () =>
|
||||
Effect.gen(function*() {
|
||||
yield* Effect.log("test")
|
||||
assert.lengthOf(exporter.getFinishedLogRecords(), 0)
|
||||
}).pipe(Effect.provide(TracingLive)))
|
||||
})
|
||||
})
|
||||
339
repos/effect/packages/opentelemetry/test/OtelMetrics.test.ts
Normal file
339
repos/effect/packages/opentelemetry/test/OtelMetrics.test.ts
Normal file
@@ -0,0 +1,339 @@
|
||||
import * as internal from "@effect/opentelemetry/internal/metrics"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { ValueType } from "@opentelemetry/api"
|
||||
import { resourceFromAttributes } from "@opentelemetry/resources"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Metric from "effect/Metric"
|
||||
|
||||
const findMetric = (metrics: any, name: string) =>
|
||||
metrics.resourceMetrics.scopeMetrics[0].metrics.find((_: any) => _.descriptor.name === name)
|
||||
|
||||
describe("Metrics", () => {
|
||||
it.effect("gauge", () =>
|
||||
Effect.gen(function*() {
|
||||
const services = yield* Effect.context<never>()
|
||||
const resource = resourceFromAttributes({
|
||||
name: "test",
|
||||
version: "1.0.0"
|
||||
})
|
||||
const producer = new internal.MetricProducerImpl(resource, services)
|
||||
const gauge = Metric.gauge("rps")
|
||||
|
||||
yield* Metric.withAttributes(gauge, {
|
||||
key: "value",
|
||||
unit: "requests"
|
||||
}).pipe(Metric.update(10))
|
||||
yield* Metric.withAttributes(gauge, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(10))
|
||||
yield* Metric.withAttributes(gauge, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(20))
|
||||
|
||||
const results = yield* Effect.promise(() => producer.collect())
|
||||
const object = JSON.parse(JSON.stringify(results))
|
||||
assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [
|
||||
["name", "test"],
|
||||
["version", "1.0.0"]
|
||||
])
|
||||
assert.equal(object.resourceMetrics.scopeMetrics.length, 1)
|
||||
const metric = findMetric(object, "rps")
|
||||
assert.deepStrictEqual(metric, {
|
||||
"dataPointType": 2,
|
||||
"descriptor": {
|
||||
"advice": {},
|
||||
"name": "rps",
|
||||
"description": "",
|
||||
"unit": "requests",
|
||||
"type": "OBSERVABLE_GAUGE",
|
||||
"valueType": ValueType.DOUBLE
|
||||
},
|
||||
"aggregationTemporality": 1,
|
||||
"dataPoints": [
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"unit": "requests",
|
||||
"key": "value"
|
||||
},
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"key": "value"
|
||||
},
|
||||
"value": 20
|
||||
}
|
||||
]
|
||||
})
|
||||
}))
|
||||
|
||||
it.effect("gauge bigint", () =>
|
||||
Effect.gen(function*() {
|
||||
const services = yield* Effect.context<never>()
|
||||
const producer = new internal.MetricProducerImpl(
|
||||
resourceFromAttributes({
|
||||
name: "test",
|
||||
version: "1.0.0"
|
||||
}),
|
||||
services
|
||||
)
|
||||
const gauge = Metric.gauge("rps-bigint", { bigint: true })
|
||||
|
||||
yield* Metric.withAttributes(gauge, {
|
||||
key: "value",
|
||||
unit: "requests"
|
||||
}).pipe(Metric.update(10n))
|
||||
yield* Metric.withAttributes(gauge, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(10n))
|
||||
yield* Metric.withAttributes(gauge, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(20n))
|
||||
|
||||
const results = yield* Effect.promise(() => producer.collect())
|
||||
const object = JSON.parse(JSON.stringify(results))
|
||||
assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [
|
||||
["name", "test"],
|
||||
["version", "1.0.0"]
|
||||
])
|
||||
assert.equal(object.resourceMetrics.scopeMetrics.length, 1)
|
||||
const metric = findMetric(object, "rps-bigint")
|
||||
assert.deepEqual(metric, {
|
||||
"dataPointType": 2,
|
||||
"descriptor": {
|
||||
"advice": {},
|
||||
"name": "rps-bigint",
|
||||
"description": "",
|
||||
"unit": "requests",
|
||||
"type": "OBSERVABLE_GAUGE",
|
||||
"valueType": ValueType.INT
|
||||
},
|
||||
"aggregationTemporality": 1,
|
||||
"dataPoints": [
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"unit": "requests",
|
||||
"key": "value"
|
||||
},
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"key": "value"
|
||||
},
|
||||
"value": 20
|
||||
}
|
||||
]
|
||||
})
|
||||
}))
|
||||
|
||||
it.effect("counter", () =>
|
||||
Effect.gen(function*() {
|
||||
const services = yield* Effect.context<never>()
|
||||
const producer = new internal.MetricProducerImpl(
|
||||
resourceFromAttributes({
|
||||
name: "test",
|
||||
version: "1.0.0"
|
||||
}),
|
||||
services
|
||||
)
|
||||
const counter = Metric.counter("counter", { description: "Example" })
|
||||
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value",
|
||||
unit: "requests"
|
||||
}).pipe(Metric.update(1))
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(1))
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(1))
|
||||
|
||||
const results = yield* Effect.promise(() => producer.collect())
|
||||
const object = JSON.parse(JSON.stringify(results))
|
||||
assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [
|
||||
["name", "test"],
|
||||
["version", "1.0.0"]
|
||||
])
|
||||
assert.equal(object.resourceMetrics.scopeMetrics.length, 1)
|
||||
const metric = findMetric(object, "counter")
|
||||
assert.deepEqual(metric, {
|
||||
"dataPointType": 3,
|
||||
"descriptor": {
|
||||
"advice": {},
|
||||
"name": "counter",
|
||||
"description": "Example",
|
||||
"unit": "requests",
|
||||
"type": "UP_DOWN_COUNTER",
|
||||
"valueType": ValueType.DOUBLE
|
||||
},
|
||||
"isMonotonic": false,
|
||||
"aggregationTemporality": 1,
|
||||
"dataPoints": [
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"unit": "requests",
|
||||
"key": "value"
|
||||
},
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"key": "value"
|
||||
},
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
})
|
||||
}))
|
||||
|
||||
it.effect("counter-inc", () =>
|
||||
Effect.gen(function*() {
|
||||
const services = yield* Effect.context<never>()
|
||||
const producer = new internal.MetricProducerImpl(
|
||||
resourceFromAttributes({
|
||||
name: "test",
|
||||
version: "1.0.0"
|
||||
}),
|
||||
services
|
||||
)
|
||||
const counter = Metric.counter("counter-inc", {
|
||||
description: "Example",
|
||||
incremental: true
|
||||
})
|
||||
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value",
|
||||
unit: "requests"
|
||||
}).pipe(Metric.update(1))
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(1))
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(1))
|
||||
|
||||
const results = yield* Effect.promise(() => producer.collect())
|
||||
const object = JSON.parse(JSON.stringify(results))
|
||||
assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [
|
||||
["name", "test"],
|
||||
["version", "1.0.0"]
|
||||
])
|
||||
assert.equal(object.resourceMetrics.scopeMetrics.length, 1)
|
||||
const metric = findMetric(object, "counter-inc")
|
||||
assert.deepEqual(metric, {
|
||||
"dataPointType": 3,
|
||||
"descriptor": {
|
||||
"advice": {},
|
||||
"name": "counter-inc",
|
||||
"description": "Example",
|
||||
"unit": "requests",
|
||||
"type": "COUNTER",
|
||||
"valueType": ValueType.DOUBLE
|
||||
},
|
||||
"isMonotonic": true,
|
||||
"aggregationTemporality": 1,
|
||||
"dataPoints": [
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"unit": "requests",
|
||||
"key": "value"
|
||||
},
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"key": "value"
|
||||
},
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
})
|
||||
}))
|
||||
|
||||
it.effect("counter-bigint", () =>
|
||||
Effect.gen(function*() {
|
||||
const services = yield* Effect.context<never>()
|
||||
const producer = new internal.MetricProducerImpl(
|
||||
resourceFromAttributes({
|
||||
name: "test",
|
||||
version: "1.0.0"
|
||||
}),
|
||||
services
|
||||
)
|
||||
const counter = Metric.counter("counter-bigint", {
|
||||
description: "Example",
|
||||
incremental: true,
|
||||
bigint: true
|
||||
})
|
||||
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value",
|
||||
unit: "requests"
|
||||
}).pipe(Metric.update(1n))
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(1n))
|
||||
yield* Metric.withAttributes(counter, {
|
||||
key: "value"
|
||||
}).pipe(Metric.update(1n))
|
||||
|
||||
const results = yield* Effect.promise(() => producer.collect())
|
||||
const object = JSON.parse(JSON.stringify(results))
|
||||
assert.deepEqual(object.resourceMetrics.resource._rawAttributes, [
|
||||
["name", "test"],
|
||||
["version", "1.0.0"]
|
||||
])
|
||||
assert.equal(object.resourceMetrics.scopeMetrics.length, 1)
|
||||
const metric = findMetric(object, "counter-bigint")
|
||||
assert.deepEqual(metric, {
|
||||
"dataPointType": 3,
|
||||
"descriptor": {
|
||||
"advice": {},
|
||||
"name": "counter-bigint",
|
||||
"description": "Example",
|
||||
"unit": "requests",
|
||||
"type": "COUNTER",
|
||||
"valueType": ValueType.INT
|
||||
},
|
||||
"isMonotonic": true,
|
||||
"aggregationTemporality": 1,
|
||||
"dataPoints": [
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"unit": "requests",
|
||||
"key": "value"
|
||||
},
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"startTime": metric.dataPoints[0].startTime,
|
||||
"endTime": metric.dataPoints[0].endTime,
|
||||
"attributes": {
|
||||
"key": "value"
|
||||
},
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
})
|
||||
}))
|
||||
})
|
||||
212
repos/effect/packages/opentelemetry/test/OtelTracer.test.ts
Normal file
212
repos/effect/packages/opentelemetry/test/OtelTracer.test.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import * as NodeSdk from "@effect/opentelemetry/NodeSdk"
|
||||
import * as OtelTracer from "@effect/opentelemetry/OtelTracer"
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import * as OtelApi from "@opentelemetry/api"
|
||||
import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks"
|
||||
import { InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base"
|
||||
import * as Cause from "effect/Cause"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as EffectTracer from "effect/Tracer"
|
||||
|
||||
const TracingLive = NodeSdk.layer(Effect.sync(() => ({
|
||||
resource: {
|
||||
serviceName: "test"
|
||||
},
|
||||
spanProcessor: [new SimpleSpanProcessor(new InMemorySpanExporter())]
|
||||
})))
|
||||
|
||||
// needed to test context propagation
|
||||
const contextManager = new AsyncHooksContextManager()
|
||||
OtelApi.context.setGlobalContextManager(contextManager)
|
||||
|
||||
describe("Tracer", () => {
|
||||
describe("provided", () => {
|
||||
it.effect("withSpan", () =>
|
||||
Effect.gen(function*() {
|
||||
const span = yield* Effect.currentSpan
|
||||
assert.instanceOf(span, OtelTracer.OtelSpan)
|
||||
}).pipe(
|
||||
Effect.withSpan("ok"),
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
|
||||
it.effect("withSpan links", () =>
|
||||
Effect.gen(function*() {
|
||||
const linkedSpan = yield* Effect.makeSpanScoped("B")
|
||||
const span = yield* Effect.currentSpan.pipe(
|
||||
Effect.withSpan("A"),
|
||||
Effect.linkSpans(linkedSpan)
|
||||
)
|
||||
assert.instanceOf(span, OtelTracer.OtelSpan)
|
||||
assert.lengthOf(span.links, 1)
|
||||
}).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
|
||||
it.effect("nested withSpan sets correct parent chain", () =>
|
||||
Effect.gen(function*() {
|
||||
const child = yield* Effect.currentSpan.pipe(
|
||||
Effect.withSpan("child"),
|
||||
Effect.withSpan("parent")
|
||||
)
|
||||
assert.instanceOf(child, OtelTracer.OtelSpan)
|
||||
assert.strictEqual(child.name, "child")
|
||||
assert.isDefined(child.parent)
|
||||
assert.strictEqual((child.parent.valueOrUndefined! as OtelTracer.OtelSpan).name, "parent")
|
||||
}).pipe(
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
|
||||
it.effect("supervisor sets context", () =>
|
||||
Effect.sync(() => {
|
||||
const context = OtelApi.context.active()
|
||||
assert.isDefined(OtelApi.trace.getSpan(context))
|
||||
}).pipe(
|
||||
Effect.withSpan("ok"),
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
|
||||
it.effect("supervisor sets context generator", () =>
|
||||
Effect.gen(function*() {
|
||||
yield* Effect.yieldNow
|
||||
const context = OtelApi.context.active()
|
||||
assert.isDefined(OtelApi.trace.getSpan(context))
|
||||
}).pipe(
|
||||
Effect.withSpan("ok"),
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
|
||||
it.effect("currentOtelSpan", () =>
|
||||
Effect.gen(function*() {
|
||||
const span = yield* Effect.currentSpan
|
||||
const otelSpan = yield* OtelTracer.currentOtelSpan
|
||||
assert.strictEqual((span as OtelTracer.OtelSpan).span, otelSpan)
|
||||
}).pipe(
|
||||
Effect.withSpan("ok"),
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
|
||||
it.effect("preserves the sampling decision of generic external spans", () =>
|
||||
Effect.gen(function*() {
|
||||
const span = yield* Effect.currentSpan
|
||||
assert.instanceOf(span, OtelTracer.OtelSpan)
|
||||
assert.strictEqual(span.span.spanContext().traceFlags, OtelApi.TraceFlags.NONE)
|
||||
}).pipe(
|
||||
Effect.withSpan("child"),
|
||||
Effect.withParentSpan(EffectTracer.externalSpan({
|
||||
traceId: "1".repeat(32),
|
||||
spanId: "2".repeat(16),
|
||||
sampled: false
|
||||
})),
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
|
||||
it.effect("records every pretty error", () =>
|
||||
Effect.gen(function*() {
|
||||
const exporter = new InMemorySpanExporter()
|
||||
const spanProcessor = new SimpleSpanProcessor(exporter)
|
||||
const firstFailure = Cause.fail(new Error("first"))
|
||||
const secondFailure = Cause.fail(new Error("second"))
|
||||
const cause = Cause.combine(firstFailure, secondFailure)
|
||||
|
||||
yield* Effect.failCause(cause).pipe(
|
||||
Effect.withSpan("error-span"),
|
||||
Effect.andThen(Effect.never), // keep the exporter alive
|
||||
Effect.provide(NodeSdk.layer(() => ({
|
||||
resource: {
|
||||
serviceName: "test"
|
||||
},
|
||||
spanProcessor: [spanProcessor]
|
||||
}))),
|
||||
Effect.forkChild({ startImmediately: true })
|
||||
)
|
||||
|
||||
const spanData = exporter.getFinishedSpans()[0]
|
||||
if (spanData === undefined) {
|
||||
return yield* Effect.die("Missing span data")
|
||||
}
|
||||
const exceptionEvents = spanData.events.filter((event) => event.name === "exception")
|
||||
assert.lengthOf(exceptionEvents, 2)
|
||||
assert.strictEqual(spanData.status.message, "first")
|
||||
}))
|
||||
|
||||
it.effect("renders nested error causes in the stacktrace", () =>
|
||||
Effect.gen(function*() {
|
||||
const exporter = new InMemorySpanExporter()
|
||||
const spanProcessor = new SimpleSpanProcessor(exporter)
|
||||
const error = new Error("outer failure", { cause: new Error("inner cause") })
|
||||
|
||||
yield* Effect.die(error).pipe(
|
||||
Effect.withSpan("error-span"),
|
||||
Effect.andThen(Effect.never), // keep the exporter alive
|
||||
Effect.provide(NodeSdk.layer(() => ({
|
||||
resource: {
|
||||
serviceName: "test"
|
||||
},
|
||||
spanProcessor: [spanProcessor]
|
||||
}))),
|
||||
Effect.forkChild({ startImmediately: true })
|
||||
)
|
||||
|
||||
const spanData = exporter.getFinishedSpans()[0]
|
||||
if (spanData === undefined) {
|
||||
return yield* Effect.die("Missing span data")
|
||||
}
|
||||
const exceptionEvent = spanData.events.find((event) => event.name === "exception")
|
||||
assert(exceptionEvent !== undefined)
|
||||
const stacktrace = exceptionEvent.attributes?.["exception.stacktrace"]
|
||||
assert.isString(stacktrace)
|
||||
assert.include(stacktrace as string, "[cause]: Error: inner cause")
|
||||
}))
|
||||
|
||||
it.effect("withSpanContext", () =>
|
||||
Effect.gen(function*() {
|
||||
const effect = Effect.gen(function*() {
|
||||
const span = yield* Effect.currentParentSpan
|
||||
assert(span._tag === "Span")
|
||||
if (span.parent._tag === "None") {
|
||||
return yield* Effect.die("No parent span")
|
||||
}
|
||||
return span.parent.value
|
||||
}).pipe(Effect.withSpan("child"))
|
||||
|
||||
const services = yield* Effect.context<never>()
|
||||
|
||||
yield* Effect.promise(async () => {
|
||||
await OtelApi.trace.getTracer("test").startActiveSpan("otel-span", {
|
||||
root: true,
|
||||
attributes: { "root": "yes" }
|
||||
}, async (span) => {
|
||||
try {
|
||||
const parent = await effect.pipe(
|
||||
OtelTracer.withSpanContext(span.spanContext()),
|
||||
Effect.provideContext(services),
|
||||
Effect.runPromise
|
||||
)
|
||||
const { spanId, traceId } = span.spanContext()
|
||||
assert.containsSubset(parent, {
|
||||
spanId,
|
||||
traceId
|
||||
})
|
||||
} finally {
|
||||
span.end()
|
||||
}
|
||||
})
|
||||
})
|
||||
}).pipe(
|
||||
Effect.scoped,
|
||||
Effect.provide(TracingLive)
|
||||
))
|
||||
})
|
||||
|
||||
describe("not provided", () => {
|
||||
it.effect("withSpan", () =>
|
||||
Effect.gen(function*() {
|
||||
const span = yield* Effect.currentSpan
|
||||
assert.notInstanceOf(span, OtelTracer.OtelSpan)
|
||||
}).pipe(
|
||||
Effect.withSpan("ok")
|
||||
))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user