Skip to content

feat: add VOUCH_LOG_FORMAT=json structured logging#113

Merged
plind-junior merged 1 commit into
vouchdev:testfrom
Tet-9:feat/97-vouch-log-format-json
Jun 15, 2026
Merged

feat: add VOUCH_LOG_FORMAT=json structured logging#113
plind-junior merged 1 commit into
vouchdev:testfrom
Tet-9:feat/97-vouch-log-format-json

Conversation

@Tet-9

@Tet-9 Tet-9 commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

VOUCH_LOG_FORMAT is referenced in ROADMAP and adapters/generic-mcp/README.md but was never implemented anywhere in src/. Running vouch as a service made logs unparseable by log aggregators and monitoring tools.

Changes

Add src/vouch/logging_config.py with a configure_logging() function that reads VOUCH_LOG_FORMAT at process startup:

  • Unset or "text" — human-readable format, no behavior change for existing users
  • "json" — one JSON object per log line: {"time", "level", "logger", "message"}

Also supports VOUCH_LOG_LEVEL (defaults to WARNING) for controlling verbosity.

configure_logging() is called from all three entrypoints:

  • cli() group in cli.py — covers all vouch CLI commands
  • run_stdio() in server.py — MCP stdio server
  • run_jsonl() in jsonl_server.py — JSONL tool server

Testing

Two new tests in tests/test_logging_config.py:

  • test_json_format_emits_valid_json — verifies JSON output shape
  • test_text_format_is_default — verifies human-readable output when unset

156 existing tests passing, no regressions.

Closes #97

Summary by CodeRabbit

  • New Features

    • Added configurable logging format via VOUCH_LOG_FORMAT — choose single-line JSON logs (time, level, logger, message) or human-readable text (default). Logging is initialized automatically at application startup for all entry points.
  • Tests

    • Added tests validating JSON vs. text logging output and that the format setting is respected.
  • Documentation

    • Added changelog entry documenting the new logging format option.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 35e912b3-3228-488e-8209-50011859768f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements structured JSON logging via a new VOUCH_LOG_FORMAT=json, centralizes startup configuration in src/vouch/logging_config.py, applies it at CLI, JSONL, and stdio entrypoints, and adds tests plus a CHANGELOG entry.

Changes

Structured JSON Logging Configuration

Layer / File(s) Summary
Core logging configuration module
src/vouch/logging_config.py
Introduces _JsonFormatter to serialize log records as compact JSON objects with timestamp, level, logger name, message, and exception info. Implements configure_logging(*, force=False) to read VOUCH_LOG_FORMAT and VOUCH_LOG_LEVEL, configure the root logger with JSON or text formatting, attach a stderr handler, and support idempotent reconfiguration.
Logging initialization at process entrypoints
src/vouch/cli.py, src/vouch/jsonl_server.py, src/vouch/server.py
CLI top-level group, JSONL run_jsonl(), and stdio run_stdio() each import and call configure_logging() at startup, ensuring logging is configured for all subcommands and request loops.
Tests and documentation
tests/test_logging_config.py, CHANGELOG.md
Two pytest tests validate that VOUCH_LOG_FORMAT=json produces valid JSON with expected fields, and that unset format produces non-JSON human-readable output. Changelog documents the new feature and its behavior.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant JSONLServer
  participant StdioServer
  participant LoggingConfig
  participant stderr
  CLI->>LoggingConfig: call configure_logging()
  JSONLServer->>LoggingConfig: call configure_logging()
  StdioServer->>LoggingConfig: call configure_logging()
  LoggingConfig->>stderr: attach StreamHandler(sys.stderr) with chosen formatter
  LoggingConfig->>stderr: emit formatted log lines (JSON/text)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through code to set logs right,
