This commit is contained in:
Waylon S. Walker 2025-05-23 10:24:41 -05:00
parent 0a4b32dffe
commit 774ea44af1
9 changed files with 160 additions and 115 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.null-ls_806023_main.py
.null-ls_378979_main.py

View file

@ -1,21 +1,41 @@
forward_loki:
# export KUBECONFIG=kubeconfig.yaml
kind-up:
kind create cluster --name grafana
kind get kubeconfig --name grafana > kubeconfig.yaml
kubectl create namespace meta && kubectl create namespace prod
helm repo add grafana https://grafana.github.io/helm-charts && helm repo update
helm install loki grafana/loki --namespace meta --values loki-values.yaml
helm install kps prometheus-community/kube-prometheus-stack --namespace meta -f prometheus-values.yaml
helm install grafana grafana/grafana --values grafana-values.yml --namespace meta
helm install k8s-monitoring grafana/k8s-monitoring --values ./k8s-monitoring-values.yml -n meta --create-namespace
kubectl apply -f pyservice.yaml
kind-down:
kind delete cluster --name grafana
forward-loki:
kubectl port-forward --namespace meta svc/loki-gateway 3100:80
forward_grafana:
forward-grafana:
#!/bin/bash
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace meta port-forward $POD_NAME 3000
forward_alloy_logs:
forward-alloy-logs:
#!/bin/bash
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=alloy-logs,app.kubernetes.io/instance=k8s" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace meta port-forward $POD_NAME 12345
forward_otel_collector:
forward-otel-collector:
#!/bin/bash
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=otel-collector,app.kubernetes.io/instance=k8s" -o jsonpath="{.items[0].metadata.name}")
kubectl --namespace meta port-forward $POD_NAME 4317
get_inotify_max_user_watches:
get-inotify-max-user-watches:
#!/bin/bashcat /proc/sys/fs/inotify/max_user_watches
export POD_NAME=$(kubectl get pods --namespace meta -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}")

View file

@ -24,6 +24,7 @@ podLogs:
trace_id: ${body.trace_id}
span_id: ${body.span_id}
service_name: ${body.service_name}
app: "${kubernetes.labels.app}"
structuredMetadata:
pod: pod # Set structured metadata "pod" from label "pod"
namespaces: []
@ -36,8 +37,8 @@ nodeExporter:
enabled: true
kube-state-metrics:
enabled: true
nodeLogs:
enabled: true
# nodeLogs:
# enabled: true
kubeletMetrics:
enabled: true
cAdvisor:

20
kubeconfig.yaml Normal file
View file

