Skip to content

feat: add TRACE log level and patch_logging()#84

Merged
joamag merged 3 commits intomasterfrom
feat/trace-log-level
Apr 3, 2026
Merged

feat: add TRACE log level and patch_logging()#84
joamag merged 3 commits intomasterfrom
feat/trace-log-level

Conversation

@joamag
Copy link
Copy Markdown
Contributor

@joamag joamag commented Apr 3, 2026

Summary

  • Ports TRACE log level support from netius (hivesolutions/netius#49) to appier
  • Adds TRACE constant (logging.DEBUG - 5 = 5) for fine-grained protocol-level debugging
  • Adds patch_logging() to register the TRACE level with Python's logging (idempotent, called automatically in _load_logging())
  • Adds is_trace() method to App and trace() to DummyLogger
  • Adds "TRACE" special case in App._level() (matching the existing "SILENT" pattern)
  • Wraps in_signature() return in bool() for type correctness
  • Comprehensive test coverage (21 new tests)

Test plan

  • All 23 log tests pass (pytest src/appier/test/log.py -v)
  • Full test suite passes (231 passed, 6 skipped)
  • Black formatting verified

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Introduced a new TRACE log level for ultra-fine-grained protocol-level debugging with deeper visibility into application behavior beyond standard DEBUG logging
    • Added logging enhancement capabilities that automatically register and enable comprehensive protocol tracing for advanced diagnostics and troubleshooting
    • Enhanced logging configuration to seamlessly support the new tracing capabilities during app initialization

…gging

Ports the TRACE log level support from netius (hivesolutions/netius#49)
to the appier framework, enabling protocol-level verbose logging below
DEBUG for fine-grained debugging of low-level operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

This change introduces a new TRACE logging level (finer-grained than DEBUG) for detailed protocol-level debugging. Key additions include a TRACE constant, a patch_logging() function to register the level dynamically, an is_trace() method to check logging status, and comprehensive unit tests validating all new functionality.

Changes

Cohort / File(s) Summary
Documentation & Exports
CHANGELOG.md, src/appier/__init__.py
Added changelog entry for TRACE level feature; re-exported SILENT, TRACE, and patch_logging() to public appier namespace.
Core Logging Implementation
src/appier/log.py
Introduced TRACE constant set to logging.DEBUG - 5; implemented patch_logging() to register level with logging module and attach .trace() method to Logger; added _trace() function and no-op trace() method to DummyLogger; wrapped in_signature() return value in bool().
App Integration
src/appier/base.py
Added App.is_trace() method to check if logging level is at or below TRACE; extended App._level() to map string "TRACE" to log.TRACE constant; updated App._load_logging() to invoke patch_logging() before standard logging configuration.
Unit Tests
src/appier/test/log.py
Comprehensive test coverage for TRACE level constant, patch_logging() idempotency, Logger .trace() method emission and filtering, level mapping in App._level(), rotating_handler() parameters, and in_signature() detection logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A trace of magic, five levels deep,
Where protocol whispers are mine to keep,
Patching and logging with careful grace,
Now debugging shines at the finest place! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introduction of a TRACE log level and patch_logging() function, which are the primary additions across all modified files.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/trace-log-level

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.

joamag and others added 2 commits April 3, 2026 09:58
/dev/null does not exist on Windows, causing test failures on CI.
Use tempfile.mkstemp() for cross-platform compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@joamag joamag marked this pull request as ready for review April 3, 2026 10:01
Copilot AI review requested due to automatic review settings April 3, 2026 10:01
@joamag joamag self-assigned this Apr 3, 2026
@joamag joamag added the enhancement New feature or request label Apr 3, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1858dc7713

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/appier/log.py
Comment on lines +77 to 81
logging of protocol-level operations, this is meant to be
used for fine-grained debugging of low-level operations
like raw byte transfers and frame parsing """

LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include TRACE in memory handler level ordering

After introducing TRACE, MemoryHandler still uses LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), so emit() cannot index "TRACE" in get_messages_l() and silently skips per-level storage for trace records. In practice, get_latest(level="TRACE") (and flush_to_file(..., level="TRACE")) always returns no trace messages even when trace logs were emitted, which makes the new level partially unusable for log inspection.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new ultra-verbose TRACE logging level to Appier (ported from netius) and wires it into Python’s logging system via a new patch_logging() helper, while expanding logging-related test coverage.

Changes:

  • Added TRACE log level (DEBUG - 5) plus patch_logging() to register the level name and Logger.trace() method (and auto-call it during _load_logging()).
  • Added App.is_trace() and DummyLogger.trace(), and taught App._level() to resolve "TRACE" similarly to "SILENT".
  • Adjusted in_signature() to always return a bool and added extensive logging tests.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/appier/log.py Adds TRACE, patch_logging(), Logger.trace() implementation, and tweaks in_signature()
src/appier/base.py Integrates TRACE into level resolution and logging initialization; adds is_trace()
src/appier/__init__.py Re-exports TRACE/SILENT and patch_logging()
src/appier/test/log.py Adds tests for TRACE level semantics, patching behavior, and some logging helpers
CHANGELOG.md Documents the new TRACE support in Unreleased
Comments suppressed due to low confidence (1)

src/appier/log.py:84

  • TRACE is introduced as a new level, but LEVELS (used by MemoryHandler.get_messages_l() / emit() and therefore get_latest(level=...)) does not include "TRACE". This means TRACE records will not be added to any per-threshold queue and get_latest(level="TRACE") will always return an empty list; the /logging debug endpoint (which passes the request level string through to get_latest) will also be unable to return TRACE logs. Consider adding "TRACE" to LEVELS (before "DEBUG") and extending tests to cover MemoryHandler.get_latest(level="TRACE").
TRACE = logging.DEBUG - 5
""" The trace level used for extremely detailed and verbose
logging of protocol-level operations, this is meant to be
used for fine-grained debugging of low-level operations
like raw byte transfers and frame parsing """

LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
""" The sequence of levels from the least sever to the
most sever this sequence may be used to find all the
levels that are considered more sever that a level """

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/appier/log.py
Comment on lines 379 to +386
def in_signature(callable, name):
has_full = hasattr(inspect, "getfullargspec")
if has_full:
spec = inspect.getfullargspec(callable)
else:
spec = inspect.getargspec(callable)
args, _varargs, kwargs = spec[:3]
return (args and name in args) or (kwargs and "secure" in kwargs)
return bool((args and name in args) or (kwargs and "secure" in kwargs))
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

in_signature() is checking the var-keyword parameter incorrectly: kwargs from spec[:3] is the name of the **kwargs parameter (a string) or None, not a collection of accepted kwarg names. As written, (kwargs and "secure" in kwargs) will usually be False even when **kwargs is present, and it hard-codes "secure" instead of using the name argument. This can cause smtp_handler() to incorrectly decide that logging.handlers.SMTPHandler.__init__ does not support the secure argument on Python versions where it is accepted via **kwargs or as a kw-only arg. Consider updating the logic to return True when name is present in positional/kw-only args, or when **kwargs is present at all (and avoid the hard-coded "secure").

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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)
CHANGELOG.md (1)

18-20: Consider documenting the rotating handler test fix.

The PR objectives mention a separate commit that "Fixes rotating handler tests to use tempfile.mkstemp() instead of /dev/null for cross-platform (Windows) compatibility." This cross-platform compatibility improvement could be worth documenting in the Fixed section. As per coding guidelines, CHANGELOG.md should be updated for changes.

📝 Suggested addition to the Fixed section
 ### Fixed
 
-*
+* Rotating handler tests now use `tempfile.mkstemp()` instead of `/dev/null` for cross-platform (Windows) compatibility
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 18 - 20, Add a bullet to the "Fixed" section of
CHANGELOG.md documenting the test change that makes rotating handler tests
cross-platform: mention that tests now use tempfile.mkstemp() instead of
hardcoding /dev/null (reference the rotating handler tests change) and briefly
state this fixes Windows compatibility; keep the entry concise and follow the
existing changelog bullet style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/appier/base.py`:
- Around line 3674-3677: The is_trace method incorrectly treats level==0 as
unset by using "if not self.level"; change the unset check to explicitly test
for None (e.g., "if self.level is None") so logging.NOTSET (0) is handled as a
valid level and the subsequent comparison against log.TRACE still runs; update
the is_trace function (referencing is_trace, self.level, and log.TRACE)
accordingly.

In `@src/appier/log.py`:
- Around line 301-302: The trace method currently uses the parameter name
"object", shadowing the built-in and triggering Ruff A002; rename the parameter
in the trace function signature (e.g., to "obj" or "value") and update any
internal references inside trace (and any callers if they rely on keyword
argument naming) to use the new name so the builtin is no longer shadowed.

---

Nitpick comments:
In `@CHANGELOG.md`:
- Around line 18-20: Add a bullet to the "Fixed" section of CHANGELOG.md
documenting the test change that makes rotating handler tests cross-platform:
mention that tests now use tempfile.mkstemp() instead of hardcoding /dev/null
(reference the rotating handler tests change) and briefly state this fixes
Windows compatibility; keep the entry concise and follow the existing changelog
bullet style.
🪄 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

Run ID: 69dc1140-349f-4be0-ba03-6c5d9f1f30f8

📥 Commits

Reviewing files that changed from the base of the PR and between 5784214 and 1858dc7.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/appier/__init__.py
  • src/appier/base.py
  • src/appier/log.py
  • src/appier/test/log.py

Comment thread src/appier/base.py
Comment on lines +3674 to +3677
def is_trace(self):
if not self.level:
return False
return self.level <= log.TRACE
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 | 🟡 Minor

is_trace() should not treat level 0 as unset.

if not self.level returns False for logging.NOTSET (0), but 0 should still satisfy trace-enabled checks.

🔧 Proposed fix
     def is_trace(self):
-        if not self.level:
+        if self.level == None:
             return False
         return self.level <= log.TRACE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/appier/base.py` around lines 3674 - 3677, The is_trace method incorrectly
treats level==0 as unset by using "if not self.level"; change the unset check to
explicitly test for None (e.g., "if self.level is None") so logging.NOTSET (0)
is handled as a valid level and the subsequent comparison against log.TRACE
still runs; update the is_trace function (referencing is_trace, self.level, and
log.TRACE) accordingly.

Comment thread src/appier/log.py
Comment on lines +301 to +302
def trace(self, object):
pass
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 | 🟡 Minor

Rename object parameter to avoid builtin shadowing.

This triggers Ruff A002 and is easy to fix.

✏️ Proposed fix
-    def trace(self, object):
+    def trace(self, message):
         pass
📝 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
def trace(self, object):
pass
def trace(self, message):
pass
🧰 Tools
🪛 Ruff (0.15.7)

[error] 301-301: Function argument object is shadowing a Python builtin

(A002)

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

In `@src/appier/log.py` around lines 301 - 302, The trace method currently uses
the parameter name "object", shadowing the built-in and triggering Ruff A002;
rename the parameter in the trace function signature (e.g., to "obj" or "value")
and update any internal references inside trace (and any callers if they rely on
keyword argument naming) to use the new name so the builtin is no longer
shadowed.

@joamag joamag merged commit e8c6c4e into master Apr 3, 2026
139 of 149 checks passed
@joamag joamag deleted the feat/trace-log-level branch April 3, 2026 10:35
joamag added a commit to hivesolutions/colony that referenced this pull request Apr 3, 2026
Port TRACE log level support from appier (hivesolutions/appier#84) to colony.
Adds TRACE constant (logging.DEBUG - 5 = 5) and SILENT constant
(logging.CRITICAL + 1 = 51) for fine-grained protocol-level debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
joamag added a commit to hivesolutions/colony that referenced this pull request Apr 7, 2026
* feat: add TRACE log level and patch_logging()

Port TRACE log level support from appier (hivesolutions/appier#84) to colony.
Adds TRACE constant (logging.DEBUG - 5 = 5) and SILENT constant
(logging.CRITICAL + 1 = 51) for fine-grained protocol-level debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add trace() method and *args/**kwargs to PluginManager log methods

Adds trace() convenience method to PluginManager and makes all log
methods (debug, info, warning, error, critical) accept *args and
**kwargs for lazy evaluation support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add TRACE-aware logging format with pathname and lineno

When the log level is set to TRACE, automatically switches to a more
detailed format that includes file path and line number for fine-grained
debugging of low-level operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: new module starting

* fix: handle GLOBAL_CONFIG override for TRACE logging format

The GLOBAL_CONFIG always has a hardcoded logging_format value, so the
.get() default never triggers. Added explicit check to switch to the
trace format when the config value matches the default and the level
is TRACE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: new logging format for trace

* chore: new black format

* chore: new logging format for trace

Passes stacklevel=2 in all PluginManager log methods (trace, debug,
info, warning, error, critical) so that pathname and lineno in the
format string reflect the actual caller, not the internal log method.
Guards stacklevel for Python < 3.8 where it is not supported, and
makes DEFAULT_LOGGING_FORMAT_TRACE conditional on Python 3.8+.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: trace reference

* fix: small spelling error in logging_util.py docstring

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants