diff --git a/docs/environment-configs.md b/docs/environment-configs.md new file mode 100644 index 0000000..f415d93 --- /dev/null +++ b/docs/environment-configs.md @@ -0,0 +1,35 @@ +# Environment configurations + +`daqpytools` has the power for you to configure your current instance of the loggings! This allows each user to change the timezone and the color themes to suit them as the logs come out. + +The way this is configured is by setting environment variables. By default, these are _unset_ in the terminal, which daqpytools will read and resolve to a default value. These default values can be found in `logging/log_format.ini`. + +If a non-default time zone/theme is preferred, it is recommended to set the env vars `DUNEDAQ_TIMEZONE` and `DUNEDAQ_LOGGING_THEME` in your `~/.bashrc`. + +## Timezones + +To change the timezone, simply do + +`export DUNEDAQ_TIMEZONE="[timezone]"` + +eg. `export DUNEDAQ_TIMEZONE="UTC"` + +Valid values are those that are listed in `pytz.all_timezones`. + +An example with `Europe/London`. + +![timezones](img/timezone.png) + + +## Logging themes + +The themes are defined in daqpytools, in `logging/themes`, where you can see all the different themes available. To change between them, simply do + +`export DUNEDAQ_LOGGING_THEME="[themename]"` + +eg. `export DUNEDAQ_LOGGING_THEME="dark"` + +There are checks in place to ensure that the theme choice is valid before logs are printed. + +An example with `black`. +![themes](img/theme.png) \ No newline at end of file diff --git a/docs/img/theme.png b/docs/img/theme.png new file mode 100644 index 0000000..cae3a02 Binary files /dev/null and b/docs/img/theme.png differ diff --git a/docs/img/timezone.png b/docs/img/timezone.png new file mode 100644 index 0000000..6ceb0db Binary files /dev/null and b/docs/img/timezone.png differ diff --git a/docs/index.md b/docs/index.md index 86e7124..fa61cea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,4 +23,5 @@ This documentation is split into two sections depending on your role: | [Best practices](./how-to/best-practices.md) | Recommended patterns for structuring logging in your application | | [Troubleshooting](./reference/troubleshooting.md) | Common symptoms, causes, and fixes | | [API reference](../APIref/) | Auto-generated kwargs, types, and defaults for all public APIs (redirects to MKDocs website) | +| [Environment configs](./environment-configs.md) | Extra information on how to configure your local copy of the logging. | diff --git a/docs_dev/how-to/update-documentation.md b/docs_dev/how-to/update-documentation.md index 10c634e..2f46254 100644 --- a/docs_dev/how-to/update-documentation.md +++ b/docs_dev/how-to/update-documentation.md @@ -10,7 +10,7 @@ If you are new to this repo, read this first before editing docs. The docs are assembled from multiple sources at build time: -- `docs/` contains user-facing content +- `docs/` contains user-facing content. **Note that this is also used in the dunedaq readthedocs.** - `docs_dev/` contains developer-facing content - `docs_dev/utils/generate_logging_autodocs.py` generates API reference pages from Python registries - `docs_dev/utils/mirror_docs.py` mirrors everything into a virtual docs tree diff --git a/mkdocs.yml b/mkdocs.yml index 0595fe9..29daebd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,6 +28,8 @@ nav: - How docs work and how to update them: dev/how-to/update-documentation.md - Common patterns: dev/reference/patterns.md - API reference: APIref + - Environment configurations: user/environment-configs.md + theme: name: material palette: diff --git a/pyproject.toml b/pyproject.toml index 88b9308..30caa70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ version = "1.0.0" where = ["src"] [tool.setuptools.package-data] -"daqpytools.logging" = ["log_format.ini"] +"daqpytools.logging" = ["log_format.ini", "themes/*.ini"] "daqpytools.uml" = ["uml_format.ini"] [project.optional-dependencies] diff --git a/src/daqpytools/logging/formatter.py b/src/daqpytools/logging/formatter.py index b14324c..7e27fae 100644 --- a/src/daqpytools/logging/formatter.py +++ b/src/daqpytools/logging/formatter.py @@ -22,9 +22,28 @@ LOG_FORMAT = config_loader.safe_load_config("logging", "record_format") DATE_TIME_FORMAT = config_loader.safe_load_config("logging", "date_time") DATE_TIME_BASE_FORMAT = config_loader.safe_load_config("logging", "date_time_base") + + +def load_and_return(env: str) -> str: + """Loads the env into the config, and then returns it safely.""" + config_loader.load_env(env) + return config_loader.safe_load_config( + "environment", env + ) + + +theme_path = Path(DAQPYTOOLS_LOGGING_ROOT / + f"themes/{load_and_return('DUNEDAQ_LOGGING_THEME')}.ini") +if not theme_path.is_file(): + err_msg = (f"{load_and_return('DUNEDAQ_LOGGING_THEME')} is not" + "a valid theme file! Change your DUNEDAQ_LOGGING_THEME variable") + raise ValueError(err_msg) + +config_loader.read_conf(theme_path) CONSOLE_THEME = Theme(config_loader.safe_load_config("theme")) -timezone_name = config_loader.safe_load_config("logging", "timezone") + +timezone_name = load_and_return("DUNEDAQ_TIMEZONE") try: TIME_ZONE = timezone(timezone_name) except UnknownTimeZoneError as e: diff --git a/src/daqpytools/logging/log_format.ini b/src/daqpytools/logging/log_format.ini index c7981d7..1e0c58e 100644 --- a/src/daqpytools/logging/log_format.ini +++ b/src/daqpytools/logging/log_format.ini @@ -24,30 +24,13 @@ logger_name = 50 record_format=%%(asctime)s %%(levelname)s %%(filename)s %%(name)s %%(message)s date_time_base=%%Y/%%m/%%d %%H:%%M:%%S %%Z date_time=[%(date_time_base)s] -timezone=UTC - -[theme] -# Define the color theme for logging output -# Colors can be set to 'dim', 'bold', or 'bright' variants of the colors -# Available colors: black, red, green, yellow, blue, magenta, cyan -# Background colors can be set with 'on_' prefix - -# === Context elements === -# Available colors: black, red, green, yellow, blue, magenta, cyan, white -# You can also use: bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white -# Styles: dim, bold, italic, underline, reverse, blink, conceal, strike -# Combine styles and colors, e.g., "bold magenta", "dim cyan", "bold white on red" -logging.time = dim blue -logging.location = dim white -logging.logger_name = dim white - -# === Log level colors === -logging.level.debug = dim cyan -logging.level.info = bold green -logging.level.warning = bold yellow -logging.level.error = bold red -logging.level.critical = bold white on red [cli] # Command-line interface options -help_option_names = -h,--help \ No newline at end of file +help_option_names = -h,--help + +[environment] +# These can be overridden from the environment variables +# the current ones here show what the default is +DUNEDAQ_LOGGING_THEME = default +DUNEDAQ_TIMEZONE = UTC \ No newline at end of file diff --git a/src/daqpytools/logging/themes/black.ini b/src/daqpytools/logging/themes/black.ini new file mode 100644 index 0000000..0dd1a3b --- /dev/null +++ b/src/daqpytools/logging/themes/black.ini @@ -0,0 +1,27 @@ +[theme] +# Define the color theme for logging output +# Colors can be set to 'dim', 'bold', or 'bright' variants of the colors +# Available colors: black, red, green, yellow, blue, magenta, cyan +# Background colors can be set with 'on_' prefix + +# === Context elements === +# Available colors: black, red, green, yellow, blue, magenta, cyan, white +# You can also use: bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white +# Styles: dim, bold, italic, underline, reverse, blink, conceal, strike +# Combine styles and colors, e.g., "bold magenta", "dim cyan", "bold white on red" +# Rich color reference: https://rich.readthedocs.io/en/stable/appendix/colors.html +# Rich style reference: https://rich.readthedocs.io/en/latest/style.html + +# Theme specific details +# This one makes everything black, which is a nice colorless default for people to use + +logging.time = black +logging.location = black +logging.logger_name = black + +# === Log level colors === +logging.level.debug = black +logging.level.info = black +logging.level.warning = black +logging.level.error = black +logging.level.critical = black \ No newline at end of file diff --git a/src/daqpytools/logging/themes/dark.ini b/src/daqpytools/logging/themes/dark.ini new file mode 100644 index 0000000..664b41a --- /dev/null +++ b/src/daqpytools/logging/themes/dark.ini @@ -0,0 +1,27 @@ +[theme] +# Define the color theme for logging output +# Colors can be set to 'dim', 'bold', or 'bright' variants of the colors +# Available colors: black, red, green, yellow, blue, magenta, cyan +# Background colors can be set with 'on_' prefix + +# === Context elements === +# Available colors: black, red, green, yellow, blue, magenta, cyan, white +# You can also use: bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white +# Styles: dim, bold, italic, underline, reverse, blink, conceal, strike +# Combine styles and colors, e.g., "bold magenta", "dim cyan", "bold white on red" +# Rich color reference: https://rich.readthedocs.io/en/stable/appendix/colors.html +# Rich style reference: https://rich.readthedocs.io/en/latest/style.html + +# Theme specific details +# Safe to use on all dark (including black) themes. The colors should still be legible. + +logging.time = blue +logging.location = grey74 +logging.logger_name = grey74 + +# === Log level colors === +logging.level.debug = cyan +logging.level.info = bold green +logging.level.warning = bold yellow +logging.level.error = bold red +logging.level.critical = bold white on red \ No newline at end of file diff --git a/src/daqpytools/logging/themes/default.ini b/src/daqpytools/logging/themes/default.ini new file mode 100644 index 0000000..5e39892 --- /dev/null +++ b/src/daqpytools/logging/themes/default.ini @@ -0,0 +1,27 @@ +[theme] +# Define the color theme for logging output +# Colors can be set to 'dim', 'bold', or 'bright' variants of the colors +# Available colors: black, red, green, yellow, blue, magenta, cyan +# Background colors can be set with 'on_' prefix + +# === Context elements === +# Available colors: black, red, green, yellow, blue, magenta, cyan, white +# You can also use: bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white +# Styles: dim, bold, italic, underline, reverse, blink, conceal, strike +# Combine styles and colors, e.g., "bold magenta", "dim cyan", "bold white on red" +# Rich color reference: https://rich.readthedocs.io/en/stable/appendix/colors.html +# Rich style reference: https://rich.readthedocs.io/en/latest/style.html + +# Theme specific details +# Safe to use on all light (including white) themes. The colors should still be legible. + +logging.time = dim blue +logging.location = dim white +logging.logger_name = dim white + +# === Log level colors === +logging.level.debug = dim cyan +logging.level.info = bold green +logging.level.warning = bold yellow +logging.level.error = bold red +logging.level.critical = bold white on red \ No newline at end of file diff --git a/src/daqpytools/logging/themes/light.ini b/src/daqpytools/logging/themes/light.ini new file mode 100644 index 0000000..7e258b3 --- /dev/null +++ b/src/daqpytools/logging/themes/light.ini @@ -0,0 +1,27 @@ +[theme] +# Define the color theme for logging output +# Colors can be set to 'dim', 'bold', or 'bright' variants of the colors +# Available colors: black, red, green, yellow, blue, magenta, cyan +# Background colors can be set with 'on_' prefix + +# === Context elements === +# Available colors: black, red, green, yellow, blue, magenta, cyan, white +# You can also use: bright_black, bright_red, bright_green, bright_yellow, bright_blue, bright_magenta, bright_cyan, bright_white +# Styles: dim, bold, italic, underline, reverse, blink, conceal, strike +# Combine styles and colors, e.g., "bold magenta", "dim cyan", "bold white on red" +# Rich color reference: https://rich.readthedocs.io/en/stable/appendix/colors.html +# Rich style reference: https://rich.readthedocs.io/en/latest/style.html + +# Theme specific details +# This one makes everything black, which is a nice colorless default for people to use + +logging.time = blue +logging.location = bright_black +logging.logger_name = bright_black + +# === Log level colors === +logging.level.debug = cyan +logging.level.info = bold green +logging.level.warning = bold yellow +logging.level.error = bold red +logging.level.critical = bold white on red \ No newline at end of file diff --git a/src/daqpytools/utils/config_loader.py b/src/daqpytools/utils/config_loader.py index 4cfca7b..d563a0e 100644 --- a/src/daqpytools/utils/config_loader.py +++ b/src/daqpytools/utils/config_loader.py @@ -1,5 +1,5 @@ - import configparser +import os from pathlib import Path from typing import overload @@ -34,8 +34,12 @@ def __init__(self, config_file: Path | str) -> None: """ self.config_file = config_file self.config: configparser.ConfigParser = configparser.ConfigParser() - - if not self.config.read(self.config_file): + self.read_conf(self.config_file) + + + def read_conf(self, conf: str) -> None: + """Reads and loads a given config file.""" + if not self.config.read(conf): err_msg = ( f"Configuration file '{self.config_file}' " "not found or could not be read." @@ -60,6 +64,34 @@ def safe_load_config( ... + # define a thing that lets you parse environment variables + def load_env(self, env_key: str) -> str | None: + """Read an environment variable and insert it into the + ``environment`` section of the internal ``ConfigParser``. + + If the section does not exist it will be created. The method stores + the variable only when it is set and non-empty; if the variable is + not set (or is an empty string), the config is left unchanged and + the method returns ``None``. This differs from earlier behaviour that + used to write an empty string for unset variables. + + Returns the environment value or ``None`` if the variable is not set + or is empty. + """ + env_value = os.environ.get(env_key) + section = "environment" + if not self.config.has_section(section): + self.config.add_section(section) + + # Only store the value when the environment variable is set and + # non-empty. If the variable is unset or empty, leave the config + # unchanged and return None. + if env_value: + self.config.set(section, env_key, env_value) + + return env_value + + def safe_load_config( self, section: str,