diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f75524..7dfa900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Revision History +## 1.1 (2018-10-26) + +- Added `%(relpath)s` logging format. +- Added `verbosity` as `init()` option to work with Django admin commands. + ## 1.0 (2018-09-27) - Initial stable release. diff --git a/Pipfile b/Pipfile index 4fbc409..4456894 100644 --- a/Pipfile +++ b/Pipfile @@ -22,6 +22,7 @@ pytest-expecter = "*" pytest-random = "*" pytest-ordering = "*" pytest-cov = "*" +pathlib2 = "*" # missing dependency for Python 3.5 # Reports coverage-space = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 0dd2a48..4727750 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ee9fb37ba9665625792efb56d668d1ba9f3f5e22f91556819fbff4e53cd0161c" + "sha256": "0a6827512a750b089506e15e20e98d661de28b4bd674ceb069edb4172bcf8149" }, "pipfile-spec": 6, "requires": { @@ -53,6 +53,13 @@ ], "version": "==1.0.0" }, + "bleach": { + "hashes": [ + "sha256:c39d25d9ada62009853b0281efdc35a792db8cdee89465433e6d0aaaf5defc3f", + "sha256:f680cc08e2eea821f3173b875f68763960006f1e764c92b5d2b8354af3a47468" + ], + "version": "==3.0.1" + }, "certifi": { "hashes": [ "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", @@ -60,6 +67,44 @@ ], "version": "==2018.8.24" }, + "cffi": { + "hashes": [ + "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", + "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", + "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", + "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", + "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", + "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", + "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", + "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==1.11.5" + }, "chardet": { "hashes": [ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", @@ -69,17 +114,53 @@ }, "click": { "hashes": [ - "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", - "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" - ], - "version": "==6.7" + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==7.0" + }, + "cmarkgfm": { + "hashes": [ + "sha256:0186dccca79483e3405217993b83b914ba4559fe9a8396efc4eea56561b74061", + "sha256:1a625afc6f62da428df96ec325dc30866cc5781520cbd904ff4ec44cf018171c", + "sha256:207b7673ff4e177374c572feeae0e4ef33be620ec9171c08fd22e2b796e03e3d", + "sha256:275905bb371a99285c74931700db3f0c078e7603bed383e8cf1a09f3ee05a3de", + "sha256:50098f1c4950722521f0671e54139e0edc1837d63c990cf0f3d2c49607bb51a2", + "sha256:50ed116d0b60a07df0dc7b180c28569064b9d37d1578d4c9021cff04d725cb63", + "sha256:61a72def110eed903cd1848245897bcb80d295cd9d13944d4f9f30cba5b76655", + "sha256:64186fb75d973a06df0e6ea12879533b71f6e7ba1ab01ffee7fc3e7534758889", + "sha256:665303d34d7f14f10d7b0651082f25ebf7107f29ef3d699490cac16cdc0fc8ce", + "sha256:70b18f843aec58e4e64aadce48a897fe7c50426718b7753aaee399e72df64190", + "sha256:761ee7b04d1caee2931344ac6bfebf37102ffb203b136b676b0a71a3f0ea3c87", + "sha256:811527e9b7280b136734ed6cb6845e5fbccaeaa132ddf45f0246cbe544016957", + "sha256:987b0e157f70c72a84f3c2f9ef2d7ab0f26c08f2bf326c12c087ff9eebcb3ff5", + "sha256:9fc6a2183d0a9b0974ec7cdcdad42bd78a3be674cc3e65f87dd694419b3b0ab7", + "sha256:a3d17ee4ae739fe16f7501a52255c2e287ac817cfd88565b9859f70520afffea", + "sha256:ba5b5488719c0f2ced0aa1986376f7baff1a1653a8eb5fdfcf3f84c7ce46ef8d", + "sha256:c573ea89dd95d41b6d8cf36799c34b6d5b1eac4aed0212dee0f0a11fb7b01e8f", + "sha256:c5f1b9e8592d2c448c44e6bc0d91224b16ea5f8293908b1561de1f6d2d0658b1", + "sha256:cbe581456357d8f0674d6a590b1aaf46c11d01dd0a23af147a51a798c3818034", + "sha256:cf219bec69e601fe27e3974b7307d2f06082ab385d42752738ad2eb630a47d65", + "sha256:cf5014eb214d814a83a7a47407272d5db10b719dbeaf4d3cfe5969309d0fcf4b", + "sha256:d08bad67fa18f7e8ff738c090628ee0cbf0505d74a991c848d6d04abfe67b697", + "sha256:d6f716d7b1182bf35862b5065112f933f43dd1aa4f8097c9bcfb246f71528a34", + "sha256:e08e479102627641c7cb4ece421c6ed4124820b1758765db32201136762282d9", + "sha256:e20ac21418af0298437d29599f7851915497ce9f2866bc8e86b084d8911ee061", + "sha256:e25f53c37e319241b9a412382140dffac98ca756ba8f360ac7ab5e30cad9670a", + "sha256:e8932bddf159064f04e946fbb64693753488de21586f20e840b3be51745c8c09", + "sha256:f20900f16377f2109783ae9348d34bc80530808439591c3d3df73d5c7ef1a00c" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==0.4.2" }, "colorama": { "hashes": [ - "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", - "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1" + "sha256:a3d89af5db9e9806a779a50296b5fdb466e281147c2c235e8225ecc6dbf7bbf3", + "sha256:c9b54bebe91a6a803e0772c8561d53f2926bfeb17cd141fbabcb08424086595c" ], - "version": "==0.3.9" + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==0.4.0" }, "coverage": { "hashes": [ @@ -120,11 +201,11 @@ }, "coverage-space": { "hashes": [ - "sha256:ab48b9729e54972708a6430321a0e552c10ece7c4561010d669484f453fa4e03", - "sha256:e47459028a0580d916ac3f3ccfe2cf03d1d073b3284da05c4a09f5b05114ee74" + "sha256:37e38169bceffe8fe836670203e4dc49e6d21cba66a6e77521b7dbe2821d9704", + "sha256:b9dde0f5a06433bc22a3f7457fc3cb292b4e8a763ba90644baf6230cb8fe1997" ], "index": "pypi", - "version": "==1.0.2" + "version": "==1.0.2.post1" }, "docopt": { "hashes": [ @@ -228,10 +309,11 @@ }, "markdown": { "hashes": [ - "sha256:9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f", - "sha256:a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81" + "sha256:c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa", + "sha256:d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c" ], - "version": "==2.6.11" + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==3.0.1" }, "markupsafe": { "hashes": [ @@ -270,6 +352,14 @@ ], "version": "==1.3.7" }, + "pathlib2": { + "hashes": [ + "sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", + "sha256:d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a" + ], + "index": "pypi", + "version": "==2.3.2" + }, "pefile": { "hashes": [ "sha256:4c5b7e2de0c8cb6c504592167acf83115cbbde01fe4a507c16a1422850e86cd6" @@ -307,6 +397,13 @@ "index": "pypi", "version": "==2.4.0" }, + "pycparser": { + "hashes": [ + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==2.19" + }, "pydocstyle": { "hashes": [ "sha256:08a870edc94508264ed90510db466c6357c7192e0e866561d740624a8fc7d90c", @@ -326,10 +423,10 @@ }, "pyinstaller": { "hashes": [ - "sha256:715f81f24b1ef0e5fe3b3c71e7540551838e46e9de30882aa7c0a521147fd1ce" + "sha256:a5a6e04a66abfcf8761e89a2ebad937919c6be33a7b8963e1a961b55cb35986b" ], "index": "pypi", - "version": "==3.3.1" + "version": "==3.4" }, "pylint": { "hashes": [ @@ -424,6 +521,14 @@ ], "version": "==3.13" }, + "readme-renderer": { + "hashes": [ + "sha256:237ca8705ffea849870de41101dba41543561da05c0ae45b2f1c547efa9843d2", + "sha256:f75049a3a7afa57165551e030dd8f9882ebf688b9600535a3f7e23596651875d" + ], + "markers": "python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*'", + "version": "==22.0" + }, "requests": { "hashes": [ "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", @@ -472,19 +577,19 @@ }, "tqdm": { "hashes": [ - "sha256:5ef526702c0d265d5a960a3b27f3971fac13c26cf0fb819294bfa71fc6026c88", - "sha256:a3364bd83ce4777320b862e3c8a93d7da91e20a95f06ef79bed7dd71c654cafa" + "sha256:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889", + "sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9" ], - "markers": "python_version != '3.0.*' and python_version != '3.1.*' and python_version >= '2.6'", - "version": "==4.25.0" + "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6'", + "version": "==4.26.0" }, "twine": { "hashes": [ - "sha256:08eb132bbaec40c6d25b358f546ec1dc96ebd2638a86eea68769d9e67fe2b129", - "sha256:2fd9a4d9ff0bcacf41fdc40c8cb0cfaef1f1859457c9653fd1b92237cc4e9f25" + "sha256:7d89bc6acafb31d124e6e5b295ef26ac77030bf098960c2a4c4e058335827c5c", + "sha256:fad6f1251195f7ddd1460cb76d6ea106c93adb4e56c41e0da79658e56e547d2c" ], "index": "pypi", - "version": "==1.11.0" + "version": "==1.12.1" }, "typed-ast": { "hashes": [ @@ -523,13 +628,20 @@ "markers": "python_version != '3.0.*' and python_version < '4' and python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version >= '2.6'", "version": "==1.23" }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, "wheel": { "hashes": [ - "sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c", - "sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f" + "sha256:9fa1f772f1a2df2bd00ddb4fa57e1cc349301e1facb98fbe62329803a9ff1196", + "sha256:d215f4520a1ba1851a3c00ba2b4122665cd3d6b0834d2ba2816198b1e3024a0e" ], "index": "pypi", - "version": "==0.31.1" + "version": "==0.32.1" }, "wrapt": { "hashes": [ diff --git a/README.md b/README.md index 4584941..4bcb22e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Unix: [![Unix Build Status](https://img.shields.io/travis/jacebrowning/minilog/develop.svg)](https://travis-ci.org/jacebrowning/minilog) Windows: [![Windows Build Status](https://img.shields.io/appveyor/ci/jacebrowning/minilog/develop.svg)](https://ci.appveyor.com/project/jacebrowning/minilog)
Metrics: [![Coverage Status](https://img.shields.io/coveralls/jacebrowning/minilog/develop.svg)](https://coveralls.io/r/jacebrowning/minilog) [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/jacebrowning/minilog.svg)](https://scrutinizer-ci.com/g/jacebrowning/minilog/?branch=develop)
Usage: [![PyPI Version](https://img.shields.io/pypi/v/minilog.svg)](https://pypi.org/project/minilog) +Unix: [![Unix Build Status](https://img.shields.io/travis/jacebrowning/minilog/develop.svg)](https://travis-ci.org/jacebrowning/minilog) Windows: [![Windows Build Status](https://img.shields.io/appveyor/ci/jacebrowning/minilog/develop.svg)](https://ci.appveyor.com/project/jacebrowning/minilog)
Metrics: [![Coverage Status](https://img.shields.io/coveralls/jacebrowning/minilog/develop.svg)](https://coveralls.io/r/jacebrowning/minilog) [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/jacebrowning/minilog.svg)](https://scrutinizer-ci.com/g/jacebrowning/minilog/?branch=develop)
Usage: [![PyPI Version](https://img.shields.io/pypi/v/minilog.svg)](https://pypi.org/project/minilog) [![PyPI License](https://img.shields.io/pypi/l/minilog.svg)](https://pypi.org/project/minilog) # Overview diff --git a/docs/extras.md b/docs/extras.md new file mode 100644 index 0000000..9f7a5be --- /dev/null +++ b/docs/extras.md @@ -0,0 +1,54 @@ +# Initialization + +To customize logging formats and levels, `minilog` supports the same initialization arguments as [`logging.basicConfig`](https://docs.python.org/3/library/logging.html#logging.basicConfig). To set the format for all logging handlers: + +```python +log.init(format="%(levelname)s: %(name)s: %(message)s") +``` + +To set the level for the root logging handler: + +```python +log.init(format=<…>, level=log.WARNING) +``` + +### Debug Option + +To simply enable debug-level logging, a convenience option is provided: + +```python +log.init(format=<…>, debug=True) +``` + +### Verbosity Option + +To work with frameworks that provide a `verbosity` level in their CLI frameworks (such as [Django](https://docs.djangoproject.com/en/2.1/ref/django-admin/#cmdoption-verbosity)), that can be used instead: + +```python +log.init(format=<…>, verbosity=verbosity) +``` + +### Silencing Loggers + +To set the logging level for specific named loggers: + +```python +log.silence('selenium') +log.silence('werkzeug', 'requests', allow_warning=True) +``` + +### Reset Loggers + +Finally, if another package has already set the logging format or level, that can be reset so that `minilog` takes over: + +```python +log.init(…, reset=True) +``` + +# Records + +In addition to the standard [`LogRecord`](https://docs.python.org/3/library/logging.html#logrecord-attributes) attributes, the following additional patterns are available: + +| Logging Format | Description +| --- | --- | +| `%(relpath)s` | Full pathname of the source file where the logging call was issued relative to the current working directory. | diff --git a/docs/api.md b/docs/logging.md similarity index 52% rename from docs/api.md rename to docs/logging.md index 0d306f1..852d847 100644 --- a/docs/api.md +++ b/docs/logging.md @@ -1,4 +1,4 @@ -# Logging +# API This package intends to be a drop-in replacement for `logging.Logger` objects. It supports the standard logging API: @@ -26,31 +26,3 @@ log.e(message, *args) # error log.exc(message, *args) # exception ``` - -# Configuration - -Set the format for all logging handlers: - -```python -log.init(format="%(levelname)s: %(name)s: %(message)s") -``` - -Set the level for the root logging handler: - -```python -log.init(format=<…>, debug=True) -log.init(format=<…>, level=log.WARNING) -``` - -Replace all existing loggers before initialization: - -```python -log.init(reset=True, format=<…>, level=<…>) -``` - -Set the logging level for specific named loggers: - -```python -log.silence('selenium') -log.silence('werkzeug', 'requests', allow_warning=True) -``` diff --git a/log/__init__.py b/log/__init__.py index 464a11f..516482c 100644 --- a/log/__init__.py +++ b/log/__init__.py @@ -1,7 +1,7 @@ from logging import DEBUG, INFO, WARNING, ERROR from .logger import * # pylint: disable=wildcard-import -from .helpers import init, silence +from .helpers import init, install_additional_formats, silence WARN = WARNING @@ -13,4 +13,4 @@ exc = exception __project__ = 'minilog' -__version__ = '1.0' +__version__ = '1.1' diff --git a/log/filters.py b/log/filters.py new file mode 100644 index 0000000..eefa57a --- /dev/null +++ b/log/filters.py @@ -0,0 +1,22 @@ +import logging +import os +import sys + + +class RelpathFormatFilter(logging.Filter): + """Adds '%(relpath)s' as a 'LogRecord' attribute.""" + + def filter(self, record): + pathname = record.pathname + record.relpath = None + abs_sys_paths = map(os.path.abspath, sys.path) + for path in sorted(abs_sys_paths, key=len, reverse=True): + if not path.endswith(os.sep): + path += os.sep + if pathname.startswith(path): + record.relpath = os.path.relpath(pathname, path) + break + return True + + +relpath_format_filter = RelpathFormatFilter() diff --git a/log/helpers.py b/log/helpers.py index c59f0b4..be6e803 100644 --- a/log/helpers.py +++ b/log/helpers.py @@ -2,20 +2,32 @@ import logging +from . filters import relpath_format_filter +from . import state + DEFAULT_LEVEL = logging.INFO DEFAULT_FORMAT = "%(levelname)s: %(name)s: %(message)s" - -initialized = False +VERBOSITY_TO_LEVEL = { + 0: logging.ERROR, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG, +} -def init(*, reset=False, debug=False, **kwargs): +def init(*, reset=False, debug=False, verbosity=None, **kwargs): if reset: for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) custom_format = kwargs.get('format') - default_level = logging.DEBUG if debug else DEFAULT_LEVEL + if debug: + default_level = logging.DEBUG + elif verbosity is not None: + default_level = VERBOSITY_TO_LEVEL[verbosity] + else: + default_level = DEFAULT_LEVEL kwargs['level'] = kwargs.get('level', default_level) kwargs['format'] = kwargs.get('format', DEFAULT_FORMAT) @@ -26,8 +38,14 @@ def init(*, reset=False, debug=False, **kwargs): for handler in logging.root.handlers: handler.setFormatter(formatter) - global initialized - initialized = True + install_additional_formats(logging.root) + + state.initialized = True + + +def install_additional_formats(logger): + for handler in logger.handlers: + handler.addFilter(relpath_format_filter) def silence(*names, allow_info=False, allow_warning=False, allow_error=False): diff --git a/log/state.py b/log/state.py new file mode 100644 index 0000000..0ba115c --- /dev/null +++ b/log/state.py @@ -0,0 +1 @@ +initialized = False diff --git a/log/tests/test_helpers.py b/log/tests/test_helpers.py index e69de29..f73bbbd 100644 --- a/log/tests/test_helpers.py +++ b/log/tests/test_helpers.py @@ -0,0 +1,33 @@ +# pylint: disable=unused-variable,expression-not-assigned + +from unittest.mock import patch, call + +from log import helpers + + +def describe_init(): + + @patch('logging.basicConfig') + def with_verbosity_0(config, expect): + helpers.init(format='%(message)s', verbosity=0) + expect(config.mock_calls) == [call(format='%(message)s', level=40)] + + @patch('logging.basicConfig') + def with_verbosity_1(config, expect): + helpers.init(format='%(message)s', verbosity=1) + expect(config.mock_calls) == [call(format='%(message)s', level=30)] + + @patch('logging.basicConfig') + def with_verbosity_2(config, expect): + helpers.init(format='%(message)s', verbosity=2) + expect(config.mock_calls) == [call(format='%(message)s', level=20)] + + @patch('logging.basicConfig') + def with_verbosity_3(config, expect): + helpers.init(format='%(message)s', verbosity=3) + expect(config.mock_calls) == [call(format='%(message)s', level=10)] + + @patch('logging.basicConfig') + def with_verbosity_0_and_debug(config, expect): + helpers.init(format='%(message)s', verbosity=0, debug=True) + expect(config.mock_calls) == [call(format='%(message)s', level=10)] diff --git a/log/utils.py b/log/utils.py index 310bdf4..ae84e37 100644 --- a/log/utils.py +++ b/log/utils.py @@ -4,11 +4,11 @@ import logging import inspect -from . import helpers +from . import helpers, state def create_logger_record(level, message, *args, exc_info=None, **kwargs): - if not helpers.initialized and 'pytest' not in sys.modules: + if not state.initialized and 'pytest' not in sys.modules: helpers.init() frame, filename, lineno, *_ = inspect.stack()[3] @@ -18,6 +18,8 @@ def create_logger_record(level, message, *args, exc_info=None, **kwargs): if not logger.isEnabledFor(level): return + helpers.install_additional_formats(logger) + record = logger.makeRecord( module.__name__, level, diff --git a/mkdocs.yml b/mkdocs.yml index a0c3423..dcf459d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,7 +7,8 @@ theme: readthedocs pages: - Home: index.md -- API: api.md +- Logging: logging.md +- Extras: extras.md - About: - Release Notes: about/changelog.md - Contributing: about/contributing.md diff --git a/tests/test_records.py b/tests/test_records.py index e1a9800..4b4ab15 100644 --- a/tests/test_records.py +++ b/tests/test_records.py @@ -1,5 +1,7 @@ # pylint: disable=redefined-outer-name,unused-variable,expression-not-assigned,singleton-comparison +import os + import pytest import log @@ -11,22 +13,30 @@ def describe_text(): def it_includes_the_caller_location(expect, caplog): demo.greet("caller") + expect(caplog.text) == \ "demo.py 5 ERROR Hello, caller!\n" @pytest.mark.last def it_can_be_formatted_with_init(expect, caplog): - log.init(format=log.helpers.DEFAULT_FORMAT, level=log.WARNING) + log.init(format='%(relpath)s:%(lineno)s: %(message)s', + level=log.WARNING) + demo.greet("format") - expect(caplog.text) == "ERROR: tests.demo: Hello, format!\n" + + if os.name == 'nt': + expect(caplog.text) == "tests\\demo.py:5: Hello, format!\n" + else: + expect(caplog.text) == "tests/demo.py:5: Hello, format!\n" def it_can_include_exceptions(expect, caplog): try: print(1 / 0) except ZeroDivisionError: log.exception("exception") + expect(caplog.text).contains('Traceback ') - expect(caplog.text).contains('test_records.py", line 25, ') + expect(caplog.text).contains('test_records.py", line 34, ') expect(caplog.text).contains('ZeroDivisionError') @@ -34,20 +44,28 @@ def describe_silence(): def when_off(expect, caplog): log.silence('3rd-party') + other.do_3rd_party_thing() + expect(caplog.records) == [] def with_errors(expect, caplog): log.silence('3rd-party', allow_error=True) + other.do_3rd_party_thing() + expect(len(caplog.records)) == 1 def with_warnings(expect, caplog): log.silence('3rd-party', allow_warning=True) + other.do_3rd_party_thing() + expect(len(caplog.records)) == 2 def with_infos(expect, caplog): log.silence('3rd-party', allow_info=True) + other.do_3rd_party_thing() + expect(len(caplog.records)) == 3