From bc87dd803dddc97ce5dd76f3c82c91c998846c91 Mon Sep 17 00:00:00 2001 From: pmady Date: Fri, 1 May 2026 12:24:47 -0500 Subject: [PATCH 1/3] otel: call super().shutdown() in exporter classes Both GRPCSpanExporter and HTTPSpanExporter close the session but skip the parent shutdown, which flushes buffered spans. This drops pending trace data on process exit. Fixes part of #164 Signed-off-by: pmady --- python/fi_instrumentation/otel.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/fi_instrumentation/otel.py b/python/fi_instrumentation/otel.py index 60abc744..7dbcf469 100644 --- a/python/fi_instrumentation/otel.py +++ b/python/fi_instrumentation/otel.py @@ -568,6 +568,8 @@ def shutdown(self) -> None: self._session.close() except Exception as e: print(f"Error during shutdown: {e}") + finally: + super().shutdown() class HTTPSpanExporter(_HTTPSpanExporter): @@ -609,6 +611,8 @@ def shutdown(self) -> None: self._session.close() except Exception as e: print(f"Error during shutdown: {e}") + finally: + super().shutdown() def _exporter_transport(exporter: SpanExporter) -> str: From c2133e8d3b8e2d73532244b8c3cddfe45a26b736 Mon Sep 17 00:00:00 2001 From: pmady Date: Fri, 1 May 2026 12:33:40 -0500 Subject: [PATCH 2/3] otel: guard shutdown snapshot with a lock The snapshot (line 445) and clear (line 458) in SimpleSpanProcessor.shutdown() can race with on_start writes from other threads. Lock only around those two operations per review feedback -- the hot path stays lock-free since dict insert/pop are atomic under GIL. Fixes part of #164 Signed-off-by: pmady --- python/fi_instrumentation/otel.py | 36 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/python/fi_instrumentation/otel.py b/python/fi_instrumentation/otel.py index 7dbcf469..0c8e82d0 100644 --- a/python/fi_instrumentation/otel.py +++ b/python/fi_instrumentation/otel.py @@ -5,6 +5,7 @@ import os import signal import sys +import threading import uuid from enum import Enum from typing import Any, Dict, List, Optional, Tuple, Type, Union @@ -401,6 +402,7 @@ def __init__( transport: Transport = Transport.HTTP, ): self._active_spans = {} + self._shutdown_lock = threading.Lock() if span_exporter is None: if transport == Transport.HTTP: @@ -437,27 +439,21 @@ def on_end(self, span: Any) -> None: def shutdown(self) -> None: """Override shutdown to ensure all active spans get exported""" try: - # Process any spans that haven't been ended - if self._active_spans: - print(f"Ending {len(self._active_spans)} active spans during shutdown") - - # Create a copy to avoid modification during iteration - active_spans = list(self._active_spans.values()) - - # End all active spans and mark them as leaked - for span in active_spans: - if hasattr(span, "is_recording") and span.is_recording(): - try: - # Mark the span as leaked - span.set_attribute("gen_ai.span.leaked", True) - span.end() - except Exception as e: - pass - - # Clear the tracking dictionary - self._active_spans.clear() + with self._shutdown_lock: + if self._active_spans: + print(f"Ending {len(self._active_spans)} active spans during shutdown") + active_spans = list(self._active_spans.values()) + + for span in active_spans: + if hasattr(span, "is_recording") and span.is_recording(): + try: + span.set_attribute("gen_ai.span.leaked", True) + span.end() + except Exception: + pass + + self._active_spans.clear() finally: - # Call the parent shutdown method super().shutdown() From 605b2616e884355209c8b73ea791f32820d993b5 Mon Sep 17 00:00:00 2001 From: pmady Date: Fri, 1 May 2026 12:37:47 -0500 Subject: [PATCH 3/3] otel: use logger instead of print() in shutdown paths The module-level logger is already defined at line 55 but the shutdown methods use print(). Switch to logger.warning and logger.error so platform teams can route these through their logging pipeline. Partially addresses #164 finding 5 Signed-off-by: pmady --- python/fi_instrumentation/otel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/fi_instrumentation/otel.py b/python/fi_instrumentation/otel.py index 0c8e82d0..1d8cd80c 100644 --- a/python/fi_instrumentation/otel.py +++ b/python/fi_instrumentation/otel.py @@ -441,7 +441,7 @@ def shutdown(self) -> None: try: with self._shutdown_lock: if self._active_spans: - print(f"Ending {len(self._active_spans)} active spans during shutdown") + logger.warning("Ending %d active spans during shutdown", len(self._active_spans)) active_spans = list(self._active_spans.values()) for span in active_spans: @@ -563,7 +563,7 @@ def shutdown(self) -> None: if hasattr(self, "_session") and self._session: self._session.close() except Exception as e: - print(f"Error during shutdown: {e}") + logger.error("Error during gRPC exporter shutdown: %s", e) finally: super().shutdown() @@ -606,7 +606,7 @@ def shutdown(self) -> None: if hasattr(self, "_session") and self._session: self._session.close() except Exception as e: - print(f"Error during shutdown: {e}") + logger.error("Error during HTTP exporter shutdown: %s", e) finally: super().shutdown()