Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ curl http://localhost:3000/api/v1/health # backend (via web proxy)

```text
src/synthorg/
api/ # Litestar REST + WebSocket API (controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation)), AppState hot-reload slots (provider_registry, model_router with swap methods, provider_management), settings dispatcher lifecycle, service auto-wiring (auto_wire.py: Phase 1 at construction -- message bus/cost tracker/provider registry/task engine; Phase 2 in on_startup after persistence connects -- settings service + config resolver + provider management), lifecycle helpers (lifecycle.py: _safe_startup, _safe_shutdown, _cleanup_on_failure, _init_persistence, _try_stop)
api/ # Litestar REST + WebSocket API (controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors (ErrorCategory, ErrorCode, ErrorDetail, ProblemDetail, CATEGORY_TITLES, category_title, category_type_uri, content negotiation)), AppState hot-reload slots (provider_registry, model_router with swap methods, provider_management), settings dispatcher lifecycle, logging bootstrap (_bootstrap_app_logging, SYNTHORG_LOG_DIR env var override, called before all other setup in create_app), service auto-wiring (auto_wire.py: Phase 1 at construction -- message bus/cost tracker/provider registry/task engine; Phase 2 in on_startup after persistence connects -- settings service + config resolver + provider management), lifecycle helpers (lifecycle.py: _safe_startup, _safe_shutdown, _cleanup_on_failure, _init_persistence, _try_stop)
auth/ # Authentication subpackage (controller, service, middleware, JWT + API key + WS ticket store, models, config, secret resolution)
backup/ # Backup and restore -- scheduled/manual/lifecycle backups of persistence DB, agent memory, and company config. BackupService orchestrator, BackupScheduler (periodic asyncio task), RetentionManager (count + age pruning), tar.gz compression, SHA-256 checksums, manifest tracking, validated restore with atomic rollback and safety backup
handlers/ # ComponentHandler protocol + concrete handlers: PersistenceComponentHandler (SQLite VACUUM INTO), MemoryComponentHandler (copytree), ConfigComponentHandler (copy2)
Expand All @@ -128,7 +128,7 @@ src/synthorg/
hr/ # HR engine: hiring, firing, onboarding, offboarding, agent registry, performance tracking (task metrics, collaboration scoring, LLM calibration sampling, collaboration overrides, trend detection), promotion/demotion (criteria evaluation, approval strategies, model mapping)
memory/ # Persistent agent memory (pluggable MemoryBackend protocol), backends/ (Mem0 adapter: backends/mem0/), retrieval pipeline (ranking, RRF fusion, injection, context formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (consolidation/, dual-mode density-aware archival: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)
persistence/ # Operational data persistence — pluggable PersistenceBackend protocol, SQLite initial, SettingsRepository (namespaced settings CRUD) (see Memory & Persistence design page)
observability/ # Structured logging, correlation tracking, log sinks
observability/ # Structured logging (8-sink pipeline: console + 7 file sinks with logger-name routing), correlation tracking (request_id/task_id/agent_id via contextvars), sensitive field redaction, SYNTHORG_LOG_LEVEL env var override, critical sink enforcement (audit.log/access.log), log sinks
providers/ # LLM provider abstraction (LiteLLM adapter), auth types (AuthType enum: api_key/oauth/custom_header/none), presets (ProviderPreset, PROVIDER_PRESETS for Ollama/LM Studio/OpenRouter/vLLM), runtime CRUD (management/ -- ProviderManagementService, asyncio.Lock-serialized create/update/delete/test, hot-reload of ProviderRegistry + ModelRouter via AppState swap)
settings/ # Runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces, including JSON type for structural data), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic models/collections), ConfigResolver (typed scalar + structural data accessors for controllers — get_agents, get_departments, get_provider_configs with validation fallbacks to YAML), validation, registry, change notifications via message bus, SettingsSubscriber protocol (subscriber.py), SettingsChangeDispatcher (dispatcher.py, polls #settings channel, routes to subscribers, restart_required filtering)
definitions/ # Per-namespace setting definitions (api, company, providers, memory, budget, security, coordination, observability, backup)
Expand Down
1 change: 1 addition & 0 deletions cli/internal/compose/compose.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
SYNTHORG_MEMORY_DIR: "/data/memory"
SYNTHORG_PERSISTENCE_BACKEND: {{yamlStr .PersistenceBackend}}
SYNTHORG_MEMORY_BACKEND: {{yamlStr .MemoryBackend}}
SYNTHORG_LOG_DIR: "/data/logs"
MEM0_TELEMETRY: "false"
SYNTHORG_LOG_LEVEL: {{yamlStr .LogLevel}}
{{- if .JWTSecret}}
Expand Down
1 change: 1 addition & 0 deletions cli/testdata/compose_custom_ports.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
SYNTHORG_MEMORY_DIR: "/data/memory"
SYNTHORG_PERSISTENCE_BACKEND: "sqlite"
SYNTHORG_MEMORY_BACKEND: "mem0"
SYNTHORG_LOG_DIR: "/data/logs"
MEM0_TELEMETRY: "false"
SYNTHORG_LOG_LEVEL: "debug"
SYNTHORG_JWT_SECRET: "test-secret-value"
Expand Down
1 change: 1 addition & 0 deletions cli/testdata/compose_default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
SYNTHORG_MEMORY_DIR: "/data/memory"
SYNTHORG_PERSISTENCE_BACKEND: "sqlite"
SYNTHORG_MEMORY_BACKEND: "mem0"
SYNTHORG_LOG_DIR: "/data/logs"
MEM0_TELEMETRY: "false"
SYNTHORG_LOG_LEVEL: "info"
user: "65532:65532"
Expand Down
1 change: 1 addition & 0 deletions cli/testdata/compose_digest_pins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
SYNTHORG_MEMORY_DIR: "/data/memory"
SYNTHORG_PERSISTENCE_BACKEND: "sqlite"
SYNTHORG_MEMORY_BACKEND: "mem0"
SYNTHORG_LOG_DIR: "/data/logs"
MEM0_TELEMETRY: "false"
SYNTHORG_LOG_LEVEL: "info"
user: "65532:65532"
Expand Down
6 changes: 6 additions & 0 deletions docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ SYNTHORG_PERSISTENCE_BACKEND=sqlite
# Memory backend (currently: mem0)
SYNTHORG_MEMORY_BACKEND=mem0

# Log file directory (inside container: /data/logs, persisted via volume)
SYNTHORG_LOG_DIR=/data/logs

# Console log level override (valid: debug, info, warning, error, critical)
# SYNTHORG_LOG_LEVEL=info

# --- Container Networking ----------------------------------------------------
# Host port for the backend API
BACKEND_PORT=8000
Expand Down
5 changes: 4 additions & 1 deletion docker/backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,7 @@ HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=30s \
ENV PYTHONWARNINGS="ignore::UserWarning:pydantic.v1"

ENTRYPOINT []
CMD ["/app/.venv/bin/uvicorn", "synthorg.api.app:create_app", "--factory"]
# --no-access-log: Uvicorn's default access logger is disabled because
# RequestLoggingMiddleware provides richer structured access logging via
# structlog (method, path, status_code, duration_ms, request_id).
CMD ["/app/.venv/bin/uvicorn", "synthorg.api.app:create_app", "--factory", "--no-access-log"]
Comment on lines +125 to +128

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

🌐 Web query:

In current uvicorn source (uvicorn/main.py), what value does the CLI pass to run(..., log_config=...)when--log-config is not provided?

💡 Result:

When --log-config is not provided (so log_config is None), the CLI calls:

run(..., log_config=LOGGING_CONFIG) — i.e., it passes uvicorn.config.LOGGING_CONFIG. [1]

Sources: [1] (raw.githubusercontent.com)

Citations:


Disable Uvicorn's default logging config to match the structured logging pipeline.

The --no-access-log flag disables only the access logger. However, when --log-config is omitted from the CLI invocation, Uvicorn defaults to applying its standard LOGGING_CONFIG dict, which bypasses the structured logging pipeline. Either pass --log-config /dev/null to suppress it, or refactor the container startup to call uvicorn.run(log_config=None) directly to ensure all logs route through RequestLoggingMiddleware.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker/backend/Dockerfile` around lines 125 - 128, The Docker CMD currently
starts Uvicorn with "--no-access-log" but still allows Uvicorn's default
LOGGING_CONFIG to be applied, bypassing RequestLoggingMiddleware; fix by either
adding the CLI flag "--log-config /dev/null" to the existing CMD that runs
"/app/.venv/bin/uvicorn synthorg.api.app:create_app --factory --no-access-log"
so the default logging config is suppressed, or refactor startup so that
synthorg.api.app calls uvicorn.run(..., log_config=None) (and remove the Docker
CMD invocation) ensuring all logs flow through RequestLoggingMiddleware.

