trace - Trace/audit for class instances

Overview

This module provides trace/audit logging for functions or object methods through context-based logging provided by logging module.

The trace logging is performed by traced decorator. You can use this decorator directly, or use TracedMixin class to automatically decorate methods of class instances on creation. Each decorated callable could log messages before execution, after successful execution or on failed execution (when unhandled exception is raised by callable). The trace decorator can automatically add agent and context information, and include parameters passed to callable, execution time, return value, information about raised exception etc. to log messages.

Trace behavior can be configured dynamically at runtime using the TraceManager. This includes:

  • Enabling/disabling tracing globally or for specific aspects (before/after/fail).

  • Registering classes whose methods should be traced.

  • Adding specific trace configurations (like custom messages or levels) for individual methods using TraceManager.add_trace().

  • Loading comprehensive trace configurations from ConfigParser files using TraceManager.load_config(), which allows specifying traced classes, methods, and decorator parameters via INI-style sections (see TraceConfig).

Example

The following program is an example of small but complex enough code that you can use to experiment with code tracing options. The parts relevant to tracing are highlighted in the code by embedded comments.

# test-trace.py
from __future__ import annotations
import logging
from time import monotonic
from decimal import Decimal
from enum import IntEnum, auto
from firebird.base.types import *
from firebird.base.logging import LogLevel, LoggingIdMixin, get_logger
from firebird.base.trace import TracedMixin, add_trace, trace_manager, TraceFlag, traced

class Mood(IntEnum):
    "Agent moods"
    ANGRY = auto()
    SAD = auto()
    NEUTRAL = auto()
    PLEASED = auto()
    HAPPY = auto()

class Person(TracedMixin): # TRACE
    "Sample virtual human agent"
    def __init__(self, name: str, mood: Mood=Mood.NEUTRAL):
        self.name: str = name
        self.mood: Mood = mood
        self.partners: List[Person] = []
    # >>> LOGGING & TRACE
    @property
    def _agent_name_(self) -> str:
        return f"{self.mood.name} {self.name}"
    # <<< LOGGING & TRACE
    def change_mood(self, offset: int) -> None:
        result = self.mood + offset
        if result < Mood.ANGRY:
            self.mood = Mood.ANGRY
        elif result > Mood.HAPPY:
            self.mood = Mood.HAPPY
        else:
            self.mood = Mood(result)
    def process(self, message: str) -> None:
        msg = message.lower()
        if msg == "what you are doing here":
            self.change_mood(-1)
        if 'awful' in msg:
            self.change_mood(-1)
        if ('nice' in msg) or ('wonderful' in msg) or ('pleased' in msg):
            if self.mood != Mood.ANGRY:
                self.change_mood(1)
        if 'happy' in msg:
            if self.mood != Mood.ANGRY:
                self.change_mood(2)
        if 'very nice' in msg:
            if self.mood != Mood.ANGRY:
                self.change_mood(1)
        if 'get lost' in msg:
            self.change_mood(-2)
        if self.name.lower() in msg:
            if self.mood == Mood.SAD:
                self.change_mood(1)
        if self.name.lower() not in msg:
            if self.mood == Mood.NEUTRAL:
                self.change_mood(-1)
    def process_response(self, to: str, mood: Mood) -> None:
        if to == 'greeting':
            if self.mood == Mood.NEUTRAL:
                if mood > Mood.NEUTRAL:
                    self.mood = Mood.PLEASED
                elif mood == Mood.ANGRY:
                    self.mood = Mood.SAD
            elif self.mood == Mood.SAD:
                if mood == Mood.SAD:
                    self.mood = Mood.NEUTRAL
                elif mood == Mood.HAPPY:
                    self.mood = Mood.ANGRY
            elif self.mood == Mood.ANGRY and mood == Mood.SAD:
                self.mood = Mood.NEUTRAL
        elif to == 'chat':
            if self.mood == Mood.SAD and mood > Mood.NEUTRAL:
                self.mood = Mood.NEUTRAL
            elif self.mood == Mood.ANGRY and mood == Mood.SAD:
                self.mood = Mood.NEUTRAL
            elif self.mood == Mood.PLEASED and mood == Mood.ANGRY:
                self.mood = Mood.NEUTRAL
            elif self.mood == Mood.HAPPY and mood == Mood.ANGRY:
                self.mood = Mood.SAD
        elif to == 'bye':
            if self.mood == Mood.NEUTRAL:
                if mood == Mood.ANGRY:
                    self.mood = Mood.ANGRY
                elif mood > Mood.NEUTRAL:
                    self.mood = Mood.PLEASED
            elif self.mood == Mood.HAPPY and mood == Mood.ANGRY:
                self.mood = Mood.NEUTRAL
    def meet(self, other: Person) -> None:
        self.partners.append(other)
        self.greeting(other)
    def interact(self, other: Person, message: str) -> Mood:
        print(f"[{other.name}] {message}")
        self.process(message)
        return self.mood
    def greeting(self, other: Person) -> None:
        if self.mood == Mood.NEUTRAL:
            msg = f"Hi {other.name}, I'm {self.name}"
        elif self.mood == Mood.ANGRY:
            msg = "Hi"
        elif self.mood == Mood.SAD:
            msg = f"Hi {other.name}"
        else:
            msg = f"Hi {other.name}, I'm {self.name}. I'm {self.mood.name} to meet you."
        self.process_response('greeting', other.interact(self, msg))
    def chat(self) -> None:
        for other in self.partners:
            if self.mood == Mood.ANGRY:
                msg = "What you are doing here?"
            elif self.mood == Mood.SAD:
                msg = "The weather is awful today, don't you think?"
            elif self.mood == Mood.NEUTRAL:
                msg = "It's a fine day, don't you think?"
            elif self.mood == Mood.PLEASED:
                msg = "It's a very nice day, don't you think?"
            else:
                msg = "Today is a wonderful day!"
            self.process_response('chat', other.interact(self, msg))
    def bye(self) -> str:
        while self.partners:
            other = self.partners.pop()
            if self.mood == Mood.ANGRY:
                msg = "Get lost!"
            elif self.mood == Mood.SAD:
                msg = "Bye"
            elif self.mood == Mood.NEUTRAL:
                msg = f"Bye, {other.name}."
            elif self.mood == Mood.PLEASED:
                msg = f"See you, {other.name}!"
            else:
                msg = f"Bye, {other.name}. Have a nice day!"
            self.process_response('bye', other.interact(self, msg))
        if self.mood == Mood.ANGRY:
            result = "I hate this meeting!"
        elif self.mood == Mood.SAD:
            result = "It was a waste of time!"
        elif self.mood == Mood.NEUTRAL:
            result = "It was OK."
        elif self.mood == Mood.PLEASED:
            result = "Nice meeting, I think."
        else:
            result = "What a wonderful meeting!"
        return result
    def __repr__(self) -> str:
        # Replace "..Person object at .." with something more suitable for trace
        return f"Person('{self.name}', {self.mood.name})"

def meeting(name: str, persons: List[Person]):
    "Simulation of virtual agents meeting"

    for person in persons:
        person.log_context = name

    start = monotonic()
    print("Meeting started...")
    print(f"Attendees: {', '.join(f'{x.name} [{x.mood.name}]' for x in persons)}")

    for person in persons:
        for other in persons:
            if other is not person:
                person.meet(other)

    for person in persons:
        person.chat()

    for person in persons:
        person.bye()

    e = str(Decimal(monotonic() - start))
    print(f"Meeting closed in {e[:e.find('.')+6]} sec.")
    print(f"Outcome: {', '.join(f'{x.name} [{x.mood.name}]' for x in persons)}")


def test_trace(name: str, first: Mood, second: Mood) -> None:
    print("- without trace ----------")
    meeting(name, [Person('Alex', first), Person('David', second)])

    print("- trace ------------------")
    # >>> TRACE
    add_trace(Person, 'greeting')
    add_trace(Person, 'bye')
    add_trace(Person, 'chat')
    add_trace(Person, 'change_mood')
    add_trace(Person, 'process', with_args=False)
    add_trace(Person, 'process_response')
    # <<< TRACE
    meeting(name, [Person('Alex', first), Person('David', second)])

if __name__ == '__main__':
    # >>> LOGGING
    logger = logging.getLogger()
    logger.setLevel(LogLevel.NOTSET)
    sh = logging.StreamHandler()
    sh.setFormatter(logging.Formatter('%(levelname)-10s: [%(topic)s][%(agent)s][%(context)s] %(message)s'))
    logger.addHandler(sh)
    # <<< LOGGING
    # >>> TRACE
    trace_manager.flags |= TraceFlag.ACTIVE
    trace_manager.flags |= (TraceFlag.FAIL | TraceFlag.BEFORE | TraceFlag.AFTER)
    # <<< TRACE
    test_trace('TEST-1', Mood.SAD, Mood.PLEASED)

