Skip to content

feat(package)!: Emit structured JSON logs.#2339

Merged
rishikeshdevsot merged 46 commits into
y-scope:mainfrom
rishikeshdevsot:pr-log-context
Jun 30, 2026
Merged

feat(package)!: Emit structured JSON logs.#2339
rishikeshdevsot merged 46 commits into
y-scope:mainfrom
rishikeshdevsot:pr-log-context

Conversation

@rishikeshdevsot

@rishikeshdevsot rishikeshdevsot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Description

Note

BREAKING CHANGE:

  • Loggers configured through clp_py_utils.clp_logging now emit JSON logs instead of the previous
    text format.
  • Timestamps are emitted as UTC ISO-8601 values instead of the previous local-time %(asctime)s text.

Celery's own startup banner and early Python warnings can still appear as non-JSON process output,
but CLP logger records and Celery logger records after logging setup use JSON.

Adds a reusable bind_log_context helper and formatter for appending scoped correlation fields to CLP logs, such as job_id, task_id etc.

Updates CLP's shared Python logging setup so CLP logger records are emitted as structured JSON while preserving the existing stdlib logging call sites.

Existing callers can keep using:

  • clp_py_utils.clp_logging.get_logger
  • clp_py_utils.clp_logging.configure_logging
  • regular logging.Logger APIs
  • existing Celery task loggers returned by celery.utils.log.get_task_logger

The shared formatter is implemented with structlog.stdlib.ProcessorFormatter, so stdlib records and Celery-configured task loggers use the same JSON renderer. No service code needs to swap to structlog loggers in this PR.

This follows structlog's documented ProcessorFormatter standard-library integration: stdlib/Celery records are processed with foreign_pre_chain, structlog-originated records are prepared with ProcessorFormatter.wrap_for_formatter, stdlib extra={...} fields are preserved with structlog.stdlib.ExtraAdder, and structlog.contextvars.merge_contextvars is configured so future contextvars-bound fields flow through the formatter.

The JSON records include:

  • timestamp
  • event
  • logger
  • level
  • exception, when exc_info is present
  • stack, when stack_info is present

This PR configures support for contextvars-backed fields, but it does not currently bind or emit any correlation fields. It only changes the shared formatter output to structured JSON and applies that formatter to CLP-owned stdlib/Celery logger records.

Longer term, we should transition CLP-owned Python code to structlog loggers directly and avoid adding new stdlib logger call sites. This PR keeps stdlib compatibility because it is the least disruptive bridge for the current services, especially Celery workers whose task loggers are created by Celery. For the same compatibility reason, clp_py_utils.clp_logging.get_logger still configures handlers as it did before; moving handler setup to a single service bootstrap path should be a separate cleanup instead of being bundled into this formatter change.

If we later add per-job/per-task correlation fields in a follow-up PR, the formatter side is already ready because structlog.contextvars.merge_contextvars is in the processor chain. The remaining work is to bind and clear fields at request/task boundaries using structlog's native contextvars API:

  • structlog.contextvars.bound_contextvars(...) for scoped context.
  • structlog.contextvars.bind_contextvars(...), structlog.contextvars.unbind_contextvars(...), and structlog.contextvars.clear_contextvars() for explicit lifecycle management.

A small CLP wrapper can still make sense later if we need CLP-specific behavior that structlog does not provide directly, such as enforcing a fixed correlation field schema or mapping CLP domain objects into log fields. For this PR, deferring that wrapper keeps the first step focused on the operational log format change without adding an unused API surface.

Logging style references:

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

Setup: rebuilt build/clp-package with task package before running runtime validation. The generated image used by Docker Compose and Helm was clp-package:dev-junhao-08c6 (sha256:08c657258de039943cd76df8304d4e4ae4de95f53fcaf9c4be6d5dd6df9c2639).

Scenario 1: Docker Compose package startup

Task: Start the rebuilt CLP package with Docker Compose and verify services become healthy.

Command:

set -o pipefail; cd build/clp-package && ./sbin/start-clp.sh 2>&1 | tee /tmp/clp-2339-compose-start-tail.log | tail -90

Output:

 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-worker-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-otel-collector-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-otel-collector-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-database-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-database-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-worker-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-worker-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-database-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-garbage-collector-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-scheduler-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-webui-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-scheduler-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-api-server-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-scheduler-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-scheduler-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-api-server-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-garbage-collector-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-webui-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-scheduler-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-scheduler-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-reducer-1 Starting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-reducer-1 Started 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-webui-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-garbage-collector-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-database-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-otel-collector-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-api-server-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-worker-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-scheduler-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-worker-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-reducer-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-scheduler-1 Waiting 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-scheduler-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-scheduler-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-compression-worker-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-query-worker-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-db-table-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-garbage-collector-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-indices-creator-1 Exited 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-reducer-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-api-server-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-queue-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-otel-collector-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-redis-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-results-cache-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-database-1 Healthy 
 Container clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d-webui-1 Healthy 
2026-06-23T17:19:06.118 INFO [controller] Started CLP.

Scenario 2: Docker Compose package compression

Task: Submit a compression job through the rebuilt Docker Compose package.

Command:

cd build/clp-package && ./sbin/compress.sh --timestamp-key timestamp ~/samples/postgresql.jsonl

Output:

 Container clp-package-clp-runtime-run-16cd853d37a6 Creating 
 Container clp-package-clp-runtime-run-16cd853d37a6 Created 
