Thành Đạt Vũ
10/29/2025, 3:21 AMMarvin
10/29/2025, 3:21 AMMarvin
10/29/2025, 3:25 AMPREFECT_LOGGING_TO_API_ENABLED=true on the worker and on any flow containers.
- Optionally tweak batching/levels via settings.
- Docs:
- Logging customization
- Settings reference
- Workers
Approach B: OpenTelemetry logs/traces via auto-instrumentation
- Install OTEL SDK + exporter in your worker image and start the worker via opentelemetry-instrument so Python logs/HTTP calls/etc. are exported to your collector.
1) Minimal Dockerfile for your worker image
FROM prefecthq/prefect:3-latest
# Or your existing worker base image
# Install OTEL Python SDK and OTLP exporter
RUN pip install --no-cache-dir \
opentelemetry-sdk \
opentelemetry-exporter-otlp \
opentelemetry-instrumentation \
opentelemetry-instrumentation-logging
2) docker-compose.yml snippet
services:
otel-collector:
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otelcol/config.yaml"]
volumes:
- ./otel-collector.yaml:/etc/otelcol/config.yaml:ro
ports:
- "4317:4317" # gRPC
- "4318:4318" # HTTP
prefect-worker:
image: your-prefect-worker:latest
# Run the worker through OTEL auto-instrumentation
command: >
opentelemetry-instrument
--traces_exporter otlp
--metrics_exporter otlp
--logs_exporter otlp
prefect worker start --pool YOUR_POOL_NAME
environment:
# Prefect connectivity
- PREFECT_API_URL=<https://api.prefect.cloud/api/accounts/xxx/workspaces/yyy>
- PREFECT_API_KEY=${PREFECT_API_KEY}
# Optionally also send logs to Prefect (independent of OTEL)
- PREFECT_LOGGING_TO_API_ENABLED=true
# OTEL config
- OTEL_SERVICE_NAME=prefect-worker
- OTEL_RESOURCE_ATTRIBUTES=service.namespace=prefect,work.pool=YOUR_POOL_NAME
- OTEL_EXPORTER_OTLP_ENDPOINT=<http://otel-collector:4317>
- OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# If your collector/back-end needs headers (example)
# - OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer YOUR_TOKEN
# Helpful tuning
- OTEL_LOG_LEVEL=info
- OTEL_PYTHON_LOG_CORRELATION=true
depends_on:
- otel-collector
restart: unless-stopped
3) Collector config (example)
receivers:
otlp:
protocols:
grpc:
http:
exporters:
# Replace with the exporter(s) you need (Tempo, Loki, Datadog, etc.)
logging:
loglevel: info
service:
pipelines:
traces:
receivers: [otlp]
exporters: [logging]
logs:
receivers: [otlp]
exporters: [logging]
metrics:
receivers: [otlp]
exporters: [logging]
Important notes
- If your work pool launches flows in separate containers (e.g., Docker or Kubernetes pools), you must add the same OTEL dependencies and opentelemetry-instrument startup to those containers as well. Otherwise you’ll only get worker-level telemetry, not flow-run telemetry.Marvin
10/29/2025, 3:25 AMPREFECT_LOGGING_TO_API_ENABLED=true for simplicity.
Related Prefect docs
- Logging customization
- Settings reference
- Workers
- Self-hosted with Docker Compose
OpenTelemetry Python docs
- Auto-instrumentation
- Logs
If you share which backend you’re targeting and your work pool type, I can tailor the compose + config (and the flow container setup if needed).Thành Đạt Vũ
10/29/2025, 3:47 AMMarvin
10/29/2025, 3:47 AMMarvin
10/29/2025, 3:49 AMpip install --no-cache-dir \
opentelemetry-sdk \
opentelemetry-exporter-otlp \
opentelemetry-instrumentation \
opentelemetry-instrumentation-logging
2) Run both processes via opentelemetry-instrument
- Worker (docker-compose):
command: >
opentelemetry-instrument
--traces_exporter otlp
--metrics_exporter otlp
--logs_exporter otlp
prefect worker start --pool YOUR_DOCKER_POOL
- Flow image (so it instruments whatever command Prefect injects):
ENTRYPOINT ["opentelemetry-instrument", "--traces_exporter", "otlp", "--logs_exporter", "otlp"]
# no CMD here — Prefect will set the flow command at runtime
3) Environment variables (debug + connectivity)
Set these in BOTH worker and flow containers:
# Identify the service
OTEL_SERVICE_NAME=prefect-worker # worker image
# for flows, build/override to something like:
# OTEL_SERVICE_NAME=prefect-flow
OTEL_RESOURCE_ATTRIBUTES=service.namespace=prefect,work.pool=docker
# Endpoint/protocol — match your collector
OTEL_EXPORTER_OTLP_ENDPOINT=<http://otel-collector:4317>
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# If using HTTP receiver instead, use:
# OTEL_EXPORTER_OTLP_ENDPOINT=<http://otel-collector:4318>
# OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# Enable all three signals
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
# Debug and helpful extras
OTEL_LOG_LEVEL=debug
OTEL_PYTHON_LOG_CORRELATION=true
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
# If your collector is plaintext gRPC and the SDK complains about TLS:
# OTEL_EXPORTER_OTLP_INSECURE=true
# If your backend needs auth:
# OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer YOUR_TOKEN
4) Collector config must have a logs pipeline
Make sure your collector has all three pipelines and that the receiver matches your chosen protocol:
receivers:
otlp:
protocols:
grpc:
http:
exporters:
logging:
loglevel: debug
# add your real exporter(s) here too
service:
pipelines:
traces:
receivers: [otlp]
exporters: [logging] # plus your real exporter
logs:
receivers: [otlp]
exporters: [logging] # plus your real exporter
metrics:
receivers: [otlp]
exporters: [logging] # plus your real exporter
Common pitfalls that cause “I see OTEL debug logs but the collector shows nothing”
- Using localhost in a container: from the worker/flow container, use http://otel-collector:4317 (service name), not http://localhost.
- Protocol/port mismatch: 4317 is gRPC; 4318 is HTTP. Set OTEL_EXPORTER_OTLP_PROTOCOL accordingly.
- Logs pipeline missing in the collector (traces might work, logs won’t).
- Flow image not instrumented: Docker work pool runs your flows in separate containers. Instrument those too (ENTRYPOINT trick above).
- Version skew: use recent OTEL libs (sdk/exporter/instrumentation) and a recent collector image — logs signal support matured fairly recently.
Minimal docker-compose example (worker + collector)
```
services:
otel-collector:
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otelcol/config.yaml"]
volumes:
- ./otel-collector.yaml/etc/otelcol/config.yamlro
ports:
- "4317:4317"
- "4318:4318"
prefect-worker:
image: your-prefect-worker:latest
command: >
opentelemetry-instrument
--traces_exporter otlp
--metrics_exporter otlp
--logs_exporter otlp
prefect worker start --pool YOUR_DOCKER_POOL
environment:Marvin
10/29/2025, 3:49 AMQuick ways to verify
- Temporarily keep the collector’s logging exporter enabled to see incoming traces/logs in its stdout.
- From inside the worker container, run a quick log generator:
opentelemetry-instrument --traces_exporter otlp --logs_exporter otlp python - <<'PY'
import logging, time
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("otel-test")
for i in range(3):
log.warning("hello from worker %s", i)
time.sleep(1)
PY
```
If the collector’s logging exporter prints those, your path is good.
If you can, please share:
- your worker service section from docker-compose
- your flow image Dockerfile (or base image)
- your otel-collector.yaml pipelines
- the exact OTEL_* env vars you’re using
With that I can pinpoint why the collector isn’t seeing the data.
References
- OpenTelemetry Python auto-instrumentation: https://opentelemetry.io/docs/languages/python/automatic/
- OpenTelemetry Python logs: https://opentelemetry.io/docs/languages/python/logs/
- Prefect workers: https://docs-3.prefect.io/v3/concepts/workers
- Logging in Prefect (if you also want logs in the Prefect UI): https://docs-3.prefect.io/v3/advanced/logging-customizationThành Đạt Vũ
10/29/2025, 4:32 AMMarvin
10/29/2025, 4:32 AMMarvin
10/29/2025, 4:39 AMPREFECT_LOGGING_TO_API_ENABLED=true (sends logs to Prefect)
- PREFECT_LOGGING_LOG_PRINTS=true (captures print statements)
- Optionally: PREFECT_LOGGING_ROOT_LEVEL=INFO and PREFECT_LOGGING_EXTRA_LOGGERS=requests,urllib3 to capture 3rd‑party loggers
How to set these for all runs in a Docker work pool:
- Add them to your deployment’s job variables so the Docker work pool injects them into each flow container.
prefect.yaml example:
deployments:
- name: my-deploy
entrypoint: path/to/flow.py:my_flow
work_pool:
name: my-docker-pool
job_variables:
env:
PREFECT_LOGGING_TO_API_ENABLED: "true"
PREFECT_LOGGING_LOG_PRINTS: "true"
PREFECT_LOGGING_ROOT_LEVEL: "INFO"
PREFECT_LOGGING_EXTRA_LOGGERS: "requests,urllib3"
Then redeploy:
prefect deploy
You’ll see the logs under the Flow Run in the Prefect UI.
- Logging docs: Logging customization
- Job variables docs: Customize job variables
Option 2 — Send logs to your OpenTelemetry collector (needs instrumentation in the flow image)
Because Docker work pools launch each flow in its own container, you must instrument the flow image (not just the worker).
1) Add OTEL libs to your flow image and instrument it
- Dockerfile (flow image):
FROM your-base-image
RUN pip install --no-cache-dir \
opentelemetry-sdk \
opentelemetry-exporter-otlp \
opentelemetry-instrumentation \
opentelemetry-instrumentation-logging
# Make OTEL wrap whatever command Prefect injects at runtime
ENTRYPOINT ["opentelemetry-instrument", "--traces_exporter", "otlp", "--logs_exporter", "otlp"]
2) Set OTEL env vars via job_variables.env so they land inside the flow container:
deployments:
- name: my-deploy
entrypoint: path/to/flow.py:my_flow
work_pool:
name: my-docker-pool
job_variables:
image: your-flow-image:latest
env:
OTEL_SERVICE_NAME: "prefect-flow"
OTEL_RESOURCE_ATTRIBUTES: "service.namespace=prefect,work.pool=docker"
OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" # or "http/protobuf"
OTEL_EXPORTER_OTLP_ENDPOINT: "<http://HOSTNAME_OR_IP:4317>"
OTEL_TRACES_EXPORTER: "otlp"
OTEL_LOGS_EXPORTER: "otlp"
OTEL_METRICS_EXPORTER: "otlp"
OTEL_LOG_LEVEL: "debug"
OTEL_PYTHON_LOG_CORRELATION: "true"
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED: "true"
# If using plaintext gRPC:
# OTEL_EXPORTER_OTLP_INSECURE: "true"
# If your backend needs auth:
# OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer YOUR_TOKEN"
Connectivity tips (common reasons logs don’t reach the collector):
- Don’t use localhost inside the flow container unless your collector is in the same container. Use a reachable address:
- If your collector runs in Docker Compose on the same host, expose 4317/4318 and use:
- Mac/Windows: <http://host.docker.internal:4317>
- Linux: use the Docker host gateway IP (often 172.17.0.1) or enable host-gateway and set endpoint to <http://host.docker.internal:4317>
- Ensure your collector has a logs pipeline enabled:
receivers:
otlp:
protocols:
grpc:
http:
exporters:
logging:
loglevel: debug
service:
pipelines:
logs:
receivers: [otlp]
exporters: [logging] # plus your real exporter(s)
Verify quickly from a flow container
- Run a test flow that logs, or exec into a similar container and run:
```
opentelemetry-instrument --traces_exporter otlp --logs_exporter otlp python - <<'PY'
import logging, time
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("otel-test")
for i in range(3):Marvin
10/29/2025, 4:39 AM