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
1 change: 1 addition & 0 deletions .changelog/5244.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-instrumentation-fastapi`: add rolldice reference application example
102 changes: 102 additions & 0 deletions .github/workflows/reference-app-rolldice.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: "Reference App: rolldice"

on:
push:
branches: [main]
paths:
- "instrumentation/opentelemetry-instrumentation-fastapi/examples/rolldice/**"
pull_request:
paths:
- "instrumentation/opentelemetry-instrumentation-fastapi/examples/rolldice/**"

permissions:
contents: read

jobs:
smoke-test:
strategy:
matrix:
variant: [instrumented, uninstrumented]
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
working-directory: instrumentation/opentelemetry-instrumentation-fastapi/examples/rolldice
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install instrumented version
if: matrix.variant == 'instrumented'
run: pip install .

- name: Install uninstrumented version
if: matrix.variant == 'uninstrumented'
run: pip install fastapi "uvicorn[standard]>=0.20"

- name: Start server (instrumented)
if: matrix.variant == 'instrumented'
run: |
python -m app.main &
echo "SERVER_PID=$!" >> "$GITHUB_ENV"

- name: Start server (uninstrumented)
if: matrix.variant == 'uninstrumented'
run: |
cd uninstrumented
python -m app.main &
echo "SERVER_PID=$!" >> "$GITHUB_ENV"

- name: Wait for server to be ready
run: |
for i in $(seq 1 30); do
if curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/rolldice | grep -q 200; then
echo "Server ready"
exit 0
fi
sleep 1
done
echo "Server failed to start"
exit 1

- name: "Smoke test: default roll (rolls omitted)"
run: |
status=$(curl -s -o /tmp/response.txt -w '%{http_code}' http://localhost:8080/rolldice)
echo "Status: $status, Body: $(cat /tmp/response.txt)"
test "$status" = "200"
# Body should be a single digit 1-6
grep -qE '^[1-6]$' /tmp/response.txt

- name: "Smoke test: multiple rolls"
run: |
status=$(curl -s -o /tmp/response.txt -w '%{http_code}' 'http://localhost:8080/rolldice?rolls=3')
echo "Status: $status, Body: $(cat /tmp/response.txt)"
test "$status" = "200"
# Body should be a JSON array of 3 numbers
python -c "import json; d=json.load(open('/tmp/response.txt')); assert len(d)==3 and all(1<=v<=6 for v in d)"

- name: "Smoke test: invalid rolls (non-numeric) → 400"
run: |
status=$(curl -s -o /tmp/response.txt -w '%{http_code}' 'http://localhost:8080/rolldice?rolls=abc')
echo "Status: $status, Body: $(cat /tmp/response.txt)"
test "$status" = "400"
python -c "import json; d=json.load(open('/tmp/response.txt')); assert d['message']=='Parameter rolls must be a positive integer'"

- name: "Smoke test: zero rolls → 500"
run: |
status=$(curl -s -o /tmp/response.txt -w '%{http_code}' 'http://localhost:8080/rolldice?rolls=0')
echo "Status: $status, Body: $(cat /tmp/response.txt)"
test "$status" = "500"

- name: "Smoke test: player parameter"
run: |
status=$(curl -s -o /tmp/response.txt -w '%{http_code}' 'http://localhost:8080/rolldice?rolls=1&player=Alice')
echo "Status: $status, Body: $(cat /tmp/response.txt)"
test "$status" = "200"

- name: Stop server
if: always()
run: kill $SERVER_PID 2>/dev/null || true
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

FROM python:3.14-slim

WORKDIR /app

# Copy the project definition first so pip can install dependencies.
# Layering this before the source copy lets Docker cache the dependency
# layer when only application code changes.
COPY pyproject.toml .
RUN pip install --no-cache-dir .

# Copy application source and configuration after dependencies are installed.
COPY otel-config.yaml .
COPY app/ app/
COPY library/ library/

EXPOSE 8080

# Key environment variables (override with -e / --env):
# OTEL_EXPORTER_OTLP_ENDPOINT — OTLP HTTP endpoint (default: http://localhost:4318)
# OTEL_RESOURCE_ATTRIBUTES — extra resource attributes, e.g. deployment.environment=prod
# APPLICATION_PORT — listening port (default: 8080)
CMD ["python", "-m", "app.main"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
Rolldice — OpenTelemetry Reference Application (FastAPI)
=========================================================

This is the Python/FastAPI implementation of the
`OpenTelemetry reference application specification <https://opentelemetry.io/docs/getting-started/reference-application-specification/>`_.

It demonstrates all four OpenTelemetry signal types in a single service:

* **Traces** — automatic HTTP spans via ``FastAPIInstrumentor``, plus manual
child spans for the dice-rolling business logic.
* **Metrics** — a call counter, an outcome histogram, and an observable gauge,
defined in the library using only the OTel API.
* **Logs** — structured log records emitted through Python's ``logging`` module
and forwarded to OpenTelemetry via ``LoggingHandler`` (the log bridge).
* **Resources** — process, OS, and environment metadata attached automatically
via resource detectors.

Architecture
------------

The code is split into two modules to illustrate the recommended library/application boundary:

``library/rolldice.py``
Dice logic. Imports only from ``opentelemetry`` (the API package) — **no**
SDK imports. This makes the library usable in any application regardless
of which SDK implementation is chosen.

``otel-config.yaml``
Declarative configuration file. Defines the trace, metric, and log
pipelines (exporters, processors, samplers, resource detectors) in a YAML
file following the `OpenTelemetry Configuration Schema
<https://github.com/open-telemetry/opentelemetry-configuration>`_. This
replaces programmatic SDK initialization and allows operators to change
telemetry behavior without modifying code.

``app/telemetry.py``
SDK initialization. Loads ``otel-config.yaml`` using the SDK's file
configuration API and installs the configured providers globally. Also
sets up the Python logging bridge (``LoggingHandler``), which is not
covered by the config schema.

``app/main.py``
FastAPI application. Imports ``app.telemetry`` first, then creates the
``FastAPI`` app, instruments it, and defines the ``/rolldice`` endpoint.

Prerequisites
-------------

* Python ≥ 3.12
* ``pip`` or ``uv``

Install
-------

.. code-block:: bash

cd instrumentation/opentelemetry-instrumentation-fastapi/examples/rolldice
pip install .

Run
---

.. code-block:: bash

uvicorn app.main:app --host 0.0.0.0 --port 8080

Or directly:

.. code-block:: bash

python -m app.main

The server listens on port 8080 by default. Set ``APPLICATION_PORT`` to use a
different port.

Test the API
------------

.. code-block:: bash

# Single roll (default) — returns a number, e.g. "4"
curl "http://localhost:8080/rolldice"

# Five rolls — returns a JSON array, e.g. [3, 5, 2, 6, 1]
curl "http://localhost:8080/rolldice?rolls=5"

# With a player name (shown in DEBUG logs)
curl "http://localhost:8080/rolldice?rolls=3&player=Alice"

# Invalid input — returns HTTP 400 with error JSON
curl "http://localhost:8080/rolldice?rolls=abc"

# Non-positive rolls — returns HTTP 500 with empty body
curl "http://localhost:8080/rolldice?rolls=-1"

Telemetry output
~~~~~~~~~~~~~~~~

Span and metric data is printed to stdout (via the console exporters) even
without a running collector, so you can verify instrumentation locally.

Configuration
-------------

Telemetry behavior is defined in ``otel-config.yaml``. You can edit this file
to add or remove exporters, change sampling strategies, or adjust processor
settings — no code changes required.

Environment variables
---------------------

+-----------------------------------+----------------------------------------------+-------------------------+
| Variable | Description | Default |
+===================================+==============================================+=========================+
| ``OTEL_SERVICE_NAME`` | Service name reported in telemetry | ``rolldice`` |
+-----------------------------------+----------------------------------------------+-------------------------+
| ``OTEL_EXPORTER_OTLP_ENDPOINT`` | OTLP HTTP endpoint for the backend | ``http://localhost:4318``|
+-----------------------------------+----------------------------------------------+-------------------------+
| ``OTEL_RESOURCE_ATTRIBUTES`` | Extra resource attributes (``key=value,...``)| *(none)* |
+-----------------------------------+----------------------------------------------+-------------------------+
| ``OTEL_LOG_LEVEL`` | Verbosity of OTel diagnostic output | ``info`` |
+-----------------------------------+----------------------------------------------+-------------------------+
| ``APPLICATION_PORT`` | Listening port | ``8080`` |
+-----------------------------------+----------------------------------------------+-------------------------+

Run with an OTLP backend
------------------------

Start the `OpenTelemetry Collector <https://opentelemetry.io/docs/collector/>`_ or
another OTLP-compatible backend, then:

.. code-block:: bash

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
uvicorn app.main:app --host 0.0.0.0 --port 8080

Docker
------

Build:

.. code-block:: bash

docker build -t rolldice .

Run (without a collector — console output only):

.. code-block:: bash

docker run -p 8080:8080 rolldice

Run (with a collector on the host):

.. code-block:: bash

docker run -p 8080:8080 \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4318 \
rolldice
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
Loading
Loading