2 changes: 2 additions & 0 deletions docker/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ services:
SYNTHORG_PORT: "${SYNTHORG_PORT:-8000}"
SYNTHORG_DB_PATH: "/data/synthorg.db"
SYNTHORG_MEMORY_DIR: "/data/memory"
SYNTHORG_LOG_DIR: "${SYNTHORG_LOG_DIR:-/data/logs}"
SYNTHORG_LOG_LEVEL: "${SYNTHORG_LOG_LEVEL:-info}"
# Disable Mem0 telemetry by default. Override in .env to enable.
MEM0_TELEMETRY: "${MEM0_TELEMETRY:-false}"
# Bridge SYNTHORG_* to uvicorn's native env vars (exec form CMD, no shell)
Expand Down
111 changes: 111 additions & 0 deletions docs/design/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1133,3 +1133,114 @@ Backup settings live in the `backup` namespace with runtime editability via `Bac
| `GET` | `/api/v1/admin/backups/{id}` | Get backup details |
| `DELETE` | `/api/v1/admin/backups/{id}` | Delete a specific backup |
| `POST` | `/api/v1/admin/backups/restore` | Restore from backup (requires `confirm=true`) |

## Observability and Logging

Structured logging pipeline built on **structlog** + stdlib, with automatic sensitive field
redaction, async-safe correlation tracking, and per-domain log routing.

### Sink Layout

Eight default sinks, activated at startup via `bootstrap_logging()`:

| Sink | Type | Level | Format | Routes | Description |
|------|------|-------|--------|--------|-------------|
| Console | stderr | INFO | Colored text | All loggers | Human-readable development output |
| `synthorg.log` | File | INFO | JSON | All loggers | Main application log (catch-all) |
| `audit.log` | File | INFO | JSON | `synthorg.security.*` | Security events only |
| `errors.log` | File | ERROR | JSON | All loggers | Errors and above only |
| `agent_activity.log` | File | DEBUG | JSON | `synthorg.engine.*`, `synthorg.core.*` | Agent execution and task lifecycle |
| `cost_usage.log` | File | INFO | JSON | `synthorg.budget.*`, `synthorg.providers.*` | Cost records and provider calls |
| `debug.log` | File | DEBUG | JSON | All loggers | Full debug trace (catch-all) |
| `access.log` | File | INFO | JSON | `synthorg.api.*` | HTTP request/response access log |
Comment on lines +1146 to +1155

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

Narrow access.log routing before calling it an HTTP access log.

Routing synthorg.api.* into this sink will also capture non-request events like API_APP_STARTUP and API_APP_SHUTDOWN from src/synthorg/api/app.py, so access.log will not stay request-only. Use a dedicated access-logger namespace or an event filter if this file is meant to be the single structured HTTP access log.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/operations.md` around lines 1146 - 1155, The current sink routing
sends all synthorg.api.* events to access.log which also captures non-request
events like API_APP_STARTUP and API_APP_SHUTDOWN; update the routing so
access.log only receives actual HTTP access entries by either using a dedicated
logger name (e.g., synthorg.api.access or synthorg.api.http_access) in the code
that emits request logs, or add an event-level filter to exclude lifecycle
events (API_APP_STARTUP, API_APP_SHUTDOWN) from the access.log sink; change the
logger used in src/synthorg/api/app.py where startup/shutdown are emitted or
adjust the sink routes in the logging configuration to reference the new
namespace/filter so access.log remains request-only.