Output from sample code:

> python test-trace.py
- without trace ----------
Meeting started...
Attendees: Alex [SAD], David [PLEASED]
[Alex] Hi David
[David] Hi Alex, I'm David. I'm PLEASED to meet you.
[Alex] It's a fine day, don't you think?
[David] It's a very nice day, don't you think?
[Alex] Bye, David. Have a nice day!
[David] Bye, Alex. Have a nice day!
Meeting closed in 0.00014 sec.
Outcome: Alex [HAPPY], David [HAPPY]
- trace ------------------
Meeting started...
Attendees: Alex [SAD], David [PLEASED]
DEBUG     : [trace][SAD Alex][TEST-1] >>> greeting(other=Person('David', PLEASED))
DEBUG     : [trace][PLEASED David][TEST-1] >>> interact(other=Person('Alex', SAD), message='Hi David')
[Alex] Hi David
DEBUG     : [trace][PLEASED David][TEST-1] >>> process
DEBUG     : [trace][PLEASED David][TEST-1] <<< process[0.00002]
DEBUG     : [trace][PLEASED David][TEST-1] <<< interact[0.00020] Result: <Mood.PLEASED: 4>
DEBUG     : [trace][SAD Alex][TEST-1] >>> process_response(to='greeting', mood=<Mood.PLEASED: 4>)
DEBUG     : [trace][SAD Alex][TEST-1] <<< process_response[0.00000]
DEBUG     : [trace][SAD Alex][TEST-1] <<< greeting[0.00060]
DEBUG     : [trace][PLEASED David][TEST-1] >>> greeting(other=Person('Alex', SAD))
DEBUG     : [trace][SAD Alex][TEST-1] >>> interact(other=Person('David', PLEASED), message="Hi Alex, I'm David. I'm PLEASED to meet you.")
[David] Hi Alex, I'm David. I'm PLEASED to meet you.
DEBUG     : [trace][SAD Alex][TEST-1] >>> process
DEBUG     : [trace][SAD Alex][TEST-1] >>> change_mood(offset=1)
DEBUG     : [trace][SAD Alex][TEST-1] <<< change_mood[0.00000]
DEBUG     : [trace][SAD Alex][TEST-1] <<< process[0.00016]
DEBUG     : [trace][SAD Alex][TEST-1] <<< interact[0.00030] Result: <Mood.NEUTRAL: 3>
DEBUG     : [trace][PLEASED David][TEST-1] >>> process_response(to='greeting', mood=<Mood.NEUTRAL: 3>)
DEBUG     : [trace][PLEASED David][TEST-1] <<< process_response[0.00000]
DEBUG     : [trace][PLEASED David][TEST-1] <<< greeting[0.00061]
DEBUG     : [trace][NEUTRAL Alex][TEST-1] >>> chat()
DEBUG     : [trace][PLEASED David][TEST-1] >>> interact(other=Person('Alex', NEUTRAL), message="It's a fine day, don't you think?")
[Alex] It's a fine day, don't you think?
DEBUG     : [trace][PLEASED David][TEST-1] >>> process
DEBUG     : [trace][PLEASED David][TEST-1] <<< process[0.00000]
DEBUG     : [trace][PLEASED David][TEST-1] <<< interact[0.00013] Result: <Mood.PLEASED: 4>
DEBUG     : [trace][NEUTRAL Alex][TEST-1] >>> process_response(to='chat', mood=<Mood.PLEASED: 4>)
DEBUG     : [trace][NEUTRAL Alex][TEST-1] <<< process_response[0.00000]
DEBUG     : [trace][NEUTRAL Alex][TEST-1] <<< chat[0.00042]
DEBUG     : [trace][PLEASED David][TEST-1] >>> chat()
DEBUG     : [trace][NEUTRAL Alex][TEST-1] >>> interact(other=Person('David', PLEASED), message="It's a very nice day, don't you think?")
[David] It's a very nice day, don't you think?
DEBUG     : [trace][NEUTRAL Alex][TEST-1] >>> process
DEBUG     : [trace][NEUTRAL Alex][TEST-1] >>> change_mood(offset=1)
DEBUG     : [trace][NEUTRAL Alex][TEST-1] <<< change_mood[0.00000]
DEBUG     : [trace][PLEASED Alex][TEST-1] >>> change_mood(offset=1)
DEBUG     : [trace][PLEASED Alex][TEST-1] <<< change_mood[0.00000]
DEBUG     : [trace][NEUTRAL Alex][TEST-1] <<< process[0.00027]
DEBUG     : [trace][NEUTRAL Alex][TEST-1] <<< interact[0.00039] Result: <Mood.HAPPY: 5>
DEBUG     : [trace][PLEASED David][TEST-1] >>> process_response(to='chat', mood=<Mood.HAPPY: 5>)
DEBUG     : [trace][PLEASED David][TEST-1] <<< process_response[0.00000]
DEBUG     : [trace][PLEASED David][TEST-1] <<< chat[0.00068]
DEBUG     : [trace][HAPPY Alex][TEST-1] >>> bye()
DEBUG     : [trace][PLEASED David][TEST-1] >>> interact(other=Person('Alex', HAPPY), message='Bye, David. Have a nice day!')
[Alex] Bye, David. Have a nice day!
DEBUG     : [trace][PLEASED David][TEST-1] >>> process
DEBUG     : [trace][PLEASED David][TEST-1] >>> change_mood(offset=1)
DEBUG     : [trace][PLEASED David][TEST-1] <<< change_mood[0.00000]
DEBUG     : [trace][PLEASED David][TEST-1] <<< process[0.00013]
DEBUG     : [trace][PLEASED David][TEST-1] <<< interact[0.00024] Result: <Mood.HAPPY: 5>
DEBUG     : [trace][HAPPY Alex][TEST-1] >>> process_response(to='bye', mood=<Mood.HAPPY: 5>)
DEBUG     : [trace][HAPPY Alex][TEST-1] <<< process_response[0.00000]
DEBUG     : [trace][HAPPY Alex][TEST-1] <<< bye[0.00052] Result: 'What a wonderful meeting!'
DEBUG     : [trace][HAPPY David][TEST-1] >>> bye()
DEBUG     : [trace][HAPPY Alex][TEST-1] >>> interact(other=Person('David', HAPPY), message='Bye, Alex. Have a nice day!')
[David] Bye, Alex. Have a nice day!
DEBUG     : [trace][HAPPY Alex][TEST-1] >>> process
DEBUG     : [trace][HAPPY Alex][TEST-1] >>> change_mood(offset=1)
DEBUG     : [trace][HAPPY Alex][TEST-1] <<< change_mood[0.00000]
DEBUG     : [trace][HAPPY Alex][TEST-1] <<< process[0.00013]
DEBUG     : [trace][HAPPY Alex][TEST-1] <<< interact[0.00024] Result: <Mood.HAPPY: 5>
DEBUG     : [trace][HAPPY David][TEST-1] >>> process_response(to='bye', mood=<Mood.HAPPY: 5>)
DEBUG     : [trace][HAPPY David][TEST-1] <<< process_response[0.00000]
DEBUG     : [trace][HAPPY David][TEST-1] <<< bye[0.00052] Result: 'What a wonderful meeting!'
Meeting closed in 0.00432 sec.
Outcome: Alex [HAPPY], David [HAPPY]

