A small Flask-based demo app to explore Cloud Run observability features (logging, tracing, metrics) and simple Firestore integration.
- A minimal Flask application demonstrating:
- JSON structured logging (using
python-json-logger) - Trace linkage via
X-Cloud-Trace-Contextheader - Simulated latency and CPU load endpoints for tracing/metrics
- Basic Firestore usage for sample user writes
- JSON structured logging (using
- Intended for running on Google Cloud Run (Dockerfile uses a builder + distroless runtime), but also runnable locally.
main.py— Flask app and all endpointsrequirements.txt— Python dependenciesDockerfile— multi-stage build, runtime uses distroless and runsgunicornas a module.gitignore— ignores common Python artifacts
PROJECT_ID(optional) — Google Cloud project id used to initialize Firestore clientDATABASE_ID(optional) — Firestore database id (defaults to(default))
If PROJECT_ID is not provided, Firestore initialization will fail and the app continues with db = None (you can still use non-Firestore endpoints).
- Create and activate a virtual environment:
- python -m venv .venv
- source .venv/bin/activate (or
.venv\Scripts\activateon Windows)
- Install dependencies:
- pip install -r requirements.txt
- Run:
- For simple dev run:
python main.py(starts Flask builtin server on port 8080) - To run with Gunicorn (recommended, matches production):
python -m gunicorn --bind :8080 --workers 1 --threads 8 main:app
- For simple dev run:
- Build:
- docker build -t cloud-run-observability-sandbox .
- Run (example):
- docker run -p 8080:8080 -e PROJECT_ID=your-gcp-project cloud-run-observability-sandbox
The Dockerfile uses a builder stage to install packages into /app/site-packages and a distroless runtime image for a smaller surface area.
GET /— Health / welcome messagePOST /users— Add a user to Firestore- Expects JSON with
idandname
- Expects JSON with
GET /slow-trace— Simulates a slow dependency (sleeps, touches Firestore) — useful to create longer tracesGET /cpu-heavy— Computes some arbitrary CPU task — useful to observe CPU metricsGET /flaky— Randomly returns 500 to simulate intermittent errorsGET /crash— Demonstrate a runtime error and how Error reporting records and keeps track of errorsGET /cached-config— Demonstrates cold vs warm start behaviour using a global cache
- Logging:
- Uses
python-json-loggerto emit JSON logs to stdout. - Logs include a
trace_idfield extracted from theX-Cloud-Trace-Contextheader so logs can be correlated with Cloud Trace. - Logs also include a
componentfield to filter by logical area (e.g.,trace-demo,cpu-demo,cache,user-module).
- Uses
- Traces:
- The app reads
X-Cloud-Trace-Contextheader and includes the trace id in log payloads to link logs and traces.
- The app reads
- Metrics & Alerts:
- Use
cpu-heavyandslow-traceto generate observable signals for metrics dashboards and alerting rules.
- Use
Optional: Service-to-service tracing (service_b integration)
- Purpose
- If you want richer, linked traces for requests that span services (service A → service B) and more visibility into individual spans within each service, enable service-to-service trace context propagation and per-service instrumentation.
- What this repo already provides (service_b example)
service_b.pydemonstrates per-service instrumentation:FlaskInstrumentor().instrument_app(app)to capture incoming HTTP spans.RequestsInstrumentor().instrument()to automatically inject/extract trace propagation headers on outgoing requests.
setup_opentelemetry.pyconfigures OTLP exporters for traces, logs, and metrics so spans and logs are exported to your collector/Cloud Trace.
- How to enable better cross-service traces
- Ensure every service:
- Initializes OpenTelemetry SDK (tracer provider and OTLP exporter) like
setup_opentelemetry.py. - Instruments the web framework (e.g., Flask) to capture incoming spans.
- Instruments the HTTP client (requests, httpx, etc.) to propagate context on outgoing calls so the downstream service receives the same trace id.
- Initializes OpenTelemetry SDK (tracer provider and OTLP exporter) like
- In practice:
- On the caller side, instrument the outgoing HTTP client (RequestsInstrumentor is used in
service_b.py) — this will add the necessary headers for propagation. - On the callee side, instrument the web framework (FlaskInstrumentor) so incoming requests create child spans under the propagated trace.
- On the caller side, instrument the outgoing HTTP client (RequestsInstrumentor is used in
- Manual propagation (optional):
- If you need explicit control, read the incoming header (e.g.,
X-Cloud-Trace-Context) and forward it on outgoing requests:- Read:
trace_header = request.headers.get("X-Cloud-Trace-Context") - Forward:
requests.post(url, json=payload, headers={"X-Cloud-Trace-Context": trace_header})
- Read:
- Automatic instrumentation (RequestsInstrumentor) usually handles this for you and is recommended.
- If you need explicit control, read the incoming header (e.g.,
- Ensure every service:
- Single-service detailed spans and visibility
- To get more granular spans inside a service, use an explicit tracer and create spans around important operations:
- Use the SDK tracer (via
trace.get_tracer(__name__)) andwith tracer.start_as_current_span("operation-name"):around database calls, business logic, or outgoing requests. - This gives you fine-grained spans within a service so Cloud Trace shows not only the request-level span but also sub-operations.
- Use the SDK tracer (via
- To get more granular spans inside a service, use an explicit tracer and create spans around important operations:
- When to use this
- Enable service-to-service propagation when you want end-to-end traces across microservices.
- Add extra internal spans when you need deeper visibility into bottlenecks or want to surface internal operations in Cloud Trace.
- The app initializes a Firestore client at startup using
google-cloud-firestore. - If initialization fails,
dbfalls back toNoneand DB-related endpoints will skip database operations (but still run). POST /userswrites to thesampleUserscollection.
- Keep changes small and focused: this repo is a demo to exercise observability features.
- When adding endpoints, include structured logging with
componentandtrace_idinextrafor consistency. - Avoid adding secrets or service account keys to the repo. Use environment variables or Cloud Run/IAM service accounts for production credentials.
- If Firestore calls fail locally, ensure application has GCP credentials (e.g.,
gcloud auth application-default login) or unsetPROJECT_IDto run without DB. - For local tracing/log correlation, include a header like:
X-Cloud-Trace-Context: TRACE_ID/123;o=1whereTRACE_IDis a hex trace id.
- MIT