@ -0,0 +1,20 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJYXAwZDdtK0k2bVV3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBMU1qSXlNREk1TkRGYUZ3MHpOVEExTWpBeU1ETTBOREZhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURENXVQVnJEVVFuQkd4bGtKYmdSWm1Xc21yWDgwYlR1R3RIOXZsWFNaTmd3L3NFZy9uK1FXRncrd3oKbGFlbytWNnpFQStjSkVvMVFyVm5kTUFUSjdjUkxra0oyenVpM2pCMVpTRm5lcjZKMXVpRmcyYmN0ZjJ0R29jQQpZbGx1KzVqdmlOamNmcHZhcUFqaFR2SzdzVE1nc1pYTHFlQ3I5ZGoybnVUWG12QlhEVHFtSmJ2MDAyaVJ5UEV6ClNPUENYOG13QUxuVFlteVgyRVlDbU00K3NqNzhQWVoyZ3FqV1VtZGd5Mnl1WjczOGNtc0tEeVNXZlRmcXhEMS8KamRoN2pWbUtwZXZaM1MrVnhkaHdKR0lSbm1oK3NMd3NCQjFhamNwZWY4QVp2U3hwdlJ0YWxIeVgxTCs5SmVJegpKSHptTVJ3U21SRGRnQUZucFIyR3I1YitjQXdIQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTd2l0dDRPM2gydGd3L1hFUi8zZ1BXbjRMczd6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQmxhaVNGckttUQpGWFIvQWhKUUZjY3U3L2N3by9HMCtZT0xDbElsZ2tGaHNqMVlhUERkci9FTWxVeWpwMkNNaGYrTjBYUXJyM0wvCnNnMCt1R1J2dzV6VDI0VzhYbXBURjhiTGdtMlo3azMrMWdyVzlVUHVKczVVQzJURWJYWnhXZUtnRkl2Mm56OGoKUWMwVGdqaUtNLzNMaTdlWWQ4QUNZbzlyNnMvMUJXNVlxR01VOWt5ZktlTE1wQy8wMHlPRWJBRHRHRzVEeDFtbApweko2bURwQml5WERNQjE1VUJyVW96SllxUys5Mk1VcUM1WXZHTXpKTm1UMzhpcWY4TzlxMzVNWUJvSWI3WjM1CjM4S0h3MGgwZFhocnB5by8yYW1JRSs0a0lXc2hPWEl2Q0RrVCsyWDVUd1o0RkFmU1htazlNeC9jS1l1VkJ0Y0oKMld3VVFtYXdiWUFiCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server: https://127.0.0.1:42441
name: kind-grafana
contexts:
- context:
cluster: kind-grafana
user: kind-grafana
name: kind-grafana
current-context: kind-grafana
kind: Config
preferences: {}
users:
- name: kind-grafana
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJRExoYllYczFrcGt3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBMU1qSXlNREk1TkRGYUZ3MHlOakExTWpJeU1ETTBOREZhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFETVBKZmMKVjY0MElNRXYzVFM5R3pkTS9zMkgvTGEwcWRXdDNlOHY0TlRRUTBKN3EzbldldlpMYmp2TXVJNDQ3czRxRGY3Qgo1VjVpMFY1UUZIREVXWHFOM2h5Mnc0dG1zVVUrQ3R0UCt4QWp0RFlDaC9pT0NrUFA3UEJ1eG5ndm82R2Qyczl3CnF4emd0ZW56UHZ1ekdsMzdaSDN2L0t2bDVoMWVoMXo4OHh5RjNFZUNmN0RGUW44aFFMSStNOUt1d0ZLNXdqcUUKeGRXWlRrbWg4WElIVy8vQVRybHhEZjAra3ZuaUgzNFVVSGZxSnhCUHpDYjNsQk9WMmRpOTlOdWdvcEp2QTZxdQorcXRXRGhqTFR5MFF5WWdYcmE0ZjFVYUpGVGZqS2lXTy9VWm1FcjE4bEd1THd6TVQrRUp6SitSUERRVlo0YVA5CkZNRDQzNE96MmQxMGJjZWpBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkxDSzIzZzdlSGEyREQ5YwpSSC9lQTlhZmd1enZNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJSNWZjNVEzNEtiZmZLclVyU3lNMng1STh2CnNUdDhYeTdsOTFoeHd4N1RObXk3ZkUrdUdHSzRWVDlMNnZ4dUw4UHRLbXZNSUtpM0pSbUtTYXVRZXJMeW16bGEKNE42eTQzbStJdTdxRjRwbG1RMUlNL1kzY1JFQTBhcXhwOVhUUVBjRDROeGxFNy81MEhTZ3NZeitNWXVYQUliMgpGYi8xWmRQMkUrWElGam9JbVFtNE1lNkV2ZjlkWW1mdFN1Sm1qOVVtM3h5ZG1jelRsbkdEK051UDZ2T01IS2o5CmJUUlBUUEVCUk5pMGJzck5ycXBLUkQzUno1UWZqcUVaUE9FNVFMTThBRUUzTkQ1SHQrUnVUcmZ3a2lGdlo2SVEKZk42eFFtOWp4VFpRSEx1WFUvZy9UdzFOajlrZDJqSm1HOGFBRkR4OEw0MFJMSXFqUmNtU1F3SS9MOVcvCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBekR5WDNGZXVOQ0RCTDkwMHZSczNUUDdOaC95MnRLblZyZDN2TCtEVTBFTkNlNnQ1CjFucjJTMjQ3ekxpT09PN09LZzMrd2VWZVl0RmVVQlJ3eEZsNmpkNGN0c09MWnJGRlBncmJUL3NRSTdRMkFvZjQKamdwRHorendic1o0TDZPaG5kclBjS3NjNExYcDh6NzdzeHBkKzJSOTcveXI1ZVlkWG9kYy9QTWNoZHhIZ24rdwp4VUovSVVDeVBqUFNyc0JTdWNJNmhNWFZtVTVKb2ZGeUIxdi93RTY1Y1EzOVBwTDU0aDkrRkZCMzZpY1FUOHdtCjk1UVRsZG5ZdmZUYm9LS1Nid09xcnZxclZnNFl5MDh0RU1tSUY2MnVIOVZHaVJVMzR5b2xqdjFHWmhLOWZKUnIKaThNekUvaENjeWZrVHcwRldlR2ovUlRBK04rRHM5bmRkRzNIb3dJREFRQUJBb0lCQUVia0VXSXVIUTJEQVF0NApJTUl6SERMaGpyM1EvaW9mZVVmc2JRV1ZhTWtSVDVaVm91akxyWW5wTFdDVi91Zk1IRXVFcUJUdFpLR3dRcWhSCk1BTTNlODZhZGlVS0l1ejJReTZSM2lZWTR6VkJiQzNjdkcyeEtuQ1ZzYThCdk4zc1VrRFVub1JoSUpqdkM5R2UKM3diLzgvYzZubXdhckNBVWk4VXYveHJkMTl5dVJuYnRsV1F5UmpPTVh0RHl4bnlIVy8vZnViZVl4dUhBdU9VQgpFZjFOaHc5SXhZaFh1WlJoYTZoUHM1M1FZaS9RSnNkb2w0TmFHcU1LbmkybUZOTGQxUXAvNXdvNmhKanYzeHBZCkdGc3psSVVvbExpMmRWV0pXVEl3UUJiMjlwc1dxN0pVY0FKWGc5NmhSNU5qNHF1cW5kSDY1bURxdDFvL3hrSmUKOUhwWi9VRUNnWUVBNDlyUExRSS9xZEgybjJzK1FKY1ovWFJ6MWpuRjNRNGc2NS8va0EvZlZMODVGRHB6QmozWgp4T2lYRXVqelN3N3NDcGpucmhsRXl4cXFnZU1yakJxL3VhcWRCS1BjTERaNXhLZURLMlVZeXk4MmVFblFZKzFvCm5XVmk0UkZUblpkUXFiY3lXejNrZFo1ZkI0UHQySmhoQTVyS2RRNlEwcHMvdkdId2poTWhXUHNDZ1lFQTVYYncKRW52dnJNYVlVL0lpLzBMdTc2NDlPU0dhWTZhWWhKeHAvbTg3Yis5OFVMVXNWMngwbW8zUnY3QytSYjlYQ1pjMQpndHpDMEduWERSMzlvcHdqZlQrQm1HRkNuSjFCZ0M4OVhtak1nK2tzYTFnZkd4b1hUWnhxMDRNZXhVZHR4eVR5CndxckpTYjh2WUJCMExvOW5oYms3QXQwM29MUStJcTBaRlhuazIza0NnWUI3TUFsaVpCelhTMVR5eTZCVWUxenAKMHRQdHRqNXJUUUF2WThsZnNiVWt0RjIvdWZvR0hkcG13dmtxbUJjeE5WZ1lRcUsvVlpvRDFON3FhazlZNS8xUAovQlg0TkQ4TkNFYTlNM09QT3BFMUNNbUNMeVlqWUc5MjZTR1VYVEcvdWRjNmFua25LMGNnOEFhZ29Zc3QxdlJjClpvdWV6Y2t1bEJEWllIb1YxZkhwa1FLQmdRREtWd3p2WDdaREJvUkFVZDRtZFNFNDNNNUQyS3ZKZjVneUo3TVgKbDRJei9Gd0UxeDJZb1p4WXhRSFdKTVpEdnF2RFcwRG1la0NYZ3gwTkJnc29Ic0wwcU5GZ1N6TnY0d05sUTBLOApRM0ZFU0pMUXZVNEFtZ3MrZHRXRVdiVUNoUy82VVV4MytCMnpHQkZ3aGxITTFNdVdrWFhGMnNnNHYzZWpJRHhrClFhNWJFUUtCZ1FDK05Hbk5vU2lUQTBqUG9XVERwQ3BOekg5OEtJUFBYMUl4STgvWEw1TXJ2ZTNiQndMVDVoeFAKTkh3RHpRL2ZQQUtHL3B4V1cxRmNyVVIrTVFPZlNLbVA0WkVOVVY0NndJek14WlhoR3ppSTNaTktkVzlROXczUworaVcxYnBYMmhzVGU0V3pmZEdnclBzNHdSc1pxNlRGdHJacFdwYzFGRXVXUnROdFNEbmdXN2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=

