cnv = DhCanvas(height=200)Loggers
Canvas and Logger
Unified logging across JavaScript and Python for Bridget monitoring
Bridget operates across multiple contexts: browser (JS), kernel (Python), and extensions. This module provides a unified logging system that works identically in all environments.
Architecture: - Canvas: Abstraction of a display area (where to write) - Logger: Logging interface (what/how to write)
Bridget loggers write to Bridget canvases, allowing you to monitor all Bridget activity in a single notebook cell - no need to check browser console or configure Python logging separately.
Used throughout Bridget for debugging Bridge operations, state updates, and plugin activity.
Canvas
Places to display content
Canvas
Canvas ()
Base class for output display areas in notebooks
DHCanvas
DhCanvas
DhCanvas (height:int=200)
Canvas using IPython DisplayHandle for dynamic updates
cnv.show("What's up, world!<br>")cnv.add("Take me to your leader.<br>")cnv.clear()cnv.add('<b>There and Back Again</b><br>')cnv.show()There and Back Again
cnv.add('')cnv.add('\n')cnv.add('<br>')FCanvas (kernel)
HTML element with a well-known id.
display(HTML(FCanvas_stl))class FCanvas(Canvas, T.HasTraits):
height = T.Int(200).tag(sync=True)
elid = T.Unicode('').tag(sync=True)
def show(self, content=None, **kwargs):
prev_elid = self.elid
elid = new_id('brd-logger-')
s = content or ''
display(HTML(
f"<div id='{elid}' class='brd-logger' "
f"style='width: 100%; max-height: {self.height}px;'>{(s+'<br>') if not prev_elid and s else ''}</div>"),
metadata=kwargs)
time.sleep(0.25)
self.elid = elid
if prev_elid:
display(Javascript(f"""
debugger;
const prevEl = document.getElementById('{prev_elid}');
let prevHtml = prevEl?.innerHTML ?? '';
if (prevEl) prevEl.style.display = 'none';
if (prevEl) prevEl.innerHTML = '';
{self._el()}; if (el) {{ el.innerHTML = prevHtml + '{s.replace("'", "\\'")}' + '<br>';
el.scrollTop = el.scrollHeight;}}
"""))
def hide(self): display(Javascript(f"{self._el()} el.style.display = 'none'"))
def add(self, content, **kwargs):
if content is not None: display(Javascript(self._js(str(content).replace("'", "\\'"))))
def clear(self): display(Javascript(f"{self._el()} el.innerHTML = ''"))
def _el(self): return f"const el=document.getElementById('{self.elid}')"
def _js(self, s):
return f"{self._el()}; if (el) {{el.innerHTML += '{s}'; el.scrollTop = el.scrollHeight;}}"cnv = FCanvas(height=100)cnv.show()for c in 'abcdefgehijk': cnv.add(f"{c}<br>")cnv.add(f'<span class="ts">1234567890</span> <span class="msg">msg</span><br>')cnv.add("lorem ipsum dolor sit 'amet<br>")cnv.show()cnv.add({"a": 1})This is obviously ugly and unwieldy. We need a more usable canvas, one that doen’t rely in IPython display system.. The only solution in modern Jupyter envs is a widget.
FCanvas (widget)
os.environ['DEBUG_BRIDGET'] = 'True'fcanvas_esm = bundled(fcanvas_js)(debugger=DEBUG(), ts=True)FCanvas
FCanvas (height:int=200, elid:str='', **kwargs)
Main AnyWidget base class.
cleanupwidgets('cnv')
cnv = FCanvas.create(height=100, timeout=3)
test_eq(cnv.loaded(), True)cnv.show('hello')
# test_eq(cnv.displayed(), True)cnv.add(' bye<br>')cnv.clear()cnv.add('Goodbye to all that<br>')cnv.add({'a': str(Path('a'))}) # convert to json, no '<br>'cnv.add(f'<br><span class="ts">1234567890</span> <span class="msg">msg</span><br>')cnv.hide()
cnv.add('hideous!<br>')cnv.show()cnv.close()cleanupwidgets('cnv')
cnv = FCanvas.create(elid=FCanvas.new_elid())
test_eq(cnv.loaded(), True)cnv.add('Hi & Bi<br>')cnv.close()NBLogger
NoopLogger
NoopLogger ()
Logger that discards all messages
NBLogger
NBLogger ()
Notebook loggers with show/hide/log capabilities
BasicLogger
BasicLogger
BasicLogger (msg=None, canvas:__main__.Canvas|None=None, height:int=200, show:bool=True, history:bool=True, **kwargs)
Simple logger that displays messages in a scrollable div
bl = BasicLogger('BasicLogger initialized', height=100)for i,x in enumerate(range(10)): bl.log(f'test{i}')bl.msg(f'''<span style="color: red;">{'red '*100}</span>''')bl.history()[-1]'<span style="color: red;">red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red red </span>'
Wrong HTML: the message is shortened with the HTML format
bl.log('green '*100, fmt='<span style="color: green;">{s}</span><br>')bl.show()bl.active(False, 'BasicLogger disabled')False
bl.error('test')bl.show("Enabled")BasicLogger disabled
green green green green green green green green green green green green green green green green green green green green green green green gr…
red red red red red red red red red red red red red red red red red red red red red red red red red red red red re…
test9
test8
test7
test6
test5
test4
test3
test2
test1
test0
BasicLogger initialized
bl.close_canvas()
bl.show()
bl.log('closed')
test_eq(bl.active(), False)This logger is closed.
FLogger
FLogger
FLogger (msg=None, canvas:__main__.Canvas|None=None, height:int=200, show:bool=True, history:bool=True, **kwargs)
Simple logger that displays messages in a scrollable div
bl = FLogger('FLogger initialized', FCanvas.create(height=100, elid=FCanvas.new_elid(), timeout=2))for i,x in enumerate(range(5)): bl.log(f'test{i}')bl.error('red '*100, fmt=lambda s: f'<span style="color: red;">{s}</span>')bl.show()bl.active(False, 'FLogger disabled')False
bl.log('test')bl.close_canvas()
bl.log('closed')
bl.show()
test_eq(bl.active(), False)This logger is closed.
Loguru logger (WIP)
class LoguruBasicLogger(BasicLogger):
def __init__(self):
super().__init__()
self._fmt = FC.noop
def write(self, message: str) -> None:
if rec := getattr(message, 'record', None):
level = rec['level'].name
else:
for level in level_colors:
if level in message: break
# message = f"<span style='color: {level_colors[level]}'>{message}</span>"
# self.msg(message, fmt=lambda s:f"<span style='color: {level_colors[level]}'>{s}</span>")
self.msg(message, fmt=f"<span style='color: {level_colors[level]}'>""{s}</span><br>")logger.remove() # Remove default handler
handler_id = logger.add(
(lbl := LoguruBasicLogger()).write,
format="{level} | {message}", # Simple format, we'll add HTML in the sink
colorize=False # Disable ANSI colors
)WARNING | This is a warning
INFO | This is an info message
DEBUG | This is a debug message
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning")
logger.error("This is an error")class LoguruBasicLogger(BasicLogger):
def __init__(self):
super().__init__()
self.fmt = FC.noop
def _format(self, msg, fmt: Callable[[str], str]|str|None=None, truncate:bool=True, sep:str='') -> str:
rec = getattr(msg, 'record', json.loads(msg))
return (
f"<div style='display: flex; gap: 8px'>"
f"<span style='color: #888'>{rec['time'].strftime('%H:%M:%S')}</span>"
f"<span style='color: {level_colors[rec['level'].name]}'>{rec['level'].name:8}</span>"
f"<span>{shorten(rec['message'], mode='r', limit=self.max_len or 140)}</span>"
f"</div>"
)
def write(self, message:str) -> None:
self.msg(message, sep='')
# self.msg(formatted_msg)
def configure_logger(basic_logger: BasicLogger) -> int:
"""Configure loguru to use a specific BasicLogger instance."""
logger.remove()
return logger.add(
basic_logger.write, # type: ignore
serialize=True # This makes loguru pass a json to write()
)lbl = LoguruBasicLogger()
handler_id = configure_logger(lbl)logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning")
logger.error("This is an error")(my_logger := BasicLogger()).setup_loguru_sink(logger); # type: ignorelogger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning")
logger.error("This is an error")(my_flogger := FLogger()).setup_loguru_sink(logger); # type: ignorelogger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning")
logger.error("This is an error")my_flogger.close_canvas()