diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..96ff94d5 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,84 @@ +# OpenTelemetry Examples + +This folder contains minimal OpenTelemetry examples that demonstrate how to instrument HTTP services and export telemetry signals. + +## What is OpenTelemetry? + +OpenTelemetry (often abbreviated as OTel) is an open standard and SDK ecosystem for observability. It provides a vendor-neutral way to instrument services and export telemetry to different backends without changing your application logic for each platform. + +In practice, OpenTelemetry helps you answer questions such as: + +- Which service or endpoint is slow? +- Where did a request fail across multiple components? +- Is error rate or latency increasing over time? + +It helps applications produce: + +- **Traces**: end-to-end request flow and latency across components. +- **Metrics**: numeric time series (for example request counters and error rates). +- **Logs**: structured event records from application execution. + +In these examples, services export telemetry through OTLP (OpenTelemetry Protocol) to an OpenTelemetry Collector endpoint, which redirects it to the respective backend. The collector maintained by CMS Monitoring supports metrics through a VictoriaMetrics backend, logs through Opensearch and traces through Grafana Tempo. + +## Documentation links + +- OpenTelemetry overview: [https://opentelemetry.io/docs/what-is-opentelemetry/](https://opentelemetry.io/docs/what-is-opentelemetry/) +- OpenTelemetry docs home: [https://opentelemetry.io/docs/](https://opentelemetry.io/docs/) +- OpenTelemetry specification: [https://opentelemetry.io/docs/specs/](https://opentelemetry.io/docs/specs/) +- OTLP specification: [https://opentelemetry.io/docs/specs/otlp/](https://opentelemetry.io/docs/specs/otlp/) +- OpenTelemetry Collector: [https://opentelemetry.io/docs/collector/](https://opentelemetry.io/docs/collector/) +- Python instrumentation docs: [https://opentelemetry.io/docs/languages/python/](https://opentelemetry.io/docs/languages/python/) +- Go instrumentation docs: [https://opentelemetry.io/docs/languages/go/](https://opentelemetry.io/docs/languages/go/) + +## Layout of the provided examples + +- `python_app_otel_example.py` + - General Python worker-style process (non-web example). + - Emits traces, metrics, and logs in a loop. + +- `flask_app_otel_example.py` + - Python HTTP server using Flask (`GET /`). + - Creates one span per request and increments a request counter. + +- `cherrypy_app_otel_example.py` + - Python HTTP server using CherryPy (`GET /`). + - Creates one span per request and increments a request counter. + +- `go_app_otel_example.go` + - Go HTTP server (`GET /` on `127.0.0.1:8081`). + - Creates one span per request, increments a request counter, and emits logs. + +## Common configuration + +All examples support these environment variables: + +- `OTEL_EXPORTER_OTLP_ENDPOINT`: OTLP gRPC collector endpoint (default is set in each example). +- `OTEL_SERVICE_NAME`: logical service name shown in telemetry backends. +- `OPENTELEMETRY_USERNAME` / `OPENTELEMETRY_PASSWORD`: optional basic auth credentials used to build OTLP headers. +- `OTEL_METRIC_EXPORT_INTERVAL`: export interval in milliseconds (Python examples). + +## Running the instrumented HTTP server examples + +From this directory: + +- Flask: + - `python3 flask_app_otel_example.py` + - open `http://127.0.0.1:5000/` +- CherryPy: + - `python3 cherrypy_app_otel_example.py` + - open `http://127.0.0.1:8080/` +- Go: + - `go mod init examples/go-otel-toy` + - `go mod tidy` + - `go run go_app_otel_example.go` + - open `http://127.0.0.1:8081/` + +## Client usage + +Any HTTP client can generate traffic for these instrumented servers and trigger telemetry generation. For example: + +- `curl http://127.0.0.1:5000/` (Flask) +- `curl http://127.0.0.1:8080/` (CherryPy) +- `curl http://127.0.0.1:8081/` (Go) + +The resulting metrics and traces can then be consumed from your observability backend (for example via OpenTelemetry Collector pipelines into Prometheus/Grafana, OpenSearch, Jaeger, or another OTLP-compatible system). diff --git a/examples/cherrypy_app_otel_example.py b/examples/cherrypy_app_otel_example.py new file mode 100644 index 00000000..b2139745 --- /dev/null +++ b/examples/cherrypy_app_otel_example.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Toy OpenTelemetry example for a CherryPy application. + +What this shows: +- Traces: creates one span per HTTP request. +- Metrics: increments a request counter per endpoint. +- Logs: emits request logs through OpenTelemetry to the collector. + +Prerequisites: +1) Python 3.10+ +2) Install dependencies: + pip install CherryPy opentelemetry-api opentelemetry-sdk \ + opentelemetry-exporter-otlp-proto-grpc + +Collector configuration: +- Uses OTLP gRPC endpoint from OTEL_EXPORTER_OTLP_ENDPOINT. +- Defaults to: http://cms-monitoring-test.cern.ch:30428 +- Basic authentication: + - OPENTELEMETRY_USERNAME + - OPENTELEMETRY_PASSWORD + +Run: + OTEL_SERVICE_NAME=cherrypy-toy-app python3 cherrypy_app_otel_example.py +Then open: + http://127.0.0.1:8080/ +""" + +from __future__ import annotations + +import base64 +import logging +import os +import time + +import cherrypy +from opentelemetry import metrics, trace +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor + + +def _otlp_headers() -> dict[str, str]: + username = os.getenv("OPENTELEMETRY_USERNAME", "") + password = os.getenv("OPENTELEMETRY_PASSWORD", "") + token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8") + return {"authorization": f"Basic {token}"} + + +def configure_otel(): + endpoint = os.getenv( + "OTEL_EXPORTER_OTLP_ENDPOINT", + "opentelemetry-collector.opentelemetry.svc.cluster.local:4317", + ) + service_name = os.getenv("OTEL_SERVICE_NAME", "cherrypy-toy-app") + metric_interval_ms = int(os.getenv("OTEL_METRIC_EXPORT_INTERVAL", "15000")) + headers = _otlp_headers() + + resource = Resource.create({"service.name": service_name}) + + trace_provider = TracerProvider(resource=resource) + trace_provider.add_span_processor( + BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint, insecure=True, headers=headers)) + ) + trace.set_tracer_provider(trace_provider) + + metric_reader = PeriodicExportingMetricReader( + OTLPMetricExporter(endpoint=endpoint, insecure=True, headers=headers), + export_interval_millis=metric_interval_ms, + ) + meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader]) + metrics.set_meter_provider(meter_provider) + + logger_provider = LoggerProvider(resource=resource) + logger_provider.add_log_record_processor( + BatchLogRecordProcessor( + OTLPLogExporter(endpoint=endpoint, insecure=True, headers=headers) + ) + ) + set_logger_provider(logger_provider) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.INFO) + root_logger.handlers.clear() + root_logger.addHandler(logging.StreamHandler()) + root_logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + + return trace_provider, meter_provider, logger_provider + + +TRACE_PROVIDER, METER_PROVIDER, LOGGER_PROVIDER = configure_otel() +TRACER = trace.get_tracer("examples.cherrypy.app") +METER = metrics.get_meter("examples.cherrypy.app") +REQUEST_COUNTER = METER.create_counter("toy_http_requests_total") +LOGGER = logging.getLogger("cherrypy-toy-app") + + +class ToyApp: + @cherrypy.expose + def index(self) -> str: + with TRACER.start_as_current_span("GET /"): + REQUEST_COUNTER.add(1, {"http.route": "/"}) + LOGGER.info("Handled CherryPy request", extra={"path": "/"}) + time.sleep(0.05) + return "Hello from CherryPy + OpenTelemetry toy example\n" + + +if __name__ == "__main__": + cherrypy.config.update({"server.socket_host": "127.0.0.1", "server.socket_port": 8080}) + LOGGER.info("Starting CherryPy toy app") + cherrypy.quickstart(ToyApp()) + LOGGER_PROVIDER.force_flush() + TRACE_PROVIDER.force_flush() + METER_PROVIDER.force_flush() diff --git a/examples/flask_app_otel_example.py b/examples/flask_app_otel_example.py new file mode 100644 index 00000000..390f5772 --- /dev/null +++ b/examples/flask_app_otel_example.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +Toy OpenTelemetry example for a Flask application. + +What this shows: +- Traces: creates one span per HTTP request. +- Metrics: increments a request counter per endpoint. +- Logs: emits request logs through OpenTelemetry to the collector. + +Prerequisites: +1) Python 3.10+ +2) Install dependencies: + pip install flask opentelemetry-api opentelemetry-sdk \ + opentelemetry-exporter-otlp-proto-grpc + +Collector configuration: +- Uses OTLP gRPC endpoint from OTEL_EXPORTER_OTLP_ENDPOINT. +- Defaults to: opentelemetry-collector.opentelemetry.svc.cluster.local:4317 +- Basic authentication: + - OPENTELEMETRY_USERNAME + - OPENTELEMETRY_PASSWORD + +Run: + OTEL_SERVICE_NAME=flask-toy-app python3 flask_app_otel_example.py +Then open: + http://127.0.0.1:5000/ +""" + +from __future__ import annotations + +import base64 +import logging +import os +import time + +from flask import Flask, request +from opentelemetry import metrics, trace +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor + + +def _otlp_headers() -> dict[str, str]: + username = os.getenv("OPENTELEMETRY_USERNAME", "") + password = os.getenv("OPENTELEMETRY_PASSWORD", "") + token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8") + return {"authorization": f"Basic {token}"} + + +def configure_otel(): + endpoint = os.getenv( + "OTEL_EXPORTER_OTLP_ENDPOINT", + "opentelemetry-collector.opentelemetry.svc.cluster.local:4317", + ) + service_name = os.getenv("OTEL_SERVICE_NAME", "flask-toy-app") + metric_interval_ms = int(os.getenv("OTEL_METRIC_EXPORT_INTERVAL", "15000")) + headers = _otlp_headers() + + resource = Resource.create({"service.name": service_name}) + + trace_provider = TracerProvider(resource=resource) + trace_provider.add_span_processor( + BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint, insecure=True, headers=headers)) + ) + trace.set_tracer_provider(trace_provider) + + metric_reader = PeriodicExportingMetricReader( + OTLPMetricExporter(endpoint=endpoint, insecure=True, headers=headers), + export_interval_millis=metric_interval_ms, + ) + meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader]) + metrics.set_meter_provider(meter_provider) + + logger_provider = LoggerProvider(resource=resource) + logger_provider.add_log_record_processor( + BatchLogRecordProcessor( + OTLPLogExporter(endpoint=endpoint, insecure=True, headers=headers) + ) + ) + set_logger_provider(logger_provider) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.INFO) + root_logger.handlers.clear() + root_logger.addHandler(logging.StreamHandler()) + root_logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + + return trace_provider, meter_provider, logger_provider + + +TRACE_PROVIDER, METER_PROVIDER, LOGGER_PROVIDER = configure_otel() +TRACER = trace.get_tracer("examples.flask.app") +METER = metrics.get_meter("examples.flask.app") +REQUEST_COUNTER = METER.create_counter("toy_http_requests_total") +LOGGER = logging.getLogger("flask-toy-app") + +app = Flask(__name__) + + +@app.route("/") +def index() -> str: + with TRACER.start_as_current_span("GET /") as span: + span.set_attribute("http.method", request.method) + span.set_attribute("http.route", "/") + REQUEST_COUNTER.add(1, {"http.route": "/"}) + LOGGER.info("Handled Flask request", extra={"path": "/"}) + time.sleep(0.05) + return "Hello from Flask + OpenTelemetry toy example\n" + + +if __name__ == "__main__": + LOGGER.info("Starting Flask toy app") + try: + app.run(host="127.0.0.1", port=5000, debug=False) + finally: + LOGGER_PROVIDER.force_flush() + TRACE_PROVIDER.force_flush() + METER_PROVIDER.force_flush() diff --git a/examples/go_app_otel_example.go b/examples/go_app_otel_example.go new file mode 100644 index 00000000..658188bd --- /dev/null +++ b/examples/go_app_otel_example.go @@ -0,0 +1,187 @@ +/* +Toy OpenTelemetry example for a Go application. + +What this shows: +- Traces: creates one span per HTTP request. +- Metrics: increments a request counter per endpoint. +- Logs: emits OTLP logs to the collector. + +Prerequisites: +1) Go 1.25+ +2) From this folder, initialize the module and resolve imports: + go mod init examples/go-otel-toy + go mod tidy + go run go_app_otel_example.go + +Collector configuration: +- Uses OTLP gRPC endpoint from OTEL_EXPORTER_OTLP_ENDPOINT. +- Defaults to: opentelemetry-collector.opentelemetry.svc.cluster.local:4317 +- Basic authentication: + - OPENTELEMETRY_USERNAME + - OPENTELEMETRY_PASSWORD +*/ +package main + +import ( + "context" + "encoding/base64" + "fmt" + "log" + "net/http" + "os" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/log/global" + otellog "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/metric" + sdklog "go.opentelemetry.io/otel/sdk/log" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "go.opentelemetry.io/otel/trace" +) + +type otelRuntime struct { + tp *sdktrace.TracerProvider + mp *sdkmetric.MeterProvider + lp *sdklog.LoggerProvider + tracer trace.Tracer + counter metric.Int64Counter + logger otellog.Logger +} + +func main() { + ctx := context.Background() + runtime, err := setupOTel(ctx) + if err != nil { + log.Fatalf("failed to setup OpenTelemetry: %v", err) + } + defer func() { + _ = runtime.lp.Shutdown(ctx) + _ = runtime.mp.Shutdown(ctx) + _ = runtime.tp.Shutdown(ctx) + }() + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + ctx, span := runtime.tracer.Start(r.Context(), "GET /") + defer span.End() + + runtime.counter.Add(ctx, 1, metric.WithAttributes(attribute.String("http.route", "/"))) + + record := otellog.Record{} + record.SetTimestamp(time.Now()) + record.SetBody(otellog.StringValue("Handled Go request")) + record.AddAttributes( + otellog.String("http.route", "/"), + otellog.String("http.method", r.Method), + ) + runtime.logger.Emit(ctx, record) + + fmt.Fprintln(w, "Hello from Go + OpenTelemetry toy example") + }) + + log.Println("listening on http://127.0.0.1:8081") + if err := http.ListenAndServe("127.0.0.1:8081", nil); err != nil { + log.Fatalf("server error: %v", err) + } +} + +func setupOTel(ctx context.Context) (*otelRuntime, error) { + endpoint := envOr("OTEL_EXPORTER_OTLP_ENDPOINT", "opentelemetry-collector.opentelemetry.svc.cluster.local:4317") + serviceName := envOr("OTEL_SERVICE_NAME", "go-toy-app") + headers := otlpHeaders() + + res, err := resource.New( + ctx, + resource.WithAttributes( + semconv.ServiceName(serviceName), + ), + ) + if err != nil { + return nil, err + } + + traceExporter, err := otlptracegrpc.New( + ctx, + otlptracegrpc.WithEndpoint(endpoint), + otlptracegrpc.WithInsecure(), + otlptracegrpc.WithHeaders(headers), + ) + if err != nil { + return nil, err + } + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(traceExporter), + sdktrace.WithResource(res), + ) + otel.SetTracerProvider(tp) + + metricExporter, err := otlpmetricgrpc.New( + ctx, + otlpmetricgrpc.WithEndpoint(endpoint), + otlpmetricgrpc.WithInsecure(), + otlpmetricgrpc.WithHeaders(headers), + ) + if err != nil { + return nil, err + } + reader := sdkmetric.NewPeriodicReader(metricExporter, sdkmetric.WithInterval(15*time.Second)) + mp := sdkmetric.NewMeterProvider( + sdkmetric.WithReader(reader), + sdkmetric.WithResource(res), + ) + otel.SetMeterProvider(mp) + + logExporter, err := otlploggrpc.New( + ctx, + otlploggrpc.WithEndpoint(endpoint), + otlploggrpc.WithInsecure(), + otlploggrpc.WithHeaders(headers), + ) + if err != nil { + return nil, err + } + lp := sdklog.NewLoggerProvider( + sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)), + sdklog.WithResource(res), + ) + global.SetLoggerProvider(lp) + + tracer := tp.Tracer("examples.go.app") + meter := mp.Meter("examples.go.app") + counter, err := meter.Int64Counter("toy_http_requests_total") + if err != nil { + return nil, err + } + logger := lp.Logger("examples.go.app") + + return &otelRuntime{ + tp: tp, + mp: mp, + lp: lp, + tracer: tracer, + counter: counter, + logger: logger, + }, nil +} + +func envOr(key, fallback string) string { + value := os.Getenv(key) + if value == "" { + return fallback + } + return value +} + +func otlpHeaders() map[string]string { + username := os.Getenv("OPENTELEMETRY_USERNAME") + password := os.Getenv("OPENTELEMETRY_PASSWORD") + token := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) + return map[string]string{"authorization": "Basic " + token} +} diff --git a/examples/python_app_otel_example.py b/examples/python_app_otel_example.py new file mode 100644 index 00000000..12fb4d90 --- /dev/null +++ b/examples/python_app_otel_example.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +""" +Toy OpenTelemetry example for a general Python application. + +What this shows: +- Traces: creates a span around each unit of work. +- Metrics: increments a counter for each processed work item. +- Logs: emits logs through OpenTelemetry to the collector. + +Prerequisites: +1) Python 3.10+ +2) Install dependencies: + pip install opentelemetry-api opentelemetry-sdk \ + opentelemetry-exporter-otlp-proto-grpc + +Collector configuration: +- Uses OTLP gRPC endpoint from OTEL_EXPORTER_OTLP_ENDPOINT. +- Defaults to: opentelemetry-collector.opentelemetry.svc.cluster.local:4317 +- Basic authentication: + - OPENTELEMETRY_USERNAME + - OPENTELEMETRY_PASSWORD + +Run: + OTEL_SERVICE_NAME=python-toy-app python3 python_app_otel_example.py +""" + +from __future__ import annotations + +import base64 +import logging +import os +import signal +import time + +from opentelemetry import metrics, trace +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.sdk.metrics import MeterProvider +from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor + + +def _otlp_headers() -> dict[str, str]: + username = os.getenv("OPENTELEMETRY_USERNAME", "") + password = os.getenv("OPENTELEMETRY_PASSWORD", "") + token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8") + return {"authorization": f"Basic {token}"} + + +def configure_otel(): + endpoint = os.getenv( + "OTEL_EXPORTER_OTLP_ENDPOINT", + "opentelemetry-collector.opentelemetry.svc.cluster.local:4317", + ) + service_name = os.getenv("OTEL_SERVICE_NAME", "python-toy-app") + metric_interval_ms = int(os.getenv("OTEL_METRIC_EXPORT_INTERVAL", "15000")) + headers = _otlp_headers() + + resource = Resource.create({"service.name": service_name}) + + trace_provider = TracerProvider(resource=resource) + trace_provider.add_span_processor( + BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint, insecure=True, headers=headers)) + ) + trace.set_tracer_provider(trace_provider) + + metric_reader = PeriodicExportingMetricReader( + OTLPMetricExporter(endpoint=endpoint, insecure=True, headers=headers), + export_interval_millis=metric_interval_ms, + ) + meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader]) + metrics.set_meter_provider(meter_provider) + + logger_provider = LoggerProvider(resource=resource) + logger_provider.add_log_record_processor( + BatchLogRecordProcessor( + OTLPLogExporter(endpoint=endpoint, insecure=True, headers=headers) + ) + ) + set_logger_provider(logger_provider) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.INFO) + root_logger.handlers.clear() + root_logger.addHandler(logging.StreamHandler()) + root_logger.addHandler(LoggingHandler(logger_provider=logger_provider)) + + return trace_provider, meter_provider, logger_provider + + +def main() -> None: + trace_provider, meter_provider, logger_provider = configure_otel() + tracer = trace.get_tracer("examples.python.app") + meter = metrics.get_meter("examples.python.app") + jobs_counter = meter.create_counter("toy_jobs_processed_total") + logger = logging.getLogger("python-toy-app") + + running = True + + def _stop(_sig, _frame): + nonlocal running + running = False + + signal.signal(signal.SIGINT, _stop) + signal.signal(signal.SIGTERM, _stop) + + logger.info("Toy app started") + iteration = 0 + while running: + iteration += 1 + with tracer.start_as_current_span("process_work_item") as span: + span.set_attribute("app.iteration", iteration) + jobs_counter.add(1, {"app.component": "worker"}) + logger.info("Processed toy work item %s", iteration) + time.sleep(2) + + logger.info("Toy app shutting down") + logger_provider.force_flush() + trace_provider.force_flush() + meter_provider.force_flush() + + +if __name__ == "__main__": + main()