Logger name routing is implemented via `_LoggerNameFilter` on file handlers. Sinks without
explicit routing are catch-all (accept all loggers at their configured level).

### Log Directory

- **Docker**: `/data/logs/` (under the `synthorg-data` volume, persisted across restarts)
- **Local dev**: `logs/` relative to working directory (default)
- **Override**: `SYNTHORG_LOG_DIR` env var

### Rotation

File sinks use `RotatingFileHandler` by default (10 MB max, 5 backup files). Alternative:
`WatchedFileHandler` for external logrotate (`rotation.strategy: external` in config).

### Sensitive Field Redaction

The `sanitize_sensitive_fields` processor automatically redacts values for keys matching:
`password`, `secret`, `token`, `api_key`, `api_secret`, `authorization`, `credential`,
`private_key`, `bearer`, `session`. Redaction applies at all nesting depths in structured
log events. Redacted values are replaced with `"**REDACTED**"`.

### Correlation Tracking

Three correlation IDs propagated via `contextvars` (async-safe):

- **`request_id`**: Bound per HTTP request by `RequestLoggingMiddleware`. Links all log
events during a single API call.
- **`task_id`**: Bound per task execution. Links agent activity to a specific task.
- **`agent_id`**: Bound per agent execution context.

All three are automatically injected into every log event by `merge_contextvars` in the
structlog processor chain.

