summaryrefslogtreecommitdiff
path: root/f3s/tracing-demo/docker/backend/app.py
blob: 52813561c3d7ac79c0867fb2a94037c581c7c6c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/env python3
"""
Tracing Demo - Backend Service
Final service in the chain that returns data.
Simulates database queries and demonstrates end-to-end tracing.
"""
from flask import Flask, jsonify
import os
import logging
import time
from datetime import datetime

# OpenTelemetry imports for distributed tracing
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.resources import Resource

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize OpenTelemetry tracing with resource attributes
# These attributes identify this service in traces
resource = Resource(attributes={
    "service.name": "backend",
    "service.namespace": "tracing-demo",
    "service.version": "1.0.0",
    "deployment.environment": "production"
})

provider = TracerProvider(resource=resource)

# Configure OTLP exporter to send traces to Alloy
otlp_exporter = OTLPSpanExporter(
    endpoint=os.getenv('OTEL_EXPORTER_OTLP_ENDPOINT',
                      'http://alloy.monitoring.svc.cluster.local:4317'),
    insecure=True
)

# Batch spans for efficient export
processor = BatchSpanProcessor(otlp_exporter)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

# Get tracer for manual instrumentation
tracer = trace.get_tracer(__name__)

# Create Flask application
app = Flask(__name__)

# Auto-instrument Flask
# Auto-instrument Flask to create spans for HTTP requests
# Exclude health check endpoint to reduce tracing noise
FlaskInstrumentor().instrument_app(app, excluded_urls="/health")

@app.route('/')
def index():
    """
    Health check and service information endpoint.
    Returns service metadata.
    """
    return jsonify({
        "service": "backend",
        "version": "1.0.0",
        "message": "Tracing demo backend service"
    })

@app.route('/health')
def health():
    """
    Kubernetes health check endpoint.
    Used by readiness and liveness probes.
    """
    return jsonify({"status": "healthy"}), 200

@app.route('/api/data', methods=['GET'])
def get_data():
    """
    Return data endpoint that simulates a database query.
    Creates custom spans to track query execution.
    This is the final service in the trace chain.
    """
    # Create a custom span for the database query simulation
    with tracer.start_as_current_span("backend-get-data") as span:
        # Add custom attributes to the span
        span.set_attribute("backend.handler", "get_data")

        # Simulate database query delay
        query_time = 0.1
        time.sleep(query_time)

        # Record query duration in span
        span.set_attribute("backend.query.duration_ms", query_time * 1000)
        span.set_attribute("backend.query.type", "simulated_database_query")

        # Prepare response data
        data = {
            "service": "backend",
            "data": {
                "id": 12345,
                "value": "Sample data from backend service",
                "timestamp": datetime.utcnow().isoformat(),
                "query_time_ms": query_time * 1000
            }
        }

        logger.info(f"Returning data: {data['data']['id']}")

        return jsonify(data), 200

if __name__ == '__main__':
    logger.info("Starting backend service on port 5002")
    logger.info(f"OTLP endpoint: {os.getenv('OTEL_EXPORTER_OTLP_ENDPOINT', 'default')}")
    app.run(host='0.0.0.0', port=5002, debug=False)