Logging
Plugin logger with levels, per-plugin logcat tags, tracebacks, object dumps, timers and handlers.
The log_utils module is the SDK's logger. Every plugin gets one as self.logger; it writes to logcat under the tag plugin.<your plugin id>, so you can filter your plugin's output without grepping.
Read it with:
Every entry starts with the place it was logged from:
Replaces log() and self.log()
android_utils.log() and BasePlugin.log() still work, but they are legacy: they always log at debug level under a tag you can't filter by. See Migrating from log().
Levels
| Level | Method | Use for |
|---|---|---|
LogLevel.VERBOSE | logger.verbose(...) | very chatty tracing, e.g. every hook call |
LogLevel.DEBUG | logger.debug(...) | details you need while developing |
LogLevel.INFO | logger.info(...) | normal lifecycle events |
LogLevel.WARNING | logger.warning(...) | something unexpected that the plugin survived |
LogLevel.ERROR | logger.error(...), logger.exception(...) | failures |
Levels map to Android log priorities, so adb logcat *:E shows only errors.
By default a logger shows DEBUG and above while developer mode is on (the switch on the plugins info screen, see Setup) and INFO and above otherwise. Users of your released plugin don't pay for debug logs, and you see them while developing.
Change it per logger:
Pass arguments instead of f-strings
self.logger.debug("chat %s: %s", chat_id, message) only formats the message when DEBUG is enabled. An f-string is built every time, even if nothing gets logged. This matters in hooks that run thousands of times.
Errors
Call exception() inside an except block, and the traceback is added for you:
Outside an except block, pass the exception explicitly:
Inspecting objects
dump() prints the structure of an object: Telegram TLRPC objects, other Java objects, and Python values alike.
How values are shown:
- Only fields are read. Getters, Python properties and
__getattr__never run, so dumping an object has no side effects. - Java objects are walked through their class hierarchy, without static fields. Classes from
java.*,android.*and similar platform packages are shown withtoString()instead. - Lists, maps, arrays and sparse arrays show their size and the first
max_itemsentries (default 20). - Nesting stops at
depthlevels (default 2) and is marked with{…}. An object that contains itself is marked<cycle>.
dump() is also a plain function that returns a string, so you can use it outside logging:
Child loggers
Split a large plugin into areas with their own level:
A child uses its parent's level until you set its own.
In helper modules, get a logger without passing the plugin around:
get_logger() finds which plugin is calling it from the file it is called in. In the plugin's main file it returns the same logger as self.logger, and in Elyx plugin modules it returns a child named after the module.
Measuring time
If the block raises, the entry ends with (failed) and the exception keeps propagating.
Handlers
Handlers receive every entry of a logger and of its children, after level filtering:
LogRecord fields: level, tag, message, location (file:function:line or None), traceback (str or None), created (Unix time), and text, which is the entry exactly as written to logcat.
- Handlers run synchronously on the thread that logged, so keep them fast.
- Logging from inside a handler doesn't call the handlers again.
- An exception in a handler is logged under the
pysdktag and doesn't affect your plugin. - When the plugin is unloaded, the SDK removes its handlers and resets its levels.
Helper functions and stacklevel
If you wrap the logger in your own helper, pass stacklevel=2 so the location points at the helper's caller rather than the helper:
Long messages
Logcat cuts a single entry at about 4 KB. The logger splits longer messages, such as big dumps or tracebacks, into several entries at line boundaries, so nothing gets lost.
Migrating from log()
| Before | After |
|---|---|
self.log("Loaded") | self.logger.info("Loaded") |
log(f"Value: {x}") | logger.debug("Value: %s", x) |
log(user) | logger.dump(user) |
log(f"Error: {e}\n{traceback.format_exc()}") | logger.exception("Error") |
The old functions keep working. log(obj) with a non-text value now prints the same output as dump().
If your plugin already assigns its own self.logger, that keeps working too: your value is returned instead of the SDK logger.
Reference
Logger(tag) creates a standalone logger with any tag, if you need one outside the plugin hierarchy.