Trace configuration

New in version 1.1.0.

Trace supports configuration based on config.

Sample configuration file:

[trace]
flags = ACTIVE | FAIL
;flags = ACTIVE | BEFORE | AFTER | FAIL
classes = trace_ChannelManager, trace_Channel, trace_TextIOServiceImpl, trace_PipeServerHandler

[trace_PipeServerHandler]
source = saturnin.core.protocol.fbdp.PipeServerHandler
methods = close, send_ready, send_close

[trace_ChannelManager]
source = saturnin.core.base.ChannelManager
special = trace_defer

[trace_Channel]
source = saturnin.core.base.Channel
methods = send, receive, close, bind, unbind, connect, disconnect

[trace_DealerChannel]
source = saturnin.core.base.DealerChannel
methods = send, receive, close, bind, unbind, connect, disconnect

[trace_SimpleService]
source = saturnin.core.classic.SimpleService
methods = validate, run, initialize, start
with_args = no

[trace_TextIOServiceImpl]
source = saturnin.sdk.micro.textio.service.TextIOServiceImpl
methods = initialize, configure, validate, finalize
with_args = no

[trace_defer]
method = defer
max_param_length = 50

Enums & Flags

class firebird.base.trace.TraceFlag(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Bases: IntFlag

Flags controlling the behavior of the traced decorator and TraceManager.

These flags determine whether tracing is active and which parts of a call (before, after success, after failure) should be logged.

ACTIVE = 1

Master switch; tracing is performed only if ACTIVE is set.

AFTER = 4

Log message after the decorated callable successfully returns.

BEFORE = 2

Log message before the decorated callable executes.

FAIL = 8

Log message if the decorated callable raises an exception.

NONE = 0

No tracing enabled by default flags.

Functions

firebird.base.trace.add_trace(cls: type, method: str, /, *args, **kwargs) None

Shortcut for trace_manager.add_trace()

Parameters:
Return type:

None


firebird.base.trace.remove_trace(cls: type, method: str) None

Shortcut for trace_manager.remove_trace()

Parameters:
Return type:

None


firebird.base.trace.trace_object(obj: Any, *, strict: bool = False) Any

Shortcut for trace_manager.trace_object()

Parameters:
Return type:

Any

Trace manager

class firebird.base.trace.TraceManager[source]

Bases: object

Trace manager.

add_trace(cls: type, method: str, /, *args, **kwargs) None[source]

Store or update the trace specification for a specific class method.

Registers how a method should be decorated (using self.decorator) when trace_object is called on an instance of cls or its registered descendants. This specification can be overridden or augmented by settings loaded via load_config.

Parameters:
  • cls (type) – Registered traced class type.

  • method (str) – The name of the method within cls to trace.

  • *args – Positional arguments for the decorator factory (self.decorator).

  • **kwargs – Keyword arguments for the decorator factory (self.decorator).

Return type:

None

clear() None[source]

Removes all trace specifications.

Return type:

None

clear_flag(flag: TraceFlag) None[source]

Clear flag specified by flag mask.

Parameters:

flag (TraceFlag) –

Return type:

None

is_registered(cls: type) bool[source]

Return True if class is registered.

Parameters:

cls (type) –

Return type:

bool

load_config(config: ConfigParser, section: str = 'trace') None[source]

Load and apply trace configurations from a ConfigParser instance.

Parses the specified section (and referenced sub-sections) using the TraceConfig, TracedClassConfig, and TracedMethodConfig structures. Updates the TraceManager’s flags and trace specifications (add_trace).

Parameters:
  • config (ConfigParser) – ConfigParser instance containing the trace configuration.

  • section (str) – Name of the main trace configuration section (default: ‘trace’).

Return type:

None

Note

This method adds to or updates existing trace specifications. It does not clear previous configurations unless the loaded configuration explicitly overwrites specific settings.

Raises:
  • Error – If configuration references a class that is not registered and autoregister is False, or if the class cannot be loaded via load().

  • KeyError, ValueError – If the configuration file structure is invalid or contains invalid values according to the Option types.

Parameters:
Return type:

None

register(cls: type) None[source]

Register class for trace.

Parameters:

cls (type) – Class to be registered.

Return type:

None

Does nothing if class is already registered.

remove_trace(cls: type, method: str) None[source]

Remove trace specification for class method.

Parameters:
  • cls (type) – Registered traced class

  • method (str) – Name of class method

Return type:

None

set_flag(flag: TraceFlag) None[source]

Set flag specified by flag mask.

Parameters:

flag (TraceFlag) –

Return type:

None

trace_object(obj: Any, *, strict: bool = False) Any[source]

Apply registered trace decorators to the methods of an object instance.

Iterates through the trace specifications (TracedItem) registered for the object’s class (via add_trace or load_config). For each specification, it wraps the corresponding method on the obj instance using the specified decorator and arguments. Modifies the object in place.

Parameters:
  • obj (Any) – The object instance whose methods should be instrumented.

  • strict (bool) – If True, raise TypeError if obj’s class is not registered. If False (default), return obj unmodified if not registered.

Returns:

The (potentially modified) object instance obj.

Raises:

TypeError – If obj’s class is not registered and strict is True.

Return type:

Any

decorator: Callable

traced). Can be replaced.

