55import datetime
66import logging
77import threading
8- from typing import TYPE_CHECKING , Any
8+ from typing import Any
99
1010from aws_durable_execution_sdk_python .lambda_service import OperationType
1111from aws_durable_execution_sdk_python .plugin import (
3939 DeterministicIdGenerator ,
4040 operation_id_to_span_id ,
4141)
42- from aws_durable_execution_sdk_python_otel .logger import OtelEnrichedLogger
43-
44-
45- if TYPE_CHECKING :
46- from aws_durable_execution_sdk_python .types import LoggerInterface
42+ from aws_durable_execution_sdk_python_otel .log_filter import install_log_filter
4743
4844
4945logger = logging .getLogger (__name__ )
@@ -93,6 +89,10 @@ def __init__(
9389 The provided tracer provider is configured with this plugin's
9490 deterministic ID generator and sampling strategy so spans for a durable
9591 execution share stable trace and logical operation identifiers.
92+
93+ When enrich_logger is enabled (default), the plugin installs a logging
94+ filter on the root logger at invocation start that stamps the active
95+ OTel trace context onto every emitted log record.
9696 """
9797 self ._enrich_logger = enrich_logger
9898 self ._context_extractor : ContextExtractor = (
@@ -117,23 +117,12 @@ def __init__(
117117 self ._operation_spans : dict [str | None , Span ] = {}
118118 self ._operation_spans_lock = threading .RLock ()
119119
120- def wrap_logger (self , logger : LoggerInterface ) -> LoggerInterface | None :
121- """Wrap the execution logger to inject OTel trace context.
122-
123- When enrich_logger is enabled (default), returns an OtelEnrichedLogger
124- that adds trace_id, span_id, and trace_sampled to every log message.
125- Idempotent: returns None if the logger is already an OtelEnrichedLogger.
126-
127- Args:
128- logger: The current logger interface from the execution context.
129-
130- Returns:
131- An OtelEnrichedLogger wrapping the input, or None if disabled or
132- already wrapped.
133- """
134- if not self ._enrich_logger or isinstance (logger , OtelEnrichedLogger ):
135- return None
136- return OtelEnrichedLogger (inner = logger , plugin = self )
120+ if self ._enrich_logger :
121+ # Install the root-logger filter so every log record is stamped with
122+ # the active span context. The Lambda runtime attaches its root
123+ # handler before the handler module is imported (and thus before the
124+ # plugin is constructed), so the handlers are available here.
125+ install_log_filter (self )
137126
138127 def _set_span (self , operation_id : str | None , span : Span ) -> None :
139128 """Register the active span for an operation ID."""
@@ -225,8 +214,8 @@ def _start_span(
225214 Returns:
226215 The started OpenTelemetry span.
227216 """
228- logger .info (
229- "starting a span: operation_id=%s, name=%s, parent_span=%s" ,
217+ logger .debug (
218+ "Starting OTel span: operation_id=%s, name=%s, parent_span=%s" ,
230219 operation_id ,
231220 name ,
232221 parent_span ,
@@ -269,7 +258,7 @@ def _start_span(
269258 )
270259 self ._operation_spans [operation_id ] = span
271260
272- logger .info ( "started a span: %s" , span )
261+ logger .debug ( "Started OTel span: %s" , span )
273262 return span
274263
275264 def _end_span (
@@ -283,21 +272,21 @@ def _end_span(
283272 end_timestamp: Optional durable end timestamp to use as the span end
284273 time. When omitted, OpenTelemetry uses the current time.
285274 """
286- logger .info ( "ending a span for operation: %s" , operation_id )
275+ logger .debug ( "Ending OTel span: operation_id= %s" , operation_id )
287276 with self ._operation_spans_lock :
288277 span = self ._operation_spans .pop (operation_id , None )
289278 if span :
290279 # the span is not going to be populated if it has the same end_time and start_time
291280 end_time = _to_otel_timestamp (end_timestamp ) if end_timestamp else None
292281 span .end (end_time = end_time )
293- logger .info ( "ended otel span: %s" , span )
282+ logger .debug ( "Ended OTel span: %s" , span )
294283
295284 # ------------------------------------------------------------------
296285 # Plugin lifecycle callbacks
297286 # ------------------------------------------------------------------
298287 def on_invocation_start (self , info : InvocationStartInfo ) -> None :
299288 """Called at the start of each invocation. Creates the invocation span."""
300- logger .info ( "Invocation started: %s" , info )
289+ logger .debug ( "Durable invocation started: %s" , info )
301290 self ._execution_arn = info .execution_arn or ""
302291 self ._extracted_context = self ._context_extractor (info )
303292 self ._id_generator .set_trace_id (self ._execution_arn , info .start_time )
@@ -310,7 +299,7 @@ def on_invocation_start(self, info: InvocationStartInfo) -> None:
310299
311300 def on_invocation_end (self , info : InvocationEndInfo ) -> None :
312301 """Called at the end of each invocation. Ends the invocation span and flushes."""
313- logger .info ( f"Invocation ended: { info } " )
302+ logger .debug ( "Durable invocation ended: %s" , info )
314303 end_time = info .end_time
315304 # end all pending spans
316305 with self ._operation_spans_lock :
@@ -334,7 +323,7 @@ def on_invocation_end(self, info: InvocationEndInfo) -> None:
334323
335324 def on_operation_start (self , info : OperationStartInfo ) -> None :
336325 """Called when an operation begins. Creates a span for the operation."""
337- logger .info ( f"Operation started: { info } " )
326+ logger .debug ( "Durable operation started: %s" , info )
338327 if info .operation_type in [OperationType .CONTEXT , OperationType .STEP ]:
339328 # Context and Step operations are tracked using on_user_function_start
340329 return
@@ -358,7 +347,7 @@ def on_operation_end(self, info: OperationEndInfo) -> None:
358347 continuation span is created and linked to the deterministic logical
359348 operation span before being ended.
360349 """
361- logger .info ( f"Operation ended: { info } " )
350+ logger .debug ( "Durable operation ended: %s" , info )
362351 if info .operation_type in [OperationType .CONTEXT , OperationType .STEP ]:
363352 # Context and Step operations are tracked using on_user_function_end
364353 return
@@ -401,7 +390,7 @@ def on_user_function_start(self, info: UserFunctionStartInfo) -> None:
401390 Args:
402391 info: Information about the operation attempt.
403392 """
404- logger .info ( "User function started: %s" , info )
393+ logger .debug ( "Durable user function started: %s" , info )
405394 # Context and Step operations are tracked using on_user_function_start
406395 if info .operation_type not in [OperationType .CONTEXT , OperationType .STEP ]:
407396 raise RuntimeError (
@@ -429,7 +418,7 @@ def on_user_function_end(self, info: UserFunctionEndInfo) -> None:
429418 Args:
430419 info: Information about the operation attempt.
431420 """
432- logger .info ( "User function ended: %s" , info )
421+ logger .debug ( "Durable user function ended: %s" , info )
433422 if info .operation_type not in [OperationType .CONTEXT , OperationType .STEP ]:
434423 raise RuntimeError (
435424 "on_user_function_end should only be called for CONTEXT and STEP operations"
0 commit comments