--- # Example instrumented application to test the observability stack # This is a simple Python Flask app with OpenTelemetry instrumentation apiVersion: v1 kind: ConfigMap metadata: name: demo-app namespace: observability data: app.py: | from flask import Flask, jsonify import logging import json import time import random # OpenTelemetry imports from opentelemetry import trace, metrics 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.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter from opentelemetry.instrumentation.flask import FlaskInstrumentor from opentelemetry.sdk.resources import Resource from prometheus_flask_exporter import PrometheusMetrics # Configure structured logging logging.basicConfig( level=logging.INFO, format='%(message)s' ) class JSONFormatter(logging.Formatter): def format(self, record): log_obj = { 'timestamp': self.formatTime(record, self.datefmt), 'level': record.levelname, 'message': record.getMessage(), 'logger': record.name, } if hasattr(record, 'trace_id'): log_obj['trace_id'] = record.trace_id log_obj['span_id'] = record.span_id return json.dumps(log_obj) handler = logging.StreamHandler() handler.setFormatter(JSONFormatter()) logger = logging.getLogger(__name__) logger.addHandler(handler) logger.setLevel(logging.INFO) # Configure OpenTelemetry resource = Resource.create({"service.name": "demo-app"}) # Tracing trace_provider = TracerProvider(resource=resource) trace_provider.add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint="http://tempo.observability.svc.cluster.local:4317", insecure=True ) ) ) trace.set_tracer_provider(trace_provider) tracer = trace.get_tracer(__name__) # Create Flask app app = Flask(__name__) # Prometheus metrics metrics = PrometheusMetrics(app) # Auto-instrument Flask FlaskInstrumentor().instrument_app(app) # Sample data ITEMS = ["apple", "banana", "orange", "grape", "mango"] @app.route('/') def index(): span = trace.get_current_span() trace_id = format(span.get_span_context().trace_id, '032x') logger.info("Index page accessed", extra={ 'trace_id': trace_id, 'endpoint': '/' }) return jsonify({ 'service': 'demo-app', 'status': 'healthy', 'trace_id': trace_id }) @app.route('/items') def get_items(): with tracer.start_as_current_span("fetch_items") as span: # Simulate database query time.sleep(random.uniform(0.01, 0.1)) span.set_attribute("items.count", len(ITEMS)) trace_id = format(span.get_span_context().trace_id, '032x') logger.info("Items fetched", extra={ 'trace_id': trace_id, 'count': len(ITEMS) }) return jsonify({ 'items': ITEMS, 'count': len(ITEMS), 'trace_id': trace_id }) @app.route('/item/') def get_item(item_id): with tracer.start_as_current_span("fetch_item") as span: span.set_attribute("item.id", item_id) trace_id = format(span.get_span_context().trace_id, '032x') # Simulate processing time.sleep(random.uniform(0.01, 0.05)) if item_id < 0 or item_id >= len(ITEMS): logger.warning("Item not found", extra={ 'trace_id': trace_id, 'item_id': item_id }) return jsonify({'error': 'Item not found', 'trace_id': trace_id}), 404 item = ITEMS[item_id] logger.info("Item fetched", extra={ 'trace_id': trace_id, 'item_id': item_id, 'item': item }) return jsonify({ 'id': item_id, 'name': item, 'trace_id': trace_id }) @app.route('/slow') def slow_endpoint(): with tracer.start_as_current_span("slow_operation") as span: trace_id = format(span.get_span_context().trace_id, '032x') logger.info("Slow operation started", extra={'trace_id': trace_id}) # Simulate slow operation time.sleep(random.uniform(1, 3)) logger.info("Slow operation completed", extra={'trace_id': trace_id}) return jsonify({ 'message': 'Operation completed', 'trace_id': trace_id }) @app.route('/error') def error_endpoint(): with tracer.start_as_current_span("error_operation") as span: trace_id = format(span.get_span_context().trace_id, '032x') logger.error("Intentional error triggered", extra={'trace_id': trace_id}) span.set_attribute("error", True) return jsonify({ 'error': 'This is an intentional error', 'trace_id': trace_id }), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=8080) --- apiVersion: apps/v1 kind: Deployment metadata: name: demo-app namespace: observability labels: app: demo-app spec: replicas: 1 selector: matchLabels: app: demo-app template: metadata: labels: app: demo-app annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" prometheus.io/path: "/metrics" spec: containers: - name: demo-app image: python:3.11-slim command: - /bin/bash - -c - | pip install flask opentelemetry-api opentelemetry-sdk \ opentelemetry-instrumentation-flask \ opentelemetry-exporter-otlp-proto-grpc \ prometheus-flask-exporter && \ python /app/app.py ports: - name: http containerPort: 8080 volumeMounts: - name: app-code mountPath: /app resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi volumes: - name: app-code configMap: name: demo-app --- apiVersion: v1 kind: Service metadata: name: demo-app namespace: observability labels: app: demo-app annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" prometheus.io/path: "/metrics" spec: type: ClusterIP ports: - port: 8080 targetPort: http protocol: TCP name: http selector: app: demo-app