Type:

Decorator factory used by add_trace (default

property flags: TraceFlag

Trace flags.

property trace_active: bool

True if trace is active.

Trace/audit decorator

class firebird.base.trace.traced(*, agent: Any | DEFAULT = DEFAULT, topic: str = 'trace', msg_before: str | DEFAULT = DEFAULT, msg_after: str | DEFAULT = DEFAULT, msg_failed: str | DEFAULT = DEFAULT, flags: TraceFlag = TraceFlag.NONE, level: LogLevel = LogLevel.DEBUG, max_param_length: int | UNLIMITED = UNLIMITED, extra: dict | None = None, callback: Callable[[Any], bool] | None = None, has_result: bool | DEFAULT = DEFAULT, with_args: bool = True)[source]

Bases: object

Decorator factory for adding trace/audit logging to callables.

Creates a decorator that wraps a function or method to log messages before execution, after successful execution, and/or upon failure, based on configured flags and messages. Integrates with the firebird.base.logging context logger.

Note

The decorator is only applied if tracing is globally enabled via the FBASE_TRACE environment variable or if __debug__ is true (i.e., Python is not run with -O). If disabled globally, the original un-decorated function is returned. Runtime behavior (whether logs are actually emitted) is further controlled by TraceManager.flags.

Parameters:
  • agent (Any | DEFAULT) – Agent identifier for logging context (object or string). If DEFAULT, uses self for methods or 'function' otherwise.

  • topic (str) – Logging topic (default: ‘trace’).

  • msg_before (str | DEFAULT) – Format string (f-string style) for log message before execution. If DEFAULT, a standard message is generated.

  • msg_after (str | DEFAULT) – Format string for log message after successful execution. Available context includes _etime_ (execution time string) and _result_ (return value, if has_result is true). If DEFAULT, a standard message is generated.

  • msg_failed (str | DEFAULT) – Format string for log message on exception. Available context includes _etime_ and _exc_ (exception string). If DEFAULT, a standard message is generated.

  • flags (TraceFlag) – TraceFlag values to override TraceManager.flags for this specific decorator instance. Allows fine-grained control per traced callable.

  • level (LogLevel) – LogLevel for trace messages (default: LogLevel.DEBUG).

  • max_param_length (int | UNLIMITED) – Max length for string representation of parameters/result in logs. Longer values are truncated (default: UNLIMITED).

  • extra (dict | None) – Dictionary of extra data to add to the LogRecord.

  • callback (Callable[[Any], bool] | None) – Optional callable func(agent) -> bool. If provided, it’s called before logging to check if tracing is permitted for this specific call.

  • has_result (bool | DEFAULT) – Boolean or DEFAULT. If True, include result in msg_after. If DEFAULT, inferred from function’s return type annotation (considered True unless annotation is None).

  • with_args (bool) – If True (default), make function arguments available by name for interpolation in msg_before.

log_after(logger: ContextLoggerAdapter, params: dict) None[source]

Log the ‘after’ message using the configured template and logger.

Parameters:
Return type:

None

log_before(logger: ContextLoggerAdapter, params: dict) None[source]

Log the ‘before’ message using the configured template and logger.

Parameters:
Return type:

None

log_failed(logger: ContextLoggerAdapter, params: dict) None[source]

Log the ‘failed’ message using the configured template and logger.

Parameters:
Return type:

None

set_after_msg(fn: Callable, sig: Signature) None[source]

Generate the default log message template for successful execution.

Parameters:
Return type:

None

set_before_msg(fn: Callable, sig: Signature) None[source]

Generate the default log message template for before execution.

Parameters:
Return type:

None

set_fail_msg(fn: Callable, sig: Signature) None[source]

Generate the default log message template for failed execution.

Parameters:
Return type:

None

agent: Any | DEFAULT

Agent identification

callback: Callable[[Any], bool]

Callback function that gets the agent identification as argument, and must return True/False indicating whether trace is allowed.

extra: dict[str, Any]

Extra data for LogRecord

flags: TraceFlag

Trace flags override

has_result: bool | DEFAULT

Indicator whether function has result value. If True, _result_ is available for interpolation in msg_after.

level: LogLevel

Logging level for trace/audit messages

max_len: int | UNLIMITED

Max. length of parameters (longer will be trimmed)

msg_after: str | DEFAULT

Trace/audit message logged after decorated function

msg_before: str | DEFAULT

Trace/audit message logged before decorated function

msg_failed: str | DEFAULT

Trace/audit message logged when decorated function raises an exception

topic: str

Trace/audit logging topic

with_args: bool

If True, function arguments are available for interpolation in msg_before

Mixins

class firebird.base.trace.TracedMixin(*args, **kwargs)[source]

Bases: object

Mixin class to automatically enable tracing for descendants.

Subclasses inheriting from TracedMixin are automatically registered with the trace_manager upon definition. When instances of these subclasses are created, their methods are automatically instrumented by trace_object according to the currently active trace specifications in the trace_manager.

Globals

firebird.base.trace.trace_manager: TraceManager

Trace manager singleton instance.

Trace configuration classes

class firebird.base.trace.BaseTraceConfig(name: str)[source]

Bases: Config

Base class defining common configuration options for trace settings.

Used as a base for global trace config, per-class config, and per-method config. Corresponds typically to settings within a section of a configuration file.

Parameters:

name (str) –

agent: StrOption

Agent identification

flags: FlagOption

Trace flags override

has_result: BoolOption

Indicator whether function has result value

level: EnumOption

Logging level for trace/audit messages

max_param_length: IntOption

Max. length of parameters (longer will be trimmed)

msg_after: StrOption

Trace/audit message logged after decorated function

msg_before: StrOption

Trace/audit message logged before decorated function

msg_failed: StrOption

Trace/audit message logged when decorated function raises an exception

topic: StrOption

Trace/audit logging topic

with_args: BoolOption

If True, function arguments are available for interpolation in msg_before

class firebird.base.trace.TracedMethodConfig(name: str)[source]

Bases: BaseTraceConfig

Defines the structure for a configuration section specifying trace settings specific to a single class method.

Used within TracedClassConfig.special list. The section name itself is referenced in the parent TracedClassConfig section.

Parameters:

name (str) –

method: StrOption

Class method name [required]

class firebird.base.trace.TracedClassConfig(name: str)[source]

Bases: BaseTraceConfig

Defines the structure for a configuration section specifying trace settings for a Python class and its methods.

The section name itself is referenced in the main TraceConfig section. See the module documentation for an example INI structure.

Parameters:

name (str) –

apply_to_descendants: BoolOption

True].

