import { Duration, Effect, Exit, Metric, Stream } from "effect"; import { outcomeFromExit } from "./Attributes.ts"; import { metricAttributes, rpcRequestDuration, rpcRequestsTotal, withMetrics } from "./Metrics.ts"; const annotateRpcSpan = ( method: string, traceAttributes?: Readonly>, ): Effect.Effect => Effect.annotateCurrentSpan({ "rpc.method": method, ...traceAttributes, }); const recordRpcStreamMetrics = ( method: string, startedAt: number, exit: Exit.Exit, ): Effect.Effect => Effect.gen(function* () { yield* Metric.update( Metric.withAttributes(rpcRequestDuration, metricAttributes({ method })), Duration.millis(Math.max(0, Date.now() - startedAt)), ); yield* Metric.update( Metric.withAttributes( rpcRequestsTotal, metricAttributes({ method, outcome: outcomeFromExit(exit), }), ), 1, ); }); export const observeRpcEffect = ( method: string, effect: Effect.Effect, traceAttributes?: Readonly>, ): Effect.Effect => Effect.gen(function* () { yield* annotateRpcSpan(method, traceAttributes); return yield* effect.pipe( withMetrics({ counter: rpcRequestsTotal, timer: rpcRequestDuration, attributes: { method, }, }), ); }); export const observeRpcStream = ( method: string, stream: Stream.Stream, traceAttributes?: Readonly>, ): Stream.Stream => Stream.unwrap( Effect.gen(function* () { yield* annotateRpcSpan(method, traceAttributes); const startedAt = Date.now(); return stream.pipe(Stream.onExit((exit) => recordRpcStreamMetrics(method, startedAt, exit))); }), ); export const observeRpcStreamEffect = ( method: string, effect: Effect.Effect, EffectError, EffectContext>, traceAttributes?: Readonly>, ): Stream.Stream => Stream.unwrap( Effect.gen(function* () { yield* annotateRpcSpan(method, traceAttributes); const startedAt = Date.now(); const exit = yield* Effect.exit(effect); if (Exit.isFailure(exit)) { yield* recordRpcStreamMetrics(method, startedAt, exit); return yield* Effect.failCause(exit.cause); } return exit.value.pipe( Stream.onExit((streamExit) => recordRpcStreamMetrics(method, startedAt, streamExit)), ); }), );