From d3436d409a549eb6313f8aacfba9754e24dab6a2 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 01/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 56 ++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 976e7245bb..7d42e2ce15 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -1,3 +1,5 @@ +import logging +import logging.handlers import pkg_resources import sys import time @@ -24,6 +26,12 @@ LOG_LEVELS = {"error": LOG_ERR, "warning": LOG_WARNING, "info": LOG_INFO} +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ @@ -40,6 +48,8 @@ ENTRY_POINT_NAME = "py3status" ENTRY_POINT_KEY = "entry_point" +_logger = logging.getLogger('core') + class Runner(Thread): """ @@ -521,10 +531,30 @@ def load_modules(self, modules_list, user_modules): msg = f'Loading module "{module}" failed ({err}).' self.report_exception(msg, level="warning") + def _setup_logging(self): + """Set up the global logger.""" + root = logging.getLogger(name=None) + if self.config.get("debug"): + root.setLevel(logging.DEBUG) + else: + root.setLevel(logging.DEBUG) + + log_file = self.config.get("log_file") + if log_file: + handler = logging.FileHandler(log_file, encoding='utf8') + else: + logging.handlers.SysLogHandler() + handler.setFormatter(logging.Formatter( + fmt='%(asctime)s %(levelname)s %(module)s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + style='%')) + root.addHandler(handler) + def setup(self): """ Setup py3status and spawn i3status/events/modules threads. """ + self._setup_logging() # SIGTSTP will be received from i3bar indicating that all output should # stop and we should consider py3status suspended. It is however @@ -856,29 +886,11 @@ def notify_update(self, update, urgent=False): def log(self, msg, level="info"): """ log this information to syslog or user provided logfile. + + This is soft-deprecated; prefer using the 'logging' module directly in + new code. """ - if not self.config.get("log_file"): - # If level was given as a str then convert to actual level - level = LOG_LEVELS.get(level, level) - syslog(level, f"{msg}") - else: - # Binary mode so fs encoding setting is not an issue - with self.config["log_file"].open("ab") as f: - log_time = time.strftime("%Y-%m-%d %H:%M:%S") - # nice formatting of data structures using pretty print - if isinstance(msg, (dict, list, set, tuple)): - msg = pformat(msg) - # if multiline then start the data output on a fresh line - # to aid readability. - if "\n" in msg: - msg = "\n" + msg - out = f"{log_time} {level.upper()} {msg}\n" - try: - # Encode unicode strings to bytes - f.write(out.encode("utf-8")) - except (AttributeError, UnicodeDecodeError): - # Write any byte strings straight to log - f.write(out) + _logger.log(LOGGING_LEVELS.get(level, logging.DEBUG), msg) def create_output_modules(self): """ From 3ed7e9401c5fcd4a340468f0fd9bc226ad4d8a01 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 02/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 57 +++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 7d42e2ce15..c3eadfd9d3 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -11,7 +11,6 @@ from signal import signal, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen from threading import Event, Thread -from syslog import syslog, LOG_ERR, LOG_INFO, LOG_WARNING from traceback import extract_tb, format_tb, format_stack from py3status.command import CommandServer @@ -24,12 +23,10 @@ from py3status.profiling import profile from py3status.udev_monitor import UdevMonitor -LOG_LEVELS = {"error": LOG_ERR, "warning": LOG_WARNING, "info": LOG_INFO} - LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, } DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} @@ -48,7 +45,7 @@ ENTRY_POINT_NAME = "py3status" ENTRY_POINT_KEY = "entry_point" -_logger = logging.getLogger('core') +_logger = logging.getLogger("core") class Runner(Thread): @@ -524,8 +521,7 @@ def load_modules(self, modules_list, user_modules): # only handle modules with available methods if my_m.methods: self.modules[module] = my_m - elif self.config["debug"]: - self.log(f'ignoring module "{module}" (no methods found)') + _logger.debug('ignoring module "%s" (no methods found)', module) except Exception: err = sys.exc_info()[1] msg = f'Loading module "{module}" failed ({err}).' @@ -541,13 +537,16 @@ def _setup_logging(self): log_file = self.config.get("log_file") if log_file: - handler = logging.FileHandler(log_file, encoding='utf8') + handler = logging.FileHandler(log_file, encoding="utf8") else: logging.handlers.SysLogHandler() - handler.setFormatter(logging.Formatter( - fmt='%(asctime)s %(levelname)s %(module)s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', - style='%')) + handler.setFormatter( + logging.Formatter( + fmt="%(asctime)s %(levelname)s %(module)s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + style="%", + ) + ) root.addHandler(handler) def setup(self): @@ -590,8 +589,7 @@ def setup(self): self.log("window manager: {}".format(self.config["wm_name"])) - if self.config["debug"]: - self.log(f"py3status started with config {self.config}") + _logger.debug("py3status started with config %s", self.config) if self.config["gevent"]: self.is_gevent = self.gevent_monkey_patch_report() @@ -636,12 +634,9 @@ def setup(self): i3s_mode = "mocked" break time.sleep(0.1) - if self.config["debug"]: - self.log( - "i3status thread {} with config {}".format( - i3s_mode, self.config["py3_config"] - ) - ) + _logger.debug( + "i3status thread %s with config %s", i3s_mode, self.config["py3_config"] + ) # add i3status thread monitoring task if i3s_mode == "started": @@ -652,15 +647,13 @@ def setup(self): self.events_thread = Events(self) self.events_thread.daemon = True self.events_thread.start() - if self.config["debug"]: - self.log("events thread started") + _logger.debug("events thread started") # initialise the command server self.commands_thread = CommandServer(self) self.commands_thread.daemon = True self.commands_thread.start() - if self.config["debug"]: - self.log("commands thread started") + _logger.debug("commands thread started") # initialize the udev monitor (lazy) self.udev_monitor = UdevMonitor(self) @@ -676,8 +669,7 @@ def setup(self): # get a dict of all user provided modules self.log("modules include paths: {}".format(self.config["include_paths"])) user_modules = self.get_user_configured_modules() - if self.config["debug"]: - self.log(f"user_modules={user_modules}") + _logger.debug("user_modules=%s", user_modules) if self.py3_modules: # load and spawn i3status.conf configured modules threads @@ -775,8 +767,7 @@ def stop(self): try: self.lock.set() - if self.config["debug"]: - self.log("lock set, exiting") + _logger.debug("lock set, exiting") # run kill() method on all py3status modules for module in self.modules.values(): module.kill() @@ -807,12 +798,10 @@ def refresh_modules(self, module_string=None, exact=True): or (not exact and name.startswith(module_string)) ): if module["type"] == "py3status": - if self.config["debug"]: - self.log(f"refresh py3status module {name}") + _logger.debug("refresh py3status module %s", name) module["module"].force_update() else: - if self.config["debug"]: - self.log(f"refresh i3status module {name}") + _logger.debug("refresh i3status module %s", name) update_i3status = True if update_i3status: self.i3status_thread.refresh_i3status() From 17d826db672420f71c906e5b4d1d85784c2a762e Mon Sep 17 00:00:00 2001 From: Valentin Weber Date: Fri, 27 Aug 2021 14:24:42 +0200 Subject: [PATCH 03/66] new sway_idle module: to display the idle state of sway wm (#2058) --- py3status/modules/sway_idle.py | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100755 py3status/modules/sway_idle.py diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py new file mode 100755 index 0000000000..1a88df7554 --- /dev/null +++ b/py3status/modules/sway_idle.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" +Display sway inhibit idle status. + +This Module shows an indicator, if an idle is inhibited by an inhibitor. +For more information about inhibit idle see `man 5 sway` + +Configuration parameters: + cache_timeout: How often we refresh this module in seconds (default 1) + format: Display format (default 'Inhibit Idle: {inhibit_idle}') + +Format placeholders: + {inhibit_idle} Returns 'True' if idle is inhibited, 'False' else. + +Example: + +``` +sway_idle { + format = "Inhibit Idle: [\?if=inhibit_idle=True True]|False" +} +``` + +@author Valentin Weber +@license BSD + +SAMPLE OUTPUT +{full_text': 'Inhibit Idle: True'} +""" + + +class Py3status: + + # available configuration parameters + cache_timeout = 1 + format = "Inhibit Idle: {inhibit_idle}" + + def sway_idle(self): + sway_tree = self.py3.command_output(self.py3.get_wm_msg() + " -t get_tree") + inhibit_idle = '"inhibit_idle": true' in sway_tree + return { + "full_text": self.py3.safe_format( + self.format, param_dict={"inhibit_idle": inhibit_idle} + ), + "cached_until": self.py3.time_in(self.cache_timeout), + } + + +if __name__ == "__main__": + """ + Run module in test mode. + """ + + from py3status.module_test import module_test + + module_test(Py3status) From e085d93a9c6293e1a724cfca410b3ccc414ff0a1 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Fri, 27 Aug 2021 14:25:51 +0200 Subject: [PATCH 04/66] sway_idle module: chmod -x and remove useless encoding shebang --- py3status/modules/sway_idle.py | 1 - 1 file changed, 1 deletion(-) mode change 100755 => 100644 py3status/modules/sway_idle.py diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py old mode 100755 new mode 100644 index 1a88df7554..68787919a8 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Display sway inhibit idle status. From 56cf8b0fc6400e6a6ea1e259e38f96db44d17952 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Fri, 27 Aug 2021 15:26:13 +0200 Subject: [PATCH 05/66] speedtest module: fix error when clicking too fast, closes #2060 --- py3status/modules/speedtest.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/py3status/modules/speedtest.py b/py3status/modules/speedtest.py index ec47e12c97..baeca41138 100644 --- a/py3status/modules/speedtest.py +++ b/py3status/modules/speedtest.py @@ -219,6 +219,7 @@ def _set_speedtest_data(self): # end self.speedtest_data["elapsed"] = False self.speedtest_data["elapsed_time"] = time.perf_counter() - self.start_time + self.thread = None def speedtest(self): if self.speedtest_data.get("elapsed"): @@ -245,8 +246,6 @@ def on_click(self, event): if share: self.py3.command_run(f"xdg-open {share}") if button == self.button_refresh: - if self.thread and not self.thread.isAlive(): - self.thread = None if self.thread is None: self.thread = Thread(target=self._set_speedtest_data) self.thread.daemon = True From 355c006256a3471bd54ef79a39885186ee0562e1 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Fri, 27 Aug 2021 15:37:10 +0200 Subject: [PATCH 06/66] clock module: compatible with tzlocal 3.0, thx to @flyingapfopenguin closes #2061 --- py3status/modules/clock.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/py3status/modules/clock.py b/py3status/modules/clock.py index e0bdc19f7c..d7f8eec0b1 100644 --- a/py3status/modules/clock.py +++ b/py3status/modules/clock.py @@ -265,7 +265,12 @@ def clock(self): idx = int(h / self.block_hours * len(self.blocks)) icon = self.blocks[idx] - timezone = zone.zone + try: + # tzlocal < 3.0 + timezone = zone.zone + except AttributeError: + # tzlocal >= 3.0 + timezone = zone.key tzname = timezone.split("/")[-1].replace("_", " ") if self.multiple_tz: From e813cb9277f29fe50a4076025373a50b54fb1296 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Mon, 30 Aug 2021 10:08:52 +0200 Subject: [PATCH 07/66] version 3.39 --- CHANGELOG | 10 ++++++++++ py3status/version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4450fc6f95..dbf932455e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +version 3.39 (2021-08-30) +* documentation was refactored to use MkDocs, lots of work has been done +* add a makefile to ease tasks +* create and split project, documentation and tests requirements +* tests: update tox and module tests to match mkdocs md format +* new sway_idle module: to display the idle state of sway wm (#2058), by Valentin Weber +* clock module: compatible with tzlocal 3.0, thx to @flyingapfopenguin +* speedtest module: fix error when clicking too fast, closes #2060 +* weather_owm module: _format_sunrise and _format_sunset as aware datetime (#2055), by msk + version 3.38 (2021-06-29) * update IRC doc to OFTC * arch_updates module: add paru support (#2052), by Andreas Schleifer diff --git a/py3status/version.py b/py3status/version.py index f7d2327b8d..cac284d3aa 100644 --- a/py3status/version.py +++ b/py3status/version.py @@ -1 +1 @@ -version = "3.38" +version = "3.39" From 3a64b4230d9f34d60054ac768adca74932581900 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Mon, 30 Aug 2021 10:19:18 +0200 Subject: [PATCH 08/66] make qa run tox --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f22003dac0..f6fd881b4e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ qa: - isort --profile black . && black . && flake8 + tox clean: rm -rf dist From 07fe1b839438c984426bba06c5a50e63780a4275 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:00:21 +0200 Subject: [PATCH 09/66] README: update readthedocs links to new documentation thx to @oceyral closes #2062 --- README.md | 27 ++++++++++++++------------- py3status/modules/README.md | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8c76e1d835..538f45cecf 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Using py3status, you can take control of your i3bar easily by: - using one of the available - [modules](https://py3status.readthedocs.io/en/latest/modules.html) + [modules](https://py3status.readthedocs.io/en/latest/user-guide/modules/) shipped with py3status - grouping multiple modules and automatically or manually cycle their display @@ -40,18 +40,19 @@ You will love py3status if you're using [i3wm](https://i3wm.org) - **easily allow interactivity** with the i3bar - add some **built-in enhancement/transformation** of basic i3status modules output -We apply the [Zen of py3status](https://py3status.readthedocs.io/en/latest/contributing.html#zen-of-py3status) to improve this project and encourage everyone to read it! +We apply the [Zen of py3status](https://py3status.readthedocs.io/en/latest/dev-guide/contributing/#zen-of-py3status) to improve this project and encourage everyone to read it! ## Documentation Up-to-date [documentation](https://py3status.readthedocs.io): -- [Installation](https://py3status.readthedocs.io/en/latest/intro.html#installation) -- [Using modules](https://py3status.readthedocs.io/en/latest/configuration.html) -- [Custom click events](https://py3status.readthedocs.io/en/latest/configuration.html#custom-click-events) -- [Writing custom modules](https://py3status.readthedocs.io/en/latest/writing_modules.html) -- [Contributing](https://py3status.readthedocs.io/en/latest/contributing.html) -- [The py3-cmd command line](https://py3status.readthedocs.io/en/latest/py3-cmd.html) +- [Installation](https://py3status.readthedocs.io/en/latest/user-guide/installation/) +- [Getting started](https://py3status.readthedocs.io/en/latest/getting-started/) +- [Using Modules](https://py3status.readthedocs.io/en/latest/user-guide/configuration/#loading-and-ordering-py3status-modules) +- [Custom click events](https://py3status.readthedocs.io/en/latest/user-guide/configuration/#custom-click-events) +- [The py3-cmd command line](https://py3status.readthedocs.io/en/latest/user-guide/remote-control/) +- [Writing custom modules](https://py3status.readthedocs.io/en/latest/dev-guide/writing-modules/) +- [Contributing](https://py3status.readthedocs.io/en/latest/dev-guide/contributing/) Get help or share your ideas on IRC: @@ -59,7 +60,7 @@ Get help or share your ideas on IRC: ## Installation -See the up to date and complete [installation instructions](https://py3status.readthedocs.io/en/latest/intro.html#installation) for your favorite distribution. +See the up to date and complete [installation instructions](https://py3status.readthedocs.io/en/latest/user-guide/installation/) for your favorite distribution. ## Usage @@ -102,10 +103,10 @@ You can see the help of py3status by issuing \`py3status -h\`: ## Available modules -[All modules](https://py3status.readthedocs.io/en/latest/modules.html) -shipped with py3status are [configurable directly from your current i3status.conf](https://py3status.readthedocs.io/en/latest/configuration.html#using-modules)! +[All modules](https://py3status.readthedocs.io/en/latest/user-guide/modules/) +shipped with py3status are [configurable directly from your current i3status.conf](https://py3status.readthedocs.io/en/latest/user-guide/configuration/#loading-and-ordering-py3status-modules)! -[Check them out](https://py3status.readthedocs.io/en/latest/modules.html) +[Check them out](https://py3status.readthedocs.io/en/latest/user-guide/modules/) to see all the configuration options. ## Control from CLI @@ -117,7 +118,7 @@ signal to i3status: killall -USR1 py3status To refresh individual modules, the -[py3-cmd](http://py3status.readthedocs.io/en/latest/py3-cmd.html) +[py3-cmd](https://py3status.readthedocs.io/en/latest/user-guide/remote-control/) utility can be used, e.g: py3-cmd refresh wifi diff --git a/py3status/modules/README.md b/py3status/modules/README.md index bf712b2f4b..656e3ee1fd 100644 --- a/py3status/modules/README.md +++ b/py3status/modules/README.md @@ -1,3 +1,3 @@ # Modules documentation -Up to date documentation: https://py3status.readthedocs.io/en/latest/modules.html +Up to date modules documentation: https://py3status.readthedocs.io/en/latest/user-guide/modules From 96dbe5bba63521c48a6f63a22923cbcccf0d0407 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:04:14 +0200 Subject: [PATCH 10/66] i3 contrib page: update readthedocs links to new documentation --- contrib/py3status.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/py3status.html b/contrib/py3status.html index d8d7d47f8a..b95914dac0 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -85,7 +85,7 @@

