Skip to content

Logging

LogCategory

Bases: IntEnum

Logging categories for kernel messages.

These categories allow fine-grained control over which types of log messages are enabled. Categories can be individually enabled or disabled.

Source code in pbk/log.py
class LogCategory(IntEnum):
    """Logging categories for kernel messages.

    These categories allow fine-grained control over which types of log
    messages are enabled. Categories can be individually enabled or disabled.
    """

    ALL = 0  #: All log categories
    BENCH = 1  #: Benchmarking and performance metrics
    BLOCKSTORAGE = 2  #: Block storage operations
    COINDB = 3  #: Coin database operations
    LEVELDB = 4  #: LevelDB database operations
    MEMPOOL = 5  #: Memory pool (mempool) operations
    PRUNE = 6  #: Block pruning operations
    RAND = 7  #: Random number generation
    REINDEX = 8  #: Blockchain reindexing operations
    VALIDATION = 9  #: Block and transaction validation
    KERNEL = 10  #: General kernel operations

ALL class-attribute instance-attribute

ALL = 0

BENCH class-attribute instance-attribute

BENCH = 1

BLOCKSTORAGE class-attribute instance-attribute

BLOCKSTORAGE = 2

COINDB class-attribute instance-attribute

COINDB = 3

KERNEL class-attribute instance-attribute

KERNEL = 10

LEVELDB class-attribute instance-attribute

LEVELDB = 4

MEMPOOL class-attribute instance-attribute

MEMPOOL = 5

PRUNE class-attribute instance-attribute

PRUNE = 6

RAND class-attribute instance-attribute

RAND = 7

REINDEX class-attribute instance-attribute

REINDEX = 8

VALIDATION class-attribute instance-attribute

VALIDATION = 9

LogLevel

Bases: IntEnum

Log severity levels.

These levels control the minimum severity of messages that will be logged. Setting a level filters out messages below that severity.

Note

The TRACE level from bitcoinkernel is not exposed, as it is not natively supported by Python's logging library.

Source code in pbk/log.py
class LogLevel(IntEnum):
    """Log severity levels.

    These levels control the minimum severity of messages that will be logged.
    Setting a level filters out messages below that severity.

    Note:
        The TRACE level from bitcoinkernel is not exposed, as it is not
        natively supported by Python's logging library.
    """

    DEBUG = 1  #: Debug-level messages with detailed information
    INFO = 2  #: Informational messages about normal operations

DEBUG class-attribute instance-attribute

DEBUG = 1

INFO class-attribute instance-attribute

INFO = 2

LoggingOptions

Bases: btck_LoggingOptions

Configuration options for log message formatting.

These options control which metadata is included in log messages, such as timestamps, thread names, and source locations.

Source code in pbk/log.py
class LoggingOptions(k.btck_LoggingOptions):
    """Configuration options for log message formatting.

    These options control which metadata is included in log messages, such
    as timestamps, thread names, and source locations.
    """

    def __init__(
        self,
        log_timestamps: bool = True,
        log_time_micros: bool = False,
        log_threadnames: bool = False,
        log_sourcelocations: bool = False,
        always_print_category_levels: bool = False,
    ):
        """Create logging format options.

        Args:
            log_timestamps: If True, prepend a timestamp to log messages.
            log_time_micros: If True, use microsecond precision for timestamps.
            log_threadnames: If True, prepend the thread name to log messages.
            log_sourcelocations: If True, prepend the source file location to log messages.
            always_print_category_levels: If True, prepend the category and level to log messages.
        """
        super().__init__()
        self.log_timestamps = log_timestamps
        self.log_time_micros = log_time_micros
        self.log_threadnames = log_threadnames
        self.log_sourcelocations = log_sourcelocations
        self.always_print_category_levels = always_print_category_levels

    @property
    def _as_parameter_(self):
        """Return the ctypes reference for passing to C functions."""
        return ctypes.byref(self)

