warpzone-sdk 15.0.0.dev8__py3-none-any.whl → 15.0.0.dev10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,14 +7,28 @@ import azure.functions as func
7
7
  from warpzone.function.types import SingleArgumentCallable
8
8
  from warpzone.monitor import traces
9
9
 
10
+ # Configure tracing at import time (once per worker process)
11
+ traces.configure_tracing()
12
+
10
13
  SUBJECT_IDENTIFIER = "<Subject>"
11
14
 
12
15
 
13
16
  @contextmanager
14
17
  def run_in_trace_context(context: func.Context):
18
+ """Set up trace context and ensure log correlation for this function invocation.
19
+
20
+ This sets:
21
+ 1. OpenTelemetry trace context (for distributed tracing via Service Bus)
22
+ 2. Azure Functions invocation_id in thread-local storage (for log correlation)
23
+ """
24
+ # Ensure Azure Functions' log correlation uses this invocation's ID
25
+ # This prevents log leakage when multiple functions run concurrently
26
+ context.thread_local_storage.invocation_id = context.invocation_id
27
+
15
28
  trace_context = context.trace_context
16
- span_name = context.function_name or "azure_function"
17
- with traces.set_trace_context(trace_context.trace_parent, span_name=span_name):
29
+ with traces.set_trace_context(
30
+ trace_context.trace_parent, trace_context.trace_state
31
+ ):
18
32
  yield
19
33
 
20
34
 