2026-06-23T17:19:12.032 INFO [compress] Compression job 9 submitted.
2026-06-23T17:19:12.533 INFO [compress] Compressed 1.80KB into 1.28KB (1.41x). Speed: 4.39KB/s.
2026-06-23T17:19:13.034 INFO [compress] Compression finished.
2026-06-23T17:19:13.034 INFO [compress] Compressed 1.80KB into 1.28KB (1.41x). Speed: 3.32KB/s.

Scenario 3: Docker Compose service log output

Task: Confirm Docker Compose Python service logger records are emitted as JSON, including Celery task logger records.

Command:

cd build/clp-package && project=$(docker ps --format '{{.Names}} {{.Label "com.docker.compose.project"}} {{.Label "com.docker.compose.service"}}' | awk '$3 == "compression-scheduler" {print $2; exit}') && echo "compose_project=${project}" && docker compose -p "$project" -f docker-compose.yaml logs --no-color --tail=60 compression-scheduler query-scheduler compression-worker query-worker garbage-collector reducer

Output:

compose_project=clp-package-2b75ec31-2ed3-4a54-9821-2a5d02e63e3d
garbage-collector-1  | {"event": "Retention period is not configured, skip creating archive-garbage-collector.", "logger": "garbage_collector", "level": "info", "timestamp": "2026-06-23T17:19:03.310779Z"}
garbage-collector-1  | {"event": "Creating search-result-garbage-collector with retention period = 60 minutes", "logger": "garbage_collector", "level": "info", "timestamp": "2026-06-23T17:19:03.310874Z"}
query-worker-1       | /opt/clp/lib/python3/site-packages/celery/platforms.py:811: SecurityWarning: An entry for the specified gid or egid was not found.
query-worker-1       | We're assuming this is a potential security issue.
query-worker-1       | 
query-worker-1       |   warnings.warn(SecurityWarning(ASSUMING_ROOT))
query-worker-1       | /opt/clp/lib/python3/site-packages/celery/platforms.py:841: SecurityWarning: You're running the worker with superuser privileges: this is
query-worker-1       | absolutely not recommended!
query-worker-1       | 
query-worker-1       | Please specify a different user using the --uid option.
garbage-collector-1  | {"event": "search-result-garbage-collector started.", "logger": "search-result-garbage-collector", "level": "info", "timestamp": "2026-06-23T17:19:03.311031Z"}
query-worker-1       | 
query-worker-1       | User information: uid=1000 euid=1000 gid=999
query-worker-1       | 
query-worker-1       |   warnings.warn(SecurityWarning(ROOT_DISCOURAGED.format(
query-worker-1       |  
query-worker-1       |  -------------- query-worker@query_worker v5.6.3 (recovery)
query-worker-1       | --- ***** ----- 
query-worker-1       | -- ******* ---- Linux-6.8.0-124-generic-x86_64-with-glibc2.35 2026-06-23 17:18:59
query-worker-1       | - *** --- * --- 
query-worker-1       | - ** ---------- [config]
query-worker-1       | - ** ---------- .> app:         query:0x7c2662783dc0
query-worker-1       | - ** ---------- .> transport:   amqp://clp-user:**@queue:5672//
query-worker-1       | - ** ---------- .> results:     redis://default:**@redis:6379/0
query-worker-1       | - *** --- * --- .> concurrency: 16 (prefork)
query-worker-1       | -- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
query-worker-1       | --- ***** ----- 
query-worker-1       |  -------------- [queues]
query-worker-1       |                 .> query            exchange=query(direct) key=query
query-worker-1       |                 
query-worker-1       | 
query-worker-1       | {"event": "consumer: Cannot connect to amqp://clp-user:**@queue:5672//: [Errno 111] Connection refused.\nTrying again in 2.00 seconds... (1/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:19:00.182601Z"}
compression-scheduler-1  | {"event": "Starting compression_scheduler", "logger": "compression_scheduler", "level": "info", "timestamp": "2026-06-23T17:19:03.390479Z"}
compression-scheduler-1  | {"event": "Dispatched job 9 with 1 tasks (0 remaining).", "logger": "compression_scheduler", "level": "info", "timestamp": "2026-06-23T17:19:12.165151Z"}
compression-scheduler-1  | {"event": "Compression task job-9-task-9 completed in 0.025889 second(s).", "logger": "compression_scheduler", "level": "info", "timestamp": "2026-06-23T17:19:12.667039Z"}
query-scheduler-1        | {"event": "Connected to archive database database:3306.", "logger": "search-job-handler", "level": "info", "timestamp": "2026-06-23T17:19:03.295799Z"}
query-scheduler-1        | {"event": "query_scheduler started.", "logger": "search-job-handler", "level": "info", "timestamp": "2026-06-23T17:19:03.295891Z"}
compression-scheduler-1  | {"event": "Job 9 succeeded (1 tasks completed).", "logger": "compression_scheduler", "level": "info", "timestamp": "2026-06-23T17:19:12.667161Z"}
reducer-1                | {"event": "Reducers started.", "logger": "reducer", "level": "info", "timestamp": "2026-06-23T17:19:05.710542Z"}
reducer-1                | {"event": "Host=reducer Base port=14009 Concurrency=16 Upsert Interval=100", "logger": "reducer", "level": "info", "timestamp": "2026-06-23T17:19:05.710667Z"}
compression-worker-1     | /opt/clp/lib/python3/site-packages/celery/platforms.py:811: SecurityWarning: An entry for the specified gid or egid was not found.
compression-worker-1     | We're assuming this is a potential security issue.
compression-worker-1     | 
compression-worker-1     |   warnings.warn(SecurityWarning(ASSUMING_ROOT))
compression-worker-1     | /opt/clp/lib/python3/site-packages/celery/platforms.py:841: SecurityWarning: You're running the worker with superuser privileges: this is
compression-worker-1     | absolutely not recommended!
compression-worker-1     | 
compression-worker-1     | Please specify a different user using the --uid option.
compression-worker-1     | 
compression-worker-1     | User information: uid=1000 euid=1000 gid=999
compression-worker-1     | 
compression-worker-1     |   warnings.warn(SecurityWarning(ROOT_DISCOURAGED.format(
compression-worker-1     |  
compression-worker-1     |  -------------- compression-worker@compression_worker v5.6.3 (recovery)
compression-worker-1     | --- ***** ----- 
compression-worker-1     | -- ******* ---- Linux-6.8.0-124-generic-x86_64-with-glibc2.35 2026-06-23 17:18:59
compression-worker-1     | - *** --- * --- 
compression-worker-1     | - ** ---------- [config]
compression-worker-1     | - ** ---------- .> app:         compress:0x7593b7d3bd90
compression-worker-1     | - ** ---------- .> transport:   amqp://clp-user:**@queue:5672//
compression-worker-1     | - ** ---------- .> results:     redis://default:**@redis:6379/1
compression-worker-1     | - *** --- * --- .> concurrency: 16 (prefork)
compression-worker-1     | -- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
compression-worker-1     | --- ***** ----- 
compression-worker-1     |  -------------- [queues]
compression-worker-1     |                 .> compression      exchange=compression(direct) key=compression
compression-worker-1     |                 
compression-worker-1     | 
compression-worker-1     | {"event": "consumer: Cannot connect to amqp://clp-user:**@queue:5672//: [Errno 111] Connection refused.\nTrying again in 2.00 seconds... (1/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:19:00.182802Z"}
compression-worker-1     | {"event": "[job_id=9 task_id=9] COMPRESSION STARTED.", "logger": "job_orchestration.executor.compress.celery_compress", "level": "info", "timestamp": "2026-06-23T17:19:12.162927Z"}
compression-worker-1     | {"event": "[job_id=9 task_id=9] COMPRESSION COMPLETED.", "logger": "job_orchestration.executor.compress.celery_compress", "level": "info", "timestamp": "2026-06-23T17:19:12.188717Z"}

Explanation: CLP-owned service records are JSON. Celery's startup banner and early Python warnings remain plain process output, but Celery logger records and CLP task logger records are JSON after logging setup.

Scenario 4: Helm/kind package startup

Task: Start the Helm chart through set-up-test.sh using the rebuilt package image and verify all pods become ready.

Command:

set -o pipefail; CLP_HOME=/tmp/clp-2339-json-logs CLUSTER_NAME=clp-2339-json-logs tools/deployment/package-helm/set-up-test.sh --clp-package-image clp-package:dev-junhao-08c6 2>&1 | tee /tmp/clp-2339-helm-setup-tail.log | tail -120

Output:

 12800K .......... .......... .......... .......... .......... 86%  401K 2s
 12850K .......... .......... .......... .......... .......... 86%  131M 2s
 12900K .......... .......... .......... .......... .......... 86%  407K 2s
 12950K .......... .......... .......... .......... .......... 87%  128M 2s
 13000K .......... .......... .......... .......... .......... 87%  136M 2s
 13050K .......... .......... .......... .......... .......... 87% 99.4M 2s
 13100K .......... .......... .......... .......... .......... 88%  410K 2s
 13150K .......... .......... .......... .......... .......... 88%  115M 2s
 13200K .......... .......... .......... .......... .......... 88%  388K 2s
 13250K .......... .......... .......... .......... .......... 89%  149M 2s
 13300K .......... .......... .......... .......... .......... 89%  399K 2s
 13350K .......... .......... .......... .......... .......... 89% 93.5M 2s
 13400K .......... .......... .......... .......... .......... 90%  130M 2s
 13450K .......... .......... .......... .......... .......... 90%  118M 2s
 13500K .......... .......... .......... .......... .......... 90%  383K 1s
 13550K .......... .......... .......... .......... .......... 91%  149M 1s
 13600K .......... .......... .......... .......... .......... 91%  414K 1s
 13650K .......... .......... .......... .......... .......... 91% 72.6M 1s
 13700K .......... .......... .......... .......... .......... 92%  395K 1s
 13750K .......... .......... .......... .......... .......... 92%  128M 1s
 13800K .......... .......... .......... .......... .......... 92%  101M 1s
 13850K .......... .......... .......... .......... .......... 93%  383K 1s
 13900K .......... .......... .......... .......... .......... 93%  135M 1s
 13950K .......... .......... .......... .......... .......... 93% 98.2M 1s
 14000K .......... .......... .......... .......... .......... 94%  381K 1s
 14050K .......... .......... .......... .......... .......... 94% 88.8M 1s
 14100K .......... .......... .......... .......... .......... 94%  113M 1s
 14150K .......... .......... .......... .......... .......... 95%  110M 1s
 14200K .......... .......... .......... .......... .......... 95% 4.24M 1s
 14250K .......... .......... .......... .......... .......... 95% 1.48G 1s
 14300K .......... .......... .......... .......... .......... 96%  421K 1s
 14350K .......... .......... .......... .......... .......... 96% 98.8M 1s
 14400K .......... .......... .......... .......... .......... 96% 86.6M 1s
 14450K .......... .......... .......... .......... .......... 97% 4.20M 0s
 14500K .......... .......... .......... .......... .......... 97% 1.09G 0s
 14550K .......... .......... .......... .......... .......... 97% 1.25G 0s
 14600K .......... .......... .......... .......... .......... 98% 1.62G 0s
 14650K .......... .......... .......... .......... .......... 98%  407K 0s
 14700K .......... .......... .......... .......... .......... 98% 89.4M 0s
 14750K .......... .......... .......... .......... .......... 99%  108M 0s
 14800K .......... .......... .......... .......... .......... 99%  409K 0s
 14850K .......... .......... .......... .......... .......... 99%  399K 0s
 14900K .......... .......... ........             100%  278M=15s

2026-06-23 13:14:47 (969 KB/s) - written to stdout [15296662/15296662]

I0623 13:14:52.131149 1370389 warnings.go:110] "Warning: spec.SessionAffinity is ignored for headless services"
I0623 13:14:52.134693 1370389 warnings.go:110] "Warning: spec.SessionAffinity is ignored for headless services"
I0623 13:14:52.134721 1370389 warnings.go:110] "Warning: spec.SessionAffinity is ignored for headless services"
I0623 13:14:52.134766 1370389 warnings.go:110] "Warning: spec.SessionAffinity is ignored for headless services"
NAME: test
LAST DEPLOYED: Tue Jun 23 13:14:51 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
Sample download and extraction complete
Waiting for all pods to be ready (timeout=300s, poll=5s, wait=5s)...
NAME                                             READY   STATUS      RESTARTS   AGE
test-clp-api-server-685769b45-fsqql              0/1     Running     0          53s
test-clp-compression-scheduler-d684fd8bc-9sxmp   1/1     Running     0          53s
test-clp-compression-worker-69c6fd9dc7-g6qp4     1/1     Running     0          53s
test-clp-database-0                              1/1     Running     0          53s
test-clp-db-table-creator-6t25k                  0/1     Completed   0          53s
test-clp-garbage-collector-775fbbbd94-qrcdv      1/1     Running     0          53s
test-clp-otel-collector-5f54958d4c-gc9r6         1/1     Running     0          53s
test-clp-query-scheduler-6d7cf6b4-56sc4          0/1     Running     0          53s
test-clp-query-worker-d896bd956-q5998            1/1     Running     0          53s
test-clp-queue-0                                 1/1     Running     0          53s
test-clp-redis-0                                 1/1     Running     0          53s
test-clp-reducer-d7565c4f5-4j4fr                 0/1     Init:0/2    0          53s
test-clp-results-cache-0                         2/2     Running     0          53s
test-clp-topology-metrics-emitter-sf8k8          0/1     Completed   0          53s
test-clp-webui-5d855f79c5-zkb97                  0/1     Running     0          53s
job.batch/test-clp-db-table-creator condition met
job.batch/test-clp-topology-metrics-emitter condition met
pod/test-clp-api-server-685769b45-fsqql condition met
pod/test-clp-compression-scheduler-d684fd8bc-9sxmp condition met
pod/test-clp-compression-worker-69c6fd9dc7-g6qp4 condition met
pod/test-clp-database-0 condition met
pod/test-clp-garbage-collector-775fbbbd94-qrcdv condition met
pod/test-clp-otel-collector-5f54958d4c-gc9r6 condition met
pod/test-clp-query-scheduler-6d7cf6b4-56sc4 condition met
pod/test-clp-query-worker-d896bd956-q5998 condition met
pod/test-clp-queue-0 condition met
pod/test-clp-redis-0 condition met
---
NAME                                             READY   STATUS      RESTARTS   AGE
test-clp-api-server-685769b45-fsqql              1/1     Running     0          64s
test-clp-compression-scheduler-d684fd8bc-9sxmp   1/1     Running     0          64s
test-clp-compression-worker-69c6fd9dc7-g6qp4     1/1     Running     0          64s
test-clp-database-0                              1/1     Running     0          64s
test-clp-db-table-creator-6t25k                  0/1     Completed   0          64s
test-clp-garbage-collector-775fbbbd94-qrcdv      1/1     Running     0          64s
test-clp-otel-collector-5f54958d4c-gc9r6         1/1     Running     0          64s
test-clp-query-scheduler-6d7cf6b4-56sc4          1/1     Running     0          64s
test-clp-query-worker-d896bd956-q5998            1/1     Running     0          64s
test-clp-queue-0                                 1/1     Running     0          64s
test-clp-redis-0                                 1/1     Running     0          64s
test-clp-reducer-d7565c4f5-4j4fr                 1/1     Running     0          64s
test-clp-results-cache-0                         2/2     Running     0          64s
test-clp-topology-metrics-emitter-sf8k8          0/1     Completed   0          64s
test-clp-webui-5d855f79c5-zkb97                  1/1     Running     0          64s
job.batch/test-clp-db-table-creator condition met
job.batch/test-clp-topology-metrics-emitter condition met
pod/test-clp-api-server-685769b45-fsqql condition met
pod/test-clp-compression-scheduler-d684fd8bc-9sxmp condition met
pod/test-clp-compression-worker-69c6fd9dc7-g6qp4 condition met
pod/test-clp-database-0 condition met
pod/test-clp-garbage-collector-775fbbbd94-qrcdv condition met
pod/test-clp-otel-collector-5f54958d4c-gc9r6 condition met
pod/test-clp-query-scheduler-6d7cf6b4-56sc4 condition met
pod/test-clp-query-worker-d896bd956-q5998 condition met
pod/test-clp-queue-0 condition met
pod/test-clp-redis-0 condition met
pod/test-clp-reducer-d7565c4f5-4j4fr condition met
pod/test-clp-results-cache-0 condition met
pod/test-clp-webui-5d855f79c5-zkb97 condition met
All jobs completed and services are ready.

Scenario 5: Kubernetes service log output

Task: Confirm Kubernetes Python service logger records are JSON when CLP is started by set-up-test.sh.

Command:

kubectl --context kind-clp-2339-json-logs logs deploy/test-clp-compression-scheduler --tail=20 && printf '\n--- query scheduler ---\n' && kubectl --context kind-clp-2339-json-logs logs deploy/test-clp-query-scheduler --tail=20 && printf '\n--- compression worker ---\n' && kubectl --context kind-clp-2339-json-logs logs deploy/test-clp-compression-worker --tail=30 && printf '\n--- query worker ---\n' && kubectl --context kind-clp-2339-json-logs logs deploy/test-clp-query-worker --tail=30

Output:

{"event": "Starting compression_scheduler", "logger": "compression_scheduler", "level": "info", "timestamp": "2026-06-23T17:15:45.057819Z"}

--- query scheduler ---
{"event": "Connected to archive database test-clp-database:3306.", "logger": "search-job-handler", "level": "info", "timestamp": "2026-06-23T17:15:45.053295Z"}
{"event": "query_scheduler started.", "logger": "search-job-handler", "level": "info", "timestamp": "2026-06-23T17:15:45.053360Z"}

--- compression worker ---
 
 -------------- compression-worker@test-clp-compression-worker-69c6fd9dc7-g6qp4 v5.6.3 (recovery)
--- ***** ----- 
-- ******* ---- Linux-6.8.0-124-generic-x86_64-with-glibc2.35 2026-06-23 17:15:12
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         compress:0x70196b3a8340
- ** ---------- .> transport:   amqp://clp-user:**@test-clp-queue:5672//
- ** ---------- .> results:     redis://default:**@test-clp-redis:6379/1
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> compression      exchange=compression(direct) key=compression
                

{"event": "consumer: Cannot connect to amqp://clp-user:**@test-clp-queue:5672//: [Errno -5] No address associated with hostname.\nTrying again in 2.00 seconds... (1/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:15:12.299070Z"}
{"event": "consumer: Cannot connect to amqp://clp-user:**@test-clp-queue:5672//: [Errno -5] No address associated with hostname.\nTrying again in 4.00 seconds... (2/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:15:14.301515Z"}
{"event": "consumer: Cannot connect to amqp://clp-user:**@test-clp-queue:5672//: [Errno -5] No address associated with hostname.\nTrying again in 6.00 seconds... (3/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:15:18.323583Z"}

--- query worker ---
 
 -------------- query-worker@test-clp-query-worker-d896bd956-q5998 v5.6.3 (recovery)
--- ***** ----- 
-- ******* ---- Linux-6.8.0-124-generic-x86_64-with-glibc2.35 2026-06-23 17:15:12
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         query:0x77c1038ac340
- ** ---------- .> transport:   amqp://clp-user:**@test-clp-queue:5672//
- ** ---------- .> results:     redis://default:**@test-clp-redis:6379/0
- *** --- * --- .> concurrency: 8 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> query            exchange=query(direct) key=query
                

{"event": "consumer: Cannot connect to amqp://clp-user:**@test-clp-queue:5672//: [Errno -5] No address associated with hostname.\nTrying again in 2.00 seconds... (1/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:15:12.417272Z"}
{"event": "consumer: Cannot connect to amqp://clp-user:**@test-clp-queue:5672//: [Errno -5] No address associated with hostname.\nTrying again in 4.00 seconds... (2/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:15:14.419303Z"}
{"event": "consumer: Cannot connect to amqp://clp-user:**@test-clp-queue:5672//: [Errno -5] No address associated with hostname.\nTrying again in 6.00 seconds... (3/100)\n", "logger": "celery.worker.consumer.consumer", "level": "error", "timestamp": "2026-06-23T17:15:18.435516Z"}
Defaulted container "compression-scheduler" out of: compression-scheduler, wait-for-db-table-creator (init), wait-for-queue (init), wait-for-redis (init)
Defaulted container "query-scheduler" out of: query-scheduler, wait-for-db-table-creator (init), wait-for-queue (init), wait-for-redis (init)

Explanation: The scheduler records are JSON under kubectl logs, and Celery logger records are JSON after Celery logging setup. Celery's startup banner remains plain output.

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced context-aware JSON logging that automatically includes per-execution correlation fields with each log entry.
    • Added tools to bind and retrieve active logging context, ensuring it’s correctly managed across execution scopes.
    • Updated logging output to use the new unified JSON format across console and existing handlers.
  • Chores
    • Improved logging API type annotations for clearer, safer usage.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

clp_logging.py gains structured JSON logging via structlog processors and a ProcessorFormatter. A new set_json_formatter_on_handlers() applies the JSON formatter to existing logger handlers. Both Celery compress and query modules register signal handlers that wire JSON logging to task and worker loggers on startup. Type annotations are tightened with explicit -> None returns on logging functions. Documentation introduces logging across Python orchestration, Rust HTTP, WebUI, and native binaries, covering output formats, control variables, and deployment guidance.

Changes

JSON structured logging with Celery integration

Layer / File(s) Summary
Structlog configuration and JSON formatter
components/clp-py-utils/clp_py_utils/clp_logging.py, components/clp-py-utils/pyproject.toml
Adds structlog and structlog.typing.Processor imports, defines processor-chain constants for log level, stack rendering, and ISO UTC timestamps, configures structlog globally with shared processors, and implements get_logging_formatter() to return a ProcessorFormatter that JSON-renders logs. Adds set_json_formatter_on_handlers(logger) to apply the formatter to all existing handlers on a logger. Updates get_logger() console handler to use get_logging_formatter(). Adds structlog>=26.1.0 runtime dependency to pyproject.toml.
Logging function return type annotations
components/clp-py-utils/clp_py_utils/clp_logging.py
Updates set_logging_level() to explicitly return None and changes invalid-level warning to parameterized logger.warning(). Adds explicit -> None return type annotation to configure_logging().
Celery logger signal integration
components/job-orchestration/job_orchestration/executor/compress/celery.py, components/job-orchestration/job_orchestration/executor/query/celery.py
Adds setup_json_logging() handler in each Celery module, registered to after_setup_logger and after_setup_task_logger signals to apply CLP's JSON formatter to task and worker loggers when created. Includes logging, Celery signals imports, and imports set_json_formatter_on_handlers from clp_logging.

Logging documentation and guidance

Layer / File(s) Summary
Logging design documentation
docs/src/dev-docs/logging.md, docs/src/dev-docs/index.md
Introduces logging design documentation covering component inventory by runtime stack (logger libraries, output formats, control environment variables), per-record JSON fields for Python orchestration, Rust tracing configuration, WebUI Pino JSON behaviour, deployment log retrieval in Docker Compose and Kubernetes, and guidance for implementing logging in new Python, Rust, and WebUI services. Adds logging navigation entry to Design toctree.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly identifies the main change: adding structured JSON logging to the package, which is the primary feature across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rishikeshdevsot rishikeshdevsot changed the title Add log correlation context formatter feat(logging): Add scoped log context for CLP correlation fields Jun 18, 2026
@rishikeshdevsot
rishikeshdevsot marked this pull request as ready for review June 18, 2026 21:25
@rishikeshdevsot
rishikeshdevsot requested a review from a team as a code owner June 18, 2026 21:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/clp-py-utils/clp_py_utils/clp_logging.py`:
- Line 65: The _TIMESTAMP_PROCESSOR call at line 65 uses TimeStamper which
captures the current time when the formatter runs, causing queued or delayed log
records to get incorrect timestamps. Instead of calling _TIMESTAMP_PROCESSOR
with TimeStamper, directly build the ISO timestamp from record.created, which
Python's logging module sets when the LogRecord is created. This ensures the
timestamp reflects when the event occurred, not when the formatter executed.

In `@components/clp-py-utils/pyproject.toml`:
- Line 18: The structlog dependency version in the pyproject.toml file is set to
a floor of 25.5.0, but the code only uses contextvars helpers, JSONRenderer, and
TimeStamper which are all available in much earlier versions. Lower the
structlog version requirement to the minimum version that actually provides all
the necessary features (21.2.0 based on the structlog documentation) to avoid
creating unnecessary resolver conflicts for consumers who may have an older
compatible version already pinned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 46d8f17a-4407-4092-a777-2d23829890fa

📥 Commits

Reviewing files that changed from the base of the PR and between bc6cb86 and 962ae48.

⛔ Files ignored due to path filters (1)
  • components/clp-py-utils/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • components/clp-py-utils/clp_py_utils/clp_logging.py
  • components/clp-py-utils/pyproject.toml

event_dict["stack"] = self.formatStack(record.stack_info)

merge_contextvars(None, method_name, event_dict)
_TIMESTAMP_PROCESSOR(None, method_name, event_dict)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n components/clp-py-utils/clp_py_utils/clp_logging.py | head -100

Repository: y-scope/clp

Length of output: 3862


🌐 Web query:

Python logging LogRecord.created attribute timestamp

💡 Result:

In Python's logging module, the LogRecord.created attribute represents the time at which the LogRecord was created [1][2]. Its value is a float expressing the time in seconds since the epoch [1][3]. In modern versions of Python, this value is derived from time.time_ns divided by 1e9 to ensure precision [1][2][3]. In earlier versions of Python, it was typically generated using time.time [3]. When using the logging module's format strings, you can include this timestamp using the %(created)f placeholder [1][2]. Key related attributes also include: - %(asctime)s: A human-readable representation of the creation time [1][2]. - %(msecs)d: The millisecond portion of the creation time [1][2]. - %(relativeCreated)d: The time in milliseconds when the record was created, relative to when the logging module was first loaded [1][2].

Citations:


🌐 Web query:

structlog TimeStamper processor when does it capture timestamp

💡 Result:

The structlog TimeStamper processor captures the timestamp at the exact moment the processor function is executed within the log processing pipeline [1][2]. When you use TimeStamper in your processor chain, it functions as a callable that accepts the event dictionary (event_dict). When that processor is called by structlog during the logging process, it invokes the underlying time-fetching mechanism (such as time.time or datetime.datetime.now) to retrieve the current time and immediately injects it into the event dictionary under the configured key [1][2]. Because processors are executed sequentially in the order they appear in your structlog configuration, the timestamp is captured at the specific stage in the pipeline where the TimeStamper is placed [1][2]. If it is the first processor, it captures the time immediately when the log call is initiated; if placed later, it captures the time only after preceding processors have finished their work.

Citations:


🏁 Script executed:

python3 << 'PY'
from datetime import datetime, timezone

# Test with a sample timestamp that includes fractional seconds
test_created = 1234567890.123456

# The proposed conversion
dt = datetime.fromtimestamp(test_created, timezone.utc)
iso_str = dt.isoformat().replace("+00:00", "Z")
print(f"Input timestamp: {test_created}")
print(f"Converted ISO string: {iso_str}")

# Verify it matches the expected format
import re
iso_pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$'
if re.match(iso_pattern, iso_str):
    print("✓ Format matches expected ISO 8601 with Z suffix")
else:
    print("✗ Format does not match expected ISO 8601")
PY

Repository: y-scope/clp

Length of output: 189


🏁 Script executed:

# Check if datetime/timezone are already imported
grep -n "from datetime import\|import datetime" components/clp-py-utils/clp_py_utils/clp_logging.py

# Search for other TimeStamper usage in the codebase
rg "TimeStamper" components/clp-py-utils/

Repository: y-scope/clp

Length of output: 201


Use the LogRecord creation time for timestamp.

Line 65 stamps when the formatter runs because TimeStamper calls current-time helpers internally; queued, forwarded, or delayed records can then get formatter-run timestamps instead of event-creation timestamps. Build the ISO timestamp from record.created, which Python's logging module sets when the LogRecord is created.

🛠️ Proposed fix
+from datetime import datetime, timezone
+
 import structlog
 from structlog.contextvars import (
     bind_contextvars,
@@
-_TIMESTAMP_PROCESSOR = structlog.processors.TimeStamper(fmt="iso", utc=True, key="timestamp")
 _JSON_RENDERER = structlog.processors.JSONRenderer()
@@
-        _TIMESTAMP_PROCESSOR(None, method_name, event_dict)
+        event_dict["timestamp"] = (
+            datetime.fromtimestamp(record.created, timezone.utc)
+            .isoformat()
+            .replace("+00:00", "Z")
+        )
         rendered_log = _JSON_RENDERER(None, method_name, event_dict)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/clp-py-utils/clp_py_utils/clp_logging.py` at line 65, The
_TIMESTAMP_PROCESSOR call at line 65 uses TimeStamper which captures the current
time when the formatter runs, causing queued or delayed log records to get
incorrect timestamps. Instead of calling _TIMESTAMP_PROCESSOR with TimeStamper,
directly build the ISO timestamp from record.created, which Python's logging
module sets when the LogRecord is created. This ensures the timestamp reflects
when the event occurred, not when the formatter executed.

Comment thread components/clp-py-utils/pyproject.toml Outdated
"result>=0.17.0",
"sqlalchemy>=2.0.46",
"StrEnum>=0.4.15",
"structlog>=25.5.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Lower the structlog floor unless 25.5.0-specific behaviour is required.

The reviewed code only needs contextvars helpers plus JSONRenderer/TimeStamper; the structlog 25.5.0 docs list these contextvars APIs as added by 21.2.0 or earlier, while the renderer and timestamper are older processors. Keeping >=25.5.0 can create avoidable resolver conflicts for consumers with an older compatible structlog already pinned. (structlog.org)

♻️ Proposed dependency floor
-    "structlog>=25.5.0",
+    "structlog>=21.2.0",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"structlog>=25.5.0",
"structlog>=21.2.0",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/clp-py-utils/pyproject.toml` at line 18, The structlog dependency
version in the pyproject.toml file is set to a floor of 25.5.0, but the code
only uses contextvars helpers, JSONRenderer, and TimeStamper which are all
available in much earlier versions. Lower the structlog version requirement to
the minimum version that actually provides all the necessary features (21.2.0
based on the structlog documentation) to avoid creating unnecessary resolver
conflicts for consumers who may have an older compatible version already pinned.

@rishikeshdevsot rishikeshdevsot changed the title feat(logging): Add scoped log context for CLP correlation fields feat(logging)!: Add scoped log context for CLP correlation fields Jun 22, 2026
@rishikeshdevsot rishikeshdevsot changed the title feat(logging)!: Add scoped log context for CLP correlation fields feat(logging)!: Improve logging by adding context vars and using structlog Jun 22, 2026
@rishikeshdevsot rishikeshdevsot changed the title feat(logging)!: Improve logging by adding context vars and using structlog feat(logging)!: Emit structured JSON logs with context vars Jun 22, 2026
@junhaoliao junhaoliao changed the title feat(logging)!: Emit structured JSON logs with context vars feat(package)!: Emit structured JSON logs. Jun 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
components/clp-py-utils/clp_py_utils/clp_logging.py (1)

28-36: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use record creation time for foreign log timestamps.

Line 35 stamps time at formatter execution, not LogRecord creation time. With delayed/queued handlers, that can skew timestamp from when the event actually happened.

🛠️ Suggested fix
+from datetime import datetime, timezone
@@
+def _add_foreign_record_created_timestamp(
+    _: object, __: str, event_dict: dict[str, object]
+) -> dict[str, object]:
+    record = event_dict.get("_record")
+    if isinstance(record, logging.LogRecord):
+        event_dict["timestamp"] = (
+            datetime.fromtimestamp(record.created, timezone.utc)
+            .isoformat()
+            .replace("+00:00", "Z")
+        )
+    return event_dict
+
 _FOREIGN_PRE_CHAIN: tuple[Processor, ...] = (
@@
-    structlog.processors.TimeStamper(fmt="iso", utc=True, key="timestamp"),
+    _add_foreign_record_created_timestamp,
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/clp-py-utils/clp_py_utils/clp_logging.py` around lines 28 - 36,
The TimeStamper processor in the _FOREIGN_PRE_CHAIN tuple is generating a
timestamp at processor execution time rather than using the LogRecord creation
time, which can cause timestamp inaccuracy when handlers are delayed or queued.
Replace the structlog.processors.TimeStamper call to instead extract the
timestamp from the LogRecord's creation time, which is available in the stdlib
integration context. You should create a custom processor or use structlog's
built-in mechanism to access _record.created and convert it to ISO format with
UTC timezone, then assign it to the timestamp key, ensuring the timestamp
reflects when the logging event actually occurred rather than when it was
processed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/src/dev-docs/logging.md`:
- Around line 34-42: Add GitHub source code links to the logging documentation
to help developers navigate from the reference material to the actual
implementation. Insert links using the DOCS_VAR_CLP_GIT_REF variable that point
to the clp_py_utils/clp_logging.py file and the clp_rust_utils logging module
directory. These links should be added near the table describing the log fields
or in the section that references these modules, making it easier for developers
to explore the actual code implementation mentioned in the development guidance.

---

Duplicate comments:
In `@components/clp-py-utils/clp_py_utils/clp_logging.py`:
- Around line 28-36: The TimeStamper processor in the _FOREIGN_PRE_CHAIN tuple
is generating a timestamp at processor execution time rather than using the
LogRecord creation time, which can cause timestamp inaccuracy when handlers are
delayed or queued. Replace the structlog.processors.TimeStamper call to instead
extract the timestamp from the LogRecord's creation time, which is available in
the stdlib integration context. You should create a custom processor or use
structlog's built-in mechanism to access _record.created and convert it to ISO
format with UTC timezone, then assign it to the timestamp key, ensuring the
timestamp reflects when the logging event actually occurred rather than when it
was processed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 681342e0-ce89-463a-b5a8-353892ca90ea

📥 Commits

Reviewing files that changed from the base of the PR and between 962ae48 and dd31655.

⛔ Files ignored due to path filters (5)
  • components/clp-mcp-server/uv.lock is excluded by !**/*.lock
  • components/clp-package-utils/uv.lock is excluded by !**/*.lock
  • components/clp-py-utils/uv.lock is excluded by !**/*.lock
  • components/job-orchestration/uv.lock is excluded by !**/*.lock
  • integration-tests/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • components/clp-py-utils/clp_py_utils/clp_logging.py
  • components/clp-py-utils/pyproject.toml
  • components/job-orchestration/job_orchestration/executor/compress/celery.py
  • components/job-orchestration/job_orchestration/executor/query/celery.py
  • docs/src/dev-docs/index.md
  • docs/src/dev-docs/logging.md

Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated
Comment on lines +109 to +110
[`clp_py_utils.clp_logging.get_logger`][clp-py-logging] and
[`clp_py_utils.clp_logging.configure_logging`][clp-py-logging].

@rishikeshdevsot rishikeshdevsot Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be structlog.get_logger()? I thought we want to transition to using structloggers right? that's why we added structlog.configure?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, sorry for the confusion. the recommendation was written before i added structlog.configure(). btw, i think we should wrap the structlog.configure() into a method; otherwise the method is called unconditionally when the module is imported, which is some side effect that might be undesirable

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Also I think "developer guidance" section should go right after the components table. A reader of this doc mostly cares about how they should be logging.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, can you take a look @junhaoliao

]

# Processor chain for events emitted through structlog loggers before they reach
# ``ProcessorFormatter``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# ``ProcessorFormatter``.
# `ProcessorFormatter`.

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Only reviewed the logging doc.
  • Didin't review the Python and WebUI guidelines carefully. Focused on the Rust side.

Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated
Comment thread docs/src/dev-docs/logging.md Outdated

@junhaoliao junhaoliao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed two commits to fix docstrings and typing:
ae977c0
9345052

then the rest of the code changes lgtm

waiting on the md docs changes

@LinZhihao-723 LinZhihao-723 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this is missing in the latest version otherwise lgtm.

@rishikeshdevsot
rishikeshdevsot merged commit cb6a93e into y-scope:main Jun 30, 2026
28 checks passed
@junhaoliao junhaoliao added this to the June 2026 milestone Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants