Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -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).
122 changes: 122 additions & 0 deletions examples/cherrypy_app_otel_example.py
Original file line number Diff line number Diff line change
@@ -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()
126 changes: 126 additions & 0 deletions examples/flask_app_otel_example.py
Original file line number Diff line number Diff line change
@@ -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()
Loading
Loading