warpzone/monitor/logs.py CHANGED
@@ -19,9 +19,8 @@ logging.getLogger("azure.monitor.opentelemetry.exporter").setLevel(logging.WARNI
19
19
  def get_logger(name: str):
20
20
  """Get a logger instance.
21
21
 
22
- The logger will automatically use the current OpenTelemetry trace context
23
- for correlation in Application Insights. The trace context is set by the
24
- monitor decorator's run_in_trace_context context manager.
22
+ Logs are correlated with the correct function invocation via Azure Functions'
23
+ thread-local storage invocation_id, which is set by the monitor decorator.
25
24
 
26
25
  Args:
27
26
  name: Logger name, typically __name__
@@ -1,87 +1,117 @@
1
+ import logging
2
+ import os
1
3
  from contextlib import contextmanager
2
4
 
3
5
  from azure.core.settings import settings
6
+ from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter
4
7
  from opentelemetry import context, trace
5
- from opentelemetry.sdk.trace import ReadableSpan, SpanProcessor
8
+ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
9
+ from opentelemetry.sdk.trace import ReadableSpan, SpanProcessor, TracerProvider
10
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
11
+ from opentelemetry.sdk.trace.sampling import ALWAYS_ON
6
12
  from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
7
13
 
14
+ # Enable OpenTelemetry tracing for Azure SDK (including Service Bus)
15
+ # This must be set before any Azure SDK clients are created
8
16
  settings.tracing_implementation = "opentelemetry"
9
17
 
18
+ logger = logging.getLogger(__name__)
19
+
20
+ _TRACING_IS_CONFIGURED = False
21
+
22
+ # Span name prefixes to allow from Azure SDK (Service Bus for function chaining)
23
+ _ALLOWED_AZURE_SPAN_NAMES = frozenset({"ServiceBus.message"})
24
+
10
25
 
11
26
  class AzureSDKTraceFilter(SpanProcessor):
12
- """Custom SpanProcessor to filter out Azure SDK traces except Service Bus.
27
+ """SpanProcessor that filters out noisy Azure SDK traces.
28
+
29
+ Allows:
30
+ - All non-Azure SDK spans (custom/user traces)
31
+ - Service Bus spans (for tracing function chains)
13
32
 
14
- It drops spans from Azure SDK libraries except Service Bus messages,
15
- preventing them from being exported to Azure Monitor.
33
+ Drops:
34
+ - Other Azure SDK spans (HTTP client, blob storage, etc.)
16
35
  """
17
36
 
18
37
  def __init__(self, wrapped_processor: SpanProcessor):
19
- """Initialize with the actual processor to wrap.
20
-
21
- Args:
22
- wrapped_processor: The underlying processor (e.g., BatchSpanProcessor)
23
- """
24
38
  self.wrapped_processor = wrapped_processor
25
39
 
26
40
  def on_start(
27
41
  self, span: ReadableSpan, parent_context: context.Context = None
28
42
  ) -> None:
29
- """Called when a span is started."""
30
43
  self.wrapped_processor.on_start(span, parent_context)
31
44
 
32
45
  def on_end(self, span: ReadableSpan) -> None:
33
- """Called when a span is ended. Filter based on span attributes."""
34
- # Safely get span name, handling None or mock objects
35
- span_name = getattr(span, "name", None)
36
- if span_name is None or not isinstance(span_name, str):
37
- # Pass through spans without valid names (e.g., in test mocks)
38
- self.wrapped_processor.on_end(span)
39
- return
40
-
41
- # Check if service bus span - always allow
42
- if "servicebus.message" in span_name.lower():
43
- self.wrapped_processor.on_end(span)
44
- return
45
-
46
- # Check if this is an Azure SDK span we want to suppress
47
- instrumentation_scope = span.instrumentation_scope
48
- if instrumentation_scope and instrumentation_scope.name:
49
- # Suppress spans from Azure SDK libraries
50
- if instrumentation_scope.name.startswith("azure."):
51
- return # Drop this span
52
-
53
- # Pass through all other spans
46
+ scope = span.instrumentation_scope
47
+ if scope and scope.name:
48
+ # Check if it's an Azure SDK span (uses azure.core.tracing wrapper)
49
+ if scope.name.startswith("azure."):
50
+ # Allow only specific Service Bus span names
51
+ span_name = getattr(span, "name", "") or ""
52
+ if span_name in _ALLOWED_AZURE_SPAN_NAMES:
53
+ self.wrapped_processor.on_end(span)
54
+ # Drop other Azure SDK spans
55
+ return
56
+
57
+ # Pass through: non-Azure SDK spans (custom traces)
54
58
  self.wrapped_processor.on_end(span)
55
59
 
56
60
  def shutdown(self) -> None:
57
- """Shutdown the wrapped processor."""
58
61
  self.wrapped_processor.shutdown()
59
62
 
60
63
  def force_flush(self, timeout_millis: int = 30000) -> bool:
61
- """Force flush the wrapped processor."""
62
64
  return self.wrapped_processor.force_flush(timeout_millis)
63
65
 
64
66
 
67
+ def configure_tracing():
68
+ """Configure OpenTelemetry tracing with Azure Monitor exporter.
69
+
70
+ This sets up tracing for Service Bus and custom spans.
71
+ Other Azure SDK traces (HTTP, blob, etc.) are filtered out.
72
+
73
+ Should be called once before any tracing is performed.
74
+ """
75
+ global _TRACING_IS_CONFIGURED
76
+ if _TRACING_IS_CONFIGURED:
77
+ return
78
+
79
+ # Set up tracer provider with Azure Function resource info
80
+ resource = Resource.create({SERVICE_NAME: os.getenv("WEBSITE_SITE_NAME")})
81
+ provider = TracerProvider(
82
+ sampler=ALWAYS_ON,
83
+ resource=resource,
84
+ )
85
+ trace.set_tracer_provider(provider)
86
+
87
+ # Set up Azure Monitor trace exporter with filter
88
+ try:
89
+ trace_exporter = AzureMonitorTraceExporter()
90
+ span_processor = BatchSpanProcessor(trace_exporter)
91
+ filtered_processor = AzureSDKTraceFilter(span_processor)
92
+ provider.add_span_processor(filtered_processor)
93
+ except ValueError:
94
+ logger.warning(
95
+ "Cannot set up tracing to App Insights, as no instrumentation key is set."
96
+ )
97
+
98
+ _TRACING_IS_CONFIGURED = True
99
+
100
+
65
101
  @contextmanager
66
- def set_trace_context(trace_parent: str, span_name: str = "function_execution"):
102
+ def set_trace_context(trace_parent: str, trace_state: str = ""):
67
103
  """Context manager for setting the trace context.
68
104
 
69
- Attaches the propagated trace context and creates a child span for tracing.
70
- Note: Log correlation is handled by Azure Functions' native mechanism,
71
- not OpenTelemetry, so this primarily affects span/dependency tracking.
72
-
73
105
  Args:
74
- trace_parent (str): Trace parent ID from the incoming request
75
- span_name (str): Name for the span. Defaults to "function_execution".
106
+ trace_parent (str): Trace parent ID
107
+ trace_state (str, optional): Trace state. Defaults to "".
76
108
  """
77
- carrier = {"traceparent": trace_parent}
109
+ carrier = {"traceparent": trace_parent, "tracestate": trace_state}
78
110
  ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
79
111
 
80
112
  token = context.attach(ctx)
81
113
  try:
82
- tracer = trace.get_tracer(__name__)
83
- with tracer.start_as_current_span(span_name):
84
- yield
114
+ yield
85
115
  finally:
86
116
  context.detach(token)
87
117
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: warpzone-sdk
3
- Version: 15.0.0.dev8
3
+ Version: 15.0.0.dev10
4
4
  Summary: The main objective of this package is to centralize logic used to interact with Azure Functions, Azure Service Bus and Azure Table Storage
5
5
  Author: Team Enigma
6
6
  Author-email: enigma@energinet.dk
@@ -17,7 +17,7 @@ warpzone/function/__init__.py,sha256=rJOZBpWsUgjMc7YtXMJ1rLGm45KB1AhDJ_Y2ISiSISc
17
17
  warpzone/function/checks.py,sha256=B9YqThymf16ac_fVAYKilv20ru5v9nwXgHlbxYIaG98,1018
18
18
  warpzone/function/functionize.py,sha256=bSV0QvwKbD9Vo3a_8cc1rgV2rzTdMMvidinyXItBfvs,2128
19
19
  warpzone/function/integrations.py,sha256=sDt2BTx6a4mVc-33wTITP9XQVPustwj7rkX4urTyOqo,4018
20
- warpzone/function/monitor.py,sha256=w-fLxLjzbG-PIpI-mjPykdJSBmIvexpsHT3eIstkVnE,1528
20
+ warpzone/function/monitor.py,sha256=7GySRig9mI-BtGHm_l5anhmICNjOybgR3MrVhht1ZY8,2065
21
21
  warpzone/function/process.py,sha256=nbUVywM8ChfUwuaqFisgaD98aNRgeZkK4g5sbtuBdRs,2339
22
22
  warpzone/function/processors/__init__.py,sha256=DhIdSWLBcIeSO8IJdxPqGIhgwwnkDN6_Xqwy93BCLeA,46
23
23
  warpzone/function/processors/dependencies.py,sha256=m17BwdKyQEvzCPxpQZpAW5l1uYRIHWmweDz3XJskmpA,1259
@@ -28,8 +28,8 @@ warpzone/function/types.py,sha256=5m2hRrnLC3eqIlAH5-MM9_wKjMZ6lYawZtCOVStyFuY,72
28
28
  warpzone/healthchecks/__init__.py,sha256=9gc_Mt2szs8sDSwy0V4l3JZ6d9hX41xTpZCkDP2qsY4,2108
29
29
  warpzone/healthchecks/model.py,sha256=mM7DnrirLbUpBPPfi82MUPP654D0eOR2_F65TmzsPD0,1187
30
30
  warpzone/monitor/__init__.py,sha256=ggI5fIUu-szgC44ICzuOmpYrIoVOKPbsMT3zza9ssD4,87
31
- warpzone/monitor/logs.py,sha256=fabjaB5SfHynvvfp2Js3IG-owqU5jZ3lTnnmTTjD6JM,1320
32
- warpzone/monitor/traces.py,sha256=wYjV8LYS4ntNbMwbvLvbmZ4x4PDrY-Iv04CAKrqWPqI,3865
31
+ warpzone/monitor/logs.py,sha256=4TSf5zjkLL1L6SehVxya7mjF54E3hhlnmFSB5Sk1DEc,1264
32
+ warpzone/monitor/traces.py,sha256=GNDo8-EwiV1VgS8b60JVKz0MILC1K9buGS1AguZHcPY,4613
33
33
  warpzone/servicebus/data/__init__.py,sha256=lnc0uiaGLF0qMi_rWhCpRSFvaj0CJEiMCAl6Yqn1ZiA,21
34
34
  warpzone/servicebus/data/client.py,sha256=zECS3JwedhYnDk8PntYgIYpBF_uu9YN38KzpPFK7CKs,6511
35
35
  warpzone/servicebus/events/__init__.py,sha256=lnc0uiaGLF0qMi_rWhCpRSFvaj0CJEiMCAl6Yqn1ZiA,21
@@ -52,6 +52,6 @@ warpzone/tools/copy.py,sha256=5fddotMZkXZO8avzUbGOhvs0cp8mce95pNpy0oPVjnQ,2596
52
52
  warpzone/transform/__init__.py,sha256=ruGa7tl-v4ndlWpULE1jSGU_a4_iRc3V6eyNr5xKP9E,27
53
53
  warpzone/transform/data.py,sha256=Abb8PcrgMbbNCJkkIUdtrTHdlY0OfXid387qw1nDpFY,2362
54
54
  warpzone/transform/schema.py,sha256=nbSQtDMvXkyqGKuwhuFCF0WsEDsaNyoPYpMKvbsKlv8,2423
55
- warpzone_sdk-15.0.0.dev8.dist-info/METADATA,sha256=bx1ixSa38k391uygOjfZcfAAxCcwNaEebLdUfvD0ojo,7398
56
- warpzone_sdk-15.0.0.dev8.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
57
- warpzone_sdk-15.0.0.dev8.dist-info/RECORD,,
55
+ warpzone_sdk-15.0.0.dev10.dist-info/METADATA,sha256=ZkvvLVc9pluIMkBrCfkiEVw91YXE2_hFsrq0lF4C0YU,7399
56
+ warpzone_sdk-15.0.0.dev10.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
57
+ warpzone_sdk-15.0.0.dev10.dist-info/RECORD,,