<@ULVA73B9P> I want to do an analysis on the logs ...
# ask-marvin
p
@Marvin I want to do an analysis on the logs generated by a prefect run. I am using prefect version 3.4.14. But the logs view in the prefect ui is very limiting and when i try to download the logs i get an error maybe because it is too big.
m
thought for 252.5 seconds
Thanks for flagging this — large log sets can hit UI/time limits. Since you’re on Prefect 3.4.14, here are reliable ways to get the full logs for analysis without the UI. High-signal options 1) Prefect CLI (simple, stream to a file) - View all logs for a flow run and redirect to a file:
Copy code
prefect flow-run logs <FLOW_RUN_ID> > flow_run_logs.txt
- Tail only the most recent N logs:
Copy code
prefect flow-run logs -t -n 500 <FLOW_RUN_ID> | tee flow_run_logs_tail.txt
- If you need to find the run ID:
Copy code
prefect flow-run ls --flow-name "<YOUR_FLOW_NAME>" --limit 50
Docs: CLI: flow-run commands 2) Programmatic export with the Python client (robust, paginated) This is the most reliable way to fetch very large log sets (with pagination) and write them to CSV/NDJSON/etc. Example: export all logs for one flow run to CSV
Copy code
import asyncio
import csv
from prefect.client.orchestration import get_client
from prefect.client.schemas.filters import LogFilter, LogFilterFlowRunId
from prefect.client.schemas.sorting import LogSort

async def export_flow_run_logs(flow_run_id: str, out_csv_path: str, page_size: int = 1000):
    async with get_client() as client:
        offset = 0
        with open(out_csv_path, "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(["timestamp", "level", "name", "message", "flow_run_id", "task_run_id"])
            while True:
                logs = await client.read_logs(
                    limit=page_size,
                    offset=offset,
                    logs=LogFilter(flow_run_id=LogFilterFlowRunId(any_=[flow_run_id])),
                    sort=LogSort.TIMESTAMP_ASC,
                )
                if not logs:
                    break
                for log in logs:
                    writer.writerow([
                        log.timestamp.isoformat(),
                        log.level,
                        log.name,
                        log.message,
                        str(log.flow_run_id) if log.flow_run_id else "",
                        str(log.task_run_id) if log.task_run_id else "",
                    ])
                offset += len(logs)
    print(f"Wrote logs to {out_csv_path}")

if __name__ == "__main__":
    asyncio.run(export_flow_run_logs("<FLOW_RUN_ID>", "flow_run_logs.csv"))
- This paginates until it retrieves everything, avoiding UI download limits. - The SDK handles backoff/retries automatically if you encounter rate limits. API docs: REST: read logs, Logging handler API 3) Direct REST download endpoint (CSV) - There is an endpoint that streams CSV: GET /api/flow_runs/{flow_run_id}/logs/download - Example curl (when PREFECT_API_URL already points to your workspace base URL):
Copy code
curl -L \
  -H "Authorization: Bearer $PREFECT_API_KEY" \
  "$PREFECT_API_URL/flow_runs/<FLOW_RUN_ID>/logs/download" \
  -o flow_run_logs.csv
If your log set is extremely large, this endpoint can still time out — in that case the paginated /api/logs approach in option 2 is more resilient. Docs: REST: download flow run logs If you frequently need big-log analysis - Emit logs to another sink during runs so you’re not dependent on the Prefect UI or API export: - Add a file handler to write all run logs locally or to network storage - Ship to S3, CloudWatch, Datadog, ELK, etc., via standard Python logging handlers - Prefect supports custom logging config via a YAML file (e.g., ~/.prefect/logging.yml) or the PREFECT_LOGGING_SETTINGS_PATH setting. Docs: Advanced logging customization Notes and limits
- Cloud rate limits for log reads exist; the SDK auto-retries with backoff (Rate limits). - The UI download button is convenient but can fail for massive logs — use CLI or the paginated client as above. A couple of quick questions to tailor this: - Are you on Prefect Cloud or self-hosted Server? - Do you want logs for a single flow run or many runs over a time range? - Are you comfortable running a short Python script, or would you prefer a pure-CLI approach? If you share your environment and what you want to analyze (single run vs. historical runs), I can give you a ready-to-run example for that exact case.
p
@Marvin 422 Unprocessable Entity for http://192.168.0.72:4201/api/logs/filter
m
thought for 199.8 seconds
Thanks for the detail. In Prefect 3.4.14, the endpoint you’re calling is not correct —
/api/logs/filter
was used in older APIs. You’ll get a 422 from Prefect 3 if the path or the payload doesn’t match the current schema. What to use instead - Read logs (paginated): POST
/api/logs
- Download CSV for a single flow run: GET
/api/flow_runs/<FLOW_RUN_ID>/logs/download
Working examples 1) Paginated logs via REST (POST /api/logs)
Copy code
curl -X POST <http://192.168.0.72:4201/api/logs> \
  -H "Content-Type: application/json" \
  -d '{
        "limit": 1000,
        "offset": 0,
        "logs": {
          "flow_run_id": { "any_": ["<FLOW_RUN_ID>"] }
        },
        "sort": "TIMESTAMP_ASC"
      }'