### Per-Logger Levels

Default levels per domain module (overridable via `LogConfig.logger_levels`):

| Logger | Default Level |
|--------|---------------|
| `synthorg.engine` | DEBUG |
| `synthorg.memory` | DEBUG |
| `synthorg.core` | INFO |
| `synthorg.communication` | INFO |
| `synthorg.providers` | INFO |
| `synthorg.budget` | INFO |
| `synthorg.security` | INFO |
| `synthorg.tools` | INFO |
| `synthorg.api` | INFO |
| `synthorg.cli` | INFO |
| `synthorg.config` | INFO |
| `synthorg.templates` | INFO |

Comment thread
coderabbitai[bot] marked this conversation as resolved.
### Event Taxonomy

50 domain-specific event constant modules under `observability/events/` (one per subsystem:
api, budget, tool, git, engine, communication, etc.). Every log call uses a typed constant
(e.g., `API_REQUEST_STARTED`, `BUDGET_RECORD_ADDED`) for consistent, grep-friendly event
names. Format: `"<domain>.<noun>.<verb>"` (e.g., `"api.request.started"`).

### Uvicorn Integration

Uvicorn's default access logger is **disabled** (`access_log=False`, `log_config=None`).
HTTP access logging is handled by `RequestLoggingMiddleware`, which provides richer structured
fields (method, path, status_code, duration_ms, request_id) through structlog. Uvicorn's own
startup/error messages propagate through stdlib's root handler (which structlog wraps via
`ProcessorFormatter`).

### Docker Logging

Two layers of log management:

1. **App-level** (structlog): 8 file sinks with `RotatingFileHandler` (10 MB x 5) writing
JSON to `/data/logs/`. Console sink writes colored text to stderr.
2. **Container-level** (Docker): `json-file` driver with 10 MB x 3 rotation on
stdout/stderr. Captures console sink output and any uncaught stderr.