Using py3status

Learn more about how to configure py3status on the - + configuration documentation

@@ -93,7 +93,7 @@

Using py3status modules

Like i3status, py3status comes with modules (but a lot of them!).

Learn more about the modules provided by py3status on the -modules +modules documentation

@@ -246,7 +246,7 @@

Writing new modules for py3status

Learn how to -write +write (and contribute please!) your own modules!

From 9a9ee14034c4a191a6f857f530c710586d245293 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:25:49 +0200 Subject: [PATCH 11/66] i3 contrib page: update to sync with i3.github.io --- contrib/py3status.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/py3status.html b/contrib/py3status.html index b95914dac0..8795d7edc3 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -47,7 +47,7 @@

Documentation

Read the Docs
  • - Github + Github
  • @@ -85,7 +85,7 @@

    Using py3status

    Learn more about how to configure py3status on the - + configuration documentation

    @@ -93,7 +93,7 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the -modules +modules documentation

    From 60cde6985c2f16e788c6d41fc81deb0f4d01cb2f Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 12/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 56 ++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 976e7245bb..7d42e2ce15 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -1,3 +1,5 @@ +import logging +import logging.handlers import pkg_resources import sys import time @@ -24,6 +26,12 @@ LOG_LEVELS = {"error": LOG_ERR, "warning": LOG_WARNING, "info": LOG_INFO} +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ @@ -40,6 +48,8 @@ ENTRY_POINT_NAME = "py3status" ENTRY_POINT_KEY = "entry_point" +_logger = logging.getLogger('core') + class Runner(Thread): """ @@ -521,10 +531,30 @@ def load_modules(self, modules_list, user_modules): msg = f'Loading module "{module}" failed ({err}).' self.report_exception(msg, level="warning") + def _setup_logging(self): + """Set up the global logger.""" + root = logging.getLogger(name=None) + if self.config.get("debug"): + root.setLevel(logging.DEBUG) + else: + root.setLevel(logging.DEBUG) + + log_file = self.config.get("log_file") + if log_file: + handler = logging.FileHandler(log_file, encoding='utf8') + else: + logging.handlers.SysLogHandler() + handler.setFormatter(logging.Formatter( + fmt='%(asctime)s %(levelname)s %(module)s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + style='%')) + root.addHandler(handler) + def setup(self): """ Setup py3status and spawn i3status/events/modules threads. """ + self._setup_logging() # SIGTSTP will be received from i3bar indicating that all output should # stop and we should consider py3status suspended. It is however @@ -856,29 +886,11 @@ def notify_update(self, update, urgent=False): def log(self, msg, level="info"): """ log this information to syslog or user provided logfile. + + This is soft-deprecated; prefer using the 'logging' module directly in + new code. """ - if not self.config.get("log_file"): - # If level was given as a str then convert to actual level - level = LOG_LEVELS.get(level, level) - syslog(level, f"{msg}") - else: - # Binary mode so fs encoding setting is not an issue - with self.config["log_file"].open("ab") as f: - log_time = time.strftime("%Y-%m-%d %H:%M:%S") - # nice formatting of data structures using pretty print - if isinstance(msg, (dict, list, set, tuple)): - msg = pformat(msg) - # if multiline then start the data output on a fresh line - # to aid readability. - if "\n" in msg: - msg = "\n" + msg - out = f"{log_time} {level.upper()} {msg}\n" - try: - # Encode unicode strings to bytes - f.write(out.encode("utf-8")) - except (AttributeError, UnicodeDecodeError): - # Write any byte strings straight to log - f.write(out) + _logger.log(LOGGING_LEVELS.get(level, logging.DEBUG), msg) def create_output_modules(self): """ From b4f49e2bbad11e004d976459a05b8764eb937ccd Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 13/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 57 +++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 7d42e2ce15..c3eadfd9d3 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -11,7 +11,6 @@ from signal import signal, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen from threading import Event, Thread -from syslog import syslog, LOG_ERR, LOG_INFO, LOG_WARNING from traceback import extract_tb, format_tb, format_stack from py3status.command import CommandServer @@ -24,12 +23,10 @@ from py3status.profiling import profile from py3status.udev_monitor import UdevMonitor -LOG_LEVELS = {"error": LOG_ERR, "warning": LOG_WARNING, "info": LOG_INFO} - LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, } DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} @@ -48,7 +45,7 @@ ENTRY_POINT_NAME = "py3status" ENTRY_POINT_KEY = "entry_point" -_logger = logging.getLogger('core') +_logger = logging.getLogger("core") class Runner(Thread): @@ -524,8 +521,7 @@ def load_modules(self, modules_list, user_modules): # only handle modules with available methods if my_m.methods: self.modules[module] = my_m - elif self.config["debug"]: - self.log(f'ignoring module "{module}" (no methods found)') + _logger.debug('ignoring module "%s" (no methods found)', module) except Exception: err = sys.exc_info()[1] msg = f'Loading module "{module}" failed ({err}).' @@ -541,13 +537,16 @@ def _setup_logging(self): log_file = self.config.get("log_file") if log_file: - handler = logging.FileHandler(log_file, encoding='utf8') + handler = logging.FileHandler(log_file, encoding="utf8") else: logging.handlers.SysLogHandler() - handler.setFormatter(logging.Formatter( - fmt='%(asctime)s %(levelname)s %(module)s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', - style='%')) + handler.setFormatter( + logging.Formatter( + fmt="%(asctime)s %(levelname)s %(module)s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + style="%", + ) + ) root.addHandler(handler) def setup(self): @@ -590,8 +589,7 @@ def setup(self): self.log("window manager: {}".format(self.config["wm_name"])) - if self.config["debug"]: - self.log(f"py3status started with config {self.config}") + _logger.debug("py3status started with config %s", self.config) if self.config["gevent"]: self.is_gevent = self.gevent_monkey_patch_report() @@ -636,12 +634,9 @@ def setup(self): i3s_mode = "mocked" break time.sleep(0.1) - if self.config["debug"]: - self.log( - "i3status thread {} with config {}".format( - i3s_mode, self.config["py3_config"] - ) - ) + _logger.debug( + "i3status thread %s with config %s", i3s_mode, self.config["py3_config"] + ) # add i3status thread monitoring task if i3s_mode == "started": @@ -652,15 +647,13 @@ def setup(self): self.events_thread = Events(self) self.events_thread.daemon = True self.events_thread.start() - if self.config["debug"]: - self.log("events thread started") + _logger.debug("events thread started") # initialise the command server self.commands_thread = CommandServer(self) self.commands_thread.daemon = True self.commands_thread.start() - if self.config["debug"]: - self.log("commands thread started") + _logger.debug("commands thread started") # initialize the udev monitor (lazy) self.udev_monitor = UdevMonitor(self) @@ -676,8 +669,7 @@ def setup(self): # get a dict of all user provided modules self.log("modules include paths: {}".format(self.config["include_paths"])) user_modules = self.get_user_configured_modules() - if self.config["debug"]: - self.log(f"user_modules={user_modules}") + _logger.debug("user_modules=%s", user_modules) if self.py3_modules: # load and spawn i3status.conf configured modules threads @@ -775,8 +767,7 @@ def stop(self): try: self.lock.set() - if self.config["debug"]: - self.log("lock set, exiting") + _logger.debug("lock set, exiting") # run kill() method on all py3status modules for module in self.modules.values(): module.kill() @@ -807,12 +798,10 @@ def refresh_modules(self, module_string=None, exact=True): or (not exact and name.startswith(module_string)) ): if module["type"] == "py3status": - if self.config["debug"]: - self.log(f"refresh py3status module {name}") + _logger.debug("refresh py3status module %s", name) module["module"].force_update() else: - if self.config["debug"]: - self.log(f"refresh i3status module {name}") + _logger.debug("refresh i3status module %s", name) update_i3status = True if update_i3status: self.i3status_thread.refresh_i3status() From 25184cb7d584e0efc8185ec48185f73708fbdc5a Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:36:08 -0300 Subject: [PATCH 14/66] Remove unused import. --- py3status/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index c3eadfd9d3..0b27bc4848 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -7,7 +7,6 @@ from collections import deque from json import dumps from pathlib import Path -from pprint import pformat from signal import signal, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen from threading import Event, Thread From c8421def8bc20f9f6415d2a97d5b8c188015701e Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:39:35 -0300 Subject: [PATCH 15/66] Fix oversight: Make "info" be the default logging threshold. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 0b27bc4848..387f324a9e 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -532,7 +532,7 @@ def _setup_logging(self): if self.config.get("debug"): root.setLevel(logging.DEBUG) else: - root.setLevel(logging.DEBUG) + root.setLevel(logging.INFO) log_file = self.config.get("log_file") if log_file: From 6a7229b88a856bbbff437b58720d90658614c671 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:41:53 -0300 Subject: [PATCH 16/66] Fix syslog setup. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 387f324a9e..7717192ff2 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -538,7 +538,7 @@ def _setup_logging(self): if log_file: handler = logging.FileHandler(log_file, encoding="utf8") else: - logging.handlers.SysLogHandler() + handler = logging.handlers.SysLogHandler() handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From 88361de1e5a23735ab09a63405dceb4bb48da419 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 17/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py3status/core.py b/py3status/core.py index c3eadfd9d3..a91d9ec54c 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -29,6 +29,12 @@ "info": logging.INFO, } +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From ac9140ca7538a3dbe37bf7fd79f5acbafc17cf10 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 18/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index a91d9ec54c..c3eadfd9d3 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -29,12 +29,6 @@ "info": logging.INFO, } -LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, -} - DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From 0924f4aa8363bd7f6adaa3c3e75e1ccce1b247c6 Mon Sep 17 00:00:00 2001 From: Valentin Weber Date: Fri, 27 Aug 2021 14:24:42 +0200 Subject: [PATCH 19/66] new sway_idle module: to display the idle state of sway wm (#2058) --- py3status/modules/sway_idle.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py index 68787919a8..0355127aac 100644 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,3 +1,7 @@ +<<<<<<< HEAD +======= +# -*- coding: utf-8 -*- +>>>>>>> 17d826db (new sway_idle module: to display the idle state of sway wm (#2058)) """ Display sway inhibit idle status. From eeb38b0b1237f2d6b7897b96f22da2eb7cd25daa Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Fri, 27 Aug 2021 14:25:51 +0200 Subject: [PATCH 20/66] sway_idle module: chmod -x and remove useless encoding shebang --- py3status/modules/sway_idle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py index 0355127aac..8d2e5154b6 100644 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,7 +1,10 @@ <<<<<<< HEAD +<<<<<<< HEAD ======= # -*- coding: utf-8 -*- >>>>>>> 17d826db (new sway_idle module: to display the idle state of sway wm (#2058)) +======= +>>>>>>> e085d93a (sway_idle module: chmod -x and remove useless encoding shebang) """ Display sway inhibit idle status. From 1bf1315f7d87848762fe8faedb208bcee4322e21 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:04:14 +0200 Subject: [PATCH 21/66] i3 contrib page: update readthedocs links to new documentation --- contrib/py3status.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/py3status.html b/contrib/py3status.html index 8795d7edc3..2d4fd9b945 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -85,7 +85,11 @@

    Using py3status

    Learn more about how to configure py3status on the +<<<<<<< HEAD +======= + +>>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) configuration documentation

    @@ -93,7 +97,11 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the +<<<<<<< HEAD modules +======= +modules +>>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) documentation

    From 084d92aa6ca6f208c2bf6379b6afef8e0b511077 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:25:49 +0200 Subject: [PATCH 22/66] i3 contrib page: update to sync with i3.github.io --- contrib/py3status.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/py3status.html b/contrib/py3status.html index 2d4fd9b945..b7adac53fd 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -86,10 +86,14 @@

    Using py3status

    Learn more about how to configure py3status on the <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) +======= + +>>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) configuration documentation

    @@ -98,10 +102,14 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the <<<<<<< HEAD +<<<<<<< HEAD modules ======= modules >>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) +======= +modules +>>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) documentation

    From fc6ab26b4913a43cf7de725a40afc1f41a283c41 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:36:08 -0300 Subject: [PATCH 23/66] Remove unused import. --- py3status/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index c3eadfd9d3..0b27bc4848 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -7,7 +7,6 @@ from collections import deque from json import dumps from pathlib import Path -from pprint import pformat from signal import signal, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen from threading import Event, Thread From 06f128d6e7d4c17fd9cfc29d1ca490798c0cc933 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:39:35 -0300 Subject: [PATCH 24/66] Fix oversight: Make "info" be the default logging threshold. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 0b27bc4848..387f324a9e 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -532,7 +532,7 @@ def _setup_logging(self): if self.config.get("debug"): root.setLevel(logging.DEBUG) else: - root.setLevel(logging.DEBUG) + root.setLevel(logging.INFO) log_file = self.config.get("log_file") if log_file: From 480c87e00cda7016769055e0c1a78467e4a50f42 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:41:53 -0300 Subject: [PATCH 25/66] Fix syslog setup. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 387f324a9e..7717192ff2 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -538,7 +538,7 @@ def _setup_logging(self): if log_file: handler = logging.FileHandler(log_file, encoding="utf8") else: - logging.handlers.SysLogHandler() + handler = logging.handlers.SysLogHandler() handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From 3963f25471f6a8a40823d09f40e570eb9a9b2e54 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 20:41:57 -0300 Subject: [PATCH 26/66] Allow configuring logging using a JSON file. The JSON file will take over the entire configuration; it must fully specify logging as defined in https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema --- py3status/argparsers.py | 42 ++++++++++++++++++++++++++--------------- py3status/core.py | 22 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/py3status/argparsers.py b/py3status/argparsers.py index 95c5158d09..3e55a84825 100644 --- a/py3status/argparsers.py +++ b/py3status/argparsers.py @@ -82,12 +82,6 @@ def _format_action_invocation(self, action): metavar="FILE", type=Path, ) - parser.add_argument( - "-d", - "--debug", - action="store_true", - help="enable debug logging in syslog or log file if --log-file option is passed", - ) parser.add_argument( "-g", "--gevent", @@ -104,15 +98,6 @@ def _format_action_invocation(self, action): metavar="PATH", type=Path, ) - parser.add_argument( - "-l", - "--log-file", - action="store", - dest="log_file", - help="enable logging to FILE (this option is not set by default)", - metavar="FILE", - type=Path, - ) parser.add_argument( "-s", "--standalone", @@ -164,6 +149,33 @@ def _format_action_invocation(self, action): help="specify window manager i3 or sway", ) + logging_args = parser.add_argument_group() + logging_args.add_argument( + "-d", + "--debug", + action="store_true", + help="enable debug logging in syslog or log file if --log-file option is passed", + ) + logging_args.add_argument( + "-l", + "--log-file", + action="store", + dest="log_file", + help="enable logging to FILE (this option is not set by default)", + metavar="FILE", + type=Path, + ) + logging_args.add_argument( + "--log-config", + action="store", + dest="log_config", + help="path to a file that fully configures the 'logging' module. This " + "must contain a JSON dictionary in the format expected by " + "logging.config.dictConfig.", + metavar="FILE", + type=Path, + ) + # deprecations parser.add_argument("-n", "--interval", help=argparse.SUPPRESS) diff --git a/py3status/core.py b/py3status/core.py index 7717192ff2..7107dcac14 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -528,6 +528,28 @@ def load_modules(self, modules_list, user_modules): def _setup_logging(self): """Set up the global logger.""" + log_config = self.config.get("log_config") + if log_config: + if self.config.get("debug"): + self.report_exception("--debug is invalid when --log-config is passed") + if self.config.get("log_file"): + self.report_exception( + "--log-file is invalid when --log-config is passed" + ) + import json + + with log_config.open() as f: + import logging.config + + try: + config_dict = json.load(f, strict=False) + logging.config.dictConfig(config_dict) + except json.JSONDecodeError as e: + self.report_exception(str(e)) + # Nothing else to do. All logging config is provided by the config + # dictionary. + return + root = logging.getLogger(name=None) if self.config.get("debug"): root.setLevel(logging.DEBUG) From b0c83249f4999997009d0068224255ffcbadfe7c Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 20:54:35 -0300 Subject: [PATCH 27/66] Add documentation explaining how to configure logging while testing modules. --- docs/dev-guide/writing-modules.md | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/dev-guide/writing-modules.md b/docs/dev-guide/writing-modules.md index 53591b3376..2dc2654f2c 100644 --- a/docs/dev-guide/writing-modules.md +++ b/docs/dev-guide/writing-modules.md @@ -811,6 +811,49 @@ Loadavg 1.41 1.61 1.82 Loadavg 1.41 1.61 1.82 ^C ``` + +## Logging + +Modules are encouraged to use Python's standard +[`logging`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) +module for debugging. By default, logs will be written to +[`syslog`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) +with a minimum level of `INFO`. + +Several existing modules will write to logs, with varying levels of details. +Therefore, when debugging a specific module, it may be useful to show only the +one you're interested in. This can be done by using the `--log-config` flag to +pass a JSON file that configures logging. This file must be in the format +specified in +[`logging`'s configuration schema](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema). +For example, to show only logs from your module in a `DEBUG` level, while +keeping all others at `WARNING`, you can use: + +```json +{ + "version": 1, + "handlers": { + "file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": "/tmp/py3status_log.log", + "maxBytes": 2048, + "formatter": "default" + } + }, + "formatters": { + "default": { + "validate": true, + "format":"%(asctime)s %(levelname)s %(module)s %(message)s", + "datefmt":"%Y-%m-%d %H:%M:%S" + } + }, + "loggers": { + "root": {"handlers": ["file"], "level": "WARNING"}, + "": {"level": "DEBUG"} + } +} +``` + ## Publishing custom modules on PyPI You can share your custom modules and make them available for py3status From 255c337344c8e0cec371e674766f90c5101ad2b9 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 21:04:42 -0300 Subject: [PATCH 28/66] Fix bad merge conflicts. --- contrib/py3status.html | 16 ---------------- py3status/modules/sway_idle.py | 7 ------- 2 files changed, 23 deletions(-) diff --git a/contrib/py3status.html b/contrib/py3status.html index b7adac53fd..91242f9588 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -85,15 +85,7 @@

    Using py3status

    Learn more about how to configure py3status on the -<<<<<<< HEAD -<<<<<<< HEAD -======= - ->>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) -======= - ->>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) configuration documentation

    @@ -101,15 +93,7 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the -<<<<<<< HEAD -<<<<<<< HEAD -modules -======= modules ->>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) -======= -modules ->>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) documentation

    diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py index 8d2e5154b6..68787919a8 100644 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,10 +1,3 @@ -<<<<<<< HEAD -<<<<<<< HEAD -======= -# -*- coding: utf-8 -*- ->>>>>>> 17d826db (new sway_idle module: to display the idle state of sway wm (#2058)) -======= ->>>>>>> e085d93a (sway_idle module: chmod -x and remove useless encoding shebang) """ Display sway inhibit idle status. From e5a807732cf8fa80dd07904d34426c29d61d81ee Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 21:09:33 -0300 Subject: [PATCH 29/66] Fix lint error. --- py3status/core.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 7107dcac14..84d38d3989 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -5,7 +5,7 @@ import time from collections import deque -from json import dumps +from json import load, dumps, JSONDecodeError from pathlib import Path from signal import signal, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen @@ -536,15 +536,14 @@ def _setup_logging(self): self.report_exception( "--log-file is invalid when --log-config is passed" ) - import json with log_config.open() as f: import logging.config try: - config_dict = json.load(f, strict=False) + config_dict = load(f, strict=False) logging.config.dictConfig(config_dict) - except json.JSONDecodeError as e: + except JSONDecodeError as e: self.report_exception(str(e)) # Nothing else to do. All logging config is provided by the config # dictionary. From a86242e43a96be04acc0fb9ae50c97bb9e713c60 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 21:11:39 -0300 Subject: [PATCH 30/66] Fix another merge artifact. --- contrib/py3status.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/py3status.html b/contrib/py3status.html index 91242f9588..8795d7edc3 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -93,7 +93,7 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the -modules +modules documentation

    From ff34d7b1290b7e444bcdd2d9bcf9a4d91d247fa4 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 31/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py3status/core.py b/py3status/core.py index 7107dcac14..876e3621cb 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -28,6 +28,12 @@ "info": logging.INFO, } +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From c7b5ac053cc98dd6948613d829b9af1c32fc17dd Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 32/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 876e3621cb..7107dcac14 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -28,12 +28,6 @@ "info": logging.INFO, } -LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, -} - DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From 71064857ccbd9f4202e9e522fb79043db073fea1 Mon Sep 17 00:00:00 2001 From: Valentin Weber Date: Fri, 27 Aug 2021 14:24:42 +0200 Subject: [PATCH 33/66] new sway_idle module: to display the idle state of sway wm (#2058) --- py3status/modules/sway_idle.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py index 8d2e5154b6..68787919a8 100644 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,10 +1,3 @@ -<<<<<<< HEAD -<<<<<<< HEAD -======= -# -*- coding: utf-8 -*- ->>>>>>> 17d826db (new sway_idle module: to display the idle state of sway wm (#2058)) -======= ->>>>>>> e085d93a (sway_idle module: chmod -x and remove useless encoding shebang) """ Display sway inhibit idle status. From fa13e22928020026b368345aa982da277de916c6 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:04:14 +0200 Subject: [PATCH 34/66] i3 contrib page: update readthedocs links to new documentation --- contrib/py3status.html | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/contrib/py3status.html b/contrib/py3status.html index b7adac53fd..8795d7edc3 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -85,15 +85,7 @@

    Using py3status

    Learn more about how to configure py3status on the -<<<<<<< HEAD -<<<<<<< HEAD -======= - ->>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) -======= - ->>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) configuration documentation

    @@ -101,15 +93,7 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the -<<<<<<< HEAD -<<<<<<< HEAD -modules -======= -modules ->>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) -======= modules ->>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) documentation

    From 0bbfa70e1b0e5970154794fd75e05c9918a43c6a Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 21:09:33 -0300 Subject: [PATCH 35/66] Fix lint error. --- py3status/core.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 7107dcac14..84d38d3989 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -5,7 +5,7 @@ import time from collections import deque -from json import dumps +from json import load, dumps, JSONDecodeError from pathlib import Path from signal import signal, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen @@ -536,15 +536,14 @@ def _setup_logging(self): self.report_exception( "--log-file is invalid when --log-config is passed" ) - import json with log_config.open() as f: import logging.config try: - config_dict = json.load(f, strict=False) + config_dict = load(f, strict=False) logging.config.dictConfig(config_dict) - except json.JSONDecodeError as e: + except JSONDecodeError as e: self.report_exception(str(e)) # Nothing else to do. All logging config is provided by the config # dictionary. From d8ab680d7c1ec61bc8d89c72ac048db5a34036ea Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 5 Oct 2021 17:48:03 -0300 Subject: [PATCH 36/66] Fix syslog handler, and eagerly import module. --- py3status/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 84d38d3989..c85c245e2e 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -1,5 +1,6 @@ import logging import logging.handlers +import logging.config import pkg_resources import sys import time @@ -538,8 +539,6 @@ def _setup_logging(self): ) with log_config.open() as f: - import logging.config - try: config_dict = load(f, strict=False) logging.config.dictConfig(config_dict) @@ -559,7 +558,8 @@ def _setup_logging(self): if log_file: handler = logging.FileHandler(log_file, encoding="utf8") else: - handler = logging.handlers.SysLogHandler() + # https://stackoverflow.com/a/3969772/340862 + handler = logging.handlers.SysLogHandler(address='/dev/log') handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From 1df8341e674f85a38e89f22f963433d93d4cfcb3 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Fri, 8 Oct 2021 09:29:20 -0300 Subject: [PATCH 37/66] Run black formatter. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index c85c245e2e..b4d0d5c914 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -559,7 +559,7 @@ def _setup_logging(self): handler = logging.FileHandler(log_file, encoding="utf8") else: # https://stackoverflow.com/a/3969772/340862 - handler = logging.handlers.SysLogHandler(address='/dev/log') + handler = logging.handlers.SysLogHandler(address="/dev/log") handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From e83a67ad54c3ee734763e4409e3052b57d3ae67c Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Thu, 16 Dec 2021 21:09:41 -0300 Subject: [PATCH 38/66] Makes 'disable_existing_loggers=False' the default. The Python library defaults to True, but that is a confusing behavior since it disables the "core" logger, which was created before logging was set up. Users can still set 'disable_existing_loggers' explicitly in their configs. Since most existing modules (who are using self.py3.log) will be logged by "core", that means that most logs would be suppressed. --- py3status/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py3status/core.py b/py3status/core.py index b4d0d5c914..b184ddff99 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -541,6 +541,7 @@ def _setup_logging(self): with log_config.open() as f: try: config_dict = load(f, strict=False) + config_dict.setdefault('disable_existing_loggers', False) logging.config.dictConfig(config_dict) except JSONDecodeError as e: self.report_exception(str(e)) From d04ab0d6a57ce4a76a5036f74c9579ea1fa32baa Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Mon, 3 Jan 2022 12:41:40 -0300 Subject: [PATCH 39/66] Run formatter. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 1702e69882..d98bb6932b 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -542,7 +542,7 @@ def _setup_logging(self): with log_config.open() as f: try: config_dict = load(f, strict=False) - config_dict.setdefault('disable_existing_loggers', False) + config_dict.setdefault("disable_existing_loggers", False) logging.config.dictConfig(config_dict) except JSONDecodeError as e: self.report_exception(str(e)) From 5023536701169f9ef421e217aca7719b56110ff9 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Wed, 5 Jan 2022 19:01:08 -0300 Subject: [PATCH 40/66] Port the xrandr module to use the builtin logging library. Also, use it as the logging example in the docs. --- docs/dev-guide/writing-modules.md | 11 ++++++++--- py3status/modules/xrandr.py | 15 +++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/dev-guide/writing-modules.md b/docs/dev-guide/writing-modules.md index 2dc2654f2c..69af0687d2 100644 --- a/docs/dev-guide/writing-modules.md +++ b/docs/dev-guide/writing-modules.md @@ -816,7 +816,8 @@ Loadavg 1.41 1.61 1.82 Modules are encouraged to use Python's standard [`logging`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) -module for debugging. By default, logs will be written to +module for debugging -- see the `xrandr` module as an example. By default, logs +will be written to [`syslog`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) with a minimum level of `INFO`. @@ -826,7 +827,7 @@ one you're interested in. This can be done by using the `--log-config` flag to pass a JSON file that configures logging. This file must be in the format specified in [`logging`'s configuration schema](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema). -For example, to show only logs from your module in a `DEBUG` level, while +For example, to show only logs from the `xrandr` module in a `DEBUG` level, while keeping all others at `WARNING`, you can use: ```json @@ -849,11 +850,15 @@ keeping all others at `WARNING`, you can use: }, "loggers": { "root": {"handlers": ["file"], "level": "WARNING"}, - "": {"level": "DEBUG"} + "xrandr": {"level": "DEBUG"} } } ``` +In this example, logs will be written to the `/tmp/py3status_log.log` file, +but you can configure the logging library to log to other destinations like +stderr or syslog. + ## Publishing custom modules on PyPI You can share your custom modules and make them available for py3status diff --git a/py3status/modules/xrandr.py b/py3status/modules/xrandr.py index 2b65d3cb30..8e9d03fc07 100644 --- a/py3status/modules/xrandr.py +++ b/py3status/modules/xrandr.py @@ -143,8 +143,11 @@ from collections import deque from collections import OrderedDict from itertools import combinations +import logging from time import sleep +_logger = logging.getLogger("xrandr") + class Py3status: """""" @@ -226,7 +229,7 @@ def _get_layout(self): else: continue except Exception as err: - self.py3.log(f'xrandr error="{err}"') + _logger.exception("xrandr error", err) else: layout[state][output] = {"infos": infos, "mode": mode, "state": state} @@ -313,7 +316,7 @@ def _choose_what_to_display(self, force_refresh=False): if force_refresh: self.displayed = self.available_combinations[0] else: - self.py3.log('xrandr error="displayed combination is not available"') + _logger.warning('xrandr error="displayed combination is not available"') def _center(self, s): """ @@ -349,7 +352,7 @@ def _apply(self, force=False): resolution = f"--mode {resolution}" if resolution else "--auto" rotation = getattr(self, f"{output}_rotate", "normal") if rotation not in ["inverted", "left", "normal", "right"]: - self.py3.log(f"configured rotation {rotation} is not valid") + _logger.warning("configured rotation %s is not valid", rotation) rotation = "normal" # if primary is True and not primary_added: @@ -378,7 +381,7 @@ def _apply(self, force=False): self.active_comb = combination self.active_layout = self.displayed self.active_mode = mode - self.py3.log(f'command "{cmd}" exit code {code}') + _logger.info('command "%s" exit code %s', cmd, code) if self.command: self.py3.command_run(self.command) @@ -410,7 +413,7 @@ def _apply_workspaces(self, combination, mode): ) self.py3.command_run(cmd) # log this - self.py3.log(f"moved workspace {workspace} to output {output}") + _logger.info("moved workspace %s to output %s", workspace, output) def _fallback_to_available_output(self): """ @@ -513,7 +516,7 @@ def xrandr(self): and self.force_on_change and self._layout_changed() ): - self.py3.log("detected change of monitor setup") + _logger.info("detected change of monitor setup") self._force_on_change() # this was a click event triggered update From 4b2ee491f306853c536197c739793721929e8664 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 41/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 56 ++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 797e92ee5d..7d90086649 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -1,3 +1,5 @@ +import logging +import logging.handlers import pkg_resources import sys import time @@ -24,6 +26,12 @@ LOG_LEVELS = {"error": LOG_ERR, "warning": LOG_WARNING, "info": LOG_INFO} +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ @@ -40,6 +48,8 @@ ENTRY_POINT_NAME = "py3status" ENTRY_POINT_KEY = "entry_point" +_logger = logging.getLogger('core') + class Runner(Thread): """ @@ -523,10 +533,30 @@ def load_modules(self, modules_list, user_modules): msg = f'Loading module "{module}" failed ({err}).' self.report_exception(msg, level="warning") + def _setup_logging(self): + """Set up the global logger.""" + root = logging.getLogger(name=None) + if self.config.get("debug"): + root.setLevel(logging.DEBUG) + else: + root.setLevel(logging.DEBUG) + + log_file = self.config.get("log_file") + if log_file: + handler = logging.FileHandler(log_file, encoding='utf8') + else: + logging.handlers.SysLogHandler() + handler.setFormatter(logging.Formatter( + fmt='%(asctime)s %(levelname)s %(module)s %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + style='%')) + root.addHandler(handler) + def setup(self): """ Setup py3status and spawn i3status/events/modules threads. """ + self._setup_logging() # log py3status and python versions self.log("=" * 8) @@ -879,29 +909,11 @@ def notify_update(self, update, urgent=False): def log(self, msg, level="info"): """ log this information to syslog or user provided logfile. + + This is soft-deprecated; prefer using the 'logging' module directly in + new code. """ - if not self.config.get("log_file"): - # If level was given as a str then convert to actual level - level = LOG_LEVELS.get(level, level) - syslog(level, f"{msg}") - else: - # Binary mode so fs encoding setting is not an issue - with self.config["log_file"].open("ab") as f: - log_time = time.strftime("%Y-%m-%d %H:%M:%S") - # nice formatting of data structures using pretty print - if isinstance(msg, (dict, list, set, tuple)): - msg = pformat(msg) - # if multiline then start the data output on a fresh line - # to aid readability. - if "\n" in msg: - msg = "\n" + msg - out = f"{log_time} {level.upper()} {msg}\n" - try: - # Encode unicode strings to bytes - f.write(out.encode("utf-8")) - except (AttributeError, UnicodeDecodeError): - # Write any byte strings straight to log - f.write(out) + _logger.log(LOGGING_LEVELS.get(level, logging.DEBUG), msg) def create_output_modules(self): """ From 37bc19e54b9fc8cf813a0f4e77094e7e606cfcb1 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 42/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 57 +++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 7d90086649..098f52b3e3 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -11,7 +11,6 @@ from signal import signal, Signals, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen from threading import Event, Thread -from syslog import syslog, LOG_ERR, LOG_INFO, LOG_WARNING from traceback import extract_tb, format_tb, format_stack from py3status.command import CommandServer @@ -24,12 +23,10 @@ from py3status.profiling import profile from py3status.udev_monitor import UdevMonitor -LOG_LEVELS = {"error": LOG_ERR, "warning": LOG_WARNING, "info": LOG_INFO} - LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, } DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} @@ -48,7 +45,7 @@ ENTRY_POINT_NAME = "py3status" ENTRY_POINT_KEY = "entry_point" -_logger = logging.getLogger('core') +_logger = logging.getLogger("core") class Runner(Thread): @@ -526,8 +523,7 @@ def load_modules(self, modules_list, user_modules): # only handle modules with available methods if my_m.methods: self.modules[module] = my_m - elif self.config["debug"]: - self.log(f'ignoring module "{module}" (no methods found)') + _logger.debug('ignoring module "%s" (no methods found)', module) except Exception: err = sys.exc_info()[1] msg = f'Loading module "{module}" failed ({err}).' @@ -543,13 +539,16 @@ def _setup_logging(self): log_file = self.config.get("log_file") if log_file: - handler = logging.FileHandler(log_file, encoding='utf8') + handler = logging.FileHandler(log_file, encoding="utf8") else: logging.handlers.SysLogHandler() - handler.setFormatter(logging.Formatter( - fmt='%(asctime)s %(levelname)s %(module)s %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', - style='%')) + handler.setFormatter( + logging.Formatter( + fmt="%(asctime)s %(levelname)s %(module)s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + style="%", + ) + ) root.addHandler(handler) def setup(self): @@ -584,8 +583,7 @@ def setup(self): self.log("window manager: {}".format(self.config["wm_name"])) - if self.config["debug"]: - self.log(f"py3status started with config {self.config}") + _logger.debug("py3status started with config %s", self.config) if self.config["gevent"]: self.is_gevent = self.gevent_monkey_patch_report() @@ -630,12 +628,9 @@ def setup(self): i3s_mode = "mocked" break time.sleep(0.1) - if self.config["debug"]: - self.log( - "i3status thread {} with config {}".format( - i3s_mode, self.config["py3_config"] - ) - ) + _logger.debug( + "i3status thread %s with config %s", i3s_mode, self.config["py3_config"] + ) # add i3status thread monitoring task if i3s_mode == "started": @@ -646,15 +641,13 @@ def setup(self): self.events_thread = Events(self) self.events_thread.daemon = True self.events_thread.start() - if self.config["debug"]: - self.log("events thread started") + _logger.debug("events thread started") # initialise the command server self.commands_thread = CommandServer(self) self.commands_thread.daemon = True self.commands_thread.start() - if self.config["debug"]: - self.log("commands thread started") + _logger.debug("commands thread started") # initialize the udev monitor (lazy) self.udev_monitor = UdevMonitor(self) @@ -699,8 +692,7 @@ def setup(self): # get a dict of all user provided modules self.log("modules include paths: {}".format(self.config["include_paths"])) user_modules = self.get_user_configured_modules() - if self.config["debug"]: - self.log(f"user_modules={user_modules}") + _logger.debug("user_modules=%s", user_modules) if self.py3_modules: # load and spawn i3status.conf configured modules threads @@ -798,8 +790,7 @@ def stop(self): try: self.lock.set() - if self.config["debug"]: - self.log("lock set, exiting") + _logger.debug("lock set, exiting") # run kill() method on all py3status modules for module in self.modules.values(): module.kill() @@ -830,12 +821,10 @@ def refresh_modules(self, module_string=None, exact=True): or (not exact and name.startswith(module_string)) ): if module["type"] == "py3status": - if self.config["debug"]: - self.log(f"refresh py3status module {name}") + _logger.debug("refresh py3status module %s", name) module["module"].force_update() else: - if self.config["debug"]: - self.log(f"refresh i3status module {name}") + _logger.debug("refresh i3status module %s", name) update_i3status = True if update_i3status: self.i3status_thread.refresh_i3status() From ae4ccbc4bbeed87f23f87f2b8acbdcc3d251fa01 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 43/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py3status/core.py b/py3status/core.py index 098f52b3e3..1911b372bc 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -29,6 +29,12 @@ "info": logging.INFO, } +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From 0257927a0ae2a08ee0a49e6acffc3a0d2eed4f5a Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 44/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 1911b372bc..098f52b3e3 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -29,12 +29,6 @@ "info": logging.INFO, } -LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, -} - DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From d6da4ca6b11df52515c799e917a9bf0fd5973462 Mon Sep 17 00:00:00 2001 From: Valentin Weber Date: Fri, 27 Aug 2021 14:24:42 +0200 Subject: [PATCH 45/66] new sway_idle module: to display the idle state of sway wm (#2058) --- py3status/modules/sway_idle.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py index 68787919a8..0355127aac 100644 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,3 +1,7 @@ +<<<<<<< HEAD +======= +# -*- coding: utf-8 -*- +>>>>>>> 17d826db (new sway_idle module: to display the idle state of sway wm (#2058)) """ Display sway inhibit idle status. From 531b45bcded1d9d9a0527e2625dbc3dbbd2b5b29 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Fri, 27 Aug 2021 14:25:51 +0200 Subject: [PATCH 46/66] sway_idle module: chmod -x and remove useless encoding shebang --- py3status/modules/sway_idle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py index 0355127aac..8d2e5154b6 100644 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,7 +1,10 @@ <<<<<<< HEAD +<<<<<<< HEAD ======= # -*- coding: utf-8 -*- >>>>>>> 17d826db (new sway_idle module: to display the idle state of sway wm (#2058)) +======= +>>>>>>> e085d93a (sway_idle module: chmod -x and remove useless encoding shebang) """ Display sway inhibit idle status. From 62c6accc0fac64624b9545906770aee7ab86e885 Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:04:14 +0200 Subject: [PATCH 47/66] i3 contrib page: update readthedocs links to new documentation --- contrib/py3status.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/py3status.html b/contrib/py3status.html index 8795d7edc3..2d4fd9b945 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -85,7 +85,11 @@

    Using py3status

    Learn more about how to configure py3status on the +<<<<<<< HEAD +======= + +>>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) configuration documentation

    @@ -93,7 +97,11 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the +<<<<<<< HEAD modules +======= +modules +>>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) documentation

    From c2260d14c51e0f40db0baf78e6e6760a7fc94fcc Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:25:49 +0200 Subject: [PATCH 48/66] i3 contrib page: update to sync with i3.github.io --- contrib/py3status.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/py3status.html b/contrib/py3status.html index 2d4fd9b945..b7adac53fd 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -86,10 +86,14 @@

    Using py3status

    Learn more about how to configure py3status on the <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) +======= + +>>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) configuration documentation

    @@ -98,10 +102,14 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the <<<<<<< HEAD +<<<<<<< HEAD modules ======= modules >>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) +======= +modules +>>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) documentation

    From 868805a7b60793c3e2b8178b3c788279ba4fde3d Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:36:08 -0300 Subject: [PATCH 49/66] Remove unused import. --- py3status/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 098f52b3e3..ac5d3e77c0 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -7,7 +7,6 @@ from collections import deque from json import dumps from pathlib import Path -from pprint import pformat from signal import signal, Signals, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen from threading import Event, Thread From abaaef5f68dcd4ccab67638c43694465df4d1e72 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:39:35 -0300 Subject: [PATCH 50/66] Fix oversight: Make "info" be the default logging threshold. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index ac5d3e77c0..1718b92c35 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -534,7 +534,7 @@ def _setup_logging(self): if self.config.get("debug"): root.setLevel(logging.DEBUG) else: - root.setLevel(logging.DEBUG) + root.setLevel(logging.INFO) log_file = self.config.get("log_file") if log_file: From fd41bf384ee98c5b59fc3f217a86c817271e068a Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:41:53 -0300 Subject: [PATCH 51/66] Fix syslog setup. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 1718b92c35..e9c037b09d 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -540,7 +540,7 @@ def _setup_logging(self): if log_file: handler = logging.FileHandler(log_file, encoding="utf8") else: - logging.handlers.SysLogHandler() + handler = logging.handlers.SysLogHandler() handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From e56fb64721b85319f1053a9af5ee5881f77759fc Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 20:41:57 -0300 Subject: [PATCH 52/66] Allow configuring logging using a JSON file. The JSON file will take over the entire configuration; it must fully specify logging as defined in https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema --- py3status/argparsers.py | 42 ++++++++++++++++++++++++++--------------- py3status/core.py | 22 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/py3status/argparsers.py b/py3status/argparsers.py index cccc64d6a4..29f8c73e14 100644 --- a/py3status/argparsers.py +++ b/py3status/argparsers.py @@ -82,12 +82,6 @@ def _format_action_invocation(self, action): metavar="FILE", type=Path, ) - parser.add_argument( - "-d", - "--debug", - action="store_true", - help="enable debug logging in syslog or log file if --log-file option is passed", - ) parser.add_argument( "-g", "--gevent", @@ -104,15 +98,6 @@ def _format_action_invocation(self, action): metavar="PATH", type=Path, ) - parser.add_argument( - "-l", - "--log-file", - action="store", - dest="log_file", - help="enable logging to FILE (this option is not set by default)", - metavar="FILE", - type=Path, - ) parser.add_argument( "-s", "--standalone", @@ -164,6 +149,33 @@ def _format_action_invocation(self, action): help="specify window manager i3 or sway", ) + logging_args = parser.add_argument_group() + logging_args.add_argument( + "-d", + "--debug", + action="store_true", + help="enable debug logging in syslog or log file if --log-file option is passed", + ) + logging_args.add_argument( + "-l", + "--log-file", + action="store", + dest="log_file", + help="enable logging to FILE (this option is not set by default)", + metavar="FILE", + type=Path, + ) + logging_args.add_argument( + "--log-config", + action="store", + dest="log_config", + help="path to a file that fully configures the 'logging' module. This " + "must contain a JSON dictionary in the format expected by " + "logging.config.dictConfig.", + metavar="FILE", + type=Path, + ) + # deprecations parser.add_argument("-n", "--interval", help=argparse.SUPPRESS) diff --git a/py3status/core.py b/py3status/core.py index e9c037b09d..21c47fcd64 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -530,6 +530,28 @@ def load_modules(self, modules_list, user_modules): def _setup_logging(self): """Set up the global logger.""" + log_config = self.config.get("log_config") + if log_config: + if self.config.get("debug"): + self.report_exception("--debug is invalid when --log-config is passed") + if self.config.get("log_file"): + self.report_exception( + "--log-file is invalid when --log-config is passed" + ) + import json + + with log_config.open() as f: + import logging.config + + try: + config_dict = json.load(f, strict=False) + logging.config.dictConfig(config_dict) + except json.JSONDecodeError as e: + self.report_exception(str(e)) + # Nothing else to do. All logging config is provided by the config + # dictionary. + return + root = logging.getLogger(name=None) if self.config.get("debug"): root.setLevel(logging.DEBUG) From 9bbbbe3226ff433f7f24bc8aa094c9d95052787e Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 20:54:35 -0300 Subject: [PATCH 53/66] Add documentation explaining how to configure logging while testing modules. --- docs/dev-guide/writing-modules.md | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/dev-guide/writing-modules.md b/docs/dev-guide/writing-modules.md index 53591b3376..2dc2654f2c 100644 --- a/docs/dev-guide/writing-modules.md +++ b/docs/dev-guide/writing-modules.md @@ -811,6 +811,49 @@ Loadavg 1.41 1.61 1.82 Loadavg 1.41 1.61 1.82 ^C ``` + +## Logging + +Modules are encouraged to use Python's standard +[`logging`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) +module for debugging. By default, logs will be written to +[`syslog`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) +with a minimum level of `INFO`. + +Several existing modules will write to logs, with varying levels of details. +Therefore, when debugging a specific module, it may be useful to show only the +one you're interested in. This can be done by using the `--log-config` flag to +pass a JSON file that configures logging. This file must be in the format +specified in +[`logging`'s configuration schema](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema). +For example, to show only logs from your module in a `DEBUG` level, while +keeping all others at `WARNING`, you can use: + +```json +{ + "version": 1, + "handlers": { + "file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": "/tmp/py3status_log.log", + "maxBytes": 2048, + "formatter": "default" + } + }, + "formatters": { + "default": { + "validate": true, + "format":"%(asctime)s %(levelname)s %(module)s %(message)s", + "datefmt":"%Y-%m-%d %H:%M:%S" + } + }, + "loggers": { + "root": {"handlers": ["file"], "level": "WARNING"}, + "": {"level": "DEBUG"} + } +} +``` + ## Publishing custom modules on PyPI You can share your custom modules and make them available for py3status From 5ebd7bf8d19f72dbda402b9e9464e11ee9ec2e9b Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 54/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py3status/core.py b/py3status/core.py index 21c47fcd64..4f9eea4673 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -28,6 +28,12 @@ "info": logging.INFO, } +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From 7d8f5b948e2d74ed563f77bc30e83fc8bbc4faa7 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 55/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 4f9eea4673..21c47fcd64 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -28,12 +28,6 @@ "info": logging.INFO, } -LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, -} - DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From 664a0067606914dca82f712d478d9b3672b154ec Mon Sep 17 00:00:00 2001 From: Valentin Weber Date: Fri, 27 Aug 2021 14:24:42 +0200 Subject: [PATCH 56/66] new sway_idle module: to display the idle state of sway wm (#2058) --- py3status/modules/sway_idle.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/py3status/modules/sway_idle.py b/py3status/modules/sway_idle.py index 8d2e5154b6..68787919a8 100644 --- a/py3status/modules/sway_idle.py +++ b/py3status/modules/sway_idle.py @@ -1,10 +1,3 @@ -<<<<<<< HEAD -<<<<<<< HEAD -======= -# -*- coding: utf-8 -*- ->>>>>>> 17d826db (new sway_idle module: to display the idle state of sway wm (#2058)) -======= ->>>>>>> e085d93a (sway_idle module: chmod -x and remove useless encoding shebang) """ Display sway inhibit idle status. From 58f00c32e3a80244c79909a65eba7e8fb95c328b Mon Sep 17 00:00:00 2001 From: Ultrabug Date: Tue, 31 Aug 2021 12:04:14 +0200 Subject: [PATCH 57/66] i3 contrib page: update readthedocs links to new documentation --- contrib/py3status.html | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/contrib/py3status.html b/contrib/py3status.html index b7adac53fd..8795d7edc3 100644 --- a/contrib/py3status.html +++ b/contrib/py3status.html @@ -85,15 +85,7 @@

    Using py3status

    Learn more about how to configure py3status on the -<<<<<<< HEAD -<<<<<<< HEAD -======= - ->>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) -======= - ->>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) configuration documentation

    @@ -101,15 +93,7 @@

    Using py3status modules

    Like i3status, py3status comes with modules (but a lot of them!).

    Learn more about the modules provided by py3status on the -<<<<<<< HEAD -<<<<<<< HEAD -modules -======= -modules ->>>>>>> 96dbe5bb (i3 contrib page: update readthedocs links to new documentation) -======= modules ->>>>>>> 9a9ee140 (i3 contrib page: update to sync with i3.github.io) documentation

    From 1b2ba503c9d8c89fc3aab895171665739324f376 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 19 Sep 2021 21:09:33 -0300 Subject: [PATCH 58/66] Fix lint error. --- py3status/core.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 21c47fcd64..ef488ddd2b 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -5,7 +5,7 @@ import time from collections import deque -from json import dumps +from json import load, dumps, JSONDecodeError from pathlib import Path from signal import signal, Signals, SIGTERM, SIGUSR1, SIGTSTP, SIGCONT from subprocess import Popen @@ -538,15 +538,14 @@ def _setup_logging(self): self.report_exception( "--log-file is invalid when --log-config is passed" ) - import json with log_config.open() as f: import logging.config try: - config_dict = json.load(f, strict=False) + config_dict = load(f, strict=False) logging.config.dictConfig(config_dict) - except json.JSONDecodeError as e: + except JSONDecodeError as e: self.report_exception(str(e)) # Nothing else to do. All logging config is provided by the config # dictionary. From 37b19a5aa985b7ec211c19e862378f4a904f2237 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 22 Aug 2021 21:01:46 -0300 Subject: [PATCH 59/66] Use the logging module directly instead of home-grown code. This has the advantage of overall less custom code; as well as support for per-module configuration. This would enable a potential solution for https://github.com/ultrabug/py3status/issues/1479, since in the future it can allow per-module configuration of log levels. I expect this to mainly help module creators, allowing them to enable logging for only their module, while disabling all other messages. It also is easier to use, since the `logging` module's interface is generally simpler than `self.py3`. Here, I've made the decision to keep the message format as close as possible to the existing log messages. --- py3status/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py3status/core.py b/py3status/core.py index ef488ddd2b..aa27ee9b1a 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -28,6 +28,12 @@ "info": logging.INFO, } +LOGGING_LEVELS = { + "error": logging.ERROR, + "warning": logging.WARNING, + "info": logging.INFO, +} + DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} CONFIG_SPECIAL_SECTIONS = [ From 6277b648062de46840bf0b13ca807315845582a0 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 24 Aug 2021 18:49:03 -0300 Subject: [PATCH 60/66] Use standard logging infrastructure on "debug" statements. Instead of checking for the "debug" option, we just log with the DEBUG level. The root logger's configuration will suppress log levels as needed. --- py3status/core.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index aa27ee9b1a..3a4ab3feaa 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -28,11 +28,6 @@ "info": logging.INFO, } -LOGGING_LEVELS = { - "error": logging.ERROR, - "warning": logging.WARNING, - "info": logging.INFO, -} DBUS_LEVELS = {"error": "critical", "warning": "normal", "info": "low"} @@ -567,7 +562,11 @@ def _setup_logging(self): if log_file: handler = logging.FileHandler(log_file, encoding="utf8") else: +<<<<<<< HEAD handler = logging.handlers.SysLogHandler() +======= + logging.handlers.SysLogHandler() +>>>>>>> 3ed7e940 (Use standard logging infrastructure on "debug" statements.) handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From ffd2f9e979d2151a291a8ad3f860aa8cd843134f Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Sun, 5 Sep 2021 17:36:08 -0300 Subject: [PATCH 61/66] Remove unused import. --- py3status/core.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index 3a4ab3feaa..e558a90b0a 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -562,11 +562,7 @@ def _setup_logging(self): if log_file: handler = logging.FileHandler(log_file, encoding="utf8") else: -<<<<<<< HEAD handler = logging.handlers.SysLogHandler() -======= - logging.handlers.SysLogHandler() ->>>>>>> 3ed7e940 (Use standard logging infrastructure on "debug" statements.) handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From 70fa768bdca265a7ce1616e13babe0c7356847ef Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Tue, 5 Oct 2021 17:48:03 -0300 Subject: [PATCH 62/66] Fix syslog handler, and eagerly import module. --- py3status/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py3status/core.py b/py3status/core.py index e558a90b0a..84e71958a6 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -1,5 +1,6 @@ import logging import logging.handlers +import logging.config import pkg_resources import sys import time @@ -541,8 +542,6 @@ def _setup_logging(self): ) with log_config.open() as f: - import logging.config - try: config_dict = load(f, strict=False) logging.config.dictConfig(config_dict) @@ -562,7 +561,8 @@ def _setup_logging(self): if log_file: handler = logging.FileHandler(log_file, encoding="utf8") else: - handler = logging.handlers.SysLogHandler() + # https://stackoverflow.com/a/3969772/340862 + handler = logging.handlers.SysLogHandler(address='/dev/log') handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From 0ef5c3e3cc11cce0f0081d316dc1eb0da3fa5830 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Fri, 8 Oct 2021 09:29:20 -0300 Subject: [PATCH 63/66] Run black formatter. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 84e71958a6..d049cbcbab 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -562,7 +562,7 @@ def _setup_logging(self): handler = logging.FileHandler(log_file, encoding="utf8") else: # https://stackoverflow.com/a/3969772/340862 - handler = logging.handlers.SysLogHandler(address='/dev/log') + handler = logging.handlers.SysLogHandler(address="/dev/log") handler.setFormatter( logging.Formatter( fmt="%(asctime)s %(levelname)s %(module)s %(message)s", From 63542157b8ae027754efa08d40ca6fed555ee566 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Thu, 16 Dec 2021 21:09:41 -0300 Subject: [PATCH 64/66] Makes 'disable_existing_loggers=False' the default. The Python library defaults to True, but that is a confusing behavior since it disables the "core" logger, which was created before logging was set up. Users can still set 'disable_existing_loggers' explicitly in their configs. Since most existing modules (who are using self.py3.log) will be logged by "core", that means that most logs would be suppressed. --- py3status/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py3status/core.py b/py3status/core.py index d049cbcbab..56ec6527c3 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -544,6 +544,7 @@ def _setup_logging(self): with log_config.open() as f: try: config_dict = load(f, strict=False) + config_dict.setdefault('disable_existing_loggers', False) logging.config.dictConfig(config_dict) except JSONDecodeError as e: self.report_exception(str(e)) From bd3a338df9a99372c79f4691b5d7cce3ace7ab8d Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Mon, 3 Jan 2022 12:41:40 -0300 Subject: [PATCH 65/66] Run formatter. --- py3status/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py3status/core.py b/py3status/core.py index 56ec6527c3..221b4ebb61 100644 --- a/py3status/core.py +++ b/py3status/core.py @@ -544,7 +544,7 @@ def _setup_logging(self): with log_config.open() as f: try: config_dict = load(f, strict=False) - config_dict.setdefault('disable_existing_loggers', False) + config_dict.setdefault("disable_existing_loggers", False) logging.config.dictConfig(config_dict) except JSONDecodeError as e: self.report_exception(str(e)) From 18817ac780a4ee7d02b9e95ca841cf966a752155 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Wed, 5 Jan 2022 19:01:08 -0300 Subject: [PATCH 66/66] Port the xrandr module to use the builtin logging library. Also, use it as the logging example in the docs. --- docs/dev-guide/writing-modules.md | 11 ++++++++--- py3status/modules/xrandr.py | 15 +++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/dev-guide/writing-modules.md b/docs/dev-guide/writing-modules.md index 2dc2654f2c..69af0687d2 100644 --- a/docs/dev-guide/writing-modules.md +++ b/docs/dev-guide/writing-modules.md @@ -816,7 +816,8 @@ Loadavg 1.41 1.61 1.82 Modules are encouraged to use Python's standard [`logging`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) -module for debugging. By default, logs will be written to +module for debugging -- see the `xrandr` module as an example. By default, logs +will be written to [`syslog`](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema) with a minimum level of `INFO`. @@ -826,7 +827,7 @@ one you're interested in. This can be done by using the `--log-config` flag to pass a JSON file that configures logging. This file must be in the format specified in [`logging`'s configuration schema](https://docs.python.org/3/library/logging.config.html?highlight=logging#logging-config-dictschema). -For example, to show only logs from your module in a `DEBUG` level, while +For example, to show only logs from the `xrandr` module in a `DEBUG` level, while keeping all others at `WARNING`, you can use: ```json @@ -849,11 +850,15 @@ keeping all others at `WARNING`, you can use: }, "loggers": { "root": {"handlers": ["file"], "level": "WARNING"}, - "": {"level": "DEBUG"} + "xrandr": {"level": "DEBUG"} } } ``` +In this example, logs will be written to the `/tmp/py3status_log.log` file, +but you can configure the logging library to log to other destinations like +stderr or syslog. + ## Publishing custom modules on PyPI You can share your custom modules and make them available for py3status diff --git a/py3status/modules/xrandr.py b/py3status/modules/xrandr.py index 2b65d3cb30..8e9d03fc07 100644 --- a/py3status/modules/xrandr.py +++ b/py3status/modules/xrandr.py @@ -143,8 +143,11 @@ from collections import deque from collections import OrderedDict from itertools import combinations +import logging from time import sleep +_logger = logging.getLogger("xrandr") + class Py3status: """""" @@ -226,7 +229,7 @@ def _get_layout(self): else: continue except Exception as err: - self.py3.log(f'xrandr error="{err}"') + _logger.exception("xrandr error", err) else: layout[state][output] = {"infos": infos, "mode": mode, "state": state} @@ -313,7 +316,7 @@ def _choose_what_to_display(self, force_refresh=False): if force_refresh: self.displayed = self.available_combinations[0] else: - self.py3.log('xrandr error="displayed combination is not available"') + _logger.warning('xrandr error="displayed combination is not available"') def _center(self, s): """ @@ -349,7 +352,7 @@ def _apply(self, force=False): resolution = f"--mode {resolution}" if resolution else "--auto" rotation = getattr(self, f"{output}_rotate", "normal") if rotation not in ["inverted", "left", "normal", "right"]: - self.py3.log(f"configured rotation {rotation} is not valid") + _logger.warning("configured rotation %s is not valid", rotation) rotation = "normal" # if primary is True and not primary_added: @@ -378,7 +381,7 @@ def _apply(self, force=False): self.active_comb = combination self.active_layout = self.displayed self.active_mode = mode - self.py3.log(f'command "{cmd}" exit code {code}') + _logger.info('command "%s" exit code %s', cmd, code) if self.command: self.py3.command_run(self.command) @@ -410,7 +413,7 @@ def _apply_workspaces(self, combination, mode): ) self.py3.command_run(cmd) # log this - self.py3.log(f"moved workspace {workspace} to output {output}") + _logger.info("moved workspace %s to output %s", workspace, output) def _fallback_to_available_output(self): """ @@ -513,7 +516,7 @@ def xrandr(self): and self.force_on_change and self._layout_changed() ): - self.py3.log("detected change of monitor setup") + _logger.info("detected change of monitor setup") self._force_on_change() # this was a click event triggered update