always_print_category_levels instance-attribute

always_print_category_levels = always_print_category_levels

log_sourcelocations instance-attribute

log_sourcelocations = log_sourcelocations

log_threadnames instance-attribute

log_threadnames = log_threadnames

log_time_micros instance-attribute

log_time_micros = log_time_micros

log_timestamps instance-attribute

log_timestamps = log_timestamps

__init__

__init__(log_timestamps: bool = True, log_time_micros: bool = False, log_threadnames: bool = False, log_sourcelocations: bool = False, always_print_category_levels: bool = False)

Create logging format options.

PARAMETER DESCRIPTION
log_timestamps

If True, prepend a timestamp to log messages.

TYPE: bool DEFAULT: True

log_time_micros

If True, use microsecond precision for timestamps.

TYPE: bool DEFAULT: False

log_threadnames

If True, prepend the thread name to log messages.

TYPE: bool DEFAULT: False

log_sourcelocations

If True, prepend the source file location to log messages.

TYPE: bool DEFAULT: False

always_print_category_levels

If True, prepend the category and level to log messages.

TYPE: bool DEFAULT: False

Source code in pbk/log.py
def __init__(
    self,
    log_timestamps: bool = True,
    log_time_micros: bool = False,
    log_threadnames: bool = False,
    log_sourcelocations: bool = False,
    always_print_category_levels: bool = False,
):
    """Create logging format options.

    Args:
        log_timestamps: If True, prepend a timestamp to log messages.
        log_time_micros: If True, use microsecond precision for timestamps.
        log_threadnames: If True, prepend the thread name to log messages.
        log_sourcelocations: If True, prepend the source file location to log messages.
        always_print_category_levels: If True, prepend the category and level to log messages.
    """
    super().__init__()
    self.log_timestamps = log_timestamps
    self.log_time_micros = log_time_micros
    self.log_threadnames = log_threadnames
    self.log_sourcelocations = log_sourcelocations
    self.always_print_category_levels = always_print_category_levels

logging_set_options

logging_set_options(options: LoggingOptions) -> None

Set formatting options for the global internal logger.

This changes global settings that affect all existing LoggingConnection instances. The changes take effect immediately.

PARAMETER DESCRIPTION
options

Logging format options to apply.

TYPE: LoggingOptions

Source code in pbk/log.py
def logging_set_options(options: LoggingOptions) -> None:
    """Set formatting options for the global internal logger.

    This changes global settings that affect all existing LoggingConnection
    instances. The changes take effect immediately.

    Args:
        options: Logging format options to apply.
    """
    with LOGGING_LOCK:
        k.btck_logging_set_options(options)

set_log_level_category

set_log_level_category(category: LogCategory, level: LogLevel) -> None

Set the minimum log level for a category.

This changes the minimum severity of messages that will be logged for the specified category. Affects all existing LoggingConnection instances.

Note

This does not enable categories. Use enable_log_category() to start logging from a category. If LogCategory.ALL is chosen, sets both the global fallback log level and the level for the ALL category itself.

PARAMETER DESCRIPTION
category

The log category to configure.

TYPE: LogCategory

level

The minimum log level for this category.

TYPE: LogLevel

Source code in pbk/log.py
def set_log_level_category(category: LogCategory, level: LogLevel) -> None:
    """Set the minimum log level for a category.

    This changes the minimum severity of messages that will be logged for
    the specified category. Affects all existing LoggingConnection instances.

    Note:
        This does not enable categories. Use `enable_log_category()` to start
        logging from a category. If LogCategory.ALL is chosen, sets both the
        global fallback log level and the level for the ALL category itself.

    Args:
        category: The log category to configure.
        level: The minimum log level for this category.
    """
    with LOGGING_LOCK:
        k.btck_logging_set_level_category(category, level)

enable_log_category

enable_log_category(category: LogCategory) -> None

Enable logging for a category.