26
logspammer.yaml Normal file
View file

@ -0,0 +1,26 @@
apiVersion: v1
kind: Namespace
metadata:
name: logspammer
---
apiVersion: v1
kind: Pod
metadata:
name: logspammer
namespace: prod
labels:
app: logger
service_name: logspammer-service-name
spec:
restartPolicy: Always
containers:
- name: logspammer
image: busybox:latest
command: ["/bin/sh", "-c"]
args:
- |
while true; do
echo "$(date -Is) ⚙️ Hello from the Kubernetes logger pod!"
sleep 5
done
---

75
main.py
View file

@ -1,36 +1,42 @@
import time
import random
from flask import Flask
from opentelemetry import trace
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
import logging
from opentelemetry.trace import get_current_span
# === Resource ===
resource = Resource(attributes={"service.name": "my-flask-app"})
# Configure tracing
trace.set_tracer_provider(
TracerProvider(resource=Resource.create({"service.name": "my-flask-app"}))
)
# === Tracing ===
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
span_processor = BatchSpanProcessor(OTLPSpanExporter())
trace.get_tracer_provider().add_span_processor(span_processor)
# trace_exporter = OTLPSpanExporter(
# endpoint="http://otel-collector.meta.svc:4318/v1/traces"
# )
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(trace_exporter))
# === Metrics ===
reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="http://otel-collector.meta.svc:4318/v1/metrics")
)
provider = MeterProvider(resource=resource, metric_readers=[reader])
metrics.set_meter_provider(provider)
# Setup logging with trace_id
# === Logging with trace ID ===
class TraceIdFilter(logging.Filter):
def filter(self, record):
span = trace.get_current_span()
ctx = span.get_span_context()
if ctx.trace_id != 0:
record.trace_id = format(ctx.trace_id, "032x")
record.span_id = format(ctx.span_id, "016x")
else:
record.trace_id = None
record.span_id = None
record.trace_id = format(ctx.trace_id, "032x") if ctx.trace_id != 0 else None
record.span_id = format(ctx.span_id, "016x") if ctx.span_id != 0 else None
return True
@ -45,43 +51,22 @@ logger = logging.getLogger("myapp")
logger.setLevel(logging.INFO)
logger.addHandler(handler)
# Setup Tracer
resource = Resource(
attributes={
"service.name": "python-otel-demo-app", # 👈 choose any name you like
}
)
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
# Configure OTLP HTTP exporter
otlp_exporter = OTLPSpanExporter(
endpoint="http://otel-collector.meta.svc:4318/v1/traces",
# insecure=True,
)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
# Flask App
# === Flask App ===
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
def get_trace_id():
span = get_current_span()
if span:
return span.get_span_context().trace_id
return None
# FlaskInstrumentor().instrument_app(app) # auto-instruments metrics + traces
@app.route("/")
def root():
with tracer.start_as_current_span("handle_homepage"):
logger.info("handling request", extra={"trace_id": get_trace_id()})
logger.info(
"handling request",
extra={"trace_id": get_current_span().get_span_context().trace_id},
)
time.sleep(random.random() * 0.05) # simulate request parsing
fetch_user()
calculate_recommendations()
return "Hello from / with traces!"
return "Hello from / with traces and metrics!"
def fetch_user():

