Spaces:
Sleeping
Sleeping
Christian Kniep commited on
Commit Β·
1ab5126
1
Parent(s): 5148b3d
disable tracing
Browse files- src/app.py +4 -2
- src/routes/contacts.py +19 -5
- src/services/backend_client.py +4 -2
- src/services/storage_service.py +14 -2
- src/utils/__pycache__/tracing.cpython-313.pyc +0 -0
- src/utils/tracing.py +20 -1
- test_tracing_toggle.py +172 -0
src/app.py
CHANGED
|
@@ -139,11 +139,13 @@ def register_middleware(app):
|
|
| 139 |
@app.before_request
|
| 140 |
def before_request():
|
| 141 |
"""Start request timer and generate request ID, create tracing span."""
|
|
|
|
|
|
|
| 142 |
g.start_time = time.time()
|
| 143 |
g.request_id = request.headers.get("X-Request-ID", os.urandom(8).hex())
|
| 144 |
|
| 145 |
-
# Skip tracing for health endpoint
|
| 146 |
-
if request.path == "/health":
|
| 147 |
return
|
| 148 |
|
| 149 |
# Extract parent span context from headers if present
|
|
|
|
| 139 |
@app.before_request
|
| 140 |
def before_request():
|
| 141 |
"""Start request timer and generate request ID, create tracing span."""
|
| 142 |
+
from .utils.tracing import is_tracing_enabled
|
| 143 |
+
|
| 144 |
g.start_time = time.time()
|
| 145 |
g.request_id = request.headers.get("X-Request-ID", os.urandom(8).hex())
|
| 146 |
|
| 147 |
+
# Skip tracing if disabled or for health endpoint
|
| 148 |
+
if not is_tracing_enabled() or request.path == "/health":
|
| 149 |
return
|
| 150 |
|
| 151 |
# Extract parent span context from headers if present
|
src/routes/contacts.py
CHANGED
|
@@ -38,15 +38,24 @@ def list_contacts():
|
|
| 38 |
- search: Filter contacts by name (case-insensitive)
|
| 39 |
- sort: Sort order (recent, alphabetical, oldest)
|
| 40 |
"""
|
|
|
|
|
|
|
| 41 |
tracer = trace.get_tracer(__name__)
|
| 42 |
user_id = session.get("user_id")
|
| 43 |
search_query = request.args.get("search", "").strip()
|
| 44 |
sort_order = request.args.get("sort", "recent")
|
| 45 |
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
| 47 |
span.set_attribute("user_id", user_id)
|
| 48 |
span.set_attribute("search_query", search_query)
|
| 49 |
span.set_attribute("sort_order", sort_order)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
try:
|
| 52 |
# Get all contacts for user
|
|
@@ -68,8 +77,9 @@ def list_contacts():
|
|
| 68 |
contacts = sorted(contacts, key=lambda c: c.created_at)
|
| 69 |
# Default "recent" is already sorted by last_interaction DESC
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
|
|
|
| 73 |
|
| 74 |
return render_template(
|
| 75 |
"contacts/list.html",
|
|
@@ -80,11 +90,15 @@ def list_contacts():
|
|
| 80 |
)
|
| 81 |
|
| 82 |
except Exception as e:
|
| 83 |
-
|
| 84 |
-
|
|
|
|
| 85 |
logger.error(f"Error loading contacts for user {user_id}: {e}")
|
| 86 |
flash("Error loading contacts. Please try again.", "danger")
|
| 87 |
return render_template("contacts/list.html", contacts=[], contact_count=0)
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
|
| 90 |
@contacts_bp.route("/", methods=["POST"])
|
|
|
|
| 38 |
- search: Filter contacts by name (case-insensitive)
|
| 39 |
- sort: Sort order (recent, alphabetical, oldest)
|
| 40 |
"""
|
| 41 |
+
from ..utils.tracing import is_tracing_enabled
|
| 42 |
+
|
| 43 |
tracer = trace.get_tracer(__name__)
|
| 44 |
user_id = session.get("user_id")
|
| 45 |
search_query = request.args.get("search", "").strip()
|
| 46 |
sort_order = request.args.get("sort", "recent")
|
| 47 |
|
| 48 |
+
# Only create span if tracing is enabled
|
| 49 |
+
tracing_enabled = is_tracing_enabled()
|
| 50 |
+
if tracing_enabled:
|
| 51 |
+
span = tracer.start_span("list_contacts")
|
| 52 |
span.set_attribute("user_id", user_id)
|
| 53 |
span.set_attribute("search_query", search_query)
|
| 54 |
span.set_attribute("sort_order", sort_order)
|
| 55 |
+
else:
|
| 56 |
+
span = None
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
|
| 60 |
try:
|
| 61 |
# Get all contacts for user
|
|
|
|
| 77 |
contacts = sorted(contacts, key=lambda c: c.created_at)
|
| 78 |
# Default "recent" is already sorted by last_interaction DESC
|
| 79 |
|
| 80 |
+
if span:
|
| 81 |
+
span.set_attribute("contact_count", len(contacts))
|
| 82 |
+
span.set_attribute("filtered", bool(search_query))
|
| 83 |
|
| 84 |
return render_template(
|
| 85 |
"contacts/list.html",
|
|
|
|
| 90 |
)
|
| 91 |
|
| 92 |
except Exception as e:
|
| 93 |
+
if span:
|
| 94 |
+
span.set_attribute("error", True)
|
| 95 |
+
span.add_event("error", {"message": str(e)})
|
| 96 |
logger.error(f"Error loading contacts for user {user_id}: {e}")
|
| 97 |
flash("Error loading contacts. Please try again.", "danger")
|
| 98 |
return render_template("contacts/list.html", contacts=[], contact_count=0)
|
| 99 |
+
finally:
|
| 100 |
+
if span:
|
| 101 |
+
span.end()
|
| 102 |
|
| 103 |
|
| 104 |
@contacts_bp.route("/", methods=["POST"])
|
src/services/backend_client.py
CHANGED
|
@@ -38,9 +38,11 @@ class BackendAPIClient:
|
|
| 38 |
if self.bearer_token:
|
| 39 |
headers["Authorization"] = f"Bearer {self.bearer_token}"
|
| 40 |
|
| 41 |
-
# Inject trace context into headers automatically
|
| 42 |
try:
|
| 43 |
-
|
|
|
|
|
|
|
| 44 |
except Exception:
|
| 45 |
# Don't fail request if tracing injection fails
|
| 46 |
pass
|
|
|
|
| 38 |
if self.bearer_token:
|
| 39 |
headers["Authorization"] = f"Bearer {self.bearer_token}"
|
| 40 |
|
| 41 |
+
# Inject trace context into headers automatically (only if tracing enabled)
|
| 42 |
try:
|
| 43 |
+
from ..utils.tracing import is_tracing_enabled
|
| 44 |
+
if is_tracing_enabled():
|
| 45 |
+
TraceContextTextMapPropagator().inject(headers)
|
| 46 |
except Exception:
|
| 47 |
# Don't fail request if tracing injection fails
|
| 48 |
pass
|
src/services/storage_service.py
CHANGED
|
@@ -399,11 +399,19 @@ def list_contact_sessions(
|
|
| 399 |
List of ContactSession objects
|
| 400 |
"""
|
| 401 |
from opentelemetry import trace
|
|
|
|
|
|
|
| 402 |
tracer = trace.get_tracer(__name__)
|
| 403 |
|
| 404 |
-
|
|
|
|
|
|
|
| 405 |
span.set_attribute("user_id", user_id)
|
| 406 |
span.set_attribute("sort_by", sort_by)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
conn = get_db_connection()
|
| 409 |
cursor = conn.cursor()
|
|
@@ -437,8 +445,12 @@ def list_contact_sessions(
|
|
| 437 |
for row in rows
|
| 438 |
]
|
| 439 |
|
| 440 |
-
|
|
|
|
| 441 |
return contacts
|
|
|
|
|
|
|
|
|
|
| 442 |
|
| 443 |
|
| 444 |
def get_contact_session(session_id: str) -> Optional[ContactSession]:
|
|
|
|
| 399 |
List of ContactSession objects
|
| 400 |
"""
|
| 401 |
from opentelemetry import trace
|
| 402 |
+
from ..utils.tracing import is_tracing_enabled
|
| 403 |
+
|
| 404 |
tracer = trace.get_tracer(__name__)
|
| 405 |
|
| 406 |
+
# Only create span if tracing is enabled
|
| 407 |
+
if is_tracing_enabled():
|
| 408 |
+
span = tracer.start_span("storage.list_contact_sessions")
|
| 409 |
span.set_attribute("user_id", user_id)
|
| 410 |
span.set_attribute("sort_by", sort_by)
|
| 411 |
+
else:
|
| 412 |
+
span = None
|
| 413 |
+
|
| 414 |
+
try:
|
| 415 |
|
| 416 |
conn = get_db_connection()
|
| 417 |
cursor = conn.cursor()
|
|
|
|
| 445 |
for row in rows
|
| 446 |
]
|
| 447 |
|
| 448 |
+
if span:
|
| 449 |
+
span.set_attribute("result_count", len(contacts))
|
| 450 |
return contacts
|
| 451 |
+
finally:
|
| 452 |
+
if span:
|
| 453 |
+
span.end()
|
| 454 |
|
| 455 |
|
| 456 |
def get_contact_session(session_id: str) -> Optional[ContactSession]:
|
src/utils/__pycache__/tracing.cpython-313.pyc
ADDED
|
Binary file (3.78 kB). View file
|
|
|
src/utils/tracing.py
CHANGED
|
@@ -18,6 +18,16 @@ from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
def init_tracer(service_name: str) -> trace.Tracer:
|
| 22 |
"""
|
| 23 |
Initialize OpenTelemetry tracer with OTLP HTTP exporter for Jaeger.
|
|
@@ -25,12 +35,21 @@ def init_tracer(service_name: str) -> trace.Tracer:
|
|
| 25 |
Uses HTTP protocol like the Go API (instead of unreliable UDP).
|
| 26 |
Jaeger >= 1.35 supports OTLP natively on port 4318.
|
| 27 |
|
|
|
|
|
|
|
| 28 |
Args:
|
| 29 |
service_name: Name of the service for tracing
|
| 30 |
|
| 31 |
Returns:
|
| 32 |
-
Configured tracer instance
|
| 33 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
# Jaeger OTLP HTTP endpoint (port 4318)
|
| 35 |
otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://jaeger:4318")
|
| 36 |
sampling_rate = float(os.getenv("JAEGER_SAMPLING_RATE", "1.0"))
|
|
|
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
|
| 21 |
+
def is_tracing_enabled() -> bool:
|
| 22 |
+
"""
|
| 23 |
+
Check if tracing is enabled via ENABLE_TRACING environment variable.
|
| 24 |
+
|
| 25 |
+
Returns:
|
| 26 |
+
True if tracing is enabled (default), False otherwise
|
| 27 |
+
"""
|
| 28 |
+
return os.getenv("ENABLE_TRACING", "true").lower() in ("true", "1", "yes")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
def init_tracer(service_name: str) -> trace.Tracer:
|
| 32 |
"""
|
| 33 |
Initialize OpenTelemetry tracer with OTLP HTTP exporter for Jaeger.
|
|
|
|
| 35 |
Uses HTTP protocol like the Go API (instead of unreliable UDP).
|
| 36 |
Jaeger >= 1.35 supports OTLP natively on port 4318.
|
| 37 |
|
| 38 |
+
Can be disabled by setting ENABLE_TRACING=false environment variable.
|
| 39 |
+
|
| 40 |
Args:
|
| 41 |
service_name: Name of the service for tracing
|
| 42 |
|
| 43 |
Returns:
|
| 44 |
+
Configured tracer instance (or no-op tracer if disabled)
|
| 45 |
"""
|
| 46 |
+
# Check if tracing is enabled
|
| 47 |
+
if not is_tracing_enabled():
|
| 48 |
+
print(f"[TRACING] Tracing disabled via ENABLE_TRACING environment variable")
|
| 49 |
+
logger.info("OpenTelemetry tracing disabled")
|
| 50 |
+
# Return default no-op tracer
|
| 51 |
+
return trace.get_tracer(service_name)
|
| 52 |
+
|
| 53 |
# Jaeger OTLP HTTP endpoint (port 4318)
|
| 54 |
otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://jaeger:4318")
|
| 55 |
sampling_rate = float(os.getenv("JAEGER_SAMPLING_RATE", "1.0"))
|
test_tracing_toggle.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to verify ENABLE_TRACING toggle works correctly.
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
# Test with tracing enabled (default)
|
| 7 |
+
python test_tracing_toggle.py
|
| 8 |
+
|
| 9 |
+
# Test with tracing disabled
|
| 10 |
+
ENABLE_TRACING=false python test_tracing_toggle.py
|
| 11 |
+
"""
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
|
| 15 |
+
# Add src to path
|
| 16 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
| 17 |
+
|
| 18 |
+
from utils.tracing import is_tracing_enabled, init_tracer
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def test_is_tracing_enabled():
|
| 22 |
+
"""Test the is_tracing_enabled function with various env values."""
|
| 23 |
+
print("Testing is_tracing_enabled()...")
|
| 24 |
+
|
| 25 |
+
test_cases = [
|
| 26 |
+
("true", True),
|
| 27 |
+
("True", True),
|
| 28 |
+
("TRUE", True),
|
| 29 |
+
("1", True),
|
| 30 |
+
("yes", True),
|
| 31 |
+
("YES", True),
|
| 32 |
+
("false", False),
|
| 33 |
+
("False", False),
|
| 34 |
+
("FALSE", False),
|
| 35 |
+
("0", False),
|
| 36 |
+
("no", False),
|
| 37 |
+
("NO", False),
|
| 38 |
+
("", True), # Default is enabled
|
| 39 |
+
(None, True), # Default is enabled
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
for value, expected in test_cases:
|
| 43 |
+
# Set or unset environment variable
|
| 44 |
+
if value is None:
|
| 45 |
+
os.environ.pop("ENABLE_TRACING", None)
|
| 46 |
+
else:
|
| 47 |
+
os.environ["ENABLE_TRACING"] = value
|
| 48 |
+
|
| 49 |
+
result = is_tracing_enabled()
|
| 50 |
+
status = "β" if result == expected else "β"
|
| 51 |
+
print(f" {status} ENABLE_TRACING={value!r} β {result} (expected {expected})")
|
| 52 |
+
|
| 53 |
+
if result != expected:
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
return True
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def test_init_tracer():
|
| 60 |
+
"""Test that init_tracer respects the ENABLE_TRACING flag."""
|
| 61 |
+
print("\nTesting init_tracer()...")
|
| 62 |
+
|
| 63 |
+
# Test with tracing disabled
|
| 64 |
+
os.environ["ENABLE_TRACING"] = "false"
|
| 65 |
+
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4318"
|
| 66 |
+
|
| 67 |
+
print(" Testing with ENABLE_TRACING=false...")
|
| 68 |
+
tracer = init_tracer("test-service")
|
| 69 |
+
|
| 70 |
+
# Should return a tracer (no-op) but not initialize OpenTelemetry
|
| 71 |
+
if tracer is None:
|
| 72 |
+
print(" β init_tracer returned None (should return no-op tracer)")
|
| 73 |
+
return False
|
| 74 |
+
|
| 75 |
+
print(" β init_tracer returned tracer (no-op)")
|
| 76 |
+
|
| 77 |
+
# Test with tracing enabled
|
| 78 |
+
os.environ["ENABLE_TRACING"] = "true"
|
| 79 |
+
print(" Testing with ENABLE_TRACING=true...")
|
| 80 |
+
|
| 81 |
+
# Note: This will actually try to connect to Jaeger if available
|
| 82 |
+
# but won't fail if Jaeger is down
|
| 83 |
+
try:
|
| 84 |
+
tracer = init_tracer("test-service-enabled")
|
| 85 |
+
print(" β init_tracer returned tracer (full initialization)")
|
| 86 |
+
except Exception as e:
|
| 87 |
+
print(f" β init_tracer failed: {e}")
|
| 88 |
+
print(" (This is expected if Jaeger is not running)")
|
| 89 |
+
|
| 90 |
+
return True
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def test_span_creation():
|
| 94 |
+
"""Test that spans are created/skipped based on toggle."""
|
| 95 |
+
print("\nTesting span creation...")
|
| 96 |
+
|
| 97 |
+
from opentelemetry import trace
|
| 98 |
+
|
| 99 |
+
# Test with tracing disabled
|
| 100 |
+
os.environ["ENABLE_TRACING"] = "false"
|
| 101 |
+
|
| 102 |
+
if is_tracing_enabled():
|
| 103 |
+
print(" β is_tracing_enabled() returned True when ENABLE_TRACING=false")
|
| 104 |
+
return False
|
| 105 |
+
|
| 106 |
+
# This should be a no-op
|
| 107 |
+
tracer = trace.get_tracer(__name__)
|
| 108 |
+
span = tracer.start_span("test-span")
|
| 109 |
+
span.end()
|
| 110 |
+
print(" β No-op span created successfully when disabled")
|
| 111 |
+
|
| 112 |
+
# Test with tracing enabled
|
| 113 |
+
os.environ["ENABLE_TRACING"] = "true"
|
| 114 |
+
|
| 115 |
+
if not is_tracing_enabled():
|
| 116 |
+
print(" β is_tracing_enabled() returned False when ENABLE_TRACING=true")
|
| 117 |
+
return False
|
| 118 |
+
|
| 119 |
+
print(" β Tracing enabled check passed")
|
| 120 |
+
|
| 121 |
+
return True
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def main():
|
| 125 |
+
"""Run all tests."""
|
| 126 |
+
print("=" * 60)
|
| 127 |
+
print("ENABLE_TRACING Toggle Test Suite")
|
| 128 |
+
print("=" * 60)
|
| 129 |
+
|
| 130 |
+
current_value = os.environ.get("ENABLE_TRACING", "<not set>")
|
| 131 |
+
print(f"\nCurrent ENABLE_TRACING: {current_value}")
|
| 132 |
+
print()
|
| 133 |
+
|
| 134 |
+
tests = [
|
| 135 |
+
("is_tracing_enabled()", test_is_tracing_enabled),
|
| 136 |
+
("init_tracer()", test_init_tracer),
|
| 137 |
+
("span creation", test_span_creation),
|
| 138 |
+
]
|
| 139 |
+
|
| 140 |
+
results = []
|
| 141 |
+
for name, test_func in tests:
|
| 142 |
+
try:
|
| 143 |
+
success = test_func()
|
| 144 |
+
results.append((name, success))
|
| 145 |
+
except Exception as e:
|
| 146 |
+
print(f"\nβ Test '{name}' raised exception: {e}")
|
| 147 |
+
results.append((name, False))
|
| 148 |
+
|
| 149 |
+
# Print summary
|
| 150 |
+
print("\n" + "=" * 60)
|
| 151 |
+
print("Test Summary")
|
| 152 |
+
print("=" * 60)
|
| 153 |
+
|
| 154 |
+
passed = sum(1 for _, success in results if success)
|
| 155 |
+
total = len(results)
|
| 156 |
+
|
| 157 |
+
for name, success in results:
|
| 158 |
+
status = "β PASS" if success else "β FAIL"
|
| 159 |
+
print(f"{status}: {name}")
|
| 160 |
+
|
| 161 |
+
print(f"\n{passed}/{total} tests passed")
|
| 162 |
+
|
| 163 |
+
if passed == total:
|
| 164 |
+
print("\nβ All tests passed!")
|
| 165 |
+
return 0
|
| 166 |
+
else:
|
| 167 |
+
print(f"\nβ {total - passed} test(s) failed")
|
| 168 |
+
return 1
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
if __name__ == "__main__":
|
| 172 |
+
sys.exit(main())
|