Once enabled, log messages from this category will be passed to all LoggingConnection callbacks. If LogCategory.ALL is chosen, all categories will be enabled.

PARAMETER DESCRIPTION
category

The log category to enable.

TYPE: LogCategory

Source code in pbk/log.py
def enable_log_category(category: LogCategory) -> None:
    """Enable logging for a category.

    Once enabled, log messages from this category will be passed to all
    LoggingConnection callbacks. If LogCategory.ALL is chosen, all
    categories will be enabled.

    Args:
        category: The log category to enable.
    """
    with LOGGING_LOCK:
        k.btck_logging_enable_category(category)

disable_log_category

disable_log_category(category: LogCategory) -> None

Disable logging for a category.

Once disabled, log messages from this category will no longer be passed to LoggingConnection callbacks. If LogCategory.ALL is chosen, all categories will be disabled.

PARAMETER DESCRIPTION
category

The log category to disable.

TYPE: LogCategory

Source code in pbk/log.py
def disable_log_category(category: LogCategory) -> None:
    """Disable logging for a category.

    Once disabled, log messages from this category will no longer be passed
    to LoggingConnection callbacks. If LogCategory.ALL is chosen, all
    categories will be disabled.

    Args:
        category: The log category to disable.
    """
    with LOGGING_LOCK:
        k.btck_logging_disable_category(category)

LoggingConnection

Bases: KernelOpaquePtr

Connection that receives kernel log messages via a callback.

A logging connection invokes a callback function for every log message produced by the kernel. The connection can be destroyed to stop receiving messages. Messages logged before the first connection is created are buffered (up to 1MB) and delivered when the connection is established.

Source code in pbk/log.py
class LoggingConnection(KernelOpaquePtr):
    """Connection that receives kernel log messages via a callback.

    A logging connection invokes a callback function for every log message
    produced by the kernel. The connection can be destroyed to stop receiving
    messages. Messages logged before the first connection is created are
    buffered (up to 1MB) and delivered when the connection is established.
    """

    _create_fn = k.btck_logging_connection_create
    _destroy_fn = k.btck_logging_connection_destroy

    def __init__(self, cb: typing.Callable[[str], None], user_data: UserData = None):
        """Create a logging connection with a callback.

        Args:
            cb: Callback function that accepts a single string parameter
                (the log message) and returns None.
            user_data: Optional user data to associate with the connection.

        Raises:
            TypeError: If the callback doesn't have the correct signature.
            RuntimeError: If the C constructor fails (propagated from base class).
        """
        if not is_valid_log_callback(cb):
            raise TypeError(
                "Log callback must be a callable with 1 string parameter and no return value."
            )
        self._cb = self._wrap_log_fn(cb)  # ensure lifetime
        self._user_data = user_data
        super().__init__(self._cb, user_data, k.btck_DestroyCallback())

    @staticmethod
    def _wrap_log_fn(fn: Callable[[str], None]):
        """Wrap a Python callback for use with the C logging API.

        Args:
            fn: Python callback function accepting a string.

        Returns:
            A C-compatible callback function.
        """

        def wrapped(user_data: None, message: bytes, message_len: int):
            """C callback wrapper that decodes the message and calls the Python function."""
            return fn(ctypes.string_at(message, message_len).decode("utf-8"))

        return k.btck_LogCallback(wrapped)

__init__

__init__(cb: Callable[[str], None], user_data: UserData = None)

Create a logging connection with a callback.

PARAMETER DESCRIPTION
cb

Callback function that accepts a single string parameter (the log message) and returns None.

TYPE: Callable[[str], None]

user_data

Optional user data to associate with the connection.

TYPE: UserData DEFAULT: None

RAISES DESCRIPTION
TypeError

If the callback doesn't have the correct signature.

RuntimeError

If the C constructor fails (propagated from base class).