View file

@ -15,11 +15,18 @@ spec:
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.81.0
image: otel/opentelemetry-collector:latest
args: ["--config=/etc/otel/otel-collector-config.yaml"]
volumeMounts:
- name: otel-config
mountPath: /etc/otel
ports:
- name: prom
containerPort: 8889
- name: grpc
containerPort: 4317
- name: http
containerPort: 4318
volumes:
- name: otel-config
configMap:
@ -40,19 +47,12 @@ data:
http:
endpoint: 0.0.0.0:4318
processors:
spanmetrics:
dimensions:
- name: operation
default: unknown
metrics_exporter: prometheus
aggregation_temporality: "AGGREGATION_TEMPORALITY_CUMULATIVE"
exporters:
otlp:
endpoint: tempo:4317
tls:
insecure: true
prometheus:
endpoint: "0.0.0.0:8889"
@ -60,18 +60,20 @@ data:
pipelines:
traces:
receivers: [otlp]
processors: [spanmetrics]
exporters: [otlp]
metrics:
receivers: []
receivers: [otlp]
exporters: [prometheus]
processors: []
---
apiVersion: v1
kind: Service
metadata:
name: otel-collector
namespace: meta
labels:
release: kps # this MUST match the `release:` label used by your Prometheus install
app: otel-collector
spec:
ports:
- name: grpc
@ -80,5 +82,23 @@ spec:
- name: http
port: 4318
targetPort: 4318
- name: prom
port: 8889
targetPort: 8889
selector:
app: otel-collector
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: otel-collector
namespace: meta
labels:
release: kps # again, must match Prometheus's release label
spec:
selector:
matchLabels:
app: otel-collector
endpoints:
- port: prom
interval: 15s