- Increase
offset
by the number of logs returned to paginate until you get an empty list. - Common 422 causes here: - Wrong path (
/api/logs/filter
instead of
/api/logs
) - Using GET instead of POST - Invalid body shape (filters must be nested, e.g.
{"flow_run_id": {"any_": ["uuid"]}}
) - Invalid UUID format for
flow_run_id
- Negative
offset
or non-integer
limit
Docs: REST: read logs 2) CSV download for one run (GET /api/flow_runs/{id}/logs/download)
Copy code
curl -L \
  "<http://192.168.0.72:4201/api/flow_runs/<FLOW_RUN_ID>/logs/download>" \
  -H "Accept: text/csv" \
  -o flow_run_logs.csv
Docs: REST: download flow run logs Alternative: CLI (often easiest)
Copy code
prefect flow-run logs <FLOW_RUN_ID> > flow_run_logs.txt
- Find IDs if needed:
Copy code
prefect flow-run ls --flow-name "<YOUR_FLOW_NAME>" --limit 50
CLI docs: CLI: flow-run commands If you prefer Python, the SDK handles pagination and schema for you (fewer chances of 422). I can share a ready-to-run snippet that writes CSV/NDJSON. If you can paste the exact request body you were sending to
/api/logs/filter
, I can point out the mismatched fields — but switching to the correct endpoint and body above should resolve the 422 on your self-hosted server.
p
@Marvin prefect.exceptions.PrefectHTTPStatusError: Server error '500 Internal Server Error' for url 'http://192.168.0.72:4201/api/logs/filter' Response: {'exception_message': 'Internal Server Error'}
m
thought for 36.0 seconds
Got it — that 500 is coming from the wrong endpoint. In Prefect 3.x,
/api/logs/filter
is not the logs read endpoint. Use POST
/api/logs
instead. Hitting the old path can return 422/500 depending on the server. Quick fixes - Minimal smoke test (POST /api/logs):
Copy code
curl -X POST <http://192.168.0.72:4201/api/logs> \
  -H "Content-Type: application/json" \
  -d '{
        "limit": 1,
        "offset": 0
      }'
- Filter by a specific flow run, with pagination:
Copy code
curl -X POST <http://192.168.0.72:4201/api/logs> \
  -H "Content-Type: application/json" \
  -d '{
        "limit": 1000,
        "offset": 0,
        "logs": {
          "flow_run_id": { "any_": ["<FLOW_RUN_ID>"] }
        },
        "sort": "TIMESTAMP_ASC"
      }'
- CSV for one flow run:
Copy code
curl -L "<http://192.168.0.72:4201/api/flow_runs/<FLOW_RUN_ID>/logs/download>" \
  -H "Accept: text/csv" \
  -o flow_run_logs.csv
- Easiest path via CLI:
Copy code
prefect flow-run logs <FLOW_RUN_ID> > flow_run_logs.txt
If you want a robust, paginated export in Python (avoids UI limits), this works in 3.4.14:
Copy code
import asyncio
import csv
from prefect.client.orchestration import get_client
from prefect.client.schemas.filters import LogFilter, LogFilterFlowRunId
from prefect.client.schemas.sorting import LogSort

