@@ -199,22 +199,44 @@ function countAttributes(attributes?: Attributes): number {
199199 return attributes ? ObjectKeys ( attributes ) . length : 0 ;
200200}
201201
202+ const currentSnapshot = getAsyncContext ;
203+ const restoreSnapshot = setAsyncContext ;
204+
202205interface AsyncContextSnapshot {
203206 __brand : "AsyncContextSnapshot" ;
204207}
205208
209+ // A `unique symbol` type requires a direct `Symbol.for()` call, which is what
210+ // makes `SpanSnapshot` below narrowable. This runs during bootstrap, before any
211+ // user code, so reaching for the global here is safe.
212+ // deno-lint-ignore deno-internal/prefer-primordials
213+ const DID_NOT_ENTER : unique symbol = Symbol . for (
214+ "Deno.telemetry.didNotEnterSpan" ,
215+ ) ;
216+
217+ /**
218+ * The result of {@linkcode enterSpan}: either the async context snapshot taken
219+ * before entering, or `DID_NOT_ENTER` if no context was entered. `undefined` is
220+ * a valid snapshot (it means "no ambient context"), so the two cases cannot be
221+ * told apart by truthiness, so always hand this to {@linkcode exitSpan}.
222+ */
223+ type SpanSnapshot = AsyncContextSnapshot | typeof DID_NOT_ENTER ;
224+
206225function enterSpan (
207226 span : Span ,
208227 context ?: Context ,
209- ) : AsyncContextSnapshot | undefined {
210- if ( ! span . isRecording ( ) ) return undefined ;
228+ ) : SpanSnapshot {
229+ if ( ! span . isRecording ( ) ) return DID_NOT_ENTER ;
230+ const snapshot = currentSnapshot ( ) ;
211231 context = ( context ?? CURRENT . get ( ) ?? ROOT_CONTEXT )
212232 . setValue ( SPAN_KEY , span ) ;
213- return CURRENT . enter ( context ) ;
233+ CURRENT . enter ( context ) ;
234+ return snapshot ;
214235}
215236
216- const currentSnapshot = getAsyncContext ;
217- const restoreSnapshot = setAsyncContext ;
237+ function exitSpan ( snapshot : SpanSnapshot ) {
238+ if ( snapshot !== DID_NOT_ENTER ) restoreSnapshot ( snapshot ) ;
239+ }
218240
219241function isDate ( value : unknown ) : value is Date {
220242 return ObjectPrototypeIsPrototypeOf ( DatePrototype , value ) ;
@@ -1927,7 +1949,9 @@ function bootstrap(
19271949internals . __telemetry = {
19281950 builtinTracer,
19291951 ContextManager,
1952+ DID_NOT_ENTER ,
19301953 enterSpan,
1954+ exitSpan,
19311955 get PROPAGATORS ( ) {
19321956 return PROPAGATORS ;
19331957 } ,
@@ -1969,6 +1993,7 @@ function wrappedBootstrap(config: Parameters<typeof bootstrap>[0]) {
19691993return {
19701994 otelState,
19711995 enterSpan,
1996+ exitSpan,
19721997 currentSnapshot,
19731998 restoreSnapshot,
19741999 SPAN_KEY ,
0 commit comments