Skip to main content

AOP-style declarative logging decorators powered by loguru

Project description

logcraft

中文文档

A lightweight AOP-style logging toolkit for Python, built on top of loguru.

It helps remove manual logger.info(...) boilerplate by injecting structured logs through decorators and context managers.

Install

pip install logcraft-aop

Quick start

from logcraft import get_logger, log_calls, log_class, no_log, log_context, setup_logging

setup_logging(level="INFO", enable_file=False)
logger = get_logger(__name__)


@log_calls
def add(a: int, b: int) -> int:
    return a + b


@log_class(default=True, exclude=["helper"])
class Service:
    def run(self, x: int) -> int:
        return x * 2

    @no_log
    def helper(self) -> str:
        return "skip"


with log_context("batch.process", size=10) as ctx:
    ctx.bind(done=10)

API Reference

setup_logging()

Initialize the global loguru sink. Call once at program startup.

from logcraft import setup_logging

setup_logging(
    level="INFO",        # or set via LOG_LEVEL env var
    log_dir="logs",      # or set via LOGCRAFT_LOG_DIR env var
    when="D",            # rotation interval unit: S/M/H/D/W/midnight
    interval=1,          # rotation every N units
    backup_count=3,      # number of rotated files to keep
    enable_file=True,    # set False to disable file output
)

If level is omitted, it falls back to the LOG_LEVEL environment variable, then "INFO". If log_dir is omitted, it falls back to LOGCRAFT_LOG_DIR, then "logs".

The log format is structured and includes timestamp, level, PID, thread ID, module, and line number:

2026-05-15 10:30:00.123 || INFO || 12345 || 67890 || myapp:42 || add.done || a=1, b=2, result=3

get_logger(name)

Return a Logger instance bound to the given module name.

logger = get_logger(__name__)
logger.info("user.login", user_id=42)

Logger

The wrapper around loguru that powers all logcraft output.

Method Description
debug(event, **fields) Emit at DEBUG level
info(event, **fields) Emit at INFO level
warning(event, **fields) Emit at WARNING level
error(event, **fields) Emit at ERROR level
exception(event, **fields) Emit at ERROR level with traceback attached
bind(**ctx) Return a new Logger with persistent context fields
context(event, **fields) Return a LogContext manager (see below)

bind() is useful for attaching correlation fields that propagate across calls:

req_log = logger.bind(request_id="abc-123")
req_log.info("handler.start")
req_log.info("handler.end", status=200)
# both lines include request_id=abc-123

@log_calls

Decorator that automatically logs function entry (.done) and exceptions (.error).

@log_calls
def add(a: int, b: int) -> int:
    return a + b

Calling add(1, 2) emits:

add.done || a=1, b=2, result=3

If the function raises, it emits .error and re-raises the exception:

@log_calls
def fail():
    raise ValueError("bad")

Calling fail() emits:

fail.error || error=ValueError: bad

Parameters:

Parameter Default Description
include_result True Attach the return value as result= in the .done log
message None Custom event name prefix (default is fn.__qualname__)
level "info" Log level for .done events (.error always uses error)

Custom event name:

@log_calls(message="payment.charge", include_result=False)
def charge(user_id: str, amount: float) -> str:
    return "tx_123"

Emits payment.charge.done || user_id=alice, amount=9.99 (no result=).

Async support: @log_calls detects coroutines automatically and wraps them with async def.

@log_calls
async def fetch(url: str) -> str:
    ...

@log_class

Class decorator that applies @log_calls to selected methods. It also injects a self._log attribute (a Logger instance) if the class doesn't already define one.

@log_class(default=True, exclude=["helper"])
class Service:
    def run(self, x: int) -> int:
        return x * 2

    def helper(self) -> str:
        return "skip"

run gets logged as Service.run.done, while helper is excluded.

Parameters:

Parameter Default Description
default True If True, log all methods (minus exclude). If False, only log methods in include.
include None List of method names to log (used when default=False)
exclude None List of method names to skip (used when default=True)
skip_private True Automatically skip methods starting with _ (dunder methods are always skipped)
include_result False Whether to include return values in .done logs
level "info" Log level for .done events

