3636import temporalio .nexus .system .workflow_service .models
3737import temporalio .worker
3838import temporalio .workflow
39+ from temporalio .contrib .opentelemetry ._context import attached_context
3940from temporalio .exceptions import ApplicationError , ApplicationErrorCategory
4041
4142# OpenTelemetry dynamically, lazily chooses its context implementation at
@@ -183,8 +184,7 @@ def _start_as_current_span(
183184 kind : opentelemetry .trace .SpanKind ,
184185 context : Context | None = None ,
185186 ) -> Iterator [None ]:
186- token = opentelemetry .context .attach (context ) if context else None
187- try :
187+ with attached_context (context ):
188188 with self .tracer .start_as_current_span (
189189 name ,
190190 attributes = attributes ,
@@ -219,9 +219,6 @@ def _start_as_current_span(
219219 )
220220 )
221221 raise
222- finally :
223- if token and context is opentelemetry .context .get_current ():
224- opentelemetry .context .detach (token )
225222
226223 def _completed_workflow_span (
227224 self , params : _CompletedWorkflowSpanParams
@@ -556,8 +553,7 @@ async def handle_query(self, input: temporalio.worker.HandleQueryInput) -> Any:
556553 # We need to put this interceptor on the context too
557554 context = self ._set_on_context (context )
558555 # Run under context with new span
559- token = opentelemetry .context .attach (context )
560- try :
556+ with attached_context (context ):
561557 # This won't be created if there was no context header
562558 self ._completed_span (
563559 f"HandleQuery:{ input .query } " ,
@@ -567,13 +563,6 @@ async def handle_query(self, input: temporalio.worker.HandleQueryInput) -> Any:
567563 kind = opentelemetry .trace .SpanKind .SERVER ,
568564 )
569565 return await super ().handle_query (input )
570- finally :
571- # In some exceptional cases this finally is executed with a
572- # different contextvars.Context than the one the token was created
573- # on. As such we do a best effort detach to avoid using a mismatched
574- # token.
575- if context is opentelemetry .context .get_current ():
576- opentelemetry .context .detach (token )
577566
578567 def handle_update_validator (
579568 self , input : temporalio .worker .HandleUpdateInput
@@ -644,31 +633,23 @@ def _top_level_workflow_context(
644633 success = False
645634 exception : Exception | None = None
646635 # Run under this context
647- token = opentelemetry .context .attach (context )
648-
649- try :
650- yield None
651- success = True
652- except temporalio .exceptions .FailureError as err :
653- # We only record the failure errors since those are the only ones
654- # that lead to workflow completions
655- exception = err
656- raise
657- finally :
658- # Create a completed span before detaching context
659- if exception or (success and success_is_complete ):
660- self ._completed_span (
661- f"CompleteWorkflow:{ temporalio .workflow .info ().workflow_type } " ,
662- exception = exception ,
663- kind = opentelemetry .trace .SpanKind .INTERNAL ,
664- )
665-
666- # In some exceptional cases this finally is executed with a
667- # different contextvars.Context than the one the token was created
668- # on. As such we do a best effort detach to avoid using a mismatched
669- # token.
670- if context is opentelemetry .context .get_current ():
671- opentelemetry .context .detach (token )
636+ with attached_context (context ):
637+ try :
638+ yield None
639+ success = True
640+ except temporalio .exceptions .FailureError as err :
641+ # We only record the failure errors since those are the only ones
642+ # that lead to workflow completions
643+ exception = err
644+ raise
645+ finally :
646+ # Create a completed span before detaching context
647+ if exception or (success and success_is_complete ):
648+ self ._completed_span (
649+ f"CompleteWorkflow:{ temporalio .workflow .info ().workflow_type } " ,
650+ exception = exception ,
651+ kind = opentelemetry .trace .SpanKind .INTERNAL ,
652+ )
672653
673654 def _context_to_headers (
674655 self , headers : Mapping [str , temporalio .api .common .v1 .Payload ]
0 commit comments