The layers are complementary -- app files provide structured, routed logs; Docker captures
the console stream for `docker logs` access.

### Runtime Settings

Two observability settings are runtime-editable via `SettingsService`:

- `root_log_level` (enum: debug/info/warning/error/critical) -- changes the root logger level
- `enable_correlation` (boolean) -- toggles correlation ID injection

Console sink level can also be overridden via `SYNTHORG_LOG_LEVEL` env var.
Comment on lines +1238 to +1243

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

Minor: Use em dash for consistency.

The documentation uses em dashes () consistently elsewhere in the file (e.g., line 1138). Consider replacing -- with for typographical consistency.

📝 Suggested fix
 - `root_log_level` (enum: debug/info/warning/error/critical) -- changes the root logger level
-- `enable_correlation` (boolean) -- toggles correlation ID injection
+- `root_log_level` (enum: debug/info/warning/error/critical) — changes the root logger level
+- `enable_correlation` (boolean) — toggles correlation ID injection
📝 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
Two observability settings are runtime-editable via `SettingsService`:
- `root_log_level` (enum: debug/info/warning/error/critical) -- changes the root logger level
- `enable_correlation` (boolean) -- toggles correlation ID injection
Console sink level can also be overridden via `SYNTHORG_LOG_LEVEL` env var.
Two observability settings are runtime-editable via `SettingsService`:
- `root_log_level` (enum: debug/info/warning/error/critical) changes the root logger level
- `enable_correlation` (boolean) toggles correlation ID injection
Console sink level can also be overridden via `SYNTHORG_LOG_LEVEL` env var.
🧰 Tools
🪛 LanguageTool

[typographical] ~1239-~1239: Consider using an em dash (—) instead of ‘--’.
Context: ... level - enable_correlation (boolean) -- toggles correlation ID injection Conso...

