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
This commit is contained in:
168
examples/otel-python/app.py
Normal file
168
examples/otel-python/app.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
═══════════════════════════════════════════════════════════
|
||||
📱 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)
|
||||
Reference in New Issue
Block a user