async def export_flow_run_logs(flow_run_id: str, out_csv_path: str, page_size: int = 1000):
    async with get_client() as client:
        offset = 0
        with open(out_csv_path, "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(["timestamp", "level", "name", "message", "flow_run_id", "task_run_id"])
            while True:
                logs = await client.read_logs(
                    limit=page_size,
                    offset=offset,
                    logs=LogFilter(flow_run_id=LogFilterFlowRunId(any_=[flow_run_id])),
                    sort=LogSort.TIMESTAMP_ASC,
                )
                if not logs:
                    break
                for log in logs:
                    writer.writerow([
                        log.timestamp.isoformat(),
                        log.level,
                        log.name,
                        log.message,
                        str(log.flow_run_id) if log.flow_run_id else "",
                        str(log.task_run_id) if log.task_run_id else "",
                    ])
                offset += len(logs)

if __name__ == "__main__":
    asyncio.run(export_flow_run_logs("<FLOW_RUN_ID>", "flow_run_logs.csv"))
References: - REST: read logs - REST: download flow run logs - CLI: flow-run commands If you still get a 500 on the correct endpoint (/api/logs), that usually indicates a server-side issue (e.g., DB error or very large unpaginated query). Try: - Lower
limit
(e.g., 200–1000) and paginate via
offset
- Add a narrower filter (flow_run_id and/or timestamp range) - Confirm both your server and SDK are on 3.4.14 (mixed 2.x/3.x can cause odd failures) - Check the Prefect server logs for the traceback when the 500 occurs If you share: - Whether you need logs for one run or many - Your current payload and a sample flow run ID I can tailor a ready-to-run command/script for your exact case.
p
@Marvin when i use your python script i get this error prefect.exceptions.PrefectHTTPStatusError: Server error '500 Internal Server Error' for url 'http://192.168.0.72:4201/api/logs/filter' Response: {'exception_message': 'Internal Server Error'}
m
thought for 260.7 seconds
Thanks for the follow-up — that traceback is very telling. Your Python client is calling /api/logs/filter, which is the Prefect 2.x route. In Prefect 3.x, the logs-read endpoint is POST /api/logs. Seeing /api/logs/filter in the URL strongly suggests there’s a version mismatch (e.g., your local SDK or the server is on 2.x), even if you believe you’re on 3.4.14. Let’s quickly confirm versions and isolate the root cause: Please share - Output of:
Copy code
prefect version
- What version/tag your self-hosted server is running (container image tag or output of
prefect version
inside the server container) - Your PREFECT_API_URL value Immediate tests (no SDK involved) - Minimal smoke test against a 3.x server:
Copy code
curl -sS -X POST <http://192.168.0.72:4201/api/logs> \
  -H "Content-Type: application/json" \
  -d '{"limit": 1, "offset": 0}'
Expected: 200 with a JSON array (possibly empty). If you get 404, it’s very likely the server is 2.x. If 500, the server is finding the route but failing internally (DB/timeouts). - CSV download for a single run (3.x):
Copy code
curl -L \
  "<http://192.168.0.72:4201/api/flow_runs/<FLOW_RUN_ID>/logs/download>" \
  -H "Accept: text/csv" \
  -o flow_run_logs.csv
This bypasses the SDK entirely and often succeeds for large outputs where the UI download stalls. If your server is 3.x but your local SDK is older You can still export logs without the SDK by calling the REST API directly:
Copy code
import os, requests, csv

PREFECT_API_URL = os.environ.get("PREFECT_API_URL", "<http://192.168.0.72:4201/api>").rstrip("/")

def export_logs(flow_run_id: str, out_csv: str, page_size: int = 1000):
    offset = 0
    with open(out_csv, "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["timestamp","level","name","message","flow_run_id","task_run_id"])
        while True:
            payload = {
                "limit": page_size,
                "offset": offset,
                "logs": {"flow_run_id": {"any_": [flow_run_id]}},
                "sort": "TIMESTAMP_ASC"
            }
            r = <http://requests.post|requests.post>(f"{PREFECT_API_URL}/logs", json=payload, timeout=60)
            r.raise_for_status()
            logs = r.json()
            if not logs:
                break
            for log in logs:
                w.writerow([
                    log["timestamp"], log["level"], log["name"], log["message"],
                    log.get("flow_run_id") or "", log.get("task_run_id") or ""
                ])
            offset += len(logs)

export_logs("<FLOW_RUN_ID>", "flow_run_logs.csv")
If your server is actually 2.x - The 3.x client and 3.x filter schema won’t be compatible and can trigger 422/500 errors. - You can either: - Align both sides on 3.4.14 (recommended), or - Use the 2.x-compatible route and payload (POST /api/logs/filter with 2.x schemas) and 2.x SDK. Why you’re seeing 500 - Calling /api/logs/filter from a mismatched client/server can hit a handler that expects different filter shapes and fail server-side (DB or validation quirks), returning 500 instead of a clear 4xx. - It can also occur if the DB query is too heavy. If you confirm the correct 3.x endpoint, reduce limit (e.g., 200–1000) and paginate. References - REST: read logs (3.x): https://docs-3.prefect.io/v3/api-ref/rest-api/server/logs/read-logs - REST: download flow run logs (CSV): https://docs-3.prefect.io/v3/api-ref/rest-api/server/flow-runs/download-logs - CLI: flow-run commands: https://docs-3.prefect.io/v3/api-ref/python/prefect-cli-flow_run Once you share the exact
prefect version
outputs and your server version, I’ll give you a precise, ready-to-run export path for your setup.