ATLAS Offline Software
Loading...
Searching...
No Matches
logging_bridge.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2#
3# @author Giordon Stark
4
5"""Bridge C++ message output to Python logging module.
6
7Installs a callback that routes C++ IMessagePrinter output to a Python
8logger named "atlas", integrating with standard logging configuration
9(handlers, formatters, levels).
10
11The bridge is installed automatically on package import with a default
12StreamHandler and INFO level, so C++ messages are visible out of the box.
13To suppress them::
14
15 import logging
16 logging.getLogger("atlas").setLevel(logging.WARNING)
17
18To see DEBUG/VERBOSE messages::
19
20 import logging
21 logging.getLogger("atlas").setLevel(logging.DEBUG)
22
23To customize the format, replace the default handler::
24
25 import logging, sys
26 logger = logging.getLogger("atlas")
27 logger.handlers.clear()
28 handler = logging.StreamHandler(sys.stderr)
29 handler.setFormatter(logging.Formatter("[%(tool)s] %(levelname)s %(message)s"))
30 logger.addHandler(handler)
31
32Tool context is passed via the `extra` dict, so formatters can reference
33`%(tool)s` to display the tool name.
34
35In Athena/AthAnalysis builds (non-standalone), IMessagePrinter is not available
36and the C++ bridge cannot be installed. The logger is still created and usable
37for Python-side logging, but C++ messages will not be routed through it.
38"""
39
40import logging
41import sys
42import atexit
43
44from ColumnarToolWrapperPython.python_tool_handle import set_python_printer, MsgLevel
45
46
47# Mapping from C++ MSG::Level to Python logging levels.
48# C++ levels (0-6) map to Python levels preserving severity ordering.
49_LEVEL_MAP = {
50 MsgLevel.NIL: logging.NOTSET,
51 MsgLevel.VERBOSE: logging.DEBUG - 5,
52 MsgLevel.DEBUG: logging.DEBUG,
53 MsgLevel.INFO: logging.INFO,
54 MsgLevel.WARNING: logging.WARNING,
55 MsgLevel.ERROR: logging.ERROR,
56 MsgLevel.FATAL: logging.CRITICAL,
57}
58
59
60# Format approximating the C++ default MessagePrinter output:
61# "ToolSvc.myTool INFO message text"
62_DEFAULT_FORMAT = "%(tool)-25s%(levelname)-8s%(message)s"
63
64
65def install_printer(name="atlas"):
66 """Install a Python logger as the C++ message printer.
67
68 Routes all C++ IMessagePrinter messages (from tools, ASG framework, etc.)
69 to a Python logger with the given name. Called automatically by the package
70 on import with a default StreamHandler and INFO level, matching the C++
71 default stdout printer behaviour.
72
73 In Athena/AthAnalysis (non-standalone) builds, IMessagePrinter is not
74 available. The logger is still created and configured, but C++ messages
75 will not be routed through it.
76
77 Tool name is passed via the `extra` dict, so formatters can use `%(tool)s`.
78
79 Parameters
80 ----------
81 name:
82 Logger name (default: "atlas").
83
84 Returns
85 -------
86 logging.Logger
87 The installed logger. Configure with handlers and setLevel as needed.
88
89 Notes
90 -----
91 Only one printer can be active at a time. Calling install_printer again
92 replaces the previous one. Call remove_printer() to restore default
93 stdout printing.
94
95 Cleanup is automatically registered via atexit, so you don't need to
96 call remove_printer() explicitly at interpreter shutdown.
97 """
98 logger = logging.getLogger(name)
99
100 # Add a default handler if none exists yet, so messages are visible
101 # out of the box without any user configuration.
102 if not logger.handlers:
103 handler = logging.StreamHandler(sys.stderr)
104 handler.setFormatter(logging.Formatter(_DEFAULT_FORMAT))
105 logger.addHandler(handler)
106
107 # Set level to INFO to match the C++ default MsgStream threshold, but
108 # only if the caller has not already configured a level (NOTSET means
109 # no explicit level was set yet).
110 if logger.level == logging.NOTSET:
111 logger.setLevel(logging.INFO)
112
113 # Don't propagate to the root logger — avoids double printing when
114 # the user has logging.basicConfig() or other root handlers configured.
115 logger.propagate = False
116
117 def printer_callback(level_int, tool_name, text):
118 """Callback invoked by C++ for each message."""
119 # Look up Python logging level for the C++ message level (int key)
120 py_level = _LEVEL_MAP.get(level_int, logging.INFO)
121 # Pass tool name via extra dict for formatter access
122 logger.log(py_level, text.rstrip(), extra={"tool": tool_name})
123
124 try:
125 set_python_printer(printer_callback)
126 # Ensure cleanup at interpreter shutdown
127 atexit.register(remove_printer)
128 except RuntimeError:
129 # Non-standalone (Athena/AthAnalysis) build: IMessagePrinter is not
130 # available. The logger is still usable for Python-side logging.
131 logger.debug(
132 "C++ message bridge unavailable (not a standalone build); "
133 "C++ tool messages will not be routed through Python logging."
134 )
135
136 return logger
137
138
140 """Remove the Python printer and restore default stdout printing."""
141 set_python_printer() # Call with no arguments to reset
install_printer(name="atlas")