{"id":33532591,"url":"https://github.com/msftcangoblowm/logging-strict","last_synced_at":"2026-06-05T18:32:23.086Z","repository":{"id":224660996,"uuid":"763430255","full_name":"msftcangoblowm/logging-strict","owner":"msftcangoblowm","description":"logging.config yaml Strict typing and editable","archived":false,"fork":false,"pushed_at":"2025-05-19T12:05:14.000Z","size":1140,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-23T04:04:18.641Z","etag":null,"topics":["curated","logging","multiprocessing","python"],"latest_commit_sha":null,"homepage":"https://logging-strict.readthedocs.io/en/latest/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/msftcangoblowm.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-26T09:28:41.000Z","updated_at":"2025-05-19T11:54:50.000Z","dependencies_parsed_at":"2024-02-27T05:27:58.270Z","dependency_job_id":"da71567e-2ced-4495-83f7-baca21b50018","html_url":"https://github.com/msftcangoblowm/logging-strict","commit_stats":null,"previous_names":["msftcangoblowm/logging-strict"],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/msftcangoblowm/logging-strict","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msftcangoblowm%2Flogging-strict","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msftcangoblowm%2Flogging-strict/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msftcangoblowm%2Flogging-strict/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msftcangoblowm%2Flogging-strict/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/msftcangoblowm","download_url":"https://codeload.github.com/msftcangoblowm/logging-strict/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msftcangoblowm%2Flogging-strict/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286079811,"owners_count":27282121,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-26T02:00:06.075Z","response_time":193,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["curated","logging","multiprocessing","python"],"created_at":"2025-11-26T17:02:13.508Z","updated_at":"2025-11-26T17:02:21.822Z","avatar_url":"https://github.com/msftcangoblowm.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":".. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0\n.. For details: https://github.com/msftcangoblowm/logging-strict/blob/master/NOTICE.txt\n\nlogging-strict\n===============\n\nlogging.config yaml Strict typing and editable\n\n|  |kit| |license| |versions|\n|  |test-status| |codecov| |quality-status| |docs|\n|  |stars| |mastodon-msftcangoblowm|\n\nFor logging.config yaml files, logging-strict does the following:\n\n- Editable logging configuration\n\n  While running a Python app, some arbritary package, out of no\n  where, decides to log an informational warning. Within a multiprocessing\n  worker (aka heavy background processing), these logging warnings go\n  from annoying --\u003e disruptive.\n\n  The best can do is *adapt to survive*. Make this situation quickly\n  solvable by adjusting the app's logging configuration.\n\n  asyncio is an example package which bleeds informational logging warnings\n\n- curates\n\n  Intention is to have all the valid logging.config yaml in one place\n\n- validator\n\n  logging_strict comes with a logging.config yaml validator. So can\n  check the editted yaml file. Supports pre-commit\n\n- validates against a strictyaml schema\n\n  The schema is specifically tailored for the logging.handlers\n\n  As long as the yaml is valid, will have the data types\n  logging.handlers expect\n\n- exports package data\n\n  Alternative to pkgutil.get_data\n\n  Export data files using a pattern rather than one file at a time\n\n.. PYVERSIONS\n\n* Python 3.9 through 3.12, and 3.13.0a3 and up.\n\n**New in 1.6.x:**\nget_locals_dynamic add support for staticmethod and classmethod;\n\n**New in 1.5.x:**\nregistry logging_strict.yml;\n\nWhy?\n------\n\nlogging.config is more often than not hardcoded within a package's\nsource code. Removing logging.config from the source code and into\nan exported yaml config file, a package becomes adaptable to\nunforeseen unexpected bleeding of logging messages.\n\nWhen a bleed occurs, open the exported logging.config yaml file. Add\nthe offending package to the ``loggers`` section or if already there, increase\nthe logging level.\n\nFor example, for asyncio, adjust logging level from\nlogging.WARNING --\u003e logging.ERROR\n\nBye bye disruptive informational logging warning messages.\n\nlogging_strict comes with a logging.config yaml validator. So can\ncheck the editted yaml file.\n\nOn app|worker restart, the logging configuration changes take effect.\n\nExporting -- when\n------------------\n\nExports occur before the logging.config yaml files are needed. There\nare two process types: worker and app\n\nWhen an app is run, it exports the app logging configuration.\n\nRight before a ProcessPool runs, it exports the worker logging configuration.\n\nRight before a thread or ThreadPool runs, G'd and Darwin sit down to decide\nwhich calamity will befall you. Best to avoid that cuz Python logging module is\nthread-safe. Changes to the logging.config in one thread affects them all\nand those changes last as long as the app runs.\n\nSafe means safe to remove you from the gene pool. Would be a great name for a\nhorror movie. Don't be in that movie.\n\nExporting -- where/what\n------------------------\n\nExport location (on linux): ``$HOME/.local/share/[package name]/``\n\nThis is xdg user data dir and the configuration is per package.\nPython logging configurations' cascade!\n\nWhats exported?\n\n- one for the app\n\n- At least one, for the multiprocessing workers\n\nIf a user|coder edits and makes a change, undo'ing those changes would be\nconsidered quite rude, minimally, poor ettiquette.\n\nSo that gauntlets stay on and package authors live long fulfilling peaceful\nuneventful lives, overwrite existing logging config yaml files never\nhappens. Although fully capable, just absolutely refuses to do so!\n\nIf confident no changes have been made, can manually delete (unlink).\n\nThere will be no need for gauntlets, can safely put those away.\n\nUpgrade path\n--------------\n\n*How to upgrade a particular logging.config yaml file?*\n\nBest to increment the version and switch the code base to use the latest version\n\nCustom changes should be upstreamed.\n\n*Preferred the previous version*\n\nThere currently isn't a means to change which logging.config yaml file\na package uses.\n\nThis sounds like a job for user preference database, gschema. Not yet\nimplemented\n\nValidation\n-----------\n\nlogging.handlers, each, expects parameters to have the correct data type.\n\nyaml package strictyaml, default data type is str, for other types, the function\nvariable name and type must be known (and supported) beforehand.\n\nFor custom (handlers, filters, and formatters) functions, there is no\nway to know beforehand the parameter name **and therefore** the data type,\nparameter type will become str\n\n(Assuming the virtual environment is activated)\n\nWithin source code (tree)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. image:: https://raw.githubusercontent.com/msftcangoblowm/logging-strict/master/docs/_static/validate_flavor_asz.gif\n   :alt: validation of package logging.config yaml files\n   :width: 1000px\n   :height: 500px\n\n.. code:: console\n\n   logging_strict_validate_yaml\n\n.. code:: text\n\n   Processed: 4 / 4\n   Success / fail: 4 / 0\n   last (3): ~/Downloads/logging_strict/src/logging_strict/configs/mp_1_asz.worker.logging.config.yaml\n\n.. code:: console\n\n   logging_strict_validate_yaml --category worker\n\n.. code:: text\n\n   Processed: 3 / 3\n   Success / fail: 3 / 0\n   last (2): ~/Downloads/logging_strict/src/logging_strict/configs/mp_1_asz.worker.logging.config.yaml\n\n.. code:: console\n\n   logging_strict_validate_yaml --category app\n\n.. code:: text\n\n   Processed: 1 / 1\n   Success / fail: 1 / 0\n   last (0): ~/Downloads/logging_strict/src/logging_strict/configs/textual_1_asz.app.logging.config.yaml\n\n.. note:: Two workers are just ordinary yaml files\n\n   Withinin logging_strict source tree, `bad_idea/folder*/*` are two folders,\n   each contains one file.\n\n   Although valid yaml, these are not actual logging.config yaml files.\n   Just there for testing purposes\n\n   The total `*.logging.config.yaml` file count and total\n   `*.worker.logging.config.yaml` are both thrown off by `+2`\n\nWithin xdg user data dir\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code:: console\n\n   logging_strict_validate_yaml $HOME/.local/share/logging_strict/ worker\n\nProcessed: 1 / 1\nSuccess / fail: 1 / 0\nlast (0): ~/.local/share/logging_strict/mp_1_asz.worker.logging.config.yaml\n\n.. code:: console\n\n   logging_strict_validate_yaml $HOME/.local/share/logging_strict/ app\n\nProcessed: 1 / 1\nSuccess / fail: 1 / 0\nlast (0): ~/.local/share/logging_strict/textual_1_asz.app.logging.config.yaml\n\npre-commit\n------------\n\nLocally\n\n.. code:: text\n\n   repos:\n     - repo: local\n       hooks:\n         - id: validate-logging-config-yaml\n           name: validate-logging-config-yaml\n           entry: logging_strict_validate_yaml\n           language: python\n           require_serial: true\n           pass_filenames: false\n\nNormal usage\n\n.. code:: text\n\n   repos:\n     - repo: https://github.com/msftcangoblowm/logging-strict\n       rev: 0.1.0\n       hooks:\n         - id: validate-logging-config-yaml\n           name: validate-logging-config-yaml\n           entry: logging_strict_validate_yaml\n           language: python\n           require_serial: true\n          pass_filenames: false\n\ninstall\n--------\n\nYou know how to use pip. This is not that.\n\nLets discuss integrating logging-strict into your app and history\ndust binning hardcoded logging configuration.\n\nUI\n~~~\n\nAn entrypoint boilerplate should be structured like, or slightly\ndifferently for an async app\n\n.. code:: text\n\n   def _process_args(): ...\n\n   def main():\n       d_out = _process_args()\n       ...\n       # app logging config stuff \u003c--- here!\n       app = MyApp()  # \u003c-- not within here\n       ...\n\n   if __name__ = \"__main__\":\n       main()\n\nThis entrypoint is testable. If the argparsing is done within main,\nit's time to refactor and rework the entrypoint.\n\nAn Entrypoint have defined and **documented** exit codes. Besides for\n``--help|-h``, never prints a message\n\nlogging.config yaml -- within logging_strict\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. code:: text\n\n   from logging_strict.constants import\n   from logging_strict import ui_yaml_curated, LoggingState\n\n   genre = \"textual\"\n   version_no = \"1\"\n   flavor = \"asz\"  # \u003c -- Yet unpublished testing UI package\n   package_start_relative_folder = \"\"\n\n   LoggingState().is_state_app = True\n   ui_yaml_curated(\n       genre,\n       flavor,\n       version_no=version_no,\n       package_start_relative_folder=package_start_relative_folder,  # \u003c-- narrows the search\n   )\n\nlogging.config yaml -- within another package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. code:: text\n\n   from mypackage.constants import urpackagename, package_data_folder_start\n   from logging_strict import setup_ui_other, LoggingState\n\n   genre = \"textual\"\n   flavor = \"asz\"  # \u003c -- Yet unpublished testing UI package\n   version_no = \"1\"\n   package_start_relative_folder = \"\"\n\n   LoggingState().is_state_app = True\n   setup_ui_other(\n       urpackagename,  # \u003c-- Would have been better to curate within logging_strict\n       package_data_folder_start,\n       genre,\n       flavor,\n       version_no=version_no,\n       package_start_relative_folder=package_start_relative_folder,\n   )\n\n- package\n\n  Package within which the `*.[app|worker].logging.config.yaml` files\n  reside.\n\n  Which is preferrably within logging_strict. So all the logging.config yaml\n  in the universe need not be duplicated to the point where it appears\n  to compete with fiat currency.\n\n- package_data_folder_start\n\n  Within that package, which is the package base folder somewhere\n  within the folder tree lies the `*.[app|worker].logging.config.yaml`\n  files. This is a str, not a relative path.\n\n  One folder name. Does not assume the folder is called ``data``. Does assume\n  data files are within at least one folder. And if not? G'd and Darwin. Or\n  panties are bound to get twisted.\n\n- category\n\n  The function name indicates the purpose. To setup ``logging.config`` for\n  a worker, call function, ``setup_worker``\n\n- genre\n\n  From a main app's POV, genre is the UI framework such as: pyside or textual\n\n  From a worker's POV, genre hints at the implementation:\n  mp (multiprocessing) or rabbitmq, ...\n\n- flavor\n\n  Like a one word brand name to a particular logging.config yaml file. For the\n  initially used the brand, ``asz``, a Python testing UI app\n\n- version_no\n\n  When changes have to be made either: Increment\n  the version by 1 or if purpose is different, fork a new flavor\n\n  If no flavor, version pertains to the genre\n\n- package_start_relative_folder\n\n  Relative to package_data_folder_start, narrows search.\n\n  For example,\n\n  ``bad_idea/folder0/`` and ``bad_idea/folder1`` both contains,\n  ``mp_1_shared.worker.logging.config.yaml``. Which one?\n\n  package_data_folder_start is ``bad_idea``, not ``configs`` or ``data``.\n  package_start_relative_folder could be ``folder0``. Which is enough\n  to identify the exact file.\n\nLoggingState\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA Singleton holding logging state. To know whether or not, run by app\nor from cli\n\n(there is also the issue of run by: coverage, unittest, or pytest)\n\nIf run from app, and testing app component, logging is redirected to\n`textual.logging.TextualHandler` and shouldn't be changed.\n\nIf run from cli, and testing app component, logging is redirected to\n`logging.handlers.StreamHandler`, not TextualHandler\n\nDuring testing, the app and workers are run in all three scenerios.\n\nFrom coverage, from unittest, and from asz.\n\nWhile the logging handler is TextualHandler, changing to StreamHandler\nwould be bad. LoggingState aim is to avoid that.\n\nWhy would want to do testing from an UI?\n\n- **Speeeeeeeeeed!**\n\nMinimizing keypresses or actions required to run commands\n\n- Associating unittests to code modules\n\nWhich unittest(s) must be run to get 100% coverage for a particular\ncode module?\n\nWithout organization, can only imagine that there must always be a 1:1\nratio between unittest and code module. And if not, the unittests\nfolder is just a jumbled mess. And which unittests matter for a\nparticular code module is unknown.\n\n**Give a brother a clue!**\n\nA clear easily maintainable verifiable guide is necessary.\n\nworker\n-------\n\nThis is a 2 step process.\n\n- Step 1 -- entrypoint\n\n  Extracts yaml from package, validates, then passes as str to the worker process\n\n- Step 2 -- worker\n\n  yaml str --\u003e logging.config.dictConfig\n\nwithin entrypoint\n~~~~~~~~~~~~~~~~~~\n\nThe ProcessPool (not ThreadPool) worker is isolated within it's own\nprocess. So the dirty nature of logging configuration has no effect\non other processes.\n\nlogging.config yaml file within package, logging_strict\n\n.. code:: text\n\n   from logging_strict import worker_yaml_curated\n\n   genre = \"mp\"\n   flavor = \"asz\"\n\n   str_yaml = worker_yaml_curated(genre, flavor)\n\nlogging.config yaml file within another package\n\n.. code:: text\n\n   from logging_strict import worker_yaml_curated\n\n   package = \"someotherpackage\"\n   package_data_folder_start = \"data\"  # differs so need to check this folder name\n\n   genre = \"mp\"\n   flavor = \"asz\"\n\n   str_yaml = setup_worker_other(package, package_data_folder_start, genre, flavor)\n\n\nwithin worker\n~~~~~~~~~~~~~~\n\nentrypoint passes str_yaml to the (ProcessPool) worker. A worker calls\n`setup_logging_yaml` with the yaml str\n\n.. code:: text\n\n   from logging_strict import setup_logging_yaml\n\n   setup_logging_yaml(str_yaml)\n\n\nTo learn more about building UI apps that have `multiprocessing.pool.ProcessPool`\nworkers, check out the `asz` source code\n\nPublic API\n-----------\n\n.. code:: text\n\n   from logging_strict import (\n      LoggingConfigCategory,\n      LoggingState,\n      LoggingYamlType,\n      setup_ui_other,\n      ui_yaml_curated,\n      setup_worker_other,\n      worker_yaml_curated,\n      setup_logging_yaml,\n      LoggingStrictError,\n      LoggingStrictPackageNameRequired,\n      LoggingStrictPackageStartFolderNameRequired,\n      LoggingStrictProcessCategoryRequired,\n      LoggingStrictGenreRequired,\n   )\n\n- LoggingConfigCategory\n\n  tl;dr; ^^ won't need this ^^\n\n  Process categories Enum. Iterate over the Enum values, using class\n  method, `categories`.\n\n  `strict_logging` public methods are convenience functions for class,\n  `strict_logging.logging_api.LoggingConfigYaml`. If LoggingConfigYaml\n  used directly, choose one of the LoggingConfigCategory values to\n  pass as param, category.\n\n- LoggingYamlType\n\n  tl;dr; ^^ won't need this ^^\n\n  Useful only during strict type checking. class LoggingConfigYaml\n  implements LoggingYamlType interface and is a direct subclass\n\n- LoggingStrictError\n\n  logging_strict catch all Exception. Base type of other exceptions.\n  Implements ValueError\n\n  The other exceptions are self explanatory. When creating worker\n  entrypoints, can set exit codes based on which exception occurred.\n\nWhats strictyaml?\n------------------\n\nUnfortunately yaml spec is too broad, allowing undesirable complexity, which\nare a frequent cause of security issues. Read more:\n\n- `[why] \u003chttps://hitchdev.com/strictyaml/why/\u003e`_\n\n- `[why nots] \u003chttps://hitchdev.com/strictyaml/why-not/\u003e`_\n\nstrictyaml (`[docs] \u003chttps://hitchdev.com/strictyaml/\u003e`_) mitigates\nyaml security issues:\n\n- by only supporting a subset of the yaml spec\n\n- type-safe YAML parsing and validation against a schema\n\n  In our case, specialized to support the built-in Python\n  logging.handlers and adaptable enough to support custom\n  handlers, filters, and formatters\n\n.. |test-status| image:: https://github.com/msftcangoblowm/logging-strict/actions/workflows/testsuite.yml/badge.svg?branch=master\u0026event=push\n    :target: https://github.com/msftcangoblowm/logging-strict/actions/workflows/testsuite.yml\n    :alt: Test suite status\n.. |quality-status| image:: https://github.com/msftcangoblowm/logging-strict/actions/workflows/quality.yml/badge.svg?branch=master\u0026event=push\n    :target: https://github.com/msftcangoblowm/logging-strict/actions/workflows/quality.yml\n    :alt: Quality check status\n.. |docs| image:: https://readthedocs.org/projects/logging-strict/badge/?version=latest\u0026style=flat\n    :target: https://logging-strict.readthedocs.io/\n    :alt: Documentation\n.. |kit| image:: https://img.shields.io/pypi/v/logging-strict\n    :target: https://pypi.org/project/logging-strict/\n    :alt: PyPI status\n.. |versions| image:: https://img.shields.io/pypi/pyversions/logging-strict.svg?logo=python\u0026logoColor=FBE072\n    :target: https://pypi.org/project/logging-strict/\n    :alt: Python versions supported\n.. |license| image:: https://img.shields.io/github/license/msftcangoblowm/logging-strict\n    :target: https://pypi.org/project/logging-strict/blob/master/LICENSE.txt\n    :alt: License\n.. |stars| image:: https://img.shields.io/github/stars/msftcangoblowm/logging-strict.svg?logo=github\n    :target: https://github.com/msftcangoblowm/logging-strict/stargazers\n    :alt: GitHub stars\n.. |mastodon-msftcangoblowm| image:: https://img.shields.io/mastodon/follow/112019041247183249\n    :target: https://mastodon.social/@msftcangoblowme\n    :alt: msftcangoblowme on Mastodon\n.. |codecov| image:: https://codecov.io/gh/msftcangoblowm/logging-strict/graph/badge.svg?token=HCBC74IABR\n    :target: https://codecov.io/gh/msftcangoblowm/logging-strict\n    :alt: logging-strict coverage percentage\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsftcangoblowm%2Flogging-strict","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmsftcangoblowm%2Flogging-strict","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsftcangoblowm%2Flogging-strict/lists"}