(TWO_HYPHENS)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/operations.md` around lines 1236 - 1241, Replace the ASCII double
hyphens used as dashes with an em dash for typographic consistency in the
observability settings description: change "`--` changes the root logger level"
to use an em dash after `root_log_level` and similarly replace "`-- toggles
correlation ID injection`" after `enable_correlation`, and also ensure the
console sink note uses an em dash before `SYNTHORG_LOG_LEVEL`; locate these
phrases near the `SettingsService`, `root_log_level`, `enable_correlation`, and
`SYNTHORG_LOG_LEVEL` mentions and swap `--` for `—`.


Full sink CRUD via SettingsService (add/remove/reconfigure sinks at runtime) is planned as a
future enhancement.
73 changes: 72 additions & 1 deletion src/synthorg/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import asyncio
import contextlib
import os
import sys
import time
from datetime import UTC, datetime
from pathlib import Path
from pathlib import Path, PurePath
from typing import TYPE_CHECKING, Any

from litestar import Litestar, Router
Expand Down Expand Up @@ -48,13 +49,15 @@
MeetingOrchestrator, # noqa: TC001
)
from synthorg.communication.meeting.scheduler import MeetingScheduler # noqa: TC001
from synthorg.config import bootstrap_logging
from synthorg.config.schema import RootConfig
from synthorg.core.approval import ApprovalItem # noqa: TC001
from synthorg.engine.coordination.service import MultiAgentCoordinator # noqa: TC001
from synthorg.engine.task_engine import TaskEngine # noqa: TC001
from synthorg.hr.performance.tracker import PerformanceTracker # noqa: TC001
from synthorg.hr.registry import AgentRegistryService # noqa: TC001
from synthorg.observability import get_logger
from synthorg.observability.config import DEFAULT_SINKS, LogConfig
from synthorg.observability.events.api import (
API_APP_SHUTDOWN,
API_APP_STARTUP,
Expand Down Expand Up @@ -328,6 +331,57 @@ async def on_shutdown() -> None:
# to code calling get_api_config(), not to the middleware itself.


def _bootstrap_app_logging(effective_config: RootConfig) -> RootConfig:
"""Activate the structured logging pipeline.

Applies the ``SYNTHORG_LOG_DIR`` env var override (for Docker
volume paths) before calling :func:`bootstrap_logging`.

When the env var is set with an existing logging config, patches
``log_dir``. When set without a logging config, creates a
default config with ``DEFAULT_SINKS``. Otherwise, delegates
directly to ``bootstrap_logging``.

Args:
effective_config: Root config (possibly without a logging
section).

Returns:
The config actually used for logging -- either the original
``effective_config`` or a patched copy with the
``SYNTHORG_LOG_DIR`` override applied. Callers should use
the returned value so that ``AppState.config.logging``
reflects the active logging configuration.

Raises:
ValueError: If ``SYNTHORG_LOG_DIR`` contains ``..`` path
traversal components.
"""
log_dir = os.environ.get("SYNTHORG_LOG_DIR", "").strip()
if not log_dir:
bootstrap_logging(effective_config)
return effective_config

# Validate before model_copy -- Pydantic validators do not run
# on model_copy(update=...), so we must check manually.
if ".." in PurePath(log_dir).parts:
msg = f"SYNTHORG_LOG_DIR contains '..' path traversal component: {log_dir!r}"
raise ValueError(msg)

base_log_cfg = effective_config.logging or LogConfig(
sinks=DEFAULT_SINKS,
)
patched = effective_config.model_copy(
update={
"logging": base_log_cfg.model_copy(
update={"log_dir": log_dir},
),
},
)
bootstrap_logging(patched)
return patched


def create_app( # noqa: PLR0913
*,
config: RootConfig | None = None,
Expand Down Expand Up @@ -371,6 +425,23 @@ def create_app( # noqa: PLR0913
Configured Litestar application.
"""
effective_config = config or RootConfig(company_name="default")

# Activate the structured logging pipeline (8 sinks) before any
# other setup so that auto-wiring, persistence, and bus logs all
# flow through the configured sinks. Respects SYNTHORG_LOG_DIR
# env var for Docker log directory override.
try:
effective_config = _bootstrap_app_logging(effective_config)
except Exception as exc:
Comment on lines +429 to +435

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

This still leaves observability settings startup-only.

The new docs advertise root_log_level and enable_correlation as runtime-editable, but they can only take effect here during app construction. _build_settings_dispatcher() in this module still wires only provider, memory, and backup subscribers, so SettingsService changes will not reconfigure logging after startup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/api/app.py` around lines 429 - 435, The app currently applies
logging settings only at startup via _bootstrap_app_logging(effective_config)
and _build_settings_dispatcher() wires only provider/memory/backup subscribers
so runtime changes to root_log_level or enable_correlation never reconfigure
logging; update _build_settings_dispatcher (or add a dedicated settings
subscriber) to subscribe the SettingsService to changes for the logging keys
(e.g., "root_log_level", "enable_correlation") and, on change, invoke a
reconfiguration path that reapplies _bootstrap_app_logging or a smaller
reconfigure_logging(effective_config) helper using the new settings (ensuring it
runs safely from the dispatcher and updates existing logger sinks/correlation
behavior without requiring process restart).

print( # noqa: T201
f"CRITICAL: Failed to initialise logging pipeline: {exc}. "
"Check SYNTHORG_LOG_DIR, SYNTHORG_LOG_LEVEL, and the "
"'logging' section of your config file.",
file=sys.stderr,
flush=True,
)
raise

api_config = effective_config.api

# Resolve runtime paths for backup service wiring.
Expand Down
2 changes: 2 additions & 0 deletions src/synthorg/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ def run_server(config: RootConfig) -> None:
reload=server.reload,
ws_ping_interval=ws_ping,
ws_ping_timeout=ws_timeout,
access_log=False,
log_config=None,
)
Loading
Loading