Repository navigation
Add rolldice reference application (FastAPI) #5244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2b97f1e
4392aa3
d871490
4456610
57bada3
e1bd722
aaa307f
672004d
9c72c96
123e706
37147ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `opentelemetry-instrumentation-fastapi`: add rolldice reference application example |
| 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/**" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Needs updating. |
||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a fix in the path here? |
||
| pip install . | ||
|
|
||
| Run | ||
| --- | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| uvicorn app.main:app --host 0.0.0.0 --port 8080 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say we can mention in advance that an OTLP backend needs to be running otherwise, the user will see a lot of failures in the logs. I know we have this instruction later in this guide, but it would be nice to "enforce" the user to have a collector running before executing this command. |
||
|
|
||
| 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 |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we follow the same approach as other tests and implement the logic in Python code and run it through tox, so anyone can run it locally without relying on the CI feedback. You can take a look at docker-tests