View file

@ -2,49 +2,42 @@ apiVersion: v1
kind: ConfigMap
metadata:
name: my-python-app
namespace: meta
namespace: prod
data:
main.py: |
import time
import random
from flask import Flask
from opentelemetry import trace
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.instrumentation.flask import FlaskInstrumentor
import logging
from opentelemetry.trace import get_current_span
SERVICE_NAME = "my-flask-app" # match your OTEL resource name
# === Resource ===
resource = Resource(attributes={"service.name": "my-flask-app"})
trace.set_tracer_provider(
TracerProvider(resource=Resource.create({"service.name": "my-flask-app"}))
)
# === Tracing ===
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
span_processor = BatchSpanProcessor(OTLPSpanExporter())
trace.get_tracer_provider().add_span_processor(span_processor)
# === Logging with trace ID ===
class TraceIdFilter(logging.Filter):
def filter(self, record):
span = trace.get_current_span()
ctx = span.get_span_context()
if ctx.trace_id != 0:
record.trace_id = format(ctx.trace_id, "032x")
record.span_id = format(ctx.span_id, "016x")
record.service_name = SERVICE_NAME
else:
record.trace_id = None
record.span_id = None
record.trace_id = format(ctx.trace_id, "032x") if ctx.trace_id != 0 else None
record.span_id = format(ctx.span_id, "016x") if ctx.span_id != 0 else None
return True
formatter = logging.Formatter(
'{"message": "%(message)s", "trace_id": "%(trace_id)s", "span_id": "%(span_id)s", "service_name": "my-flask-app"}'
'{"message": "%(message)s", "trace_id": "%(trace_id)s", "span_id": "%(span_id)s"}'
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
@ -54,43 +47,22 @@ data:
logger.setLevel(logging.INFO)
logger.addHandler(handler)
resource = Resource(
attributes={
"service.name": "python-otel-demo-app", # 👈 choose any name you like
}
)
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
otlp_exporter = OTLPSpanExporter(
endpoint="http://otel-collector.meta.svc:4318/v1/traces",
# insecure=True,
)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
# === Flask App ===
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
def get_trace_id():
span = get_current_span()
if span:
return span.get_span_context().trace_id
return None
FlaskInstrumentor().instrument_app(app) # auto-instruments metrics + traces
@app.route("/")
def root():
with tracer.start_as_current_span("handle_homepage"):
logger.info("handling request", extra={"trace_id": get_trace_id()})
logger.info(
"handling request",
extra={"trace_id": get_current_span().get_span_context().trace_id},
)
time.sleep(random.random() * 0.05) # simulate request parsing
fetch_user()
calculate_recommendations()
return "Hello from / with traces!"
return "Hello from / with traces and metrics!"
def fetch_user():
@ -112,7 +84,7 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: python-otel
namespace: meta
namespace: prod
labels:
app: python-otel
spec:
@ -156,7 +128,7 @@ apiVersion: v1
kind: Service
metadata:
name: python-otel
namespace: meta
namespace: prod
spec:
selector:
app: python-otel