Opt-in mode (default=False):

@log_class(default=False, include=["run"])
class Service:
    def run(self, x: int) -> int:
        return x * 2

    def helper(self) -> str:
        return "skip"

Only run is logged.

Custom logger on the instance:

If __init__ sets self._log, @log_class preserves it and uses it for all wrapped methods:

@log_class(default=True, include=["run"])
class Manual:
    def __init__(self):
        self._log = get_logger("manual").bind(tag="x")

    def run(self) -> str:
        return "ok"

Emits Manual.run.done || tag=x.

@no_log

Mark a method to be skipped by @log_class, even if it would otherwise be included.

@log_class(default=True)
class Worker:
    def run(self): ...       # logged
    def process(self): ...   # logged

    @no_log
    def internal(self): ...  # skipped

log_context()

Context manager for block-level logging. Emits .done on normal exit, .error on exception.

with log_context("batch.process", size=100) as ctx:
    ctx.bind(processed=42)

Normal exit emits:

batch.process.done || size=100, processed=42

If an exception occurs:

with log_context("batch.process", size=100):
    raise RuntimeError("disk full")

Emits:

batch.process.error || size=100, error=RuntimeError: disk full

Parameters:

Parameter Default Description
event required Event name prefix
logger None Custom Logger instance (default: uses the global logcraft logger)
level "info" Log level for .done events
**fields Static fields included in both .done and .error

ctx.bind(**fields) adds fields that accumulate during the block and are emitted at exit.

Async support: log_context works with async with:

async with logger.context("api.call", endpoint="/users") as ctx:
    ctx.bind(status=200)

LogContext

The object returned by log_context() and logger.context(). You typically don't construct it directly.

Method Description
bind(**fields) Accumulate fields to include in the final log
__enter__ / __exit__ Synchronous context manager protocol
__aenter__ / __aexit__ Async context manager protocol

Configuration

from logcraft import setup_logging

setup_logging(
    level="INFO",
    log_dir="logs",
    when="D",
    interval=1,
    backup_count=3,
    enable_file=True,
)

Environment variables

Variable Default Description
LOG_LEVEL "INFO" Default log level when level param is not passed
LOGCRAFT_LOG_DIR "logs" Default log directory when log_dir param is not passed

Development shortcuts

make help
make install
make test
make build
make clean
make publish-test   # publish to TestPyPI
make publish        # publish to PyPI

How it works

  • Decorators capture function parameters via inspect.signature(...)
  • self / cls parameters are automatically stripped from logged fields
  • Success path emits <event>.done
  • Error path emits <event>.error and re-raises exception
  • @log_class only wraps methods defined in cls.__dict__ (not inherited ones) to avoid side effects
  • @log_class injects self._log on first call if the instance doesn't already have one
  • @no_log sets fn._no_log = True which @log_class checks before wrapping

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

logcraft_aop-0.1.1.tar.gz (7.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

logcraft_aop-0.1.1-py3-none-any.whl (8.2 kB view details)

Uploaded Python 3

File details

Details for the file logcraft_aop-0.1.1.tar.gz.

File metadata

  • Download URL: logcraft_aop-0.1.1.tar.gz
  • Upload date:
  • Size: 7.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.10.15 Darwin/25.5.0

File hashes

Hashes for logcraft_aop-0.1.1.tar.gz
Algorithm Hash digest
SHA256 f3efe32b5dc6d5e1803978ac889f8a3795c2ec66f8ddf5a46e3e61e0eb5fb07d
MD5 f35860a424f1e9447090f3d98d6e5271
BLAKE2b-256 e9fe4fe0e504f07ffb5059ccb26515b9b63633b28807ac85f8fc6790eb64d7a4

See more details on using hashes here.

File details

Details for the file logcraft_aop-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: logcraft_aop-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 8.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.10.15 Darwin/25.5.0

File hashes

Hashes for logcraft_aop-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6220f642f6d48df13bd21f00d40534a90be7a419886ef8ec952102488d2a47a1
MD5 d06f85f6f098f16537bdb82a3fee8fd9
BLAKE2b-256 30da0748375d830bc62a741a4d9e155478c9561340cc38082e5106e942de9fde

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page