One JSON line per hop at night.
Env says "json" and lines align,
Or leave it text for humans to find.
Cheers to tidy logs and clear daylight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding structured JSON logging via the VOUCH_LOG_FORMAT environment variable.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #97: VOUCH_LOG_FORMAT=json emits one JSON object per log line (level, logger, message, time), default keeps human-readable format, and no behavioral changes beyond formatting.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the VOUCH_LOG_FORMAT feature: logging configuration module, integration into three entrypoints, comprehensive tests, and changelog entry.

✏️ 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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_logging_config.py (1)

7-35: ⚡ Quick win

Add explicit coverage for VOUCH_LOG_LEVEL threshold behavior.

The current tests validate format switching, but not whether VOUCH_LOG_LEVEL actually filters records as intended.

Suggested test addition
+def test_log_level_from_env_filters_lower_levels(monkeypatch, capsys):
+    monkeypatch.setenv("VOUCH_LOG_FORMAT", "json")
+    monkeypatch.setenv("VOUCH_LOG_LEVEL", "ERROR")
+    from vouch.logging_config import configure_logging
+    configure_logging(force=True)
+
+    logger = logging.getLogger("vouch.test")
+    logger.warning("should be filtered")
+    logger.error("should appear")
+
+    captured = capsys.readouterr()
+    lines = [ln for ln in captured.err.strip().splitlines() if ln]
+    assert lines, "Expected at least one log line"
+    last = json.loads(lines[-1])
+    assert last["level"] == "ERROR"
+    assert last["message"] == "should appear"
🤖 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 `@tests/test_logging_config.py` around lines 7 - 35, Add a new test in
tests/test_logging_config.py that verifies VOUCH_LOG_LEVEL actually filters log
records: use monkeypatch.setenv("VOUCH_LOG_LEVEL", "<LEVEL>") with
configure_logging(force=True) from vouch.logging_config, then emit messages via
logging.getLogger("vouch.test").warning(...) and .error(...) and capture output
with capsys to assert that messages below the threshold (e.g., WARNING when
level is ERROR) are not present and messages at/above the threshold are present;
repeat for at least two thresholds (e.g., "ERROR" and "WARNING") to cover both
filtered and allowed cases.
🤖 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 `@src/vouch/logging_config.py`:
- Around line 21-30: The JSON formatter's format method currently only
serializes a fixed set of fields and drops LogRecord extras (e.g., event, actor,
object_ids); update the format method (the formatter class's format function) to
merge user-provided extra fields from record.__dict__ into the obj before
json.dumps by copying record.__dict__, removing standard LogRecord attributes
(like name, msg, args, levelname, levelno, pathname, lineno, funcName, created,
msecs, relativeCreated, thread, threadName, processName, exc_info, stack_info)
and any private keys, then update obj with the remaining extras so all
structured context fields are preserved in the JSON output.

---

Nitpick comments:
In `@tests/test_logging_config.py`:
- Around line 7-35: Add a new test in tests/test_logging_config.py that verifies
VOUCH_LOG_LEVEL actually filters log records: use
monkeypatch.setenv("VOUCH_LOG_LEVEL", "<LEVEL>") with
configure_logging(force=True) from vouch.logging_config, then emit messages via
logging.getLogger("vouch.test").warning(...) and .error(...) and capture output
with capsys to assert that messages below the threshold (e.g., WARNING when
level is ERROR) are not present and messages at/above the threshold are present;
repeat for at least two thresholds (e.g., "ERROR" and "WARNING") to cover both
filtered and allowed cases.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a605936-bddc-432b-8ae7-0e338e9f2da5

📥 Commits

Reviewing files that changed from the base of the PR and between 3beb821 and 922ceff.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • src/vouch/cli.py
  • src/vouch/jsonl_server.py
  • src/vouch/logging_config.py
  • src/vouch/server.py
  • tests/test_logging_config.py

Comment thread src/vouch/logging_config.py Outdated
Comment on lines +21 to +30
def format(self, record: logging.LogRecord) -> str:
obj = {
"time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
obj["exc_info"] = self.formatException(record.exc_info)
return json.dumps(obj, separators=(",", ":"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve structured context fields in JSON logs.

At Line 22, the formatter only serializes fixed fields, so LogRecord extras like event, actor, and object_ids are dropped in VOUCH_LOG_FORMAT=json output.

Suggested patch
 class _JsonFormatter(logging.Formatter):
     """Emit one JSON object per log record."""
 
     def format(self, record: logging.LogRecord) -> str:
         obj = {
             "time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
             "level": record.levelname,
             "logger": record.name,
             "message": record.getMessage(),
         }
+        for key in ("event", "actor", "object_ids"):
+            if key in record.__dict__:
+                obj[key] = record.__dict__[key]
         if record.exc_info:
             obj["exc_info"] = self.formatException(record.exc_info)
-        return json.dumps(obj, separators=(",", ":"))
+        return json.dumps(obj, separators=(",", ":"), default=str)
🤖 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 `@src/vouch/logging_config.py` around lines 21 - 30, The JSON formatter's
format method currently only serializes a fixed set of fields and drops
LogRecord extras (e.g., event, actor, object_ids); update the format method (the
formatter class's format function) to merge user-provided extra fields from
record.__dict__ into the obj before json.dumps by copying record.__dict__,
removing standard LogRecord attributes (like name, msg, args, levelname,
levelno, pathname, lineno, funcName, created, msecs, relativeCreated, thread,
threadName, processName, exc_info, stack_info) and any private keys, then update
obj with the remaining extras so all structured context fields are preserved in
the JSON output.

@Tet-9
Tet-9 force-pushed the feat/97-vouch-log-format-json branch from 922ceff to 7e0f6b3 Compare May 27, 2026 05:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/test_logging_config.py (1)

7-22: ⚡ Quick win

JSON test bypasses the env-driven configuration path.

On Line 8 you set VOUCH_LOG_FORMAT, but on Line 11 the test directly uses _JsonFormatter, so it won’t catch regressions in configure_logging() selection/handler wiring.

Proposed update
 def test_json_format_emits_valid_json(monkeypatch, capsys):
     monkeypatch.setenv("VOUCH_LOG_FORMAT", "json")
-    from vouch.logging_config import _JsonFormatter
-    handler = logging.StreamHandler()
-    handler.setFormatter(_JsonFormatter())
-    record = logging.LogRecord(
-        name="vouch.test", level=logging.WARNING,
-        pathname="", lineno=0, msg="test message",
-        args=(), exc_info=None,
-    )
-    line = handler.format(record)
+    from vouch.logging_config import configure_logging
+    root = logging.getLogger()
+    old_handlers = root.handlers[:]
+    old_level = root.level
+    try:
+        configure_logging(force=True)
+        root.warning("test message")
+        captured = capsys.readouterr()
+        line = captured.err.strip().splitlines()[-1]
+    finally:
+        root.handlers.clear()
+        root.handlers.extend(old_handlers)
+        root.setLevel(old_level)
     obj = json.loads(line)
     assert obj["level"] == "WARNING"
     assert obj["message"] == "test message"
     assert "time" in obj
     assert "logger" in obj
🤖 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 `@tests/test_logging_config.py` around lines 7 - 22, The test
test_json_format_emits_valid_json currently instantiates _JsonFormatter directly
and thus bypasses the env-driven wiring; change it to set VOUCH_LOG_FORMAT to
"json", call configure_logging() (the function that selects and wires handlers),
obtain a logger (e.g., logging.getLogger("vouch.test")), emit a log at WARNING,
capture the output from the configured handler, and then json.loads that output
to assert "level", "message", "time", and "logger" keys; reference symbols:
test_json_format_emits_valid_json, configure_logging, and _JsonFormatter to
locate the code to modify.
🤖 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 `@tests/test_logging_config.py`:
- Around line 40-41: In the except json.JSONDecodeError handler, replace the use
of the bare assert (the line that currently triggers an assertion failure for
"Expected non-JSON output") with an explicit test failure by raising an
AssertionError or calling pytest.fail so the test fails under python -O; update
the test in tests/test_logging_config.py accordingly.
- Line 4: Remove the redundant imports in tests/test_logging_config.py: delete
the unused top-level "import os" statement and remove the local "import logging"
within the test body (the module already imports logging at top-level), ensuring
no duplicate imports remain and import-order lint (F401) is resolved; update any
references if needed to rely on the module-level logging import.

---

Nitpick comments:
In `@tests/test_logging_config.py`:
- Around line 7-22: The test test_json_format_emits_valid_json currently
instantiates _JsonFormatter directly and thus bypasses the env-driven wiring;
change it to set VOUCH_LOG_FORMAT to "json", call configure_logging() (the
function that selects and wires handlers), obtain a logger (e.g.,
logging.getLogger("vouch.test")), emit a log at WARNING, capture the output from
the configured handler, and then json.loads that output to assert "level",
"message", "time", and "logger" keys; reference symbols:
test_json_format_emits_valid_json, configure_logging, and _JsonFormatter to
locate the code to modify.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c35cf31-7c30-46d2-ac34-535f6dbfbbc8

📥 Commits

Reviewing files that changed from the base of the PR and between 922ceff and 7e0f6b3.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • src/vouch/cli.py
  • src/vouch/jsonl_server.py
  • src/vouch/logging_config.py
  • src/vouch/server.py
  • tests/test_logging_config.py
✅ Files skipped from review due to trivial changes (3)
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/vouch/cli.py
  • src/vouch/logging_config.py

Comment thread tests/test_logging_config.py Outdated
Comment thread tests/test_logging_config.py Outdated
@Tet-9
Tet-9 force-pushed the feat/97-vouch-log-format-json branch 2 times, most recently from b9998a9 to 8714c63 Compare May 27, 2026 05:42
@Tet-9

Tet-9 commented May 27, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior , review please

@Khaostica

Copy link
Copy Markdown
Contributor

@plind-junior worth flagging before merge: the acceptance criteria on #97 calls for level, event, actor, object_ids per log line, but this formatter only emits {time, level, logger, message} and drops anything passed via the stdlib extra= argument (CodeRabbit's first review noted the same thing). So actor and object_ids won't actually appear in the output even if callers attach them.

@plind-junior

Copy link
Copy Markdown
Member

PR should target test branch

@Tet-9
Tet-9 changed the base branch from main to test May 28, 2026 05:46
@Tet-9
Tet-9 force-pushed the feat/97-vouch-log-format-json branch from 8714c63 to d4df5d8 Compare May 28, 2026 05:57
@Tet-9

Tet-9 commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

PR should target test branch

All done man!
You may review it now🫡

@Tet-9
Tet-9 force-pushed the feat/97-vouch-log-format-json branch 3 times, most recently from f19cbc3 to b10e69f Compare May 29, 2026 20:22
@Tet-9

Tet-9 commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior , please check

@Tet-9

Tet-9 commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior , please check

All conflicts resolved

@Tet-9

Tet-9 commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior ??
Can you check this out now? 🫡

@Tet-9
Tet-9 force-pushed the feat/97-vouch-log-format-json branch from b10e69f to a3cec83 Compare June 1, 2026 23:22
@Tet-9

Tet-9 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto upstream/test and expanded the implementation:

  • Fixed the two merge conflicts (CHANGELOG.md and proposals.py)
  • Added VOUCH_LOG_LEVEL and VOUCH_LOG_FILE env var support
  • _JsonFormatter now merges extra= fields into the top-level JSON object for structured context
  • Replaced root.handlers.clear() with _VouchManagedHandler subclass so host/test handlers are never removed — addresses the subclass pattern you suggested
  • Updated CONTRIBUTING.md with env var docs and logging guidelines
  • 5 new tests: extra= merging, log level, log file, handler replacement on force=True, host handler preservation
  • All 230 tests pass, make check clean

@Tet-9

Tet-9 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Additional advantages of this approach over the previous implementation:

  • VOUCH_LOG_FILE means operators can capture logs to disk without wrapping the process in shell redirection — useful for systemd units, Docker deployments, and MCP host configs where stderr is swallowed
  • extra= field merging unlocks structured observability: callers can attach proposal_id, actor, session_id etc. directly to log records and downstream log aggregators (Datadog, Loki, CloudWatch) can filter and group by those fields without parsing message strings
  • _VouchManagedHandler is safe to use in test suites and embedded contexts — a host that installs its own handler (e.g. a pytest plugin, a logging proxy) won't have it silently removed on configure_logging() calls, which was a latent bug in the clear() approach
  • configure_logging() is now idempotent in a stronger sense: repeated calls replace only vouch's own handlers, never stacking them, so process-level reloads and hot-config scenarios are safe

@plind-junior

Copy link
Copy Markdown
Member

Fix the conflict

Tet-9 added a commit to Tet-9/vouch that referenced this pull request Jun 2, 2026
…LEVEL/FILE, and LoggingConfig return type

Combines the best of both PR vouchdev#113 and vouchdev#114 into a superior implementation:

From vouchdev#114 (Khaostica):
- Scoped vouch logger namespace instead of root logger
- _STDLIB_RECORD_FIELDS explicit whitelist for extra= merging
- event key with caller override via extra={event: ...}
- sort_keys=True, default=str in json.dumps
- propagate=False in json mode to prevent double-emit
- _selected_format() helper

Added on top:
- time field in JSON output (ISO 8601)
- VOUCH_LOG_LEVEL env var: sets vouch logger level (default WARNING)
- VOUCH_LOG_FILE env var: appends logs to file alongside stderr
- Sensitive field redaction: keys containing secret/token/password/key
  are replaced with ***REDACTED*** so credentials never reach log sinks
- LoggingConfig named tuple return type for testability and introspection
- force=True parameter for clean reconfiguration in tests
- CONTRIBUTING.md: documents all env vars, logging guidelines, and
  managed-handler pattern
- 11 tests covering all new behaviour

Fixes vouchdev#97
@Tet-9
Tet-9 force-pushed the feat/97-vouch-log-format-json branch from 15d11e5 to 7d3b1af Compare June 2, 2026 12:08
Tet-9 added a commit to Tet-9/vouch that referenced this pull request Jun 2, 2026
…LEVEL/FILE, and LoggingConfig return type

Combines the best of both PR vouchdev#113 and vouchdev#114 into a superior implementation:

From vouchdev#114 (Khaostica):
- Scoped vouch logger namespace instead of root logger
- _STDLIB_RECORD_FIELDS explicit whitelist for extra= merging
- event key with caller override via extra={event: ...}
- sort_keys=True, default=str in json.dumps
- propagate=False in json mode to prevent double-emit
- _selected_format() helper

Added on top:
- time field in JSON output (ISO 8601)
- VOUCH_LOG_LEVEL env var: sets vouch logger level (default WARNING)
- VOUCH_LOG_FILE env var: appends logs to file alongside stderr
- Sensitive field redaction: keys containing secret/token/password/key
  are replaced with ***REDACTED*** so credentials never reach log sinks
- LoggingConfig named tuple return type for testability and introspection
- force=True parameter for clean reconfiguration in tests
- CONTRIBUTING.md: documents all env vars, logging guidelines, and
  managed-handler pattern
- 11 tests covering all new behaviour

Fixes vouchdev#97
@Tet-9

Tet-9 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto latest upstream/test — all conflicts resolved, 265 tests passing, make check clean.

Also updated tests/test_logging.py (Khaostica's existing tests) to match our expanded return type — configure_logging() now returns a LoggingConfig named tuple instead of a plain string, so assertions updated from selected == "json" to selected.format == "json". The switch-back-to-text test now uses force=True to properly trigger reconfiguration.

All 265 tests passing. Make check clean.

@Tet-9

Tet-9 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

I guess this i perfect now, and ready to get merged @plind-junior

@plind-junior

Copy link
Copy Markdown
Member

Review

Summary: Implements VOUCH_LOG_FORMAT=json structured logging (issue #97). The core feature is solid and ships in src/vouch/logging_config.py. However, the PR bundles a large amount of unrelated changes — scope-field additions (visibility, project, agent on Claim/Source), removal of the --template / Gittensor onboarding system, removal of the color/--json/progress-callback CLI enhancements, and a version rollback from 0.1.0 → 0.0.1 — that are well outside the stated scope and have their own risks.

What works

  • src/vouch/logging_config.py:100-112_VouchManagedHandler subclass marker pattern is clean; correctly avoids attr-sentinel hacks and is idempotent with force=True.
  • src/vouch/logging_config.py:57-63_is_sensitive redaction of credential-shaped keys is a nice safety addition that wasn't in the issue but is unambiguously useful.
  • src/vouch/logging_config.py:115-122LoggingConfig NamedTuple return type is a clear improvement over returning a bare string.
  • tests/test_logging_config.py:1-154 — New test file covers JSON shape, level, file output, redaction, force-replace, and propagation. Good isolation using importlib.reload.
  • tests/test_logging.py:167-183 — Existing tests updated correctly for the time field addition and the new return type.
  • CONTRIBUTING.md:136-148 — The env-var table (VOUCH_LOG_FORMAT, VOUCH_LOG_LEVEL, VOUCH_LOG_FILE) is useful documentation.

Suggestions

  • [blocking] src/vouch/logging_config.py:238-253open(log_file, "a", ...) leaks the file descriptor when configure_logging() is called repeatedly (e.g. in tests). _VouchManagedHandler.close() is called on the old handlers at line 220, but only for the handlers from the previous call — if configure_logging is called with force=False on a process that already has a file handler, the FD is never closed. More critically, the open() call inside configure_logging is not wrapped in a try/except, so a bad VOUCH_LOG_FILE path (e.g. a directory, or a read-only FS) raises an unhandled OSError at startup and crashes the server before any log line is written. Suggest wrapping in try/except OSError and falling back to a warning on stderr.

  • [blocking] src/vouch/logging_config.py:217-218 — Early-return when existing and not force returns the current LoggingConfig values, but these are computed from the env at call time rather than from what is actually installed. If env vars changed between calls but force was not passed, the returned LoggingConfig will reflect the new env while the handlers still match the old config. This is only confusing in tests but could mislead callers who use the return value to decide whether to log. Either recompute from what is installed, or document that the return value is the requested not effective config.

  • [blocking] src/vouch/cli.py — The PR removes _color_enabled, _style, _echo, _print_findings, and _progress_cb (which were tracking-2 / epic: make vouch friendlier and more useful out of the box #54 work), drops --json from lint, doctor, and search, removes --template from init, and rolls the package name back from vouch-kb to vouch and version from 0.1.0 to 0.0.1. None of this is related to VOUCH_LOG_FORMAT. Removing lint --json and search --json is a regression for machine consumers, and the version rollback would re-publish a lower version to PyPI. These changes belong in their own PRs or should be reverted here.

  • [blocking] tests/test_cli_output.py — Deleted entirely. This test file covered color output, --json flags, and progress callbacks. Deleting it alongside the features it tests means the regressions are silent. If the intent is to remove those features, that requires its own discussion; if it was accidental, this file should be restored.

  • [non-blocking] src/vouch/logging_config.py:226 — Text-mode formatter "%(asctime)s %(levelname)-8s %(name)s: %(message)s" is a nice default but is a behavior change: before this PR, text mode was explicitly a no-op (no handler added, propagate=True, stdlib root handler does formatting). Adding a handler in text mode changes what happens when the root logger has no handler configured — the caller may now get duplicate lines if they also configure a root handler. Worth a comment acknowledging this is intentional, or consider keeping text mode as a true no-op unless VOUCH_LOG_FILE is set.

  • [non-blocking] src/vouch/models.py:267-273Visibility is identical to Scope (same four values: private, project, team, public). If these are semantically the same concept, a single enum avoids drift. If they differ in intent, a comment explaining the distinction would help reviewers.

  • [non-blocking] tests/test_logging_config.py:714-718_reconfigure calls importlib.reload(lc) on every call. This resets module-level state but also invalidates any previously imported references (e.g. lc._VouchManagedHandler from a prior import). The tests work because they re-import inside the helper, but it makes the fixture fragile. Consider a narrower isolation strategy (e.g. patching os.environ and calling configure_logging(force=True) directly).

Verdict

Request changes — The logging implementation itself (logging_config.py, its tests, and the entrypoint wiring) is correct and ready to merge. The PR is blocked by the unrelated scope regression: removing lint --json, search --json, --template, color output, progress callbacks, and rolling back the version. Those should either be reverted or broken into separate PRs with their own rationale. Once the diff is scoped to issue #97 (logging only), this is straightforward to approve.

@plind-junior

Copy link
Copy Markdown
Member

Convert it to "ready to merge" once the issue has been resolved

@plind-junior
plind-junior marked this pull request as draft June 9, 2026 06:48
@Tet-9

Tet-9 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Review

Summary: Implements VOUCH_LOG_FORMAT=json structured logging (issue #97). The core feature is solid and ships in src/vouch/logging_config.py. However, the PR bundles a large amount of unrelated changes — scope-field additions (visibility, project, agent on Claim/Source), removal of the --template / Gittensor onboarding system, removal of the color/--json/progress-callback CLI enhancements, and a version rollback from 0.1.0 → 0.0.1 — that are well outside the stated scope and have their own risks.

What works

  • src/vouch/logging_config.py:100-112_VouchManagedHandler subclass marker pattern is clean; correctly avoids attr-sentinel hacks and is idempotent with force=True.
  • src/vouch/logging_config.py:57-63_is_sensitive redaction of credential-shaped keys is a nice safety addition that wasn't in the issue but is unambiguously useful.
  • src/vouch/logging_config.py:115-122LoggingConfig NamedTuple return type is a clear improvement over returning a bare string.
  • tests/test_logging_config.py:1-154 — New test file covers JSON shape, level, file output, redaction, force-replace, and propagation. Good isolation using importlib.reload.
  • tests/test_logging.py:167-183 — Existing tests updated correctly for the time field addition and the new return type.
  • CONTRIBUTING.md:136-148 — The env-var table (VOUCH_LOG_FORMAT, VOUCH_LOG_LEVEL, VOUCH_LOG_FILE) is useful documentation.

Suggestions

  • [blocking] src/vouch/logging_config.py:238-253open(log_file, "a", ...) leaks the file descriptor when configure_logging() is called repeatedly (e.g. in tests). _VouchManagedHandler.close() is called on the old handlers at line 220, but only for the handlers from the previous call — if configure_logging is called with force=False on a process that already has a file handler, the FD is never closed. More critically, the open() call inside configure_logging is not wrapped in a try/except, so a bad VOUCH_LOG_FILE path (e.g. a directory, or a read-only FS) raises an unhandled OSError at startup and crashes the server before any log line is written. Suggest wrapping in try/except OSError and falling back to a warning on stderr.
  • [blocking] src/vouch/logging_config.py:217-218 — Early-return when existing and not force returns the current LoggingConfig values, but these are computed from the env at call time rather than from what is actually installed. If env vars changed between calls but force was not passed, the returned LoggingConfig will reflect the new env while the handlers still match the old config. This is only confusing in tests but could mislead callers who use the return value to decide whether to log. Either recompute from what is installed, or document that the return value is the requested not effective config.
  • [blocking] src/vouch/cli.py — The PR removes _color_enabled, _style, _echo, _print_findings, and _progress_cb (which were tracking-2 / epic: make vouch friendlier and more useful out of the box #54 work), drops --json from lint, doctor, and search, removes --template from init, and rolls the package name back from vouch-kb to vouch and version from 0.1.0 to 0.0.1. None of this is related to VOUCH_LOG_FORMAT. Removing lint --json and search --json is a regression for machine consumers, and the version rollback would re-publish a lower version to PyPI. These changes belong in their own PRs or should be reverted here.
  • [blocking] tests/test_cli_output.py — Deleted entirely. This test file covered color output, --json flags, and progress callbacks. Deleting it alongside the features it tests means the regressions are silent. If the intent is to remove those features, that requires its own discussion; if it was accidental, this file should be restored.
  • [non-blocking] src/vouch/logging_config.py:226 — Text-mode formatter "%(asctime)s %(levelname)-8s %(name)s: %(message)s" is a nice default but is a behavior change: before this PR, text mode was explicitly a no-op (no handler added, propagate=True, stdlib root handler does formatting). Adding a handler in text mode changes what happens when the root logger has no handler configured — the caller may now get duplicate lines if they also configure a root handler. Worth a comment acknowledging this is intentional, or consider keeping text mode as a true no-op unless VOUCH_LOG_FILE is set.
  • [non-blocking] src/vouch/models.py:267-273Visibility is identical to Scope (same four values: private, project, team, public). If these are semantically the same concept, a single enum avoids drift. If they differ in intent, a comment explaining the distinction would help reviewers.
  • [non-blocking] tests/test_logging_config.py:714-718_reconfigure calls importlib.reload(lc) on every call. This resets module-level state but also invalidates any previously imported references (e.g. lc._VouchManagedHandler from a prior import). The tests work because they re-import inside the helper, but it makes the fixture fragile. Consider a narrower isolation strategy (e.g. patching os.environ and calling configure_logging(force=True) directly).

Verdict

Request changes — The logging implementation itself (logging_config.py, its tests, and the entrypoint wiring) is correct and ready to merge. The PR is blocked by the unrelated scope regression: removing lint --json, search --json, --template, color output, progress callbacks, and rolling back the version. Those should either be reverted or broken into separate PRs with their own rationale. Once the diff is scoped to issue #97 (logging only), this is straightforward to approve.

Got it
I'd soon start working on that

Implements VOUCH_LOG_FORMAT=json for issue vouchdev#97. Scoped to logging only.

- JsonFormatter: vouch logger namespace, event key with caller override,
  _STDLIB_RECORD_FIELDS whitelist, sort_keys=True, default=str, time field
- Sensitive field redaction: keys containing secret/token/password/key
  replaced with ***REDACTED*** in JSON output
- VOUCH_LOG_LEVEL: sets vouch logger level (default WARNING)
- VOUCH_LOG_FILE: appends logs to file alongside stderr; bad path emits
  a warning to stderr and is skipped rather than crashing the server
- LoggingConfig named tuple: reflects effective installed state on
  early-return (inspects installed handlers, not requested env)
- _VouchManagedHandler subclass: host handlers never removed
- Text mode is a true no-op unless VOUCH_LOG_FILE is set
- force=True parameter for clean reconfiguration in tests
- CONTRIBUTING.md: env var table and logging guidelines
- 18 tests covering all behaviour; 429 passing total

Fixes vouchdev#97
@Tet-9
Tet-9 force-pushed the feat/97-vouch-log-format-json branch from 7d3b1af to a59d3f6 Compare June 9, 2026 22:50
@Tet-9
Tet-9 marked this pull request as ready for review June 9, 2026 22:52
@Tet-9

Tet-9 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior , all set now

@Tet-9

Tet-9 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior ,you may give this a check likewise too

@Tet-9

Tet-9 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior , can you review this now
It has been here for far too long

@plind-junior

Copy link
Copy Markdown
Member

From now on, lets focus on building desktop version before v2.0.0 release

@plind-junior
plind-junior merged commit 5ef7ab6 into vouchdev:test Jun 15, 2026
5 checks passed
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.

feat: structured JSON logging via VOUCH_LOG_FORMAT=json

3 participants