Type:

Wherher configuration should be applied also to all registered descendant classes [default

methods: ListOption

Names of traced class methods

source: StrOption

Fully qualified class name [required]

special: ConfigListOption

Configuration sections with extended config of traced class methods

class firebird.base.trace.TraceConfig(name: str)[source]

Bases: BaseTraceConfig

Defines the structure for the main trace configuration section (typically ‘[trace]’).

Holds global default trace settings and lists the sections defining specific traced classes. See the module documentation for an example INI structure.

Parameters:

name (str) –

autoregister: BoolOption

True].

Type:

When True, unregistered classes are registered automatically [default

classes: ConfigListOption

Configuration sections with traced Python classes [required].

Dataclasses

class firebird.base.trace.TracedItem(method: str, decorator: ~collections.abc.Callable, args: list[~typing.Any] = <factory>, kwargs: dict[str, ~typing.Any] = <factory>)[source]

Bases: Distinct

Holds the trace specification for a single method within a registered class.

Stored by TraceManager for each method configured via add_trace or load_config. Applied by trace_object.

Parameters:
  • method (str) – The name of the method to be traced.

  • decorator (Callable) – The decorator callable (usually traced or a custom one) to apply.

  • args (list[Any]) – Positional arguments to pass to the decorator factory.

  • kwargs (dict[str, Any]) – Keyword arguments to pass to the decorator factory.

get_key() Hashable[source]

Returns Distinct key for traced item [method].

Return type:

Hashable

args: list[Any]

Positional arguments to pass to the decorator factory.

decorator: Callable

The decorator callable (usually traced or a custom one) to apply.

kwargs: dict[str, Any]

Keyword arguments to pass to the decorator factory.

method: str

The name of the method to be traced.

class firebird.base.trace.TracedClass(cls: type, traced: ~firebird.base.collections.Registry = <factory>)[source]

Bases: Distinct

Represents a class registered for tracing within the TraceManager.

Holds a registry (Registry[TracedItem]) of trace specifications for methods belonging to this class.

Parameters:
  • cls (type) – The class type registered for tracing.

  • traced (Registry) – A registry mapping method names to TracedItem specifications.

get_key() Hashable[source]

Returns Distinct key for traced item [cls].

Return type:

Hashable

cls: type

The class type registered for tracing.

traced: Registry

A registry mapping method names to TracedItem specifications.