Files
ophion/examples/otel-python/app.py
bigtux 771cf6cf50 feat: Add OpenTelemetry OTLP HTTP receiver
- Add POST /v1/traces endpoint for OTLP JSON trace ingestion
- Convert OTLP spans to internal format and save to PostgreSQL
- Manual JSON parsing (no Go 1.24 dependencies)
- Add Node.js instrumentation example with Express
- Add Python instrumentation example with Flask
- Auto-instrumentation support for both languages
2026-02-06 14:59:29 -03:00

169 lines
5.7 KiB
Python

"""
═══════════════════════════════════════════════════════════
📱 Example Flask App with OpenTelemetry
═══════════════════════════════════════════════════════════
Run with:
python app.py
Or with auto-instrumentation:
opentelemetry-instrument python app.py
"""
import os
import time
import random
import atexit
from flask import Flask, jsonify, request
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
from opentelemetry.instrumentation.flask import FlaskInstrumentor
# Initialize tracing BEFORE creating Flask app
from tracing import init_tracing, get_tracer, shutdown
init_tracing()
tracer = get_tracer()
app = Flask(__name__)
# Auto-instrument Flask
FlaskInstrumentor().instrument_app(app)
# Register shutdown handler
atexit.register(shutdown)
# Simulated database
users = [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"},
{"id": 3, "name": "Charlie", "email": "charlie@example.com"},
]
@app.route('/health')
def health():
"""Health check endpoint."""
return jsonify({"status": "healthy", "service": "python-example"})
@app.route('/users')
def get_users():
"""Get all users with custom span."""
with tracer.start_as_current_span("db.query.users") as span:
span.set_attribute("db.system", "memory")
span.set_attribute("db.operation", "SELECT")
# Simulate database latency
time.sleep(random.uniform(0.01, 0.1))
span.set_attribute("db.row_count", len(users))
span.set_status(Status(StatusCode.OK))
return jsonify({"users": users})
@app.route('/users/<int:user_id>')
def get_user(user_id: int):
"""Get user by ID."""
with tracer.start_as_current_span("db.query.user_by_id") as span:
span.set_attribute("db.system", "memory")
span.set_attribute("db.operation", "SELECT")
span.set_attribute("user.id", user_id)
# Simulate database latency
time.sleep(random.uniform(0.01, 0.05))
user = next((u for u in users if u["id"] == user_id), None)
if not user:
span.set_status(Status(StatusCode.ERROR, "User not found"))
return jsonify({"error": "User not found"}), 404
span.set_status(Status(StatusCode.OK))
return jsonify({"user": user})
@app.route('/orders', methods=['POST'])
def create_order():
"""Create order with nested spans."""
with tracer.start_as_current_span("order.create") as parent_span:
try:
data = request.get_json() or {}
items = data.get("items", [])
# Step 1: Validate inventory
with tracer.start_as_current_span("inventory.check") as span:
span.set_attribute("order.items", len(items))
time.sleep(random.uniform(0.05, 0.1))
span.set_status(Status(StatusCode.OK))
# Step 2: Process payment
with tracer.start_as_current_span("payment.process") as span:
span.set_attribute("payment.method", "credit_card")
time.sleep(random.uniform(0.1, 0.2))
span.set_status(Status(StatusCode.OK))
# Step 3: Create order record
with tracer.start_as_current_span("db.insert.order") as span:
time.sleep(random.uniform(0.02, 0.05))
order_id = hex(int(time.time() * 1000))[2:]
span.set_attribute("order.id", order_id)
span.set_status(Status(StatusCode.OK))
parent_span.set_status(Status(StatusCode.OK))
return jsonify({"orderId": order_id, "status": "created"})
except Exception as e:
parent_span.set_status(Status(StatusCode.ERROR, str(e)))
parent_span.record_exception(e)
return jsonify({"error": str(e)}), 500
@app.route('/error')
def trigger_error():
"""Trigger error for testing."""
span = trace.get_current_span()
error = Exception("Simulated error for testing")
span.record_exception(error)
span.set_status(Status(StatusCode.ERROR, str(error)))
return jsonify({"error": str(error)}), 500
@app.route('/external-call')
def external_call():
"""Make external HTTP call (demonstrates distributed tracing)."""
import requests
with tracer.start_as_current_span("external.http.call") as span:
span.set_attribute("http.url", "https://httpbin.org/get")
try:
# Note: requests auto-instrumentation propagates trace context
response = requests.get("https://httpbin.org/get", timeout=5)
span.set_attribute("http.status_code", response.status_code)
span.set_status(Status(StatusCode.OK))
return jsonify({"status": "ok", "external_status": response.status_code})
except Exception as e:
span.record_exception(e)
span.set_status(Status(StatusCode.ERROR, str(e)))
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
port = int(os.getenv('PORT', 5000))
print(f"""
🚀 Example app listening on http://localhost:{port}
Try these endpoints:
GET http://localhost:{port}/health
GET http://localhost:{port}/users
GET http://localhost:{port}/users/1
POST http://localhost:{port}/orders
GET http://localhost:{port}/error
GET http://localhost:{port}/external-call
""")
app.run(host='0.0.0.0', port=port, debug=False)