From 142048efc303f727f7b3644433e6fa5592ee39fa Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 1 May 2024 14:27:13 +1200 Subject: [PATCH] capture stack trace for tracing spans --- .changeset/tiny-zebras-work.md | 9 ++ packages/effect/src/Tracer.ts | 1 + packages/effect/src/internal/channel.ts | 46 ++++++--- packages/effect/src/internal/core-effect.ts | 58 ++++++----- packages/effect/src/internal/fiberRuntime.ts | 31 ++++-- packages/effect/src/internal/layer.ts | 51 +++++++--- .../effect/src/internal/layer/circular.ts | 6 +- packages/effect/src/internal/stream.ts | 18 +++- packages/effect/src/internal/tracer.ts | 21 ++++ packages/effect/test/Tracer.test.ts | 16 ++-- packages/experimental/src/Machine.ts | 9 +- packages/platform/src/internal/http/client.ts | 2 +- .../platform/src/internal/http/middleware.ts | 6 +- packages/rpc/src/Router.ts | 12 ++- packages/rpc/src/Rpc.ts | 3 +- packages/sql/src/Resolver.ts | 14 ++- packages/sql/src/internal/client.ts | 96 ++++++++++--------- packages/sql/src/internal/statement.ts | 4 +- 18 files changed, 264 insertions(+), 139 deletions(-) create mode 100644 .changeset/tiny-zebras-work.md diff --git a/.changeset/tiny-zebras-work.md b/.changeset/tiny-zebras-work.md new file mode 100644 index 0000000000..b67f80da2a --- /dev/null +++ b/.changeset/tiny-zebras-work.md @@ -0,0 +1,9 @@ +--- +"effect": minor +"@effect/experimental": patch +"@effect/platform": patch +"@effect/rpc": patch +"@effect/sql": patch +--- + +capture stack trace for tracing spans diff --git a/packages/effect/src/Tracer.ts b/packages/effect/src/Tracer.ts index 37996a5852..2def76db5a 100644 --- a/packages/effect/src/Tracer.ts +++ b/packages/effect/src/Tracer.ts @@ -92,6 +92,7 @@ export interface SpanOptions { readonly root?: boolean | undefined readonly context?: Context.Context | undefined readonly kind?: SpanKind | undefined + readonly captureStackTrace?: boolean | Error | undefined } /** diff --git a/packages/effect/src/internal/channel.ts b/packages/effect/src/internal/channel.ts index d5c33048b1..fe36316738 100644 --- a/packages/effect/src/internal/channel.ts +++ b/packages/effect/src/internal/channel.ts @@ -2310,29 +2310,47 @@ export const updateService = dual< ))) /** @internal */ -export const withSpan = dual< +export const withSpan: { ( name: string, options?: Tracer.SpanOptions - ) => ( + ): ( self: Channel.Channel - ) => Channel.Channel>, + ) => Channel.Channel> ( self: Channel.Channel, name: string, options?: Tracer.SpanOptions - ) => Channel.Channel> ->(3, (self, name, options) => - unwrapScoped( - Effect.flatMap( - Effect.context(), - (context) => - Effect.map( - Effect.makeSpanScoped(name, options), - (span) => core.provideContext(self, Context.add(context, tracer.spanTag, span)) - ) + ): Channel.Channel> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const options = tracer.addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) + if (dataFirst) { + const self = arguments[0] + return unwrapScoped( + Effect.flatMap( + Effect.context(), + (context) => + Effect.map( + Effect.makeSpanScoped(name, options), + (span) => core.provideContext(self, Context.add(context, tracer.spanTag, span)) + ) + ) + ) + } + return (self: Effect.Effect) => + unwrapScoped( + Effect.flatMap( + Effect.context(), + (context) => + Effect.map( + Effect.makeSpanScoped(name, options), + (span) => core.provideContext(self, Context.add(context, tracer.spanTag, span)) + ) + ) ) - ) as any) +} as any /** @internal */ export const writeAll = ( diff --git a/packages/effect/src/internal/core-effect.ts b/packages/effect/src/internal/core-effect.ts index 1fd129e138..01d31b2884 100644 --- a/packages/effect/src/internal/core-effect.ts +++ b/packages/effect/src/internal/core-effect.ts @@ -2027,7 +2027,7 @@ const bigint0 = BigInt(0) export const unsafeMakeSpan = ( fiber: FiberRuntime, name: string, - options?: Tracer.SpanOptions + options: Tracer.SpanOptions ) => { const enabled = fiber.getFiberRef(core.currentTracerEnabled) if (enabled === false) { @@ -2045,34 +2045,38 @@ export const unsafeMakeSpan = ( const annotationsFromEnv = FiberRefs.get(fiberRefs, core.currentTracerSpanAnnotations) const linksFromEnv = FiberRefs.get(fiberRefs, core.currentTracerSpanLinks) - const parent = options?.parent + const parent = options.parent ? Option.some(options.parent) - : options?.root + : options.root ? Option.none() : Context.getOption(context, internalTracer.spanTag) const links = linksFromEnv._tag === "Some" ? - options?.links !== undefined ? + options.links !== undefined ? [ ...Chunk.toReadonlyArray(linksFromEnv.value), - ...(options?.links ?? []) + ...(options.links ?? []) ] : Chunk.toReadonlyArray(linksFromEnv.value) : - options?.links ?? Arr.empty() + options.links ?? Arr.empty() const span = tracer.span( name, parent, - options?.context ?? Context.empty(), + options.context ?? Context.empty(), links, timingEnabled ? clock.unsafeCurrentTimeNanos() : bigint0, - options?.kind ?? "internal" + options.kind ?? "internal" ) + if (typeof options.captureStackTrace === "object" && typeof options.captureStackTrace.stack === "string") { + span.attribute("code.stacktrace", options.captureStackTrace.stack) + } + if (annotationsFromEnv._tag === "Some") { HashMap.forEach(annotationsFromEnv.value, (value, key) => span.attribute(key, value)) } - if (options?.attributes !== undefined) { + if (options.attributes !== undefined) { Object.entries(options.attributes).forEach(([k, v]) => span.attribute(k, v)) } @@ -2083,7 +2087,10 @@ export const unsafeMakeSpan = ( export const makeSpan = ( name: string, options?: Tracer.SpanOptions -): Effect.Effect => core.withFiberRuntime((fiber) => core.succeed(unsafeMakeSpan(fiber, name, options))) +): Effect.Effect => { + options = internalTracer.addSpanStackTrace(options) + return core.withFiberRuntime((fiber) => core.succeed(unsafeMakeSpan(fiber, name, options))) +} /* @internal */ export const spanAnnotations: Effect.Effect> = core @@ -2108,7 +2115,7 @@ export const useSpan: { evaluate: (span: Tracer.Span) => Effect.Effect ] ) => { - const options: Tracer.SpanOptions | undefined = args.length === 1 ? undefined : args[0] + const options = internalTracer.addSpanStackTrace(args.length === 1 ? undefined : args[0]) const evaluate: (span: Tracer.Span) => Effect.Effect = args[args.length - 1] return core.withFiberRuntime((fiber) => { @@ -2134,25 +2141,26 @@ export const withParentSpan = dual< >(2, (self, span) => provideService(self, internalTracer.spanTag, span)) /** @internal */ -export const withSpan = dual< +export const withSpan: { ( name: string, - options?: Tracer.SpanOptions - ) => (self: Effect.Effect) => Effect.Effect>, + options?: Tracer.SpanOptions | undefined + ): (self: Effect.Effect) => Effect.Effect> ( self: Effect.Effect, name: string, - options?: Tracer.SpanOptions - ) => Effect.Effect> ->( - (args) => typeof args[0] !== "string", - (self, name, options) => - useSpan( - name, - options ?? {}, - (span) => withParentSpan(self, span) - ) -) + options?: Tracer.SpanOptions | undefined + ): Effect.Effect> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const options = internalTracer.addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) + if (dataFirst) { + const self = arguments[0] + return useSpan(name, options, (span) => withParentSpan(self, span)) + } + return (self: Effect.Effect) => useSpan(name, options, (span) => withParentSpan(self, span)) +} as any // ------------------------------------------------------------------------------------- // optionality diff --git a/packages/effect/src/internal/fiberRuntime.ts b/packages/effect/src/internal/fiberRuntime.ts index 15fd069f90..da3a020d5d 100644 --- a/packages/effect/src/internal/fiberRuntime.ts +++ b/packages/effect/src/internal/fiberRuntime.ts @@ -3622,8 +3622,9 @@ export const interruptWhenPossible = dual< export const makeSpanScoped = ( name: string, options?: Tracer.SpanOptions | undefined -): Effect.Effect => - core.uninterruptible( +): Effect.Effect => { + options = tracer.addSpanStackTrace(options) + return core.uninterruptible( core.withFiberRuntime((fiber) => { const scope = Context.unsafeGet(fiber.getFiberRef(core.currentContext), scopeTag) const span = internalEffect.unsafeMakeSpan(fiber, name, options) @@ -3641,27 +3642,37 @@ export const makeSpanScoped = ( ) }) ) +} /* @internal */ export const withTracerScoped = (value: Tracer.Tracer): Effect.Effect => fiberRefLocallyScopedWith(defaultServices.currentServices, Context.add(tracer.tracerTag, value)) /** @internal */ -export const withSpanScoped = dual< +export const withSpanScoped: { ( name: string, options?: Tracer.SpanOptions - ) => (self: Effect.Effect) => Effect.Effect | Scope.Scope>, + ): (self: Effect.Effect) => Effect.Effect> ( self: Effect.Effect, name: string, options?: Tracer.SpanOptions - ) => Effect.Effect | Scope.Scope> ->( - (args) => typeof args[0] !== "string", - (self, name, options) => + ): Effect.Effect> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const options = tracer.addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) + if (dataFirst) { + const self = arguments[0] + return core.flatMap( + makeSpanScoped(name, tracer.addSpanStackTrace(options)), + (span) => internalEffect.provideService(self, tracer.spanTag, span) + ) + } + return (self: Effect.Effect) => core.flatMap( - makeSpanScoped(name, options), + makeSpanScoped(name, tracer.addSpanStackTrace(options)), (span) => internalEffect.provideService(self, tracer.spanTag, span) ) -) +} as any diff --git a/packages/effect/src/internal/layer.ts b/packages/effect/src/internal/layer.ts index bb88a424c7..749ab4ccff 100644 --- a/packages/effect/src/internal/layer.ts +++ b/packages/effect/src/internal/layer.ts @@ -1113,7 +1113,7 @@ export const unwrapScoped = ( // ----------------------------------------------------------------------------- /** @internal */ -export const withSpan = dual< +export const withSpan: { ( name: string, options?: Tracer.SpanOptions & { @@ -1121,7 +1121,7 @@ export const withSpan = dual< | ((span: Tracer.Span, exit: Exit.Exit) => Effect.Effect) | undefined } - ) => (self: Layer.Layer) => Layer.Layer>, + ): (self: Layer.Layer) => Layer.Layer> ( self: Layer.Layer, name: string, @@ -1130,19 +1130,42 @@ export const withSpan = dual< | ((span: Tracer.Span, exit: Exit.Exit) => Effect.Effect) | undefined } - ) => Layer.Layer> ->((args) => isLayer(args[0]), (self, name, options) => - unwrapScoped( - core.map( - options?.onEnd - ? core.tap( - fiberRuntime.makeSpanScoped(name, options), - (span) => fiberRuntime.addFinalizer((exit) => options.onEnd!(span, exit)) - ) - : fiberRuntime.makeSpanScoped(name, options), - (span) => withParentSpan(self, span) + ): Layer.Layer> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const options = tracer.addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) as Tracer.SpanOptions & { + readonly onEnd?: + | ((span: Tracer.Span, exit: Exit.Exit) => Effect.Effect) + | undefined + } + if (dataFirst) { + const self = arguments[0] + return unwrapScoped( + core.map( + options?.onEnd + ? core.tap( + fiberRuntime.makeSpanScoped(name, options), + (span) => fiberRuntime.addFinalizer((exit) => options.onEnd!(span, exit)) + ) + : fiberRuntime.makeSpanScoped(name, options), + (span) => withParentSpan(self, span) + ) ) - )) + } + return (self: Layer.Layer) => + unwrapScoped( + core.map( + options?.onEnd + ? core.tap( + fiberRuntime.makeSpanScoped(name, options), + (span) => fiberRuntime.addFinalizer((exit) => options.onEnd!(span, exit)) + ) + : fiberRuntime.makeSpanScoped(name, options), + (span) => withParentSpan(self, span) + ) + ) +} as any /** @internal */ export const withParentSpan = dual< diff --git a/packages/effect/src/internal/layer/circular.ts b/packages/effect/src/internal/layer/circular.ts index be46420d4d..791b143fcd 100644 --- a/packages/effect/src/internal/layer/circular.ts +++ b/packages/effect/src/internal/layer/circular.ts @@ -196,8 +196,9 @@ export const span = ( | ((span: Tracer.Span, exit: Exit.Exit) => Effect.Effect) | undefined } -): Layer.Layer => - layer.scoped( +): Layer.Layer => { + options = tracer.addSpanStackTrace(options) as any + return layer.scoped( tracer.spanTag, options?.onEnd ? core.tap( @@ -206,6 +207,7 @@ export const span = ( ) : fiberRuntime.makeSpanScoped(name, options) ) +} /** @internal */ export const setTracer = (tracer: Tracer.Tracer): Layer.Layer => diff --git a/packages/effect/src/internal/stream.ts b/packages/effect/src/internal/stream.ts index d75a6536b0..4bb7b4cc87 100644 --- a/packages/effect/src/internal/stream.ts +++ b/packages/effect/src/internal/stream.ts @@ -49,6 +49,7 @@ import * as SinkEndReason from "./stream/sinkEndReason.js" import * as ZipAllState from "./stream/zipAllState.js" import * as ZipChunksState from "./stream/zipChunksState.js" import * as InternalTake from "./take.js" +import * as InternalTracer from "./tracer.js" /** @internal */ const StreamSymbolKey = "effect/Stream" @@ -6800,17 +6801,26 @@ export const whenEffect = dual< ) /** @internal */ -export const withSpan = dual< +export const withSpan: { ( name: string, options?: Tracer.SpanOptions - ) => (self: Stream.Stream) => Stream.Stream>, + ): (self: Stream.Stream) => Stream.Stream> ( self: Stream.Stream, name: string, options?: Tracer.SpanOptions - ) => Stream.Stream> ->(3, (self, name, options) => new StreamImpl(channel.withSpan(toChannel(self), name, options))) + ): Stream.Stream> +} = function() { + const dataFirst = typeof arguments[0] !== "string" + const name = dataFirst ? arguments[1] : arguments[0] + const options = InternalTracer.addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) + if (dataFirst) { + const self = arguments[0] + return new StreamImpl(channel.withSpan(toChannel(self), name, options)) + } + return (self: Stream.Stream) => new StreamImpl(channel.withSpan(toChannel(self), name, options)) +} as any /** @internal */ export const zip = dual< diff --git a/packages/effect/src/internal/tracer.ts b/packages/effect/src/internal/tracer.ts index 7fa29fb9b0..5d4d5e2f84 100644 --- a/packages/effect/src/internal/tracer.ts +++ b/packages/effect/src/internal/tracer.ts @@ -106,3 +106,24 @@ export const externalSpan = (options: { sampled: options.sampled ?? true, context: options.context ?? Context.empty() }) + +/** @internal */ +export const addSpanStackTrace = (options: Tracer.SpanOptions | undefined): Tracer.SpanOptions => { + if (options?.captureStackTrace === false) { + return options + } else if (options?.captureStackTrace !== undefined && typeof options.captureStackTrace !== "boolean") { + return options + } + const limit = Error.stackTraceLimit + Error.stackTraceLimit = 3 + const traceError = new Error() + Error.stackTraceLimit = limit + if (traceError.stack !== undefined) { + const stack = traceError.stack.trim().split("\n") + traceError.stack = stack.slice(3).join("\n").trim() + } + return { + ...options, + captureStackTrace: traceError + } +} diff --git a/packages/effect/test/Tracer.test.ts b/packages/effect/test/Tracer.test.ts index 80334fdc9f..4a567fa25d 100644 --- a/packages/effect/test/Tracer.test.ts +++ b/packages/effect/test/Tracer.test.ts @@ -18,9 +18,9 @@ describe("Tracer", () => { const span = yield* $( Effect.withSpan("A")(Effect.currentSpan) ) - assert.deepEqual(span.name, "A") assert.deepEqual(span.parent, Option.none()) + assert.include(span.attributes.get("code.stacktrace"), "Tracer.test.ts:19") })) it.effect("parent", () => @@ -140,6 +140,7 @@ describe("Tracer", () => { Effect.gen(function*(_) { const span = yield* _(Effect.scoped(Effect.makeSpanScoped("A"))) assert.deepEqual(span.status._tag, "Ended") + assert.include(span.attributes.get("code.stacktrace"), "Tracer.test.ts:141") })) it.effect("annotateCurrentSpan", () => @@ -181,6 +182,7 @@ describe("Tracer", () => { ), Option.some("parent") ) + assert.include(span.attributes.get("code.stacktrace"), "Tracer.test.ts:177") }).pipe( Effect.provide(Layer.unwrapScoped( Effect.map( @@ -193,13 +195,12 @@ describe("Tracer", () => { it.effect("Layer.span", () => Effect.gen(function*(_) { const span = yield* _(Effect.makeSpan("child")) - assert.deepEqual( - span.parent.pipe( - Option.filter((span): span is Span => span._tag === "Span"), - Option.map((span) => span.name) - ), - Option.some("parent") + const parent = span.parent.pipe( + Option.filter((span): span is Span => span._tag === "Span"), + Option.getOrThrow ) + assert.strictEqual(parent.name, "parent") + assert.include(parent.attributes.get("code.stacktrace"), "Tracer.test.ts:205") }).pipe( Effect.provide(Layer.span("parent")) )) @@ -242,6 +243,7 @@ describe("Tracer", () => { const layer = Layer.effectDiscard(Effect.gen(function*(_) { const span = yield* _(Effect.currentSpan) assert.strictEqual(span.name, "span") + assert.include(span.attributes.get("code.stacktrace"), "Tracer.test.ts:248") })).pipe( Layer.withSpan("span", { onEnd: (span, _exit) => diff --git a/packages/experimental/src/Machine.ts b/packages/experimental/src/Machine.ts index f660afd6e5..bf0af6f4f9 100644 --- a/packages/experimental/src/Machine.ts +++ b/packages/experimental/src/Machine.ts @@ -542,7 +542,8 @@ export const boot = < "effect.machine": runState.identifier, ...request }, - kind: "client" + kind: "client", + captureStackTrace: false }, (span) => Queue.offer(requests, [request, deferred, span, true]).pipe( Effect.zipRight(Deferred.await(deferred)), @@ -565,7 +566,8 @@ export const boot = < "effect.machine": runState.identifier, ...request }, - kind: "client" + kind: "client", + captureStackTrace: false }, (span) => Queue.offer(requests, [request, deferred, span, true])) } ) @@ -762,7 +764,8 @@ export const boot = < parent: span, attributes: { "effect.machine": runState.identifier - } + }, + captureStackTrace: false }) } else if (span !== undefined) { handler = Effect.provideService(handler, Tracer.ParentSpan, span) diff --git a/packages/platform/src/internal/http/client.ts b/packages/platform/src/internal/http/client.ts index 2be03beb81..a5e536dd97 100644 --- a/packages/platform/src/internal/http/client.ts +++ b/packages/platform/src/internal/http/client.ts @@ -144,7 +144,7 @@ export const makeDefault = ( addAbort, Effect.useSpan( `http.client ${request.method}`, - { kind: "client" }, + { kind: "client", captureStackTrace: false }, (span) => { span.attribute("http.request.method", request.method) span.attribute("server.address", url.origin) diff --git a/packages/platform/src/internal/http/middleware.ts b/packages/platform/src/internal/http/middleware.ts index 36df4374c6..c4cff7920e 100644 --- a/packages/platform/src/internal/http/middleware.ts +++ b/packages/platform/src/internal/http/middleware.ts @@ -125,7 +125,11 @@ export const tracer = make((httpApp) => const redactedHeaders = Headers.redact(request.headers, redactedHeaderNames) return Effect.useSpan( `http.server ${request.method}`, - { parent: Option.getOrUndefined(TraceContext.fromHeaders(request.headers)), kind: "server" }, + { + parent: Option.getOrUndefined(TraceContext.fromHeaders(request.headers)), + kind: "server", + captureStackTrace: false + }, (span) => { span.attribute("http.request.method", request.method) if (url !== undefined) { diff --git a/packages/rpc/src/Router.ts b/packages/rpc/src/Router.ts index 1c7fc92f09..b9b0a9f851 100644 --- a/packages/rpc/src/Router.ts +++ b/packages/rpc/src/Router.ts @@ -244,7 +244,8 @@ export const toHandler = >(router: R, options?: { spanId: req.spanId, sampled: req.sampled, context: Context.empty() - } + }, + captureStackTrace: false }) ) } @@ -276,7 +277,8 @@ export const toHandler = >(router: R, options?: { spanId: req.spanId, sampled: req.sampled, context: Context.empty() - } + }, + captureStackTrace: false }) ) }, { concurrency: "unbounded", discard: true }), @@ -332,7 +334,8 @@ export const toHandlerEffect = >(router: R, options?: spanId: req.spanId, sampled: req.sampled, context: Context.empty() - } + }, + captureStackTrace: false }) ) } @@ -352,7 +355,8 @@ export const toHandlerEffect = >(router: R, options?: spanId: req.spanId, sampled: req.sampled, context: Context.empty() - } + }, + captureStackTrace: false }) ) }, { concurrency: "unbounded" }) diff --git a/packages/rpc/src/Rpc.ts b/packages/rpc/src/Rpc.ts index f5d3376058..cdb3ece44a 100644 --- a/packages/rpc/src/Rpc.ts +++ b/packages/rpc/src/Rpc.ts @@ -329,7 +329,8 @@ export const request = ( ): Effect.Effect, never, Scope> => pipe( Effect.makeSpanScoped(`${options?.spanPrefix ?? "Rpc.request "}${request._tag}`, { - kind: "client" + kind: "client", + captureStackTrace: false }), Effect.zip(FiberRef.get(currentHeaders)), Effect.map(([span, headers]) => diff --git a/packages/sql/src/Resolver.ts b/packages/sql/src/Resolver.ts index c3faef4b9f..e96dedce91 100644 --- a/packages/sql/src/Resolver.ts +++ b/packages/sql/src/Resolver.ts @@ -118,7 +118,7 @@ const makeResolver = ( return function(input: I) { return Effect.useSpan( `sql.Resolver.execute ${tag}`, - { kind: "client" }, + { kind: "client", captureStackTrace: false }, (span) => Effect.withFiberRuntime((fiber) => { span.attribute("request.input", input) @@ -247,7 +247,8 @@ export const ordered = } @@ -326,7 +327,8 @@ export const grouped = } @@ -404,7 +406,8 @@ export const findById = } @@ -450,7 +453,8 @@ const void_ = ( Effect.withSpan(`sql.Resolver.batch ${tag}`, { kind: "client", links: spanLinks, - attributes: { "request.count": inputs.length } + attributes: { "request.count": inputs.length }, + captureStackTrace: false }) ) as Effect.Effect } diff --git a/packages/sql/src/internal/client.ts b/packages/sql/src/internal/client.ts index 2f34aedd95..660f69e18e 100644 --- a/packages/sql/src/internal/client.ts +++ b/packages/sql/src/internal/client.ts @@ -53,54 +53,58 @@ export function make({ effect: Effect.Effect ): Effect.Effect => Effect.uninterruptibleMask((restore) => - Effect.useSpan("sql.transaction", (span) => - Effect.withFiberRuntime((fiber) => { - for (const [key, value] of spanAttributes) { - span.attribute(key, value) - } - const context = fiber.getFiberRef(FiberRef.currentContext) - const clock = Context.get(fiber.getFiberRef(DefaultServices.currentServices), Clock.Clock) - const connOption = Context.getOption(context, TransactionConnection) - const conn = connOption._tag === "Some" - ? Effect.succeed([undefined, connOption.value[0]] as const) - : getTxConn - const id = connOption._tag === "Some" ? connOption.value[1] + 1 : 0 - return Effect.flatMap( - conn, - ( - [scope, conn] - ) => - conn.executeRaw(id === 0 ? beginTransaction : savepoint(`effect_sql_${id}`)).pipe( - Effect.zipRight(Effect.locally( - restore(effect), - FiberRef.currentContext, - Context.add(context, TransactionConnection, [conn, id]).pipe( - Context.add(Tracer.ParentSpan, span) - ) - )), - Effect.exit, - Effect.flatMap((exit) => { - let effect: Effect.Effect - if (Exit.isSuccess(exit)) { - if (id === 0) { - span.event("db.transaction.commit", clock.unsafeCurrentTimeNanos()) - effect = Effect.orDie(conn.executeRaw(commit)) + Effect.useSpan( + "sql.transaction", + { captureStackTrace: false }, + (span) => + Effect.withFiberRuntime((fiber) => { + for (const [key, value] of spanAttributes) { + span.attribute(key, value) + } + const context = fiber.getFiberRef(FiberRef.currentContext) + const clock = Context.get(fiber.getFiberRef(DefaultServices.currentServices), Clock.Clock) + const connOption = Context.getOption(context, TransactionConnection) + const conn = connOption._tag === "Some" + ? Effect.succeed([undefined, connOption.value[0]] as const) + : getTxConn + const id = connOption._tag === "Some" ? connOption.value[1] + 1 : 0 + return Effect.flatMap( + conn, + ( + [scope, conn] + ) => + conn.executeRaw(id === 0 ? beginTransaction : savepoint(`effect_sql_${id}`)).pipe( + Effect.zipRight(Effect.locally( + restore(effect), + FiberRef.currentContext, + Context.add(context, TransactionConnection, [conn, id]).pipe( + Context.add(Tracer.ParentSpan, span) + ) + )), + Effect.exit, + Effect.flatMap((exit) => { + let effect: Effect.Effect + if (Exit.isSuccess(exit)) { + if (id === 0) { + span.event("db.transaction.commit", clock.unsafeCurrentTimeNanos()) + effect = Effect.orDie(conn.executeRaw(commit)) + } else { + span.event("db.transaction.savepoint", clock.unsafeCurrentTimeNanos()) + effect = Effect.void + } } else { - span.event("db.transaction.savepoint", clock.unsafeCurrentTimeNanos()) - effect = Effect.void + span.event("db.transaction.rollback", clock.unsafeCurrentTimeNanos()) + effect = Effect.orDie( + id > 0 ? conn.executeRaw(rollbackSavepoint(`effect_sql_${id}`)) : conn.executeRaw(rollback) + ) } - } else { - span.event("db.transaction.rollback", clock.unsafeCurrentTimeNanos()) - effect = Effect.orDie( - id > 0 ? conn.executeRaw(rollbackSavepoint(`effect_sql_${id}`)) : conn.executeRaw(rollback) - ) - } - const withScope = scope !== undefined ? Effect.ensuring(effect, Scope.close(scope, exit)) : effect - return Effect.zipRight(withScope, exit) - }) - ) - ) - })) + const withScope = scope !== undefined ? Effect.ensuring(effect, Scope.close(scope, exit)) : effect + return Effect.zipRight(withScope, exit) + }) + ) + ) + }) + ) ) const client: Client.Client = Object.assign( diff --git a/packages/sql/src/internal/statement.ts b/packages/sql/src/internal/statement.ts index 0d743d26e8..37daafeb94 100644 --- a/packages/sql/src/internal/statement.ts +++ b/packages/sql/src/internal/statement.ts @@ -88,7 +88,7 @@ export class StatementPrimitive extends Effectable.Class, Er ): Effect.Effect { return Effect.useSpan( "sql.execute", - { kind: "client" }, + { kind: "client", captureStackTrace: false }, (span) => Effect.withFiberRuntime((fiber) => { const transform = fiber.getFiberRef(currentTransformer) @@ -115,7 +115,7 @@ export class StatementPrimitive extends Effectable.Class, Er get stream(): Stream.Stream { return Stream.unwrapScoped(Effect.flatMap( - Effect.makeSpanScoped("sql.execute", { kind: "client" }), + Effect.makeSpanScoped("sql.execute", { kind: "client", captureStackTrace: false }), (span) => Effect.withFiberRuntime, Error.SqlError, Scope>((fiber) => { const transform = fiber.getFiberRef(currentTransformer)