Source code in pbk/log.py
def __init__(self, cb: typing.Callable[[str], None], user_data: UserData = None):
    """Create a logging connection with a callback.

    Args:
        cb: Callback function that accepts a single string parameter
            (the log message) and returns None.
        user_data: Optional user data to associate with the connection.

    Raises:
        TypeError: If the callback doesn't have the correct signature.
        RuntimeError: If the C constructor fails (propagated from base class).
    """
    if not is_valid_log_callback(cb):
        raise TypeError(
            "Log callback must be a callable with 1 string parameter and no return value."
        )
    self._cb = self._wrap_log_fn(cb)  # ensure lifetime
    self._user_data = user_data
    super().__init__(self._cb, user_data, k.btck_DestroyCallback())

KernelLogViewer

Integration between bitcoinkernel logging and Python's logging module.

KernelLogViewer bridges bitcoinkernel's logging system with Python's standard logging module. It creates a LoggingConnection that parses kernel log messages and forwards them to a Python Logger instance.

Messages are organized by category, with each category getting its own child logger. For example, VALIDATION messages go to a logger named "bitcoinkernel.VALIDATION".

Note

To see debug messages, both the kernel category must be enabled (via the categories parameter) and the Python logger level must be set to DEBUG or lower.

Source code in pbk/log.py
class KernelLogViewer:
    """Integration between bitcoinkernel logging and Python's logging module.

    KernelLogViewer bridges bitcoinkernel's logging system with Python's
    standard logging module. It creates a LoggingConnection that parses
    kernel log messages and forwards them to a Python Logger instance.

    Messages are organized by category, with each category getting its own
    child logger. For example, VALIDATION messages go to a logger named
    "bitcoinkernel.VALIDATION".

    Note:
        To see debug messages, both the kernel category must be enabled
        (via the `categories` parameter) and the Python logger level must
        be set to DEBUG or lower.
    """

    def __init__(
        self,
        name: str = "bitcoinkernel",
        categories: typing.List[LogCategory] | None = None,
    ):
        """Create a log viewer that forwards kernel logs to Python logging.

        Args:
            name: Name for the root logger. Defaults to "bitcoinkernel".
            categories: List of log categories to enable. If None or empty,
                no categories are enabled by default.
        """
        self.name = name
        self.categories = categories or []
        self._logger = logging.getLogger(name)

        # To simplify things, just set the level for to DEBUG for all
        # log categories. This shouldn't have any effect until categories
        # are actually enabled with enable_log_category.
        set_log_level_category(LogCategory.ALL, LogLevel.DEBUG)
        if categories is None:
            categories = []
        for category in categories:
            enable_log_category(category)
        self.conn = self._create_log_connection()

    def getLogger(self, category: LogCategory | None = None) -> logging.Logger:
        """Get the Python logger for a specific category.

        Args:
            category: The log category to get a logger for. If None, returns
                the root logger.

        Returns:
            A logging.Logger instance for the specified category.
        """
        if category:
            return self._logger.getChild(category.name.upper())
        return self._logger

    @contextmanager
    def temporary_categories(self, categories: typing.List[LogCategory]) -> None:
        """Context manager to temporarily enable log categories.

        Enables the specified categories for the duration of the context,
        then disables them when exiting. Categories that were enabled during
        initialization remain enabled.

        Args:
            categories: List of categories to temporarily enable.

        Yields:
            None
        """
        [enable_log_category(category) for category in categories]
        try:
            yield
        finally:
            [
                disable_log_category(category)
                for category in categories
                if category not in self.categories
            ]

    def _create_log_connection(self) -> LoggingConnection:
        """Create the internal logging connection.

        Sets up logging options and creates a connection that parses and
        forwards kernel messages to Python loggers.

        Returns:
            A LoggingConnection instance.
        """
        # TODO: avoid logging stuff the user doesn't need
        log_opts = LoggingOptions(
            log_timestamps=True,
            log_time_micros=False,
            log_threadnames=True,
            log_sourcelocations=True,
            always_print_category_levels=True,
        )
        logging_set_options(log_opts)
        conn = LoggingConnection(cb=self._create_log_callback(self.getLogger()))
        return conn

    @staticmethod
    def _create_log_callback(logger: logging.Logger) -> typing.Callable[[str], None]:
        """Create a callback that parses kernel logs and forwards to Python logger.

        Args:
            logger: The Python logger to forward parsed messages to.

        Returns:
            A callback function that accepts kernel log messages.
        """

        def callback(msg: str) -> None:
            """Parse and forward a kernel log message to the Python logger."""
            try:
                record = parse_btck_log_string(logger.name, msg)
                logger.handle(record)
            except ValueError:
                logging.getLogger().error(f"Failed to parse log message: {msg}")

        return callback

categories instance-attribute

categories = categories or []

conn instance-attribute

conn = _create_log_connection()

name instance-attribute

name = name

__init__

__init__(name: str = 'bitcoinkernel', categories: List[LogCategory] | None = None)

Create a log viewer that forwards kernel logs to Python logging.

PARAMETER DESCRIPTION
name

Name for the root logger. Defaults to "bitcoinkernel".

TYPE: str DEFAULT: 'bitcoinkernel'

categories

List of log categories to enable. If None or empty, no categories are enabled by default.

TYPE: List[LogCategory] | None DEFAULT: None

Source code in pbk/log.py
def __init__(
    self,
    name: str = "bitcoinkernel",
    categories: typing.List[LogCategory] | None = None,
):
    """Create a log viewer that forwards kernel logs to Python logging.

    Args:
        name: Name for the root logger. Defaults to "bitcoinkernel".
        categories: List of log categories to enable. If None or empty,
            no categories are enabled by default.
    """
    self.name = name
    self.categories = categories or []
    self._logger = logging.getLogger(name)

    # To simplify things, just set the level for to DEBUG for all
    # log categories. This shouldn't have any effect until categories
    # are actually enabled with enable_log_category.
    set_log_level_category(LogCategory.ALL, LogLevel.DEBUG)
    if categories is None:
        categories = []
    for category in categories:
        enable_log_category(category)
    self.conn = self._create_log_connection()

getLogger

getLogger(category: LogCategory | None = None) -> logging.Logger

Get the Python logger for a specific category.

PARAMETER DESCRIPTION
category

The log category to get a logger for. If None, returns the root logger.

TYPE: LogCategory | None DEFAULT: None

RETURNS DESCRIPTION
Logger

A logging.Logger instance for the specified category.

Source code in pbk/log.py
def getLogger(self, category: LogCategory | None = None) -> logging.Logger:
    """Get the Python logger for a specific category.

    Args:
        category: The log category to get a logger for. If None, returns
            the root logger.

    Returns:
        A logging.Logger instance for the specified category.
    """
    if category:
        return self._logger.getChild(category.name.upper())
    return self._logger

temporary_categories

temporary_categories(categories: List[LogCategory]) -> None

Context manager to temporarily enable log categories.

Enables the specified categories for the duration of the context, then disables them when exiting. Categories that were enabled during initialization remain enabled.

PARAMETER DESCRIPTION
categories

List of categories to temporarily enable.

TYPE: List[LogCategory]

YIELDS DESCRIPTION
None

None

Source code in pbk/log.py
@contextmanager
def temporary_categories(self, categories: typing.List[LogCategory]) -> None:
    """Context manager to temporarily enable log categories.

    Enables the specified categories for the duration of the context,
    then disables them when exiting. Categories that were enabled during
    initialization remain enabled.

    Args:
        categories: List of categories to temporarily enable.

    Yields:
        None
    """
    [enable_log_category(category) for category in categories]
    try:
        yield
    finally:
        [
            disable_log_category(category)
            for category in categories
            if category not in self.categories
        ]