diff --git a/.gitignore b/.gitignore index 6899691..5c131e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .null-ls_806023_main.py +.null-ls_378979_main.py diff --git a/justfile b/justfile index b9e25ed..b376999 100644 --- a/justfile +++ b/justfile @@ -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}") diff --git a/k8s-monitoring-values.yml b/k8s-monitoring-values.yml index a5c8458..08e719b 100644 --- a/k8s-monitoring-values.yml +++ b/k8s-monitoring-values.yml @@ -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: diff --git a/kubeconfig.yaml b/kubeconfig.yaml new file mode 100644 index 0000000..3ff4ac0 --- /dev/null +++ b/kubeconfig.yaml @@ -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= + diff --git a/logspammer.yaml b/logspammer.yaml new file mode 100644 index 0000000..a053c07 --- /dev/null +++ b/logspammer.yaml @@ -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 +--- diff --git a/loki-values.yml b/loki-values.yaml similarity index 100% rename from loki-values.yml rename to loki-values.yaml diff --git a/main.py b/main.py index e179293..6d84315 100644 --- a/main.py +++ b/main.py @@ -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(): diff --git a/otel-collector.yaml b/otel-collector.yaml index 80a3ce0..bc01d58 100644 --- a/otel-collector.yaml +++ b/otel-collector.yaml @@ -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 diff --git a/pyservice.yaml b/pyservice.yaml index fcad3c2..ce0464f 100644 --- a/pyservice.yaml +++ b/pyservice.yaml @@ -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