Skip to content

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

Merged
joamag merged 10 commits into
masterfrom
feat/trace-log-level
Apr 7, 2026
Merged

feat: add TRACE log level and patch_logging()#20
joamag merged 10 commits into
masterfrom
feat/trace-log-level

Conversation

@joamag

@joamag joamag commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Ports TRACE log level support from appier (hivesolutions/appier#84) to colony
  • Adds TRACE constant (logging.DEBUG - 5 = 5) for fine-grained protocol-level debugging
  • Adds SILENT constant (logging.CRITICAL + 1 = 51) for silencing all logging
  • Adds patch_logging() to register the TRACE level with Python's logging (idempotent, called automatically in start_logger())
  • Adds is_trace() method to PluginManager
  • Adds trace() to DummyLogger
  • Updates _levelNames / _levelValues / getLevelInt() so LEVEL=TRACE configuration works
  • Comprehensive test coverage (16 new tests)

Test plan

  • All 19 logger tests pass (python -m unittest colony.test.base.loggers -v)
  • Full base test suite passes (32 passed)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added TRACE (below DEBUG) and SILENT (above CRITICAL) log levels, a trace logging method, support for formatted/parameterized logging calls, and a way to detect when trace is enabled.
    • Package can be executed as a script (python -m colony).
  • Tests

    • Added unit tests covering TRACE/SILENT behavior and trace emission.
  • Documentation

    • CHANGELOG updated to document logging additions.

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>
@coderabbitai

coderabbitai Bot commented Apr 3, 2026

Copy link
Copy Markdown

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

Walkthrough

Adds TRACE and SILENT logging levels and a patching utility that registers TRACE and Logger.trace, exposes these at package level, integrates trace-aware formatting and trace methods into PluginManager, adds tests, a package main entry, and renames an internal helper in colony_adm.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Replaced placeholder with a bullet documenting addition of TRACE level and patch_logging() for protocol-level debugging.
Core loggers
src/colony/base/loggers.py
Added SILENT and TRACE constants, _trace implementation, and patch_logging() which registers TRACE and attaches Logger.trace.
Package exports
src/colony/base/__init__.py, src/colony/libs/__init__.py
Re-exported SILENT, TRACE, and patch_logging at package level; import formatting updated.
Logging utilities
src/colony/libs/logging_util.py, src/colony/libs/logging_util.pyi
Added SILENT/TRACE constants, updated level/name mappings, and added DummyLogger.trace (with typing).
Plugin manager / runtime
src/colony/base/system.py, src/colony/base/system.pyi
Call patch_logging() during startup; added DEFAULT_LOGGING_FORMAT_TRACE; added trace() and is_trace(); extended debug/info/warning/error/critical to accept *args, **kwargs and set stacklevel where applicable.
Tests
src/colony/test/base/loggers.py
Added tests for new level values/ordering, patch_logging() idempotency and lookup, presence/callability and emission/filtering of logger.trace, and getLevelInt/getLevelName for new levels.
Module entry
src/colony/__main__.py
Added module to enable python -m colony to import and call package start routine (main).
Internal rename
src/colony_adm.py
Renamed internal helper _fitler_resources_filter_resources and fixed comment typos; no public API changes.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant PM as PluginManager
    participant Loggers as colony.base.loggers
    participant Python as Python logging

    App->>PM: start_logger(log_level)
    PM->>Loggers: patch_logging()
    Loggers->>Python: addLevelName(TRACE, "TRACE")
    Loggers->>Python: set Logger.trace = _trace
    PM->>PM: configure handlers/formatters (use TRACE format if applicable)

    App->>PM: trace("message", *args)
    PM->>Python: logger.isEnabledFor(TRACE)
    alt TRACE enabled
        Python-->>PM: True
        PM->>Python: logger.log(TRACE, message, *args, stacklevel=2)
        Python->>Python: Emit record (with pathname/lineno)
    else TRACE disabled
        Python-->>PM: False
        PM-->>App: return (no emit)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 I patched a trace with careful paws,
SILENT hums while error claws,
TRACE peeks under rocks and logs,
Hops through code and spots the fogs,
A tiny hop to brighter logs.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.42% 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 pull request title clearly and concisely summarizes the main changes: adding a TRACE log level and the patch_logging() functionality.

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

✨ Finishing Touches
🧪 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.

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>
@joamag joamag marked this pull request as ready for review April 3, 2026 12:12
Copilot AI review requested due to automatic review settings April 3, 2026 12:12

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e64ed5430e

ℹ️ 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".

or an handler, this is used as an utility for debugging
purposes more that a real feature for production systems """

TRACE = logging.DEBUG - 5

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 Update MemoryHandler level list for TRACE

Adding TRACE introduces a new record level, but MemoryHandler.get_messages_l() still indexes into LEVELS (DEBUG..CRITICAL) and returns an empty deque for unknown levels. As a result, TRACE records are never inserted into messages_l, so get_latest(level="TRACE") (or level 5 after patching) always returns no entries even when TRACE logs were emitted. This breaks level-filtered log retrieval for the new level.

Useful? React with 👍 / 👎.

Copilot AI 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.

Pull request overview

Adds support for a TRACE logging level (and SILENT helper level) across Colony’s logging utilities, integrating it into Python’s logging via a new patch_logging() hook and exposing trace helpers in the plugin manager API.

Changes:

  • Introduces TRACE/SILENT level constants and updates level name/int resolution so LEVEL=TRACE works.
  • Adds patch_logging() to register TRACE with Python logging and installs a Logger.trace() method (called from start_logger()).
  • Extends logging APIs/tests (new PluginManager.is_trace(), DummyLogger.trace(), and new/updated test coverage).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/colony/test/base/loggers.py Adds tests for TRACE/SILENT constants, patch_logging(), and trace logging behavior
src/colony/libs/logging_util.pyi Updates typing surface to include TRACE/SILENT and DummyLogger.trace()
src/colony/libs/logging_util.py Adds TRACE/SILENT constants and level mapping support
src/colony/libs/init.py Re-exports TRACE/SILENT from libs
src/colony/base/system.pyi Expands typed logging API to include trace and passthrough args/kwargs; adds is_trace()
src/colony/base/system.py Calls patch_logging() from start_logger(), adds PluginManager.trace() and is_trace()
src/colony/base/loggers.py Defines TRACE/SILENT constants and implements patch_logging() + Logger.trace()
src/colony/base/init.py Re-exports TRACE/SILENT and patch_logging() from base
CHANGELOG.md Documents the addition of TRACE log level and patch_logging()
Comments suppressed due to low confidence (1)

src/colony/base/loggers.py:81

  • MemoryHandler.get_messages_l() relies on LEVELS to recognize record.levelname values. With TRACE introduced, a TRACE record will not be added to any per-level deque (and get_latest(level="TRACE") will always return empty) because LEVELS does not include "TRACE". Consider extending LEVELS (and any related aliasing) to include TRACE so in-memory filtering works for the new level.
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/colony/base/system.py
Comment on lines +6609 to +6611
if not self.logger.level:
return False
return self.logger.level <= loggers.TRACE

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

is_trace() currently returns False when self.logger.level is 0 (NOTSET), even though the logger may still effectively be enabled for TRACE via inheritance from parent/root. Using self.logger.getEffectiveLevel() (or self.logger.isEnabledFor(loggers.TRACE)) would make this check accurate in common NOTSET configurations.

Suggested change
if not self.logger.level:
return False
return self.logger.level <= loggers.TRACE
return self.logger.isEnabledFor(loggers.TRACE)

Copilot uses AI. Check for mistakes.
Comment on lines 138 to +146
def get_author_name(self): ...
def get_uptime(self): ...
def log_stack_trace(self, level=...) -> None: ...
def debug(self, message) -> None: ...
def info(self, message) -> None: ...
def warning(self, message) -> None: ...
def error(self, message) -> None: ...
def critical(self, message) -> None: ...
def trace(self, message, *args, **kwargs) -> None: ...
def debug(self, message, *args, **kwargs) -> None: ...
def info(self, message, *args, **kwargs) -> None: ...
def warning(self, message, *args, **kwargs) -> None: ...
def error(self, message, *args, **kwargs) -> None: ...
def critical(self, message, *args, **kwargs) -> None: ...

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

system.pyi declares Plugin.trace(...), but the runtime Plugin implementation in system.py does not define a corresponding trace() method (only PluginManager.trace() exists). Either add trace() to Plugin for parity with the stub/public API, or remove it from the Plugin stub to avoid type/runtime mismatch.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +34
""" The silent level used to silent all the logging
or an handler, this is used as an utility for debugging
purposes more that a real feature for production systems """

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Docstring grammar typos in the newly added SILENT description (e.g., "silent all the logging", "more that"). Consider correcting to "silence all logging" / "more than" to keep docs polished and searchable.

Suggested change
""" The silent level used to silent all the logging
or an handler, this is used as an utility for debugging
purposes more that a real feature for production systems """
""" The silent level used to silence all logging
or a handler, this is used as a utility for debugging
purposes more than a real feature for production systems """

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +70
""" The silent level used to silent all the logging
or an handler, this is used as an utility for debugging
purposes more that a real feature for production systems """

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Docstring grammar typos in the newly added SILENT description (e.g., "silent all the logging", "more that"). Consider correcting to "silence all logging" / "more than".

Suggested change
""" The silent level used to silent all the logging
or an handler, this is used as an utility for debugging
purposes more that a real feature for production systems """
""" The silent level used to silence all logging
or a handler, this is used as a utility for debugging
purposes more than a real feature for production systems """

Copilot uses AI. Check for mistakes.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/colony/base/system.pyi (1)

44-53: ⚠️ Potential issue | 🔴 Critical

Add trace() method to System class stub.

The System class in the stub file is missing the trace() method signature that exists in the implementation. Add:

def trace(self, message, *args, **kwargs) -> None: ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/colony/base/system.pyi` around lines 44 - 53, The System stub is missing
the trace method signature present in the implementation; add a declaration for
def trace(self, message, *args, **kwargs) -> None: ... to the System class in
src/colony/base/system.pyi so the stub matches the runtime API (look for the
System class and add the trace method alongside
debug/info/warning/error/critical).
src/colony/base/system.py (1)

6066-6085: ⚠️ Potential issue | 🟡 Minor

Don't double-log the traceback when exc_info is provided.

Now that these helpers forward **kwargs, callers can pass exc_info=True. The stdlib logger will already emit the exception traceback, and the unconditional log_stack_trace() call appends the same frames again.

🔧 Guard the log_stack_trace() call; apply to all three methods: warning(), error(), critical()
         logger_message = self.format_logger_message(message)
         self.logger.error(logger_message, *args, **kwargs)
-
-        # logs the stack trace
-        self.log_stack_trace(level=logging.WARNING)
+        if not kwargs.get("exc_info", False):
+            self.log_stack_trace(level=logging.WARNING)

Also applies to: warning() (6066-6085, level=logging.INFO) and critical() (6108-6123, level=logging.ERROR).

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

In `@src/colony/base/system.py` around lines 6066 - 6085, The unconditioned call
to log_stack_trace duplicates the traceback when callers pass exc_info=True;
update warning(), error(), and critical() to check kwargs.get('exc_info') (or
'exc_info' in kwargs and truthy) and only call self.log_stack_trace(...) when
exc_info is not provided/false; keep the existing log level arguments (warning
uses level=logging.INFO, error/critical use level=logging.ERROR) and continue
forwarding *args/**kwargs to self.logger so behavior is unchanged when exc_info
is omitted.
🧹 Nitpick comments (2)
CHANGELOG.md (1)

12-12: Consider mentioning SILENT constant in the changelog entry.

The PR also adds a SILENT logging level constant, but only TRACE and patch_logging() are documented here. For completeness, consider updating to include both new constants.

📝 Suggested update
-* TRACE log level and `patch_logging()` for fine-grained protocol-level debugging
+* TRACE and SILENT log level constants and `patch_logging()` for fine-grained protocol-level debugging
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 12, Update the changelog entry to mention the newly
added SILENT logging level alongside TRACE and patch_logging(): edit the line
containing "TRACE log level and `patch_logging()` for fine-grained
protocol-level debugging" to include `SILENT` (e.g., "TRACE and SILENT log
levels and `patch_logging()`...") so both new constants are documented; ensure
you reference the SILENT constant name exactly and keep the style consistent
with the existing changelog entries.
src/colony/libs/logging_util.py (1)

31-55: Duplicated level constants may drift from loggers.py.

SILENT = 51 and TRACE = 5 are hardcoded here, while src/colony/base/loggers.py defines them as logging.CRITICAL + 1 and logging.DEBUG - 5 respectively. While the values currently match, this duplication creates a maintenance risk if the base values ever change.

Consider importing from loggers.py or using a shared definition to ensure consistency:

♻️ Possible approaches

Option 1: Import from loggers (if no circular import issues):

from colony.base.loggers import SILENT, TRACE

Option 2: Define using logging module constants here too:

+import logging
+
-SILENT = 51
+SILENT = logging.CRITICAL + 1
 """ The silent level... """

-TRACE = 5
+TRACE = logging.DEBUG - 5
 """ The trace level... """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/colony/libs/logging_util.py` around lines 31 - 55, The file defines
duplicated constants SILENT and TRACE (SILENT = 51, TRACE = 5) which can drift
from the canonical definitions; replace the hardcoded values by importing the
shared definitions (e.g. from colony.base.loggers import SILENT, TRACE) or
compute them from the logging module (SILENT = logging.CRITICAL + 1, TRACE =
logging.DEBUG - 5) so the constants in logging_util.py (SILENT, TRACE) are
always consistent with the base loggers.
🤖 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/colony/base/system.py`:
- Around line 6012-6029: The trace method currently calls
self.logger.log(loggers.TRACE, ...) which breaks when using DummyLogger (it
implements trace() but not log()); update the trace() implementation in the
trace method to call self.logger.trace(logger_message, *args, **kwargs) after
building logger_message with self.format_logger_message(message) so it matches
the other level methods (debug/info/warning/error/critical) and maintains
DummyLogger compatibility.
- Around line 6596-6611: The is_trace() method incorrectly inspects
self.logger.level directly (which may be 0/NOTSET for inherited levels); change
it to use the logger API so it respects effective-level semantics: verify
self.logger is not None and then return self.logger.isEnabledFor(loggers.TRACE)
(or False if no logger) so trace checks honor parent/inherited levels; update
references to is_trace, self.logger and loggers.TRACE accordingly.

---

Outside diff comments:
In `@src/colony/base/system.py`:
- Around line 6066-6085: The unconditioned call to log_stack_trace duplicates
the traceback when callers pass exc_info=True; update warning(), error(), and
critical() to check kwargs.get('exc_info') (or 'exc_info' in kwargs and truthy)
and only call self.log_stack_trace(...) when exc_info is not provided/false;
keep the existing log level arguments (warning uses level=logging.INFO,
error/critical use level=logging.ERROR) and continue forwarding *args/**kwargs
to self.logger so behavior is unchanged when exc_info is omitted.

In `@src/colony/base/system.pyi`:
- Around line 44-53: The System stub is missing the trace method signature
present in the implementation; add a declaration for def trace(self, message,
*args, **kwargs) -> None: ... to the System class in src/colony/base/system.pyi
so the stub matches the runtime API (look for the System class and add the trace
method alongside debug/info/warning/error/critical).

---

Nitpick comments:
In `@CHANGELOG.md`:
- Line 12: Update the changelog entry to mention the newly added SILENT logging
level alongside TRACE and patch_logging(): edit the line containing "TRACE log
level and `patch_logging()` for fine-grained protocol-level debugging" to
include `SILENT` (e.g., "TRACE and SILENT log levels and `patch_logging()`...")
so both new constants are documented; ensure you reference the SILENT constant
name exactly and keep the style consistent with the existing changelog entries.

In `@src/colony/libs/logging_util.py`:
- Around line 31-55: The file defines duplicated constants SILENT and TRACE
(SILENT = 51, TRACE = 5) which can drift from the canonical definitions; replace
the hardcoded values by importing the shared definitions (e.g. from
colony.base.loggers import SILENT, TRACE) or compute them from the logging
module (SILENT = logging.CRITICAL + 1, TRACE = logging.DEBUG - 5) so the
constants in logging_util.py (SILENT, TRACE) are always consistent with the base
loggers.
🪄 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: 6f5ce215-e001-46a1-9671-ef155054374a

📥 Commits

Reviewing files that changed from the base of the PR and between e1b34be and e64ed54.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • src/colony/base/__init__.py
  • src/colony/base/loggers.py
  • src/colony/base/system.py
  • src/colony/base/system.pyi
  • src/colony/libs/__init__.py
  • src/colony/libs/logging_util.py
  • src/colony/libs/logging_util.pyi
  • src/colony/test/base/loggers.py

Comment thread src/colony/base/system.py
Comment thread src/colony/base/system.py
Comment on lines +6596 to +6611
def is_trace(self):
"""
Checks if the current logging level is set to trace,
this check may be used to action conditional code
execution for fine-grained debugging purposes.

:rtype: bool
:return: Value indicating if the current logging level
is set to trace (for fine-grained debugging).
"""

if not self.logger:
return False
if not self.logger.level:
return False
return self.logger.level <= loggers.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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import logging

TRACE = logging.DEBUG - 5

parent = logging.getLogger("trace_parent")
child = logging.getLogger("trace_parent.child")
parent.setLevel(TRACE)
child.setLevel(logging.NOTSET)

raw = False if not child.level else child.level <= TRACE
print("raw check:", raw)
print("isEnabledFor:", child.isEnabledFor(TRACE))

child.disabled = True
raw = False if not child.level else child.level <= TRACE
print("raw check after disable:", raw)
print("isEnabledFor after disable:", child.isEnabledFor(TRACE))
PY

Repository: hivesolutions/colony

Length of output: 164


🏁 Script executed:

# First, verify the file exists and check the specified lines
if [ -f "src/colony/base/system.py" ]; then
  echo "=== File exists, checking lines 6596-6611 ==="
  sed -n '6596,6611p' src/colony/base/system.py
else
  echo "File not found at src/colony/base/system.py"
  git ls-files | grep -i system.py
fi

Repository: hivesolutions/colony

Length of output: 648


🏁 Script executed:

# Search for loggers.TRACE definition
echo "=== Searching for TRACE constant definition ==="
rg "TRACE\s*=" --type py -B 2 -A 2 | head -40

Repository: hivesolutions/colony

Length of output: 781


🏁 Script executed:

# Check for loggers module and TRACE definition
echo "=== Looking for loggers module ==="
git ls-files | grep -i logger

Repository: hivesolutions/colony

Length of output: 157


🏁 Script executed:

# Check the class context and surrounding code
echo "=== Class context (lines 6580-6620) ==="
sed -n '6580,6620p' src/colony/base/system.py

Repository: hivesolutions/colony

Length of output: 1552


🏁 Script executed:

# Check if there are any logger initializations or type hints
echo "=== Searching for logger attribute initialization ==="
rg "self\.logger\s*=" src/colony/base/system.py -B 2 -A 2 | head -50

Repository: hivesolutions/colony

Length of output: 512


🏁 Script executed:

# Verify the file uses CRLF or LF line endings
echo "=== Checking line endings ==="
file src/colony/base/system.py

Repository: hivesolutions/colony

Length of output: 155


🏁 Script executed:

# Check line endings more reliably
echo "=== Checking line endings (first 10 lines) ==="
head -10 src/colony/base/system.py | od -c | head -20

Repository: hivesolutions/colony

Length of output: 168


🏁 Script executed:

# Find the class containing is_trace method
echo "=== Finding class definition ==="
grep -n "^class " src/colony/base/system.py | head -5
awk '/^class /{class=$0; line=NR} /def is_trace/{print "Line", line": "class}' src/colony/base/system.py

Repository: hivesolutions/colony

Length of output: 298


🏁 Script executed:

# Check for type annotations in the file
echo "=== Checking for type annotations ==="
rg ":\s*(int|str|bool|List|Dict|Optional|Any)" src/colony/base/system.py | head -10

Repository: hivesolutions/colony

Length of output: 350


🏁 Script executed:

# Check the __init__ method to confirm logger initialization
echo "=== Checking PluginManager.__init__ ==="
awk '/^class PluginManager/,/^class [^(]/ {print NR": "$0}' src/colony/base/system.py | grep -A 50 "def __init__" | head -60

Repository: hivesolutions/colony

Length of output: 103


🏁 Script executed:

# Verify black compatibility of proposed fix (check if code follows black style)
echo "=== Checking existing code style in nearby methods ==="
sed -n '6620,6650p' src/colony/base/system.py

Repository: hivesolutions/colony

Length of output: 968


🏁 Script executed:

# Test the proposed fix for Python 2.7 compatibility
python - <<'PY'
import logging

TRACE = logging.DEBUG - 5

# Test the proposed fix logic
class MockLogger:
    def __init__(self):
        self.level = logging.NOTSET
        self.disabled = False
    
    def isEnabledFor(self, level):
        """Mock standard logger isEnabledFor"""
        if self.disabled:
            return False
        effective_level = self.level if self.level else logging.WARNING
        return level >= effective_level

# Simulate the proposed fix
logger = MockLogger()
is_enabled_for = getattr(logger, "isEnabledFor", None)
if is_enabled_for:
    result = is_enabled_for(TRACE)
else:
    level = getattr(logger, "level", None)
    if level == None:
        result = False
    else:
        result = level <= TRACE

print("Mock logger result (NOTSET):", result)

# Test with actual logging
real_logger = logging.getLogger("test")
parent = logging.getLogger("test_parent")
parent.setLevel(TRACE)
real_logger.setLevel(logging.NOTSET)

is_enabled_for = getattr(real_logger, "isEnabledFor", None)
if is_enabled_for:
    result = is_enabled_for(TRACE)
else:
    level = getattr(real_logger, "level", None)
    if level == None:
        result = False
    else:
        result = level <= TRACE

print("Real logger result (inherited NOTSET):", result)
print("Real logger isEnabledFor(TRACE):", real_logger.isEnabledFor(TRACE))
PY

Repository: hivesolutions/colony

Length of output: 182


🏁 Script executed:

# Verify the actual behavior difference more carefully with proper parent-child relationship
python - <<'PY'
import logging

TRACE = logging.DEBUG - 5

# Recreate the exact scenario from initial verification
parent = logging.getLogger("trace_parent")
child = logging.getLogger("trace_parent.child")
parent.setLevel(TRACE)
child.setLevel(logging.NOTSET)

print("=== Current implementation (direct .level check) ===")
# Simulate current is_trace implementation
if not child:
    raw_result = False
elif not child.level:
    raw_result = False
else:
    raw_result = child.level <= TRACE

print(f"child.level = {child.level}")
print(f"child.logger.getEffectiveLevel() = {child.getEffectiveLevel()}")
print(f"Raw check result: {raw_result}")
print(f"isEnabledFor result: {child.isEnabledFor(TRACE)}")

print("\n=== With disabled logger ===")
child.disabled = True
if not child:
    raw_result = False
elif not child.level:
    raw_result = False
else:
    raw_result = child.level <= TRACE

print(f"Raw check result: {raw_result}")
print(f"isEnabledFor result: {child.isEnabledFor(TRACE)}")
PY

Repository: hivesolutions/colony

Length of output: 300


🏁 Script executed:

# Check line ending style more directly
echo "=== Checking line ending style ==="
hexdump -C src/colony/base/system.py | head -20

Repository: hivesolutions/colony

Length of output: 161


🏁 Script executed:

# Verify the coding guideline: check for any existing "== None" vs "is None" patterns
echo "=== Checking existing None comparison style in the file ==="
rg "\s(==|is)\s+None" src/colony/base/system.py | head -20

Repository: hivesolutions/colony

Length of output: 476


🏁 Script executed:

# Verify the proposed fix aligns with black formatter expectations
echo "=== Checking the proposed fix formatting ==="
python - <<'PY'
import logging

# Simulate the proposed fix to ensure it's syntactically valid Python 2.7+
def is_trace(logger, TRACE):
    if not logger:
        return False
    is_enabled_for = getattr(logger, "isEnabledFor", None)
    if is_enabled_for:
        return is_enabled_for(TRACE)
    level = getattr(logger, "level", None)
    if level == None:
        return False
    return level <= TRACE

# Test the proposed fix
test_logger = logging.getLogger("test")
parent = logging.getLogger("test_parent")
parent.setLevel(logging.DEBUG - 5)
test_logger.setLevel(logging.NOTSET)

TRACE = logging.DEBUG - 5
result = is_trace(test_logger, TRACE)
print("Proposed fix result:", result)
print("Correct (should be True):", result == True)
PY

Repository: hivesolutions/colony

Length of output: 167


🏁 Script executed:

# Check if there are any type annotations in the method signature
echo "=== Checking method signature (no type annotations required) ==="
sed -n '6596,6600p' src/colony/base/system.py

Repository: hivesolutions/colony

Length of output: 342


🏁 Script executed:

# Verify the proposed fix more carefully with proper logger hierarchy
python - <<'PY'
import logging

TRACE = logging.DEBUG - 5

# Set up the exact same hierarchy as the verification
parent = logging.getLogger("trace_parent")
child = logging.getLogger("trace_parent.child")
parent.setLevel(TRACE)
child.setLevel(logging.NOTSET)

# Simulate the proposed fix
logger = child
if not logger:
    result = False
else:
    is_enabled_for = getattr(logger, "isEnabledFor", None)
    if is_enabled_for:
        result = is_enabled_for(TRACE)
    else:
        level = getattr(logger, "level", None)
        if level == None:
            result = False
        else:
            result = level <= TRACE

print("Proposed fix result:", result)
print("Expected (True):", result == True)
print()
print("Verification: isEnabledFor(TRACE) =", child.isEnabledFor(TRACE))
print("Verification: child.level =", child.level)
print("Verification: child.getEffectiveLevel() =", child.getEffectiveLevel())
PY

Repository: hivesolutions/colony

Length of output: 227


🏁 Script executed:

# Verify the proposed fix handles disabled loggers correctly
python - <<'PY'
import logging

TRACE = logging.DEBUG - 5

parent = logging.getLogger("trace_parent")
child = logging.getLogger("trace_parent.child")
parent.setLevel(TRACE)
child.setLevel(logging.NOTSET)
child.disabled = True

# Simulate the proposed fix with disabled logger
logger = child
if not logger:
    result = False
else:
    is_enabled_for = getattr(logger, "isEnabledFor", None)
    if is_enabled_for:
        result = is_enabled_for(TRACE)
    else:
        level = getattr(logger, "level", None)
        if level == None:
            result = False
        else:
            result = level <= TRACE

print("Proposed fix result (disabled):", result)
print("Expected (False):", result == False)
print("Verification: isEnabledFor(TRACE) =", child.isEnabledFor(TRACE))
PY

Repository: hivesolutions/colony

Length of output: 166


Use effective-level semantics for is_trace().

The current implementation reads self.logger.level directly, which returns 0 (NOTSET) for loggers that inherit their effective level from parent loggers. This causes is_trace() to incorrectly return False even when the effective level would enable trace logging. Use isEnabledFor(loggers.TRACE) to respect the standard logging semantics for level inheritance and disabled loggers.

🔧 Prefer the logger API
         if not self.logger:
             return False
-        if not self.logger.level:
-            return False
-        return self.logger.level <= loggers.TRACE
+        is_enabled_for = getattr(self.logger, "isEnabledFor", None)
+        if is_enabled_for:
+            return is_enabled_for(loggers.TRACE)
+        level = getattr(self.logger, "level", None)
+        if level == None:
+            return False
+        return level <= loggers.TRACE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/colony/base/system.py` around lines 6596 - 6611, The is_trace() method
incorrectly inspects self.logger.level directly (which may be 0/NOTSET for
inherited levels); change it to use the logger API so it respects
effective-level semantics: verify self.logger is not None and then return
self.logger.isEnabledFor(loggers.TRACE) (or False if no logger) so trace checks
honor parent/inherited levels; update references to is_trace, self.logger and
loggers.TRACE accordingly.

joamag and others added 3 commits April 3, 2026 14:51
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>
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>

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/colony/base/system.py (1)

6123-6138: ⚠️ Potential issue | 🟡 Minor

Add missing self.logger guard in critical().

Unlike debug/info/warning/error, critical() now calls into self.logger unguarded. At Line 6135 this can raise when logger initialization is absent.

💡 Suggested fix
     def critical(self, message, *args, **kwargs):
         """
         Adds the given critical message to the logger.
@@
+        # in case no logger is defined it's not possible
+        # to print the message as a critical
+        if not self.logger:
+            return
+
         # formats the critical message
         logger_message = self.format_logger_message(message)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/colony/base/system.py` around lines 6123 - 6138, The critical() method
calls self.logger.critical without checking self.logger; add the same guard used
in debug/info/warning/error: ensure self.logger exists before formatting/logging
by wrapping the call to self.format_logger_message(message) and
self.logger.critical(...) inside an if self.logger: block (or early return if
missing) and still call self.log_stack_trace(level=logging.ERROR) only as
intended; update the critical() implementation to mirror the logger-presence
checks used in the other level methods to avoid AttributeError when the logger
is not initialized.
♻️ Duplicate comments (2)
src/colony/base/system.py (2)

6027-6044: ⚠️ Potential issue | 🔴 Critical

Use self.logger.trace(...) in trace() for logger compatibility.

At Line 6043, dispatching via self.logger.log(...) can fail for logger implementations that provide trace() but not generic log(). This diverges from sibling methods (debug/info/...) that use direct level methods.

💡 Suggested fix
-        self.logger.log(loggers.TRACE, logger_message, *args, **kwargs)
+        self.logger.trace(logger_message, *args, **kwargs)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/colony/base/system.py` around lines 6027 - 6044, The trace() method
currently calls self.logger.log(loggers.TRACE, ...), which can fail for logger
implementations that expose trace() but not a generic log(); replace that call
with self.logger.trace(logger_message, *args, **kwargs) in the trace method
(keeping the prior logger_message = self.format_logger_message(message) and the
early return if not self.logger) so it matches the sibling debug/info/...
methods and ensures compatibility with loggers that only implement
level-specific methods.

6611-6626: ⚠️ Potential issue | 🟡 Minor

Use effective-level semantics in is_trace().

At Line 6624-Line 6626, checking self.logger.level directly is incorrect for NOTSET/inherited levels and disabled loggers. Use logger API semantics (isEnabledFor) instead.

💡 Suggested fix
         if not self.logger:
             return False
-        if not self.logger.level:
-            return False
-        return self.logger.level <= loggers.TRACE
+        return self.logger.isEnabledFor(loggers.TRACE)
#!/bin/bash
set -euo pipefail

python - <<'PY'
import logging

TRACE = logging.DEBUG - 5

parent = logging.getLogger("trace_parent")
child = logging.getLogger("trace_parent.child")

parent.setLevel(TRACE)
child.setLevel(logging.NOTSET)

direct_level_check = False if not child.level else child.level <= TRACE
print("direct_level_check =", direct_level_check)      # expected: False
print("isEnabledFor =", child.isEnabledFor(TRACE))     # expected: True

child.disabled = True
print("isEnabledFor_disabled =", child.isEnabledFor(TRACE))  # expected: False
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/colony/base/system.py` around lines 6611 - 6626, The current is_trace()
implementation incorrectly inspects self.logger.level directly; change it to use
the logger API semantics by returning whether the logger is enabled for the
TRACE level (use self.logger.isEnabledFor(loggers.TRACE)), and keep the existing
safety check for self.logger being truthy (e.g., return False if not
self.logger, otherwise return self.logger.isEnabledFor(loggers.TRACE)); this
ensures NOTSET/inherited levels and disabled loggers are handled correctly in
is_trace().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/colony/base/system.py`:
- Around line 6123-6138: The critical() method calls self.logger.critical
without checking self.logger; add the same guard used in
debug/info/warning/error: ensure self.logger exists before formatting/logging by
wrapping the call to self.format_logger_message(message) and
self.logger.critical(...) inside an if self.logger: block (or early return if
missing) and still call self.log_stack_trace(level=logging.ERROR) only as
intended; update the critical() implementation to mirror the logger-presence
checks used in the other level methods to avoid AttributeError when the logger
is not initialized.

---

Duplicate comments:
In `@src/colony/base/system.py`:
- Around line 6027-6044: The trace() method currently calls
self.logger.log(loggers.TRACE, ...), which can fail for logger implementations
that expose trace() but not a generic log(); replace that call with
self.logger.trace(logger_message, *args, **kwargs) in the trace method (keeping
the prior logger_message = self.format_logger_message(message) and the early
return if not self.logger) so it matches the sibling debug/info/... methods and
ensures compatibility with loggers that only implement level-specific methods.
- Around line 6611-6626: The current is_trace() implementation incorrectly
inspects self.logger.level directly; change it to use the logger API semantics
by returning whether the logger is enabled for the TRACE level (use
self.logger.isEnabledFor(loggers.TRACE)), and keep the existing safety check for
self.logger being truthy (e.g., return False if not self.logger, otherwise
return self.logger.isEnabledFor(loggers.TRACE)); this ensures NOTSET/inherited
levels and disabled loggers are handled correctly in is_trace().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c31d13a-c39f-4c11-bce3-bfcaa3037cd8

📥 Commits

Reviewing files that changed from the base of the PR and between e64ed54 and 21c6cd3.

📒 Files selected for processing (3)
  • src/colony/__main__.py
  • src/colony/base/system.py
  • src/colony_adm.py
✅ Files skipped from review due to trivial changes (1)
  • src/colony/main.py

joamag and others added 5 commits April 6, 2026 12:28
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>
@joamag joamag merged commit f30dd92 into master Apr 7, 2026
17 checks passed
@joamag joamag deleted the feat/trace-log-level branch April 7, 2026 10:41
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.

2 participants