Skip to main content
In this lesson you’ll instrument a simple Flask app with an OpenTelemetry Counter metric to track how many HTTP requests your application processes. The guide covers:
  • configuring an OpenTelemetry MeterProvider with a console exporter and periodic reader,
  • creating a Counter metric,
  • incrementing the counter on incoming requests,
  • adding attributes to break metrics down by route and method,
  • centralizing instrumentation using a before_request hook.
This is intended for development and testing. For production, use a production-ready WSGI server (examples in References).
This example uses the ConsoleMetricExporter with a PeriodicExportingMetricReader configured for a 5s export interval so you can see metrics printed to the console while testing.
The Flask development server is not suitable for production. Use a production WSGI server such as gunicorn or uwsgi for deployments.

1. Configure the meter and console exporter

Create a function to configure the MeterProvider and attach a periodic exporting reader that prints metrics every 5 seconds. Then get a named meter for your application:
This returns a meter named shopping-app which you’ll use to create instruments.

2. Create a Counter metric

A Counter is appropriate for values that only increase over time (like request counts). Create a Counter and store it:
Use unit strings like "1" for counts, "By" for bytes, or "s" for seconds when appropriate.

3. Instrument routes by incrementing the counter

A simple approach is to call requests_counter.add(1) in every route handler. Example handlers:
When the app runs, the PeriodicExportingMetricReader collects metrics and the ConsoleMetricExporter prints them every 5 seconds. Example run output for the Flask dev server:

4. Add attributes to break metrics down by route and method

To see per-route and per-method counts while retaining a single canonical metric name, add attributes when calling .add(...). Attributes attach metadata to metric data points. Example:
When the console exporter prints JSON, you’ll see the counter broken out by attribute combinations such as route="/products" and method="POST". Example (abridged):
Most metrics backends (Prometheus, observability platforms, etc.) allow grouping or summing across attributes, so you can compute the total while retaining per-route detail.

5. Why use attributes instead of separate metrics per route?

Creating separate metrics like products_requests_total and cart_requests_total is possible but has downsides:
ApproachProsCons
http_requests_total with attributes (route, method)Single canonical metric; easy aggregation across routes; flexible breakdownsRequires careful attribute cardinality control
Separate metrics per route (products_requests_total)Simple per-route metric namesProliferation of metrics, harder to aggregate across all routes
Attributes are the recommended pattern because they enable both detailed breakdowns and straightforward aggregation.

6. Centralize instrumentation with a before_request hook

Instead of invoking requests_counter.add(...) in every handler, increment the counter once for all requests using Flask’s before_request hook. This reduces duplication and ensures consistent instrumentation:
This hook runs before each request handler, counting the request centrally.

7. Complete example (main.py)

A minimal, complete example showing configuration, centralized instrumentation, and a couple of routes:
Run the app and make some requests (curl, REST client, browser). Every 5 seconds the console exporter will print the current metrics grouped by attribute combinations.

8. Summary and best practices

  • Use a Counter (http_requests_total) to count incoming HTTP requests.
  • Attach attributes such as route and method ({"route": route, "method": request.method}) to break metrics down while preserving a single canonical metric name.
  • Centralize counting with @app.before_request to avoid repeating instrumentation across handlers.
  • During development, ConsoleMetricExporter + PeriodicExportingMetricReader helps you see metric output. For production, configure a proper exporter/backend.
  • Avoid using high-cardinality attribute values (e.g., raw user IDs or full URLs). Prefer route patterns (/products/<int:id>) to keep cardinality manageable.
Additional examples and exporter integrations are available in the OpenTelemetry Python repository and documentation.

Watch Video