├── .editorconfig
├── .github
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── docs.yml
│ ├── lint.yml
│ ├── publish-to-pypi.yml
│ └── test.yml
├── .gitignore
├── .vscode
└── settings.json
├── CONTRIBUTING.rst
├── HISTORY.md
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README-various.md
├── README.md
├── docs
├── .gitignore
├── Makefile
├── _static
│ ├── demo-output-json.png
│ ├── demo-output-with-beaver.png
│ ├── demo_output.png
│ ├── demo_output_with_exception.png
│ ├── logo-420.png
│ ├── logo-big.png
│ ├── logo-text-wide.psd
│ ├── logo.ai
│ ├── logo.png
│ └── logo_1000x500.png
├── conf.py
├── contributing.rst
├── index.rst
└── make.bat
├── examples
├── demo.py
└── demo2.py
├── logzero
├── __init__.py
├── colors.py
└── jsonlogger.py
├── requirements_dev.txt
├── requirements_windows.txt
├── setup.cfg
├── setup.py
└── tests
├── __init__.py
├── test_json.py
├── test_logzero.py
└── test_new_api.py
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | charset = utf-8
11 | end_of_line = lf
12 |
13 | [*.yml]
14 | indent_size = 2
15 |
16 | [*.bat]
17 | indent_style = tab
18 | end_of_line = crlf
19 |
20 | [LICENSE]
21 | insert_final_newline = false
22 |
23 | [Makefile]
24 | indent_style = tab
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | * logzero version:
2 | * Python version:
3 | * Operating System:
4 |
5 | ### Description
6 |
7 | Describe what you were trying to get done.
8 | Tell us what happened, what went wrong, and what you expected to happen.
9 |
10 | ### What I Did
11 |
12 | ```
13 | Paste the command(s) you ran and the output.
14 | If there was a crash, please include the traceback here.
15 | ```
16 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | # Note: to test with Python 2.7, a downgrade in pytest is necessary - last supported is 4.6 series https://docs.pytest.org/en/stable/py27-py34-deprecation.html
2 | name: Generate the docs
3 |
4 | on: [push]
5 |
6 | jobs:
7 | generate_docs:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Set up Python 3.x
13 | uses: actions/setup-python@v2
14 | with:
15 | # Semantic version range syntax or exact version of a Python version
16 | python-version: '3.x'
17 | - name: Display Python version
18 | run: python -c "import sys; print(sys.version)"
19 | - name: Install dependencies
20 | run: |
21 | python -m pip install --upgrade pip
22 | pip install -r requirements_dev.txt
23 | - name: Generate the docs
24 | run: make docs
25 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | # Note: to test with Python 2.7, a downgrade in pytest is necessary - last supported is 4.6 series https://docs.pytest.org/en/stable/py27-py34-deprecation.html
2 | name: Lint the code
3 |
4 | on: [push]
5 |
6 | jobs:
7 | lint:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Set up Python 3.x
13 | uses: actions/setup-python@v2
14 | with:
15 | # Semantic version range syntax or exact version of a Python version
16 | python-version: '3.x'
17 | - name: Display Python version
18 | run: python -c "import sys; print(sys.version)"
19 | - name: Install dependencies
20 | run: |
21 | python -m pip install --upgrade pip
22 | pip install -r requirements_dev.txt
23 | - name: Lint
24 | run: make lint
25 |
--------------------------------------------------------------------------------
/.github/workflows/publish-to-pypi.yml:
--------------------------------------------------------------------------------
1 | name: Publish Python 🐍 distributions 📦 to PyPI
2 |
3 | on:
4 | push:
5 | tags:
6 | - v*
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@master
13 | - name: Set up Python 3.x
14 | uses: actions/setup-python@v2
15 | with:
16 | # Semantic version range syntax or exact version of a Python version
17 | python-version: '3.x'
18 |
19 | - name: Install dependencies
20 | run: pip install wheel
21 | - name: Build package
22 | run: python setup.py sdist bdist_wheel
23 |
24 | - name: Publish a Python distribution to PyPI
25 | uses: pypa/gh-action-pypi-publish@master
26 | with:
27 | user: __token__
28 | password: ${{ secrets.PYPI_API_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Run the tests and demo.py
2 |
3 | on: [push]
4 |
5 | jobs:
6 | test-pytest:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | python-version: [3.6, 3.7, 3.8, 3.9]
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Set up Python ${{ matrix.python-version }}
15 | uses: actions/setup-python@v2
16 | with:
17 | python-version: ${{ matrix.python-version }}
18 |
19 | - name: Install dependencies
20 | run: |
21 | pip install --upgrade pip
22 | pip install -e .
23 | pip install -r requirements_dev.txt
24 |
25 | - name: Run the tests
26 | run: make test
27 |
28 | - name: Run demo.py
29 | run: python examples/demo.py
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | .venv*
6 | venv*
7 | /test.py
8 | _tests/
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | env/
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *,cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 |
59 | # Sphinx documentation
60 | docs/_build/
61 |
62 | # PyBuilder
63 | target/
64 |
65 | # pyenv python configuration file
66 | .python-version
67 |
68 | # various
69 | *.internal.*
70 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "venv/bin/python"
3 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | .. highlight:: shell
2 |
3 | ============
4 | Contributing
5 | ============
6 |
7 | Contributions are welcome, and they are greatly appreciated! Every
8 | little bit helps, and credit will always be given.
9 |
10 | You can contribute in many ways:
11 |
12 | Types of Contributions
13 | ----------------------
14 |
15 | Report Bugs
16 | ~~~~~~~~~~~
17 |
18 | Report bugs at https://github.com/metachris/logzero/issues.
19 |
20 | If you are reporting a bug, please include:
21 |
22 | * Your operating system name and version.
23 | * Any details about your local setup that might be helpful in troubleshooting.
24 | * Detailed steps to reproduce the bug.
25 |
26 | Fix Bugs
27 | ~~~~~~~~
28 |
29 | Look through the GitHub issues for bugs. Anything tagged with "bug"
30 | and "help wanted" is open to whoever wants to implement it.
31 |
32 | Implement Features
33 | ~~~~~~~~~~~~~~~~~~
34 |
35 | Look through the GitHub issues for features. Anything tagged with "enhancement"
36 | and "help wanted" is open to whoever wants to implement it.
37 |
38 | Write Documentation
39 | ~~~~~~~~~~~~~~~~~~~
40 |
41 | logzero could always use more documentation, whether as part of the
42 | official logzero docs, in docstrings, or even on the web in blog posts,
43 | articles, and such.
44 |
45 | Submit Feedback
46 | ~~~~~~~~~~~~~~~
47 |
48 | The best way to send feedback is to file an issue at https://github.com/metachris/logzero/issues.
49 |
50 | If you are proposing a feature:
51 |
52 | * Explain in detail how it would work.
53 | * Keep the scope as narrow as possible, to make it easier to implement.
54 | * Remember that this is a volunteer-driven project, and that contributions
55 | are welcome :)
56 |
57 | Get Started!
58 | ------------
59 |
60 | Ready to contribute? Here's how to set up `logzero` for local development.
61 |
62 | 1. Fork the `logzero` repo on GitHub.
63 | 2. Clone your fork locally::
64 |
65 | $ git clone git@github.com:your_name_here/logzero.git
66 |
67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
68 |
69 | $ mkvirtualenv logzero
70 | $ cd logzero/
71 | $ python setup.py develop
72 |
73 | 4. Create a branch for local development::
74 |
75 | $ git checkout -b name-of-your-bugfix-or-feature
76 |
77 | Now you can make your changes locally.
78 |
79 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox::
80 |
81 | $ flake8 logzero tests
82 | $ python setup.py test or py.test
83 | $ tox
84 |
85 | To get flake8 and tox, just pip install them into your virtualenv.
86 |
87 | 6. Commit your changes and push your branch to GitHub::
88 |
89 | $ git add .
90 | $ git commit -m "Your detailed description of your changes."
91 | $ git push origin name-of-your-bugfix-or-feature
92 |
93 | 7. Submit a pull request through the GitHub website.
94 |
95 | Pull Request Guidelines
96 | -----------------------
97 |
98 | Before you submit a pull request, check that it meets these guidelines:
99 |
100 | 1. The pull request should include tests.
101 | 2. If the pull request adds functionality, the docs should be updated. Put
102 | your new functionality into a function with a docstring, and add the
103 | feature to the list in README.rst.
104 | 3. The pull request should work for Python 2.6, 2.7, 3.4, 3.5, 3.6 and for PyPy. Check
105 | https://travis-ci.org/metachris/logzero/pull_requests
106 | and make sure that the tests pass for all supported Python versions.
107 |
108 | Tips
109 | ----
110 |
111 | To run a subset of tests::
112 |
113 | $ py.test tests.test_logzero
114 |
115 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | History
2 | =======
3 |
4 | 1.7.0 (2021-03-17)
5 | ------------------
6 | - Export loglevels directly (you can use eg. `logzero.DEBUG` instead of `logging.DEBUG`)
7 | - `setup_default_logger` use `backupCount`
8 | - Update dependencies
9 | - PRs: (386)[https://github.com/metachris/logzero/pull/386]
10 |
11 |
12 | 1.6.3 (2020-11-15)
13 | ------------------
14 |
15 | - JSON logging with UTF-8 enabled by default ([PR 357](https://github.com/metachris/logzero/pull/357))
16 |
17 |
18 | 1.6.0 (1.6.2) (2020-10-29)
19 | --------------------------
20 |
21 | - JSON logging support ([PR 344][])
22 | - Ability to easily change colors ([\#82][])
23 | - Allow creating a root logger ([\#342][])
24 | - Bugfix: file logging with lower loglevel than stream ([PR 338][])
25 | - Running tests with Python up to 3.9
26 | - Dependency updates
27 |
28 | 1.5.0 (2018-03-07)
29 | ------------------
30 |
31 | - `logzero.syslog(..)` ([PR 83][])
32 |
33 | 1.4.0 (2018-03-02)
34 | ------------------
35 |
36 | - Allow Disabling stderr Output ([PR 83][1])
37 |
38 | 1.3.0 (2017-07-19)
39 | ------------------
40 |
41 | - Color output now works in Windows (supported by colorama)
42 |
43 | 1.2.1 (2017-07-09)
44 | ------------------
45 |
46 | - Logfiles with custom loglevels (eg. stream handler with DEBUG and
47 | file handler with ERROR).
48 |
49 | 1.2.0 (2017-07-05)
50 | ------------------
51 |
52 | - Way better API for configuring the default logger with logzero.loglevel(..), logzero.logfile(..), etc.
55 | - Built-in rotating logfile support.
56 |
57 | ``` python
58 | import logging
59 | import logzero
60 | from logzero import logger
61 |
62 | # This log message goes to the console
63 | logger.debug("hello")
64 |
65 | # Set a minimum log level
66 | logzero.loglevel(logging.INFO)
67 |
68 | # Set a logfile (all future log messages are also saved there)
69 | logzero.logfile("/tmp/logfile.log")
70 |
71 | # Set a rotating logfile (replaces the previous logfile handler)
72 | logzero.logfile("/tmp/rotating-logfile.log", maxBytes=1000000, backupCount=3)
73 |
74 | # Disable logging to a file
75 | logzero.logfile(None)
76 |
77 | # Set a custom formatter
78 | formatter = logging.Formatter('%(name)s - %(asctime)-15s - %(levelname)s: %(message)s');
79 | logzero.formatter(formatter)
80 |
81 | # Log some variables
82 | logger.info("var1: %s, var2: %s", var1, var2)
83 | ```
84 |
85 | 1.1.2 (2017-07-04)
86 | ------------------
87 |
88 | - Better reconfiguration of handlers, doesn't remove custom handlers
89 | anymore
90 |
91 | 1.1.0 (2017-07-03)
92 | ------------------
93 |
94 | - Bugfix: Disabled color logging to logfile
95 |
96 | 1.1.0 (2017-07-02)
97 | ------------------
98 |
99 | - Global default logger instance (logzero.logger)
101 | - Ability to reconfigure the default logger with (logzero.setup\_default\_logger(..))
103 | - More tests
104 | - More documentation
105 |
106 | 1.0.0 (2017-06-27)
107 | ------------------
108 |
109 | - Cleanup and documentation
110 |
111 | 0.2.0 (2017-06-12)
112 | ------------------
113 |
114 | - Working logzero package with code and tests
115 |
116 | 0.1.0 (2017-06-12)
117 | ------------------
118 |
119 | - First release on PyPI.
120 |
121 | [PR 344]: https://github.com/metachris/logzero/pull/344
122 | [\#82]: https://github.com/metachris/logzero/issues/82
123 | [\#342]: https://github.com/metachris/logzero/pull/342
124 | [PR 338]: https://github.com/metachris/logzero/pull/338
125 | [PR 83]: https://github.com/metachris/logzero/pull/84
126 | [1]: https://github.com/metachris/logzero/pull/83
127 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | MIT License
3 |
4 | Copyright (c) 2017, Chris Hager
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 |
12 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CONTRIBUTING.rst
2 | include HISTORY.md
3 | include LICENSE
4 | include README.md
5 |
6 | recursive-include tests *
7 | recursive-exclude * __pycache__
8 | recursive-exclude * *.py[co]
9 |
10 | recursive-include docs *.rst *.md conf.py Makefile make.bat *.jpg *.png *.gif
11 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: clean clean-test clean-pyc clean-build docs help
2 | .DEFAULT_GOAL := help
3 | define BROWSER_PYSCRIPT
4 | import os, webbrowser, sys
5 | try:
6 | from urllib import pathname2url
7 | except:
8 | from urllib.request import pathname2url
9 |
10 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
11 | endef
12 | export BROWSER_PYSCRIPT
13 |
14 | define PRINT_HELP_PYSCRIPT
15 | import re, sys
16 |
17 | for line in sys.stdin:
18 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
19 | if match:
20 | target, help = match.groups()
21 | print("%-20s %s" % (target, help))
22 | endef
23 | export PRINT_HELP_PYSCRIPT
24 | BROWSER := python -c "$$BROWSER_PYSCRIPT"
25 |
26 | help:
27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
28 |
29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
30 |
31 |
32 | clean-build: ## remove build artifacts
33 | rm -fr build/
34 | rm -fr dist/
35 | rm -fr .eggs/
36 | find . -name '*.egg-info' -exec rm -fr {} +
37 | find . -name '*.egg' -exec rm -f {} +
38 |
39 | clean-pyc: ## remove Python file artifacts
40 | find . -name '*.pyc' -exec rm -f {} +
41 | find . -name '*.pyo' -exec rm -f {} +
42 | find . -name '*~' -exec rm -f {} +
43 | find . -name '__pycache__' -exec rm -fr {} +
44 |
45 | clean-test: ## remove test and coverage artifacts
46 | rm -fr .tox/
47 | rm -f .coverage
48 | rm -fr htmlcov/
49 |
50 | lint: ## check style with flake8
51 | flake8 logzero tests
52 |
53 | test: ## run tests quickly with the default Python
54 | py.test
55 |
56 |
57 | test-all: ## run tests on every Python version with tox
58 | tox
59 |
60 | coverage: ## check code coverage quickly with the default Python
61 | coverage run --source logzero -m pytest
62 | coverage report -m
63 | coverage html
64 | $(BROWSER) htmlcov/index.html
65 |
66 | docs: ## generate Sphinx HTML documentation, including API docs
67 | rm -f docs/logzero.rst
68 | rm -f docs/modules.rst
69 | sphinx-apidoc -o docs/ logzero
70 | $(MAKE) -C docs clean
71 | $(MAKE) -C docs html
72 | $(BROWSER) docs/_build/html/index.html
73 |
74 | servedocs: docs ## compile the docs watching for changes
75 | watchmedo shell-command -p '*.rst;*.md' -c '$(MAKE) -C docs html' -R -D .
76 |
77 | release: clean ## package and upload a release
78 | python setup.py sdist upload
79 | python setup.py bdist_wheel upload
80 |
81 | dist: clean ## builds source and wheel package
82 | python setup.py sdist
83 | python setup.py bdist_wheel
84 | ls -l dist
85 |
86 | install: clean ## install the package to the active Python's site-packages
87 | python setup.py install
88 |
--------------------------------------------------------------------------------
/README-various.md:
--------------------------------------------------------------------------------
1 | Future Features & Ideas
2 | -----------------------
3 |
4 | * Decorator for logging function calls
5 | * Easier usage of custom log handlers (currently works `like this `_)
6 | * Send logs to remote log collector (maybe)
7 | * Structured logging a la https://structlog.readthedocs.io/en/stable/index.html (maybe)
8 |
9 |
10 | Related Projects
11 | ----------------
12 |
13 | * [pinojs](https://github.com/pinojs/pino)
14 | * https://logbook.readthedocs.io/en/stable/index.html
15 | * https://12factor.net/logs
16 | * Log collectors: fluentd, logstash, etc.
17 | * https://structlog.readthedocs.io/en/stable/why.html
18 |
19 |
20 | Notes: How to release a new version
21 | -----------------------------------
22 |
23 | via https://cookiecutter-pypackage.readthedocs.io/en/latest/pypi_release_checklist.html
24 |
25 | .. code-block:: console
26 |
27 | # Run the tests
28 | make test
29 | make lint
30 |
31 | # Update history
32 | vi HISTORY.md
33 | git add HISTORY.md
34 | git commit -m "Changelog for upcoming release"
35 |
36 | # Update version
37 | bumpversion minor
38 |
39 | # Push
40 | git push && git push --tags
41 |
42 | Update conda-forge: https://github.com/metachris/logzero/issues/67#issuecomment-353016366
43 |
44 |
45 | Credits
46 | ---------
47 |
48 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
49 |
50 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter
51 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
52 |
53 |
54 | .. _pip: https://pip.pypa.io
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # logzero
2 |
3 | 
4 | [](https://logzero.readthedocs.io/en/latest/?badge=latest)
5 | [](https://pypi.python.org/pypi/logzero)
6 | [](https://anaconda.org/conda-forge/logzero)
7 | [](https://pepy.tech/project/logzero)
8 |
9 | Robust and effective logging for Python 2 and 3.
10 |
11 | 
12 |
13 | * Documentation: https://logzero.readthedocs.io
14 | * GitHub: https://github.com/metachris/logzero
15 |
16 |
17 | Features
18 | --------
19 |
20 | * Easy logging to console and/or (rotating) file.
21 | * Provides a fully configured standard [Python logger object](https://docs.python.org/2/library/logging.html#module-level-functions>).
22 | * JSON logging (with integrated [python-json-logger](https://github.com/madzak/python-json-logger))
23 | * Pretty formatting, including level-specific colors in the console.
24 | * No dependencies
25 | * Windows color output supported by [colorama](https://github.com/tartley/colorama)
26 | * Robust against str/bytes encoding problems, works with all kinds of character encodings and special characters.
27 | * Multiple loggers can write to the same logfile (also across multiple Python files and processes).
28 | * Global default logger with [logzero.logger](https://logzero.readthedocs.io/en/latest/#i-logzero-logger) and custom loggers with [logzero.setup_logger(..)](https://logzero.readthedocs.io/en/latest/#i-logzero-setup-logger).
29 | * Compatible with Python 2 and 3.
30 | * All contained in a [single file](https://github.com/metachris/logzero/blob/master/logzero/__init__.py).
31 | * Licensed under the MIT license.
32 | * Heavily inspired by the [Tornado web framework](https://github.com/tornadoweb/tornado).
33 |
34 |
35 | Installation:
36 |
37 | ```shell
38 | python -m pip install logzero
39 | ```
40 |
41 | Example Usage
42 | -------------
43 |
44 | ```python
45 | from logzero import logger
46 |
47 | logger.debug("hello")
48 | logger.info("info")
49 | logger.warning("warn")
50 | logger.error("error")
51 |
52 | # This is how you'd log an exception
53 | try:
54 | raise Exception("this is a demo exception")
55 | except Exception as e:
56 | logger.exception(e)
57 |
58 | # JSON logging
59 | import logzero
60 | logzero.json()
61 |
62 | logger.info("JSON test")
63 |
64 | # Start writing into a logfile
65 | logzero.logfile("/tmp/logzero-demo.log")
66 |
67 | # Set a minimum loglevel
68 | logzero.loglevel(logzero.WARNING)
69 | ```
70 |
71 | This is the output:
72 |
73 | 
74 |
75 | Note: You can find more examples in the documentation: https://logzero.readthedocs.io
76 |
77 | ### JSON logging
78 |
79 | JSON logging can be enabled for the default logger with `logzero.json()`, or with `setup_logger(json=True)` for custom loggers:
80 |
81 | ```python
82 | >>> logzero.json()
83 | >>> logger.info("test")
84 | {"asctime": "2020-10-21 10:42:45,808", "filename": "", "funcName": "", "levelname": "INFO", "levelno": 20, "lineno": 1, "module": "", "message": "test", "name": "logzero_default", "pathname": "", "process": 76179, "processName": "MainProcess", "threadName": "MainThread"}
85 |
86 | >>> my_logger = setup_logger(json=True)
87 | >>> my_logger.info("test")
88 | {"asctime": "2020-10-21 10:42:45,808", "filename": "", "funcName": "", "levelname": "INFO", "levelno": 20, "lineno": 1, "module": "", "message": "test", "name": "logzero_default", "pathname": "", "process": 76179, "processName": "MainProcess", "threadName": "MainThread"}
89 | ```
90 |
91 | The logged JSON object has these fields:
92 |
93 | ```json
94 | {
95 | "asctime": "2020-10-21 10:43:40,765",
96 | "filename": "test.py",
97 | "funcName": "test_this",
98 | "levelname": "INFO",
99 | "levelno": 20,
100 | "lineno": 9,
101 | "module": "test",
102 | "message": "info",
103 | "name": "logzero",
104 | "pathname": "_tests/test.py",
105 | "process": 76204,
106 | "processName": "MainProcess",
107 | "threadName": "MainThread"
108 | }
109 | ```
110 |
111 | Exceptions logged with `logger.exception(e)` have these additional JSON fields:
112 |
113 | ```json
114 | {
115 | "levelname": "ERROR",
116 | "levelno": 40,
117 | "message": "this is a demo exception",
118 | "exc_info": "Traceback (most recent call last):\n File \"_tests/test.py\", line 15, in test_this\n raise Exception(\"this is a demo exception\")\nException: this is a demo exception"
119 | }
120 | ```
121 |
122 | Take a look at the documentation for more information and examples:
123 |
124 | * Documentation: https://logzero.readthedocs.io.
125 |
126 |
127 | Installation
128 | ------------
129 |
130 | Install `logzero` with [pip](https://pip.pypa.io):
131 |
132 | ```shell
133 | python -m pip install logzero
134 | ```
135 |
136 | Here's how you setup a virtualenv and download and run the demo:
137 |
138 | ```shell
139 | # Create and activate a virtualenv in ./venv/
140 | python3 -m venv venv
141 | . venv/bin/activate
142 |
143 | # Install logzero
144 | python -m pip install logzero
145 |
146 | # Download and run demo.py
147 | wget https://raw.githubusercontent.com/metachris/logzero/master/examples/demo.py
148 | python demo.py
149 | ```
150 |
151 | If you don't have [pip](https://pip.pypa.io) installed, this [Python installation guide](http://docs.python-guide.org/en/latest/starting/installation/) can guide
152 | you through the process.
153 |
154 | Alternatively, if you use the [Anaconda distribution](https://www.anaconda.com/download/):
155 |
156 | ```shell
157 | $ conda config --add channels conda-forge
158 | $ conda install logzero
159 | ```
160 |
161 | You can also install `logzero` from the public [Github repo](https://github.com/metachris/logzero):
162 |
163 | ```shell
164 | $ git clone https://github.com/metachris/logzero.git
165 | $ cd logzero
166 | $ python setup.py install
167 | ```
168 |
169 | Contributors
170 | ------------
171 |
172 | * [Chris Hager](https://github.com/metachris)
173 | * [carlodr](https://github.com/carlodri)
174 | * [Brian Lenz](https://github.com/brianlenz)
175 | * [David Martin](https://github.com/dmartin35)
176 | * [Zakaria Zajac](madzak) (creator of [python-json-logger](https://github.com/madzak/python-json-logger))
177 |
178 | ---
179 |
180 | Development
181 | -----------
182 |
183 | **Getting started**
184 |
185 | ```shell
186 | $ git clone https://github.com/metachris/logzero.git
187 | $ cd logzero
188 |
189 | # Activate virtualenv
190 | $ python3 -m venv venv
191 | $ . venv/bin/activate
192 |
193 | # Install main and dev dependencies
194 | $ pip install -e .
195 | $ pip install -r requirements_dev.txt
196 |
197 | # Run the tests
198 | $ make test
199 |
200 | # Run the linter
201 | $ make lint
202 |
203 | # Generate the docs (will auto-open in Chrome)
204 | $ make docs
205 |
206 | # You can enable watching mode to automatically rebuild on changes:
207 | $ make servedocs
208 | ```
209 |
210 | To test with Python 2.7, you can use Docker:
211 |
212 | ```shell
213 | docker run --rm -it -v /Users/chris/stream/logzero:/mnt python:2.7 /bin/bash
214 | ```
215 |
216 | Now you have a shell with the current directory mounted into `/mnt/` inside the container.
217 |
218 | **Notes**
219 |
220 | * [pytest](https://docs.pytest.org/en/latest/) is the test runner
221 | * CI is run with [Github actions](https://github.com/metachris/logzero/tree/master/.github/workflows).
222 | * Download stats: https://pepy.tech/project/logzero
223 |
224 | ---
225 |
226 | Changelog
227 | ---------
228 |
229 | See the changelog here: https://github.com/metachris/logzero/blob/master/HISTORY.md
230 |
231 |
232 | Feedback
233 | --------
234 |
235 | All kinds of feedback and contributions are welcome.
236 |
237 | * [Create an issue](https://github.com/metachris/logzero/issues/new)
238 | * Create a pull request
239 | * [@metachris](https://twitter.com/metachris)
240 |
241 | 
242 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | /logzero.rst
2 | /logzero.*.rst
3 | /modules.rst
4 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/logzero.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/logzero.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/logzero"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/logzero"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/docs/_static/demo-output-json.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/demo-output-json.png
--------------------------------------------------------------------------------
/docs/_static/demo-output-with-beaver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/demo-output-with-beaver.png
--------------------------------------------------------------------------------
/docs/_static/demo_output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/demo_output.png
--------------------------------------------------------------------------------
/docs/_static/demo_output_with_exception.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/demo_output_with_exception.png
--------------------------------------------------------------------------------
/docs/_static/logo-420.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/logo-420.png
--------------------------------------------------------------------------------
/docs/_static/logo-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/logo-big.png
--------------------------------------------------------------------------------
/docs/_static/logo-text-wide.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/logo-text-wide.psd
--------------------------------------------------------------------------------
/docs/_static/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/logo.ai
--------------------------------------------------------------------------------
/docs/_static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/logo.png
--------------------------------------------------------------------------------
/docs/_static/logo_1000x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/metachris/logzero/bc19173105561650bfdd0ae22d1bb0d9dcb1be99/docs/_static/logo_1000x500.png
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # logzero documentation build configuration file, created by
5 | # sphinx-quickstart on Tue Jul 9 22:26:36 2013.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | import sys
17 | import os
18 | import sphinx_rtd_theme
19 |
20 | # If extensions (or modules to document with autodoc) are in another
21 | # directory, add these directories to sys.path here. If the directory is
22 | # relative to the documentation root, use os.path.abspath to make it
23 | # absolute, like shown here.
24 | #sys.path.insert(0, os.path.abspath('.'))
25 |
26 | # Get the project root dir, which is the parent dir of this
27 | cwd = os.getcwd()
28 | project_root = os.path.dirname(cwd)
29 |
30 | # Insert the project root dir as the first element in the PYTHONPATH.
31 | # This lets us ensure that the source package is imported, and that its
32 | # version is used.
33 | sys.path.insert(0, project_root)
34 |
35 | import logzero
36 |
37 | # -- General configuration ---------------------------------------------
38 |
39 | # If your documentation needs a minimal Sphinx version, state it here.
40 | #needs_sphinx = '1.0'
41 |
42 | # Add any Sphinx extension module names here, as strings. They can be
43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
44 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx_rtd_theme']
45 |
46 | # Add any paths that contain templates here, relative to this directory.
47 | templates_path = ['_templates']
48 |
49 | # The suffix of source filenames.
50 | source_suffix = '.rst'
51 |
52 | # The encoding of source files.
53 | #source_encoding = 'utf-8-sig'
54 |
55 | # The master toctree document.
56 | master_doc = 'index'
57 |
58 | # General information about the project.
59 | project = u'logzero'
60 | copyright = u"2017, Chris Hager"
61 |
62 | # The version info for the project you're documenting, acts as replacement
63 | # for |version| and |release|, also used in various other places throughout
64 | # the built documents.
65 | #
66 | # The short X.Y version.
67 | version = logzero.__version__
68 | # The full version, including alpha/beta/rc tags.
69 | release = logzero.__version__
70 |
71 | # The language for content autogenerated by Sphinx. Refer to documentation
72 | # for a list of supported languages.
73 | #language = None
74 |
75 | # There are two options for replacing |today|: either, you set today to
76 | # some non-false value, then it is used:
77 | #today = ''
78 | # Else, today_fmt is used as the format for a strftime call.
79 | #today_fmt = '%B %d, %Y'
80 |
81 | # List of patterns, relative to source directory, that match files and
82 | # directories to ignore when looking for source files.
83 | exclude_patterns = ['_build']
84 |
85 | # The reST default role (used for this markup: `text`) to use for all
86 | # documents.
87 | #default_role = None
88 |
89 | # If true, '()' will be appended to :func: etc. cross-reference text.
90 | #add_function_parentheses = True
91 |
92 | # If true, the current module name will be prepended to all description
93 | # unit titles (such as .. function::).
94 | #add_module_names = True
95 |
96 | # If true, sectionauthor and moduleauthor directives will be shown in the
97 | # output. They are ignored by default.
98 | #show_authors = False
99 |
100 | # The name of the Pygments (syntax highlighting) style to use.
101 | pygments_style = 'sphinx'
102 |
103 | # A list of ignored prefixes for module index sorting.
104 | #modindex_common_prefix = []
105 |
106 | # If true, keep warnings as "system message" paragraphs in the built
107 | # documents.
108 | #keep_warnings = False
109 |
110 |
111 | # -- Options for HTML output -------------------------------------------
112 |
113 | # The theme to use for HTML and HTML Help pages. See the documentation for
114 | # a list of builtin themes.
115 | # html_theme = 'default'
116 | html_theme = 'sphinx_rtd_theme'
117 |
118 | # Theme options are theme-specific and customize the look and feel of a
119 | # theme further. For a list of options available for each theme, see the
120 | # documentation.
121 | html_theme_options = {
122 | 'collapse_navigation': False,
123 | 'navigation_depth': -1
124 | }
125 |
126 | # Add any paths that contain custom themes here, relative to this directory.
127 | #html_theme_path = []
128 |
129 | # The name for this set of Sphinx documents. If None, it defaults to
130 | # " v documentation".
131 | #html_title = None
132 |
133 | # A shorter title for the navigation bar. Default is the same as
134 | # html_title.
135 | #html_short_title = None
136 |
137 | # The name of an image file (relative to this directory) to place at the
138 | # top of the sidebar.
139 | #html_logo = None
140 |
141 | # The name of an image file (within the static path) to use as favicon
142 | # of the docs. This file should be a Windows icon file (.ico) being
143 | # 16x16 or 32x32 pixels large.
144 | #html_favicon = None
145 |
146 | # Add any paths that contain custom static files (such as style sheets)
147 | # here, relative to this directory. They are copied after the builtin
148 | # static files, so a file named "default.css" will overwrite the builtin
149 | # "default.css".
150 | html_static_path = ['_static']
151 |
152 | # If not '', a 'Last updated on:' timestamp is inserted at every page
153 | # bottom, using the given strftime format.
154 | #html_last_updated_fmt = '%b %d, %Y'
155 |
156 | # If true, SmartyPants will be used to convert quotes and dashes to
157 | # typographically correct entities.
158 | #html_use_smartypants = True
159 |
160 | # Custom sidebar templates, maps document names to template names.
161 | #html_sidebars = {}
162 |
163 | # Additional templates that should be rendered to pages, maps page names
164 | # to template names.
165 | #html_additional_pages = {}
166 |
167 | # If false, no module index is generated.
168 | #html_domain_indices = True
169 |
170 | # If false, no index is generated.
171 | #html_use_index = True
172 |
173 | # If true, the index is split into individual pages for each letter.
174 | #html_split_index = False
175 |
176 | # If true, links to the reST sources are added to the pages.
177 | #html_show_sourcelink = True
178 |
179 | # If true, "Created using Sphinx" is shown in the HTML footer.
180 | # Default is True.
181 | #html_show_sphinx = True
182 |
183 | # If true, "(C) Copyright ..." is shown in the HTML footer.
184 | # Default is True.
185 | #html_show_copyright = True
186 |
187 | # If true, an OpenSearch description file will be output, and all pages
188 | # will contain a tag referring to it. The value of this option
189 | # must be the base URL from which the finished HTML is served.
190 | #html_use_opensearch = ''
191 |
192 | # This is the file name suffix for HTML files (e.g. ".xhtml").
193 | #html_file_suffix = None
194 |
195 | # Output file base name for HTML help builder.
196 | htmlhelp_basename = 'logzerodoc'
197 |
198 |
199 | # -- Options for LaTeX output ------------------------------------------
200 |
201 | latex_elements = {
202 | # The paper size ('letterpaper' or 'a4paper').
203 | #'papersize': 'letterpaper',
204 |
205 | # The font size ('10pt', '11pt' or '12pt').
206 | #'pointsize': '10pt',
207 |
208 | # Additional stuff for the LaTeX preamble.
209 | #'preamble': '',
210 | }
211 |
212 | # Grouping the document tree into LaTeX files. List of tuples
213 | # (source start file, target name, title, author, documentclass
214 | # [howto/manual]).
215 | latex_documents = [
216 | ('index', 'logzero.tex',
217 | u'logzero Documentation',
218 | u'Chris Hager', 'manual'),
219 | ]
220 |
221 | # The name of an image file (relative to this directory) to place at
222 | # the top of the title page.
223 | #latex_logo = None
224 |
225 | # For "manual" documents, if this is true, then toplevel headings
226 | # are parts, not chapters.
227 | #latex_use_parts = False
228 |
229 | # If true, show page references after internal links.
230 | #latex_show_pagerefs = False
231 |
232 | # If true, show URL addresses after external links.
233 | #latex_show_urls = False
234 |
235 | # Documents to append as an appendix to all manuals.
236 | #latex_appendices = []
237 |
238 | # If false, no module index is generated.
239 | #latex_domain_indices = True
240 |
241 |
242 | # -- Options for manual page output ------------------------------------
243 |
244 | # One entry per manual page. List of tuples
245 | # (source start file, name, description, authors, manual section).
246 | man_pages = [
247 | ('index', 'logzero',
248 | u'logzero Documentation',
249 | [u'Chris Hager'], 1)
250 | ]
251 |
252 | # If true, show URL addresses after external links.
253 | #man_show_urls = False
254 |
255 |
256 | # -- Options for Texinfo output ----------------------------------------
257 |
258 | # Grouping the document tree into Texinfo files. List of tuples
259 | # (source start file, target name, title, author,
260 | # dir menu entry, description, category)
261 | texinfo_documents = [
262 | ('index', 'logzero',
263 | u'logzero Documentation',
264 | u'Chris Hager',
265 | 'logzero',
266 | 'One line description of project.',
267 | 'Miscellaneous'),
268 | ]
269 |
270 | # Documents to append as an appendix to all manuals.
271 | #texinfo_appendices = []
272 |
273 | # If false, no module index is generated.
274 | #texinfo_domain_indices = True
275 |
276 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
277 | #texinfo_show_urls = 'footnote'
278 |
279 | # If true, do not generate a @detailmenu in the "Top" node's menu.
280 | #texinfo_no_detailmenu = False
281 |
--------------------------------------------------------------------------------
/docs/contributing.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../CONTRIBUTING.rst
2 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. highlight:: shell
2 | .. _index:
3 |
4 | ===================================
5 | `logzero`: Python logging made easy
6 | ===================================
7 |
8 | Robust and effective logging for Python 2 and 3.
9 |
10 | .. image:: _static/demo-output-with-beaver.png
11 | :alt: Logo
12 |
13 | **Features**
14 |
15 | * Easy logging to console and/or (rotating) file.
16 | * Provides a fully configured `Python logger object `_.
17 | * Pretty formatting, including level-specific colors in the console.
18 | * JSON logging support (with integrated `python-json-logger `_)
19 | * Windows color output supported by `colorama`_
20 | * Robust against str/bytes encoding problems, works with all kinds of character encodings and special characters.
21 | * Multiple loggers can write to the same logfile (also works across multiple Python files).
22 | * Global default logger with `logzero.logger <#i-logzero-logger>`_ and custom loggers with `logzero.setup_logger(..) <#i-logzero-setup-logger>`_.
23 | * Compatible with Python 2 and 3.
24 | * All contained in a `single file`_.
25 | * Licensed under the MIT license.
26 | * Heavily inspired by the `Tornado web framework`_.
27 | * Hosted on GitHub: https://github.com/metachris/logzero
28 |
29 |
30 | Installation
31 | ============
32 |
33 | Install `logzero` with `pip`_:
34 |
35 | .. code-block:: console
36 |
37 | $ pip install -U logzero
38 |
39 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide
40 | you through the process.
41 |
42 | You can also install `logzero` from the public `Github repo`_:
43 |
44 | .. code-block:: console
45 |
46 | $ git clone https://github.com/metachris/logzero.git
47 | $ cd logzero
48 | $ python setup.py install
49 |
50 | .. _pip: https://pip.pypa.io
51 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
52 | .. _Github repo: https://github.com/metachris/logzero
53 | .. _tarball: https://github.com/metachris/logzero/tarball/master
54 | .. _single file: https://github.com/metachris/logzero/blob/master/logzero/__init__.py
55 | .. _Tornado web framework: https://github.com/tornadoweb/tornado
56 | .. _colorama: https://github.com/tartley/colorama
57 |
58 |
59 | Example usage
60 | =============
61 |
62 | You can use `logzero` like this (logs only to the console by default):
63 |
64 | .. code-block:: python
65 |
66 | from logzero import logger
67 |
68 | logger.debug("hello")
69 | logger.info("info")
70 | logger.warning("warn")
71 | logger.error("error")
72 |
73 | # This is how you'd log an exception
74 | try:
75 | raise Exception("this is a demo exception")
76 | except Exception as e:
77 | logger.exception(e)
78 |
79 | # JSON logging
80 | import logzero
81 | logzero.json()
82 |
83 | logger.info("JSON test")
84 |
85 | # Start writing into a logfile
86 | logzero.logfile("/tmp/logzero-demo.log")
87 |
88 | If this was a file called ``demo.py``, the output will look like this:
89 |
90 | .. image:: _static/demo-output-json.png
91 | :alt: Demo output in color
92 |
93 | Logging to files
94 | ----------------
95 |
96 | You can add logging to a (rotating) logfile like this:
97 |
98 | .. code-block:: python
99 |
100 | import logzero
101 | from logzero import logger
102 |
103 | # non-rotating logfile
104 | logzero.logfile("/tmp/logfile.log")
105 |
106 | # rotating logfile
107 | logzero.logfile("/tmp/rotating-logfile.log", maxBytes=1e6, backupCount=3)
108 |
109 | # log messages
110 | logger.info("This log message goes to the console and the logfile")
111 |
112 |
113 | JSON logging
114 | ------------
115 |
116 | JSON logging can be enabled for the default logger with `logzero.json()`, or with `setup_logger(json=True)` for custom loggers:
117 |
118 | .. code-block:: python
119 |
120 | # Configure the default logger to output JSON
121 | >>> logzero.json()
122 | >>> logger.info("test")
123 | {"asctime": "2020-10-21 10:42:45,808", "filename": "", "funcName": "", "levelname": "INFO", "levelno": 20, "lineno": 1, "module": "", "message": "test", "name": "logzero_default", "pathname": "", "process": 76179, "processName": "MainProcess", "threadName": "MainThread"}
124 |
125 | # Configure a custom logger to output JSON
126 | >>> my_logger = setup_logger(json=True)
127 | >>> my_logger.info("test")
128 | {"asctime": "2020-10-21 10:42:45,808", "filename": "", "funcName": "", "levelname": "INFO", "levelno": 20, "lineno": 1, "module": "", "message": "test", "name": "logzero_default", "pathname": "", "process": 76179, "processName": "MainProcess", "threadName": "MainThread"}
129 |
130 | The logged JSON object has these fields:
131 |
132 | .. code-block:: json
133 |
134 | {
135 | "asctime": "2020-10-21 10:43:40,765",
136 | "filename": "test.py",
137 | "funcName": "test_this",
138 | "levelname": "INFO",
139 | "levelno": 20,
140 | "lineno": 9,
141 | "module": "test",
142 | "message": "info",
143 | "name": "logzero",
144 | "pathname": "_tests/test.py",
145 | "process": 76204,
146 | "processName": "MainProcess",
147 | "threadName": "MainThread"
148 | }
149 |
150 | An exception logged with `logger.exception(e)` has these:
151 |
152 | .. code-block:: json
153 |
154 | {
155 | "asctime": "2020-10-21 10:43:25,193",
156 | "filename": "test.py",
157 | "funcName": "test_this",
158 | "levelname": "ERROR",
159 | "levelno": 40,
160 | "lineno": 17,
161 | "module": "test",
162 | "message": "this is a demo exception",
163 | "name": "logzero",
164 | "pathname": "_tests/test.py",
165 | "process": 76192,
166 | "processName": "MainProcess",
167 | "threadName": "MainThread",
168 | "exc_info": "Traceback (most recent call last):\n File \"_tests/test.py\", line 15, in test_this\n raise Exception(\"this is a demo exception\")\nException: this is a demo exception"
169 | }
170 |
171 |
172 | Advanced usage examples
173 | -----------------------
174 |
175 | Here are more examples which show how to use logfiles, custom formatters
176 | and setting a minimum loglevel.
177 |
178 | +-----------------------------------------+--------------------------------------------------+
179 | | Outcome | Method |
180 | +=========================================+==================================================+
181 | | Set a minimum log level | `logzero.loglevel(..) <#i-logzero-loglevel>`_ |
182 | +-----------------------------------------+--------------------------------------------------+
183 | | Add logging to a logfile | `logzero.logfile(..) <#i-logzero-logfile>`_ |
184 | +-----------------------------------------+--------------------------------------------------+
185 | | Setup a rotating logfile | `logzero.logfile(..) <#i-logzero-logfile>`_ |
186 | +-----------------------------------------+--------------------------------------------------+
187 | | Disable logging to a logfile | `logzero.logfile(None) <#i-logzero-logfile>`_ |
188 | +-----------------------------------------+--------------------------------------------------+
189 | | JSON logging | `logzero.json(...) <#json-logging>`_ |
190 | +-----------------------------------------+--------------------------------------------------+
191 | | Log to syslog | `logzero.syslog(...) <#i-logzero-logfile>`_ |
192 | +-----------------------------------------+--------------------------------------------------+
193 | | Use a custom formatter | `logzero.formatter(..) <#i-logzero-formatter>`_ |
194 | +-----------------------------------------+--------------------------------------------------+
195 |
196 |
197 | .. code-block:: python
198 |
199 | import logging
200 | import logzero
201 | from logzero import logger
202 |
203 | # This log message goes to the console
204 | logger.debug("hello")
205 |
206 | # Set a minimum log level
207 | logzero.loglevel(logzero.INFO)
208 |
209 | # Set a logfile (all future log messages are also saved there)
210 | logzero.logfile("/tmp/logfile.log")
211 |
212 | # Set a logfile (all future log messages are also saved there), but disable the default stderr logging
213 | logzero.logfile("/tmp/logfile.log", disableStderrLogger=True)
214 |
215 | # You can also set a different loglevel for the file handler
216 | logzero.logfile("/tmp/logfile.log", loglevel=logzero.ERROR)
217 |
218 | # Set a rotating logfile (replaces the previous logfile handler)
219 | logzero.logfile("/tmp/rotating-logfile.log", maxBytes=1000000, backupCount=3)
220 |
221 | # Disable logging to a file
222 | logzero.logfile(None)
223 |
224 | # Enable JSON log format
225 | logzero.json()
226 |
227 | # Disable JSON log format
228 | logzero.json(False)
229 |
230 | # Log to syslog, using default logzero logger and 'user' syslog facility
231 | logzero.syslog()
232 |
233 | # Log to syslog, using default logzero logger and 'local0' syslog facility
234 | logzero.syslog(facility=SysLogHandler.LOG_LOCAL0)
235 |
236 | # Set a custom formatter
237 | formatter = logging.Formatter('%(name)s - %(asctime)-15s - %(levelname)s: %(message)s');
238 | logzero.formatter(formatter)
239 |
240 | # Log some variables
241 | logger.info("var1: %s, var2: %s", var1, var2)
242 |
243 | Custom logger instances
244 | -----------------------
245 |
246 | Instead of using the default logger you can also setup specific logger instances with `logzero.setup_logger(..) <#i-logzero-setup-logger>`_:
247 |
248 | .. code-block:: python
249 |
250 | from logzero import setup_logger
251 | logger1 = setup_logger(name="mylogger1")
252 | logger2 = setup_logger(name="mylogger2", logfile="/tmp/test-logger2.log", level=logzero.INFO)
253 | logger3 = setup_logger(name="mylogger3", logfile="/tmp/test-logger3.log", level=logzero.INFO, disableStderrLogger=True)
254 |
255 | # Log something:
256 | logger1.info("info for logger 1")
257 | logger2.info("info for logger 2")
258 |
259 | # log to a file only, excluding the default stderr logger
260 | logger3.info("info for logger 3")
261 |
262 | # JSON logging in a custom logger
263 | jsonLogger = setup_logger(name="jsonLogger", json=True)
264 | jsonLogger.info("info in json")
265 |
266 |
267 |
268 | Adding custom handlers (eg. SocketHandler)
269 | ------------------------------------------
270 |
271 | Since `logzero` uses the standard `Python logger object `_,
272 | you can attach any `Python logging handlers `_ you can imagine!
273 |
274 | This is how you add a `SocketHandler `_:
275 |
276 | .. code-block:: python
277 |
278 | import logzero
279 | import logging
280 | from logging.handlers import SocketHandler
281 |
282 | # Setup the SocketHandler
283 | socket_handler = SocketHandler(address=('localhost', logging.DEFAULT_TCP_LOGGING_PORT))
284 | socket_handler.setLevel(logzero.DEBUG)
285 | socket_handler.setFormatter(logzero.LogFormatter(color=False))
286 |
287 | # Attach it to the logzero default logger
288 | logzero.logger.addHandler(socket_handler)
289 |
290 | # Log messages
291 | logzero.logger.info("this is a test")
292 |
293 |
294 | Documentation
295 | =============
296 |
297 | .. _i-logzero-logger:
298 |
299 | `logzero.logger`
300 | ----------------
301 |
302 | `logzero.logger` is an already set up standard `Python logger instance `_ for your convenience. You can use it from all your
303 | files and modules directly like this:
304 |
305 | .. code-block:: python
306 |
307 | from logzero import logger
308 |
309 | logger.debug("hello")
310 | logger.info("info")
311 | logger.warning("warning")
312 | logger.error("error")
313 |
314 | You can reconfigure the default logger globally with `logzero.setup_default_logger(..) <#i-logzero-setup-default-logger>`_.
315 |
316 | See the documentation for the `Python logger instance `_ for more information about how you can use it.
317 |
318 |
319 | .. _i-logzero-loglevel:
320 |
321 | `logzero.loglevel(..)`
322 | --------------------------
323 |
324 | .. autofunction:: logzero.loglevel
325 |
326 |
327 | .. _i-logzero-logfile:
328 |
329 | `logzero.logfile(..)`
330 | --------------------------
331 |
332 | .. autofunction:: logzero.logfile
333 |
334 |
335 | .. _i-logzero-formatter:
336 |
337 | `logzero.formatter(..)`
338 | --------------------------
339 |
340 | .. autofunction:: logzero.formatter
341 |
342 |
343 | .. _i-logzero-setup-logger:
344 |
345 | `logzero.setup_logger(..)`
346 | --------------------------
347 |
348 | .. autofunction:: logzero.setup_logger
349 |
350 | .. _i-logzero-setup-default-logger:
351 |
352 |
353 | Default Log Format
354 | ------------------
355 |
356 | This is the default log format string:
357 |
358 | .. code-block:: python
359 |
360 | DEFAULT_FORMAT = '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s'
361 |
362 | See also the `Python LogRecord attributes `_ you can use.
363 |
364 |
365 | Custom Formatting
366 | -----------------
367 |
368 | It is easy to use a custom formatter / a custom log format string:
369 |
370 | * Define your log format string (you can use any of the `LogRecord attributes `_).
371 | * Create a `Formatter object `_ (based on `logzero.LogFormatter` to get all the encoding helpers).
372 | * Supply the formatter object to the `formatter` argument in the `setup_logger(..)` method.
373 |
374 | This is a working example on how to setup logging with a custom format:
375 |
376 | .. code-block:: python
377 |
378 | import logzero
379 |
380 | log_format = '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s'
381 | formatter = logzero.LogFormatter(fmt=log_format)
382 | logzero.setup_default_logger(formatter=formatter)
383 |
384 |
385 | Issues, Feedback & Contributions
386 | ================================
387 |
388 | All kinds of feedback and contributions are welcome.
389 |
390 | * `Create an issue `_
391 | * Create a pull request
392 | * https://github.com/metachris/logzero
393 | * chris@linuxuser.at // `@metachris `_
394 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\logzero.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\logzero.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/examples/demo.py:
--------------------------------------------------------------------------------
1 | # Import the `logzero.logger` instance
2 | from logzero import logger
3 |
4 | # Start logging
5 | logger.debug("hello")
6 | logger.info("info")
7 | logger.warning("warn")
8 | logger.error("error")
9 |
10 | # Log exceptions
11 | try:
12 | raise Exception("this is a demo exception")
13 | except Exception as e:
14 | logger.exception(e)
15 |
16 | # JSON logging
17 | print("\n\nHere starts JSON logging...\n")
18 | import logzero
19 |
20 | logzero.json()
21 | logger.info("JSON test")
22 |
23 | # Logfile (check with `cat /tmp/logzero-demo.log`)
24 | logzero.logfile("/tmp/logzero-demo.log")
25 | logger.info("going into logfile, in JSON format")
26 |
27 | # Disable JSON
28 | logzero.json(False)
29 | logger.info("going into logfile, with standard formatter")
30 |
--------------------------------------------------------------------------------
/examples/demo2.py:
--------------------------------------------------------------------------------
1 | # Import the `logzero.logger` instance
2 | import logzero
3 | from logzero import logger
4 |
5 | logzero.loglevel(logzero.WARNING)
6 |
7 | # Start logging
8 | logger.debug("hello")
9 | logger.info("info")
10 | logger.warning("warn")
11 | logger.error("error")
12 |
--------------------------------------------------------------------------------
/logzero/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | This helper provides a versatile yet easy to use and beautiful logging setup.
4 | You can use it to log to the console and optionally to a logfile. This project
5 | is heavily inspired by the Tornado web framework.
6 |
7 | * https://logzero.readthedocs.io
8 | * https://github.com/metachris/logzero
9 |
10 | The call `logger.info("hello")` prints log messages in this format:
11 |
12 | [I 170213 15:02:00 test:203] hello
13 |
14 | Usage:
15 |
16 | from logzero import logger
17 |
18 | logger.debug("hello")
19 | logger.info("info")
20 | logger.warning("warn")
21 | logger.error("error")
22 |
23 | In order to also log to a file, just use `logzero.logfile(..)`:
24 |
25 | logzero.logfile("/tmp/test.log")
26 |
27 | If you want to use specific loggers instead of the global default logger, use
28 | `setup_logger(..)`:
29 |
30 | logger = logzero.setup_logger(logfile="/tmp/test.log")
31 |
32 | The default loglevel is `DEBUG`. You can set it with the
33 | parameter `level`.
34 |
35 | See the documentation for more information: https://logzero.readthedocs.io
36 | """
37 | import functools
38 | import os
39 | import sys
40 | import logging
41 | from logzero.colors import Fore as ForegroundColors
42 | from logzero.jsonlogger import JsonFormatter
43 |
44 | from logging.handlers import RotatingFileHandler, SysLogHandler
45 | from logging import CRITICAL, ERROR, WARNING, WARN, INFO, DEBUG, NOTSET # noqa: F401
46 |
47 | try:
48 | import curses # type: ignore
49 | except ImportError:
50 | curses = None
51 |
52 | __author__ = """Chris Hager"""
53 | __email__ = 'chris@linuxuser.at'
54 | __version__ = '1.7.0'
55 |
56 | # Python 2+3 compatibility settings for logger
57 | bytes_type = bytes
58 | if sys.version_info >= (3, ):
59 | unicode_type = str
60 | basestring_type = str
61 | xrange = range
62 | else:
63 | # The names unicode and basestring don't exist in py3 so silence flake8.
64 | unicode_type = unicode # noqa
65 | basestring_type = basestring # noqa
66 |
67 | # Formatter defaults
68 | DEFAULT_FORMAT = '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s'
69 | DEFAULT_DATE_FORMAT = '%y%m%d %H:%M:%S'
70 | DEFAULT_COLORS = {
71 | DEBUG: ForegroundColors.CYAN,
72 | INFO: ForegroundColors.GREEN,
73 | WARNING: ForegroundColors.YELLOW,
74 | ERROR: ForegroundColors.RED,
75 | CRITICAL: ForegroundColors.RED
76 | }
77 |
78 | # Name of the internal default logger
79 | LOGZERO_DEFAULT_LOGGER = "logzero_default"
80 |
81 | # Attribute which all internal loggers carry
82 | LOGZERO_INTERNAL_LOGGER_ATTR = "_is_logzero_internal"
83 |
84 | # Attribute signalling whether the handler has a custom loglevel
85 | LOGZERO_INTERNAL_HANDLER_IS_CUSTOM_LOGLEVEL = "_is_logzero_internal_handler_custom_loglevel"
86 |
87 | # Logzero default logger
88 | logger = None
89 |
90 | # Current state of the internal logging settings
91 | _loglevel = DEBUG
92 | _logfile = None
93 | _formatter = None
94 |
95 | # Setup colorama on Windows
96 | if os.name == 'nt':
97 | from colorama import init as colorama_init
98 | colorama_init()
99 |
100 |
101 | def setup_logger(name=__name__, logfile=None, level=DEBUG, formatter=None, maxBytes=0, backupCount=0, fileLoglevel=None, disableStderrLogger=False, isRootLogger=False, json=False, json_ensure_ascii=False):
102 | """
103 | Configures and returns a fully configured logger instance, no hassles.
104 | If a logger with the specified name already exists, it returns the existing instance,
105 | else creates a new one.
106 |
107 | If you set the ``logfile`` parameter with a filename, the logger will save the messages to the logfile,
108 | but does not rotate by default. If you want to enable log rotation, set both ``maxBytes`` and ``backupCount``.
109 |
110 | Usage:
111 |
112 | .. code-block:: python
113 |
114 | from logzero import setup_logger
115 | logger = setup_logger()
116 | logger.info("hello")
117 |
118 | :arg string name: Name of the `Logger object `_. Multiple calls to ``setup_logger()`` with the same name will always return a reference to the same Logger object. (default: ``__name__``)
119 | :arg string logfile: If set, also write logs to the specified filename.
120 | :arg int level: Minimum `logging-level `_ to display (default: ``DEBUG``).
121 | :arg Formatter formatter: `Python logging Formatter object `_ (by default uses the internal LogFormatter).
122 | :arg int maxBytes: Size of the logfile when rollover should occur. Defaults to 0, rollover never occurs.
123 | :arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs.
124 | :arg int fileLoglevel: Minimum `logging-level `_ for the file logger (is not set, it will use the loglevel from the ``level`` argument)
125 | :arg bool disableStderrLogger: Should the default stderr logger be disabled. Defaults to False.
126 | :arg bool isRootLogger: If True then returns a root logger. Defaults to False. (see also the `Python docs `_).
127 | :arg bool json: If True then log in JSON format. Defaults to False. (uses `python-json-logger `_).
128 | :arg bool json_ensure_ascii: Passed to json.dumps as `ensure_ascii`, default: False (if False: writes utf-8 characters, if True: ascii only representation of special characters - eg. '\u00d6\u00df')
129 | :return: A fully configured Python logging `Logger object `_ you can use with ``.debug("msg")``, etc.
130 | """
131 | _logger = logging.getLogger(None if isRootLogger else name)
132 | _logger.propagate = False
133 |
134 | # set the minimum level needed for the logger itself (the lowest handler level)
135 | minLevel = fileLoglevel if fileLoglevel and fileLoglevel < level else level
136 | _logger.setLevel(minLevel)
137 |
138 | # Setup default formatter
139 | _formatter = _get_json_formatter(json_ensure_ascii) if json else formatter or LogFormatter()
140 |
141 | # Reconfigure existing handlers
142 | stderr_stream_handler = None
143 | for handler in list(_logger.handlers):
144 | if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR):
145 | if isinstance(handler, logging.FileHandler):
146 | # Internal FileHandler needs to be removed and re-setup to be able
147 | # to set a new logfile.
148 | _logger.removeHandler(handler)
149 | continue
150 | elif isinstance(handler, logging.StreamHandler):
151 | stderr_stream_handler = handler
152 |
153 | # reconfigure handler
154 | handler.setLevel(level)
155 | handler.setFormatter(_formatter)
156 |
157 | # remove the stderr handler (stream_handler) if disabled
158 | if disableStderrLogger:
159 | if stderr_stream_handler is not None:
160 | _logger.removeHandler(stderr_stream_handler)
161 | elif stderr_stream_handler is None:
162 | stderr_stream_handler = logging.StreamHandler()
163 | setattr(stderr_stream_handler, LOGZERO_INTERNAL_LOGGER_ATTR, True)
164 | stderr_stream_handler.setLevel(level)
165 | stderr_stream_handler.setFormatter(_formatter)
166 | _logger.addHandler(stderr_stream_handler)
167 |
168 | if logfile:
169 | rotating_filehandler = RotatingFileHandler(filename=logfile, maxBytes=maxBytes, backupCount=backupCount)
170 | setattr(rotating_filehandler, LOGZERO_INTERNAL_LOGGER_ATTR, True)
171 | rotating_filehandler.setLevel(fileLoglevel or level)
172 | rotating_filehandler.setFormatter(_formatter)
173 | _logger.addHandler(rotating_filehandler)
174 |
175 | return _logger
176 |
177 |
178 | class LogFormatter(logging.Formatter):
179 | """
180 | Log formatter used in Tornado. Key features of this formatter are:
181 | * Color support when logging to a terminal that supports it.
182 | * Timestamps on every log line.
183 | * Robust against str/bytes encoding problems.
184 | """
185 | def __init__(self,
186 | color=True,
187 | fmt=DEFAULT_FORMAT,
188 | datefmt=DEFAULT_DATE_FORMAT,
189 | colors=DEFAULT_COLORS):
190 | r"""
191 | :arg bool color: Enables color support.
192 | :arg string fmt: Log message format.
193 | It will be applied to the attributes dict of log records. The
194 | text between ``%(color)s`` and ``%(end_color)s`` will be colored
195 | depending on the level if color support is on.
196 | :arg dict colors: color mappings from logging level to terminal color
197 | code
198 | :arg string datefmt: Datetime format.
199 | Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``.
200 | .. versionchanged:: 3.2
201 | Added ``fmt`` and ``datefmt`` arguments.
202 | """
203 | logging.Formatter.__init__(self, datefmt=datefmt)
204 |
205 | self._fmt = fmt
206 | self._colors = {}
207 | self._normal = ''
208 |
209 | if color and _stderr_supports_color():
210 | self._colors = colors
211 | self._normal = ForegroundColors.RESET
212 |
213 | def format(self, record):
214 | try:
215 | message = record.getMessage()
216 | assert isinstance(message,
217 | basestring_type) # guaranteed by logging
218 | # Encoding notes: The logging module prefers to work with character
219 | # strings, but only enforces that log messages are instances of
220 | # basestring. In python 2, non-ascii bytestrings will make
221 | # their way through the logging framework until they blow up with
222 | # an unhelpful decoding error (with this formatter it happens
223 | # when we attach the prefix, but there are other opportunities for
224 | # exceptions further along in the framework).
225 | #
226 | # If a byte string makes it this far, convert it to unicode to
227 | # ensure it will make it out to the logs. Use repr() as a fallback
228 | # to ensure that all byte strings can be converted successfully,
229 | # but don't do it by default so we don't add extra quotes to ascii
230 | # bytestrings. This is a bit of a hacky place to do this, but
231 | # it's worth it since the encoding errors that would otherwise
232 | # result are so useless (and tornado is fond of using utf8-encoded
233 | # byte strings wherever possible).
234 | record.message = _safe_unicode(message)
235 | except Exception as e:
236 | record.message = "Bad message (%r): %r" % (e, record.__dict__)
237 |
238 | record.asctime = self.formatTime(record, self.datefmt)
239 |
240 | if record.levelno in self._colors:
241 | record.color = self._colors[record.levelno]
242 | record.end_color = self._normal
243 | else:
244 | record.color = record.end_color = ''
245 |
246 | formatted = self._fmt % record.__dict__
247 |
248 | if record.exc_info:
249 | if not record.exc_text:
250 | record.exc_text = self.formatException(record.exc_info)
251 | if record.exc_text:
252 | # exc_text contains multiple lines. We need to _safe_unicode
253 | # each line separately so that non-utf8 bytes don't cause
254 | # all the newlines to turn into '\n'.
255 | lines = [formatted.rstrip()]
256 | lines.extend(
257 | _safe_unicode(ln) for ln in record.exc_text.split('\n'))
258 | formatted = '\n'.join(lines)
259 | return formatted.replace("\n", "\n ")
260 |
261 |
262 | def _stderr_supports_color():
263 | # Colors can be forced with an env variable
264 | if os.getenv('LOGZERO_FORCE_COLOR') == '1':
265 | return True
266 |
267 | # Windows supports colors with colorama
268 | if os.name == 'nt':
269 | return True
270 |
271 | # Detect color support of stderr with curses (Linux/macOS)
272 | if curses and hasattr(sys.stderr, 'isatty') and sys.stderr.isatty():
273 | try:
274 | curses.setupterm()
275 | if curses.tigetnum("colors") > 0:
276 | return True
277 |
278 | except Exception:
279 | pass
280 |
281 | return False
282 |
283 |
284 | _TO_UNICODE_TYPES = (unicode_type, type(None))
285 |
286 |
287 | def to_unicode(value):
288 | """
289 | Converts a string argument to a unicode string.
290 | If the argument is already a unicode string or None, it is returned
291 | unchanged. Otherwise it must be a byte string and is decoded as utf8.
292 | """
293 | if isinstance(value, _TO_UNICODE_TYPES):
294 | return value
295 | if not isinstance(value, bytes):
296 | raise TypeError(
297 | "Expected bytes, unicode, or None; got %r" % type(value))
298 | return value.decode("utf-8")
299 |
300 |
301 | def _safe_unicode(s):
302 | try:
303 | return to_unicode(s)
304 | except UnicodeDecodeError:
305 | return repr(s)
306 |
307 |
308 | def setup_default_logger(logfile=None, level=DEBUG, formatter=None, maxBytes=0, backupCount=0, disableStderrLogger=False):
309 | """
310 | Deprecated. Use `logzero.loglevel(..)`, `logzero.logfile(..)`, etc.
311 |
312 | Globally reconfigures the default `logzero.logger` instance.
313 |
314 | Usage:
315 |
316 | .. code-block:: python
317 |
318 | from logzero import logger, setup_default_logger
319 | setup_default_logger(level=WARN)
320 | logger.info("hello") # this will not be displayed anymore because minimum loglevel was set to WARN
321 |
322 | :arg string logfile: If set, also write logs to the specified filename.
323 | :arg int level: Minimum `logging-level `_ to display (default: `DEBUG`).
324 | :arg Formatter formatter: `Python logging Formatter object `_ (by default uses the internal LogFormatter).
325 | :arg int maxBytes: Size of the logfile when rollover should occur. Defaults to 0, rollover never occurs.
326 | :arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs.
327 | :arg bool disableStderrLogger: Should the default stderr logger be disabled. Defaults to False.
328 | """
329 | global logger
330 | logger = setup_logger(name=LOGZERO_DEFAULT_LOGGER, logfile=logfile, level=level, formatter=formatter, backupCount=backupCount, disableStderrLogger=disableStderrLogger)
331 | return logger
332 |
333 |
334 | def reset_default_logger():
335 | """
336 | Resets the internal default logger to the initial configuration
337 | """
338 | global logger
339 | global _loglevel
340 | global _logfile
341 | global _formatter
342 | _loglevel = DEBUG
343 | _logfile = None
344 | _formatter = None
345 |
346 | # Remove all handlers on exiting logger
347 | if logger:
348 | for handler in list(logger.handlers):
349 | logger.removeHandler(handler)
350 |
351 | # Resetup
352 | logger = setup_logger(name=LOGZERO_DEFAULT_LOGGER, logfile=_logfile, level=_loglevel, formatter=_formatter)
353 |
354 |
355 | # Initially setup the default logger
356 | reset_default_logger()
357 |
358 |
359 | def loglevel(level=DEBUG, update_custom_handlers=False):
360 | """
361 | Set the minimum loglevel for the default logger (`logzero.logger`) and all handlers.
362 |
363 | This reconfigures only the internal handlers of the default logger (eg. stream and logfile).
364 | You can also update the loglevel for custom handlers by using `update_custom_handlers=True`.
365 |
366 | :arg int level: Minimum `logging-level `_ to display (default: `DEBUG`).
367 | :arg bool update_custom_handlers: If you added custom handlers to this logger and want this to update them too, you need to set `update_custom_handlers` to `True`
368 | """
369 | logger.setLevel(level)
370 |
371 | # Reconfigure existing internal handlers
372 | for handler in list(logger.handlers):
373 | if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR) or update_custom_handlers:
374 | # Don't update the loglevel if this handler uses a custom one
375 | if hasattr(handler, LOGZERO_INTERNAL_HANDLER_IS_CUSTOM_LOGLEVEL):
376 | continue
377 |
378 | # Update the loglevel for all default handlers
379 | handler.setLevel(level)
380 |
381 | global _loglevel
382 | _loglevel = level
383 |
384 |
385 | def formatter(formatter, update_custom_handlers=False):
386 | """
387 | Set the formatter for all handlers of the default logger (``logzero.logger``).
388 |
389 | This reconfigures only the logzero internal handlers by default, but you can also
390 | reconfigure custom handlers by using ``update_custom_handlers=True``.
391 |
392 | Beware that setting a formatter which uses colors also may write the color codes
393 | to logfiles.
394 |
395 | :arg Formatter formatter: `Python logging Formatter object `_ (by default uses the internal LogFormatter).
396 | :arg bool update_custom_handlers: If you added custom handlers to this logger and want this to update them too, you need to set ``update_custom_handlers`` to `True`
397 | """
398 | for handler in list(logger.handlers):
399 | if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR) or update_custom_handlers:
400 | handler.setFormatter(formatter)
401 |
402 | global _formatter
403 | _formatter = formatter
404 |
405 |
406 | def logfile(filename, formatter=None, mode='a', maxBytes=0, backupCount=0, encoding=None, loglevel=None, disableStderrLogger=False):
407 | """
408 | Setup logging to file (using a `RotatingFileHandler `_ internally).
409 |
410 | By default, the file grows indefinitely (no rotation). You can use the ``maxBytes`` and
411 | ``backupCount`` values to allow the file to rollover at a predetermined size. When the
412 | size is about to be exceeded, the file is closed and a new file is silently opened
413 | for output. Rollover occurs whenever the current log file is nearly ``maxBytes`` in length;
414 | if either of ``maxBytes`` or ``backupCount`` is zero, rollover never occurs.
415 |
416 | If ``backupCount`` is non-zero, the system will save old log files by appending the
417 | extensions ‘.1’, ‘.2’ etc., to the filename. For example, with a ``backupCount`` of 5
418 | and a base file name of app.log, you would get app.log, app.log.1, app.log.2, up to
419 | app.log.5. The file being written to is always app.log. When this file is filled,
420 | it is closed and renamed to app.log.1, and if files app.log.1, app.log.2, etc. exist,
421 | then they are renamed to app.log.2, app.log.3 etc. respectively.
422 |
423 | :arg string filename: Filename of the logfile. Set to `None` to disable logging to the logfile.
424 | :arg Formatter formatter: `Python logging Formatter object `_ (by default uses the internal LogFormatter).
425 | :arg string mode: mode to open the file with. Defaults to ``a``
426 | :arg int maxBytes: Size of the logfile when rollover should occur. Defaults to 0, rollover never occurs.
427 | :arg int backupCount: Number of backups to keep. Defaults to 0, rollover never occurs.
428 | :arg string encoding: Used to open the file with that encoding.
429 | :arg int loglevel: Set a custom loglevel for the file logger, else uses the current global loglevel.
430 | :arg bool disableStderrLogger: Should the default stderr logger be disabled. Defaults to False.
431 | """
432 | # First, remove any existing file logger
433 | __remove_internal_loggers(logger, disableStderrLogger)
434 |
435 | # If no filename supplied, all is done
436 | if not filename:
437 | return
438 |
439 | # Now add
440 | rotating_filehandler = RotatingFileHandler(filename, mode=mode, maxBytes=maxBytes, backupCount=backupCount, encoding=encoding)
441 |
442 | # Set internal attributes on this handler
443 | setattr(rotating_filehandler, LOGZERO_INTERNAL_LOGGER_ATTR, True)
444 | if loglevel:
445 | setattr(rotating_filehandler, LOGZERO_INTERNAL_HANDLER_IS_CUSTOM_LOGLEVEL, True)
446 |
447 | # Configure the handler and add it to the logger
448 | rotating_filehandler.setLevel(loglevel or _loglevel)
449 | rotating_filehandler.setFormatter(formatter or _formatter or LogFormatter(color=False))
450 | logger.addHandler(rotating_filehandler)
451 |
452 | # If wanting to use a lower loglevel for the file handler, we need to reconfigure the logger level
453 | # (note: this won't change the StreamHandler loglevel)
454 | if loglevel and loglevel < logger.level:
455 | logger.setLevel(loglevel)
456 |
457 |
458 | def __remove_internal_loggers(logger_to_update, disableStderrLogger=True):
459 | """
460 | Remove the internal loggers (e.g. stderr logger and file logger) from the specific logger
461 | :param logger_to_update: the logger to remove internal loggers from
462 | :param disableStderrLogger: should the default stderr logger be disabled? defaults to True
463 | """
464 | for handler in list(logger_to_update.handlers):
465 | if hasattr(handler, LOGZERO_INTERNAL_LOGGER_ATTR):
466 | if isinstance(handler, RotatingFileHandler):
467 | logger_to_update.removeHandler(handler)
468 | elif isinstance(handler, SysLogHandler):
469 | logger_to_update.removeHandler(handler)
470 | elif isinstance(handler, logging.StreamHandler) and disableStderrLogger:
471 | logger_to_update.removeHandler(handler)
472 |
473 |
474 | def syslog(logger_to_update=logger, facility=SysLogHandler.LOG_USER, disableStderrLogger=True):
475 | """
476 | Setup logging to syslog and disable other internal loggers
477 | :param logger_to_update: the logger to enable syslog logging for
478 | :param facility: syslog facility to log to
479 | :param disableStderrLogger: should the default stderr logger be disabled? defaults to True
480 | :return the new SysLogHandler, which can be modified externally (e.g. for custom log level)
481 | """
482 | # remove internal loggers
483 | __remove_internal_loggers(logger_to_update, disableStderrLogger)
484 |
485 | # Setup logzero to only use the syslog handler with the specified facility
486 | syslog_handler = SysLogHandler(facility=facility)
487 | setattr(syslog_handler, LOGZERO_INTERNAL_LOGGER_ATTR, True)
488 | logger_to_update.addHandler(syslog_handler)
489 | return syslog_handler
490 |
491 |
492 | def json(enable=True, json_ensure_ascii=False, update_custom_handlers=False):
493 | """
494 | Enable/disable json logging for all handlers.
495 |
496 | Params:
497 | * json_ensure_ascii ... Passed to json.dumps as `ensure_ascii`, default: False (if False: writes utf-8 characters, if True: ascii only representation of special characters - eg. '\u00d6\u00df')
498 | """
499 |
500 | formatter(_get_json_formatter(json_ensure_ascii) if enable else LogFormatter(), update_custom_handlers=update_custom_handlers)
501 |
502 |
503 | def _get_json_formatter(json_ensure_ascii):
504 | supported_keys = [
505 | 'asctime',
506 | 'filename',
507 | 'funcName',
508 | 'levelname',
509 | 'levelno',
510 | 'lineno',
511 | 'module',
512 | 'message',
513 | 'name',
514 | 'pathname',
515 | 'process',
516 | 'processName',
517 | 'threadName'
518 | ]
519 |
520 | def log_format(x):
521 | return ['%({0:s})s'.format(i) for i in x]
522 | custom_format = ' '.join(log_format(supported_keys))
523 | return JsonFormatter(custom_format, json_ensure_ascii=json_ensure_ascii)
524 |
525 |
526 | def log_function_call(func):
527 | @functools.wraps(func)
528 | def wrap(*args, **kwargs):
529 | args_str = ", ".join([str(arg) for arg in args])
530 | kwargs_str = ", ".join(["%s=%s" % (key, kwargs[key]) for key in kwargs])
531 | if args_str and kwargs_str:
532 | all_args_str = ", ".join([args_str, kwargs_str])
533 | else:
534 | all_args_str = args_str or kwargs_str
535 | logger.debug("%s(%s)", func.__name__, all_args_str)
536 | return func(*args, **kwargs)
537 | return wrap
538 |
539 |
540 | if __name__ == "__main__":
541 | _logger = setup_logger()
542 | _logger.info("hello")
543 |
--------------------------------------------------------------------------------
/logzero/colors.py:
--------------------------------------------------------------------------------
1 | """
2 | Source: https://github.com/tartley/colorama/blob/master/colorama/ansi.py
3 | Copyright: Jonathan Hartley 2013. BSD 3-Clause license.
4 | """
5 |
6 | CSI = '\033['
7 | OSC = '\033]'
8 | BEL = '\007'
9 |
10 |
11 | def code_to_chars(code):
12 | return CSI + str(code) + 'm'
13 |
14 |
15 | def set_title(title):
16 | return OSC + '2;' + title + BEL
17 |
18 |
19 | def clear_screen(mode=2):
20 | return CSI + str(mode) + 'J'
21 |
22 |
23 | def clear_line(mode=2):
24 | return CSI + str(mode) + 'K'
25 |
26 |
27 | class AnsiCodes(object):
28 | def __init__(self):
29 | # the subclasses declare class attributes which are numbers.
30 | # Upon instantiation we define instance attributes, which are the same
31 | # as the class attributes but wrapped with the ANSI escape sequence
32 | for name in dir(self):
33 | if not name.startswith('_'):
34 | value = getattr(self, name)
35 | setattr(self, name, code_to_chars(value))
36 |
37 |
38 | class AnsiCursor(object):
39 | def UP(self, n=1):
40 | return CSI + str(n) + 'A'
41 |
42 | def DOWN(self, n=1):
43 | return CSI + str(n) + 'B'
44 |
45 | def FORWARD(self, n=1):
46 | return CSI + str(n) + 'C'
47 |
48 | def BACK(self, n=1):
49 | return CSI + str(n) + 'D'
50 |
51 | def POS(self, x=1, y=1):
52 | return CSI + str(y) + ';' + str(x) + 'H'
53 |
54 |
55 | class AnsiFore(AnsiCodes):
56 | BLACK = 30
57 | RED = 31
58 | GREEN = 32
59 | YELLOW = 33
60 | BLUE = 34
61 | MAGENTA = 35
62 | CYAN = 36
63 | WHITE = 37
64 | RESET = 39
65 |
66 | # These are fairly well supported, but not part of the standard.
67 | LIGHTBLACK_EX = 90
68 | LIGHTRED_EX = 91
69 | LIGHTGREEN_EX = 92
70 | LIGHTYELLOW_EX = 93
71 | LIGHTBLUE_EX = 94
72 | LIGHTMAGENTA_EX = 95
73 | LIGHTCYAN_EX = 96
74 | LIGHTWHITE_EX = 97
75 |
76 |
77 | class AnsiBack(AnsiCodes):
78 | BLACK = 40
79 | RED = 41
80 | GREEN = 42
81 | YELLOW = 43
82 | BLUE = 44
83 | MAGENTA = 45
84 | CYAN = 46
85 | WHITE = 47
86 | RESET = 49
87 |
88 | # These are fairly well supported, but not part of the standard.
89 | LIGHTBLACK_EX = 100
90 | LIGHTRED_EX = 101
91 | LIGHTGREEN_EX = 102
92 | LIGHTYELLOW_EX = 103
93 | LIGHTBLUE_EX = 104
94 | LIGHTMAGENTA_EX = 105
95 | LIGHTCYAN_EX = 106
96 | LIGHTWHITE_EX = 107
97 |
98 |
99 | class AnsiStyle(AnsiCodes):
100 | BRIGHT = 1
101 | DIM = 2
102 | NORMAL = 22
103 | RESET_ALL = 0
104 |
105 |
106 | Fore = AnsiFore()
107 | Back = AnsiBack()
108 | Style = AnsiStyle()
109 | Cursor = AnsiCursor()
110 |
--------------------------------------------------------------------------------
/logzero/jsonlogger.py:
--------------------------------------------------------------------------------
1 | '''
2 | https://github.com/madzak/python-json-logger
3 |
4 | This library is provided to allow standard python logging
5 | to output log data as JSON formatted strings
6 | '''
7 | import sys
8 | import logging
9 | import json
10 | import re
11 | import traceback
12 | import importlib
13 | from inspect import istraceback
14 | from collections import OrderedDict
15 | from datetime import date, datetime, time
16 |
17 | if sys.version_info >= (3, ):
18 | from datetime import timezone
19 | tz = timezone.utc
20 | else:
21 | tz = None
22 |
23 |
24 | # skip natural LogRecord attributes
25 | # http://docs.python.org/library/logging.html#logrecord-attributes
26 | RESERVED_ATTRS = (
27 | 'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename',
28 | 'funcName', 'levelname', 'levelno', 'lineno', 'module',
29 | 'msecs', 'message', 'msg', 'name', 'pathname', 'process',
30 | 'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName')
31 |
32 |
33 | def merge_record_extra(record, target, reserved):
34 | """
35 | Merges extra attributes from LogRecord object into target dictionary
36 |
37 | :param record: logging.LogRecord
38 | :param target: dict to update
39 | :param reserved: dict or list with reserved keys to skip
40 | """
41 | for key, value in record.__dict__.items():
42 | # this allows to have numeric keys
43 | if (key not in reserved and not (hasattr(key, "startswith") and key.startswith('_'))):
44 | target[key] = value
45 | return target
46 |
47 |
48 | class JsonEncoder(json.JSONEncoder):
49 | """
50 | A custom encoder extending the default JSONEncoder
51 | """
52 |
53 | def default(self, obj):
54 | if isinstance(obj, (date, datetime, time)):
55 | return self.format_datetime_obj(obj)
56 |
57 | elif istraceback(obj):
58 | return ''.join(traceback.format_tb(obj)).strip()
59 |
60 | elif type(obj) == Exception \
61 | or isinstance(obj, Exception) \
62 | or type(obj) == type:
63 | return str(obj)
64 |
65 | try:
66 | return super(JsonEncoder, self).default(obj)
67 |
68 | except TypeError:
69 | try:
70 | return str(obj)
71 |
72 | except Exception:
73 | return None
74 |
75 | def format_datetime_obj(self, obj):
76 | return obj.isoformat()
77 |
78 |
79 | class JsonFormatter(logging.Formatter):
80 | """
81 | A custom formatter to format logging records as json strings.
82 | Extra values will be formatted as str() if not supported by
83 | json default encoder
84 | """
85 |
86 | def __init__(self, *args, **kwargs):
87 | """
88 | :param json_default: a function for encoding non-standard objects
89 | as outlined in http://docs.python.org/2/library/json.html
90 | :param json_encoder: optional custom encoder
91 | :param json_serializer: a :meth:`json.dumps`-compatible callable
92 | that will be used to serialize the log record.
93 | :param json_indent: an optional :meth:`json.dumps`-compatible numeric value
94 | that will be used to customize the indent of the output json.
95 | :param prefix: an optional string prefix added at the beginning of
96 | the formatted string
97 | :param rename_fields: an optional dict, used to rename field names in the output.
98 | Rename message to @message: {'message': '@message'}
99 | :param json_indent: indent parameter for json.dumps
100 | :param json_ensure_ascii: ensure_ascii parameter for json.dumps
101 | :param reserved_attrs: an optional list of fields that will be skipped when
102 | outputting json log record. Defaults to all log record attributes:
103 | http://docs.python.org/library/logging.html#logrecord-attributes
104 | :param timestamp: an optional string/boolean field to add a timestamp when
105 | outputting the json log record. If string is passed, timestamp will be added
106 | to log record using string as key. If True boolean is passed, timestamp key
107 | will be "timestamp". Defaults to False/off.
108 | """
109 | self.json_default = self._str_to_fn(kwargs.pop("json_default", None))
110 | self.json_encoder = self._str_to_fn(kwargs.pop("json_encoder", None))
111 | self.json_serializer = self._str_to_fn(kwargs.pop("json_serializer", json.dumps))
112 | self.json_indent = kwargs.pop("json_indent", None)
113 | self.json_ensure_ascii = kwargs.pop("json_ensure_ascii", True)
114 | self.prefix = kwargs.pop("prefix", "")
115 | self.rename_fields = kwargs.pop("rename_fields", {})
116 | reserved_attrs = kwargs.pop("reserved_attrs", RESERVED_ATTRS)
117 | self.reserved_attrs = dict(zip(reserved_attrs, reserved_attrs))
118 | self.timestamp = kwargs.pop("timestamp", False)
119 |
120 | # super(JsonFormatter, self).__init__(*args, **kwargs)
121 | logging.Formatter.__init__(self, *args, **kwargs)
122 | if not self.json_encoder and not self.json_default:
123 | self.json_encoder = JsonEncoder
124 |
125 | self._required_fields = self.parse()
126 | self._skip_fields = dict(zip(self._required_fields,
127 | self._required_fields))
128 | self._skip_fields.update(self.reserved_attrs)
129 |
130 | def _str_to_fn(self, fn_as_str):
131 | """
132 | If the argument is not a string, return whatever was passed in.
133 | Parses a string such as package.module.function, imports the module
134 | and returns the function.
135 |
136 | :param fn_as_str: The string to parse. If not a string, return it.
137 | """
138 | if not isinstance(fn_as_str, str):
139 | return fn_as_str
140 |
141 | path, _, function = fn_as_str.rpartition('.')
142 | module = importlib.import_module(path)
143 | return getattr(module, function)
144 |
145 | def parse(self):
146 | """
147 | Parses format string looking for substitutions
148 |
149 | This method is responsible for returning a list of fields (as strings)
150 | to include in all log messages.
151 | """
152 | standard_formatters = re.compile(r'\((.+?)\)', re.IGNORECASE)
153 | return standard_formatters.findall(self._fmt)
154 |
155 | def add_fields(self, log_record, record, message_dict):
156 | """
157 | Override this method to implement custom logic for adding fields.
158 | """
159 | for field in self._required_fields:
160 | if field in self.rename_fields:
161 | log_record[self.rename_fields[field]] = record.__dict__.get(field)
162 | else:
163 | log_record[field] = record.__dict__.get(field)
164 | log_record.update(message_dict)
165 | merge_record_extra(record, log_record, reserved=self._skip_fields)
166 |
167 | if self.timestamp:
168 | key = self.timestamp if type(self.timestamp) == str else 'timestamp'
169 | log_record[key] = datetime.fromtimestamp(record.created, tz=tz)
170 |
171 | def process_log_record(self, log_record):
172 | """
173 | Override this method to implement custom logic
174 | on the possibly ordered dictionary.
175 | """
176 | return log_record
177 |
178 | def jsonify_log_record(self, log_record):
179 | """Returns a json string of the log record."""
180 | return self.json_serializer(log_record,
181 | default=self.json_default,
182 | cls=self.json_encoder,
183 | indent=self.json_indent,
184 | ensure_ascii=self.json_ensure_ascii)
185 |
186 | def serialize_log_record(self, log_record):
187 | """Returns the final representation of the log record."""
188 | return "%s%s" % (self.prefix, self.jsonify_log_record(log_record))
189 |
190 | def format(self, record):
191 | """Formats a log record and serializes to json"""
192 | message_dict = {}
193 | if isinstance(record.msg, dict):
194 | message_dict = record.msg
195 | record.message = None
196 | else:
197 | record.message = record.getMessage()
198 | # only format time if needed
199 | if "asctime" in self._required_fields:
200 | record.asctime = self.formatTime(record, self.datefmt)
201 |
202 | # Display formatted exception, but allow overriding it in the
203 | # user-supplied dict.
204 | if record.exc_info and not message_dict.get('exc_info'):
205 | message_dict['exc_info'] = self.formatException(record.exc_info)
206 | if not message_dict.get('exc_info') and record.exc_text:
207 | message_dict['exc_info'] = record.exc_text
208 | # Display formatted record of stack frames
209 | # default format is a string returned from :func:`traceback.print_stack`
210 | try:
211 | if record.stack_info and not message_dict.get('stack_info'):
212 | message_dict['stack_info'] = self.formatStack(record.stack_info)
213 | except AttributeError:
214 | # Python2.7 doesn't have stack_info.
215 | pass
216 |
217 | try:
218 | log_record = OrderedDict()
219 | except NameError:
220 | log_record = {}
221 |
222 | self.add_fields(log_record, record, message_dict)
223 | log_record = self.process_log_record(log_record)
224 |
225 | return self.serialize_log_record(log_record)
226 |
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | pytest==4.6 # pyup: ignore
2 | pytest-runner==5.2
3 | bumpversion==0.6.0
4 | watchdog==0.10.3
5 | flake8==3.8.4
6 | coverage==5.3
7 | cryptography==3.4.6
8 | Sphinx==3.3.1
9 | sphinx-rtd-theme==0.5.0
10 |
--------------------------------------------------------------------------------
/requirements_windows.txt:
--------------------------------------------------------------------------------
1 | colorama==0.4.4
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 1.7.0
3 | commit = True
4 | tag = True
5 |
6 | [bumpversion:file:setup.py]
7 | search = version='{current_version}'
8 | replace = version='{new_version}'
9 |
10 | [bumpversion:file:logzero/__init__.py]
11 | search = __version__ = '{current_version}'
12 | replace = __version__ = '{new_version}'
13 |
14 | [bdist_wheel]
15 | universal = 1
16 |
17 | [flake8]
18 | exclude = docs
19 | ignore = E501
20 |
21 | [aliases]
22 | test = pytest
23 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """The setup script."""
5 |
6 | from setuptools import setup, find_packages
7 |
8 | with open('README.md') as readme_file:
9 | readme = readme_file.read()
10 |
11 | with open('HISTORY.md') as history_file:
12 | history = history_file.read()
13 |
14 | setup(
15 | name='logzero',
16 | version='1.7.0',
17 | description="Robust and effective logging for Python 2 and 3",
18 | long_description=readme + '\n\n' + history,
19 | long_description_content_type='text/markdown',
20 | author="Chris Hager",
21 | author_email='chris@linuxuser.at',
22 | url='https://github.com/metachris/logzero',
23 | packages=find_packages(include=['logzero']),
24 | include_package_data=True,
25 | license="MIT license",
26 | zip_safe=False,
27 | keywords='logzero',
28 | classifiers=[
29 | 'Development Status :: 5 - Production/Stable',
30 | 'Intended Audience :: Developers',
31 | 'License :: OSI Approved :: MIT License',
32 | 'Natural Language :: English',
33 | "Programming Language :: Python :: 2",
34 | 'Programming Language :: Python :: 2.6',
35 | 'Programming Language :: Python :: 2.7',
36 | 'Programming Language :: Python :: 3',
37 | 'Programming Language :: Python :: 3.4',
38 | 'Programming Language :: Python :: 3.5',
39 | 'Programming Language :: Python :: 3.6',
40 | 'Programming Language :: Python :: 3.7',
41 | 'Programming Language :: Python :: 3.8',
42 | 'Programming Language :: Python :: 3.9',
43 | ],
44 | extras_require={
45 | ':sys_platform=="win32"': ['colorama']
46 | }
47 | )
48 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | """Unit test package for logzero."""
4 |
--------------------------------------------------------------------------------
/tests/test_json.py:
--------------------------------------------------------------------------------
1 | """
2 | test json related things
3 | """
4 | import json
5 | import tempfile
6 | import logzero
7 |
8 |
9 | def _test_json_obj_content(obj):
10 | # Check that all fields are contained
11 | attrs = ['asctime', 'filename', 'funcName', 'levelname', 'levelno', 'lineno', 'module', 'message', 'name', 'pathname', 'process', 'processName', 'threadName']
12 | assert obj["message"] == "info"
13 | for attr in attrs:
14 | if attr not in obj:
15 | raise Exception("obj missing key '%s'" % attr)
16 |
17 |
18 | def test_json(capsys):
19 | """
20 | Test json logging
21 | """
22 | # Test setup_logger
23 | logger = logzero.setup_logger(json=True)
24 | logger.info('info')
25 | out, err = capsys.readouterr()
26 | _test_json_obj_content(json.loads(err))
27 |
28 |
29 | def test_json_default_logger(capsys):
30 | # Test default logger
31 | logzero.reset_default_logger()
32 | logzero.logger.info('info')
33 | out, err = capsys.readouterr()
34 | assert "] info" in err
35 |
36 | logzero.json()
37 | logzero.logger.info('info')
38 | out, err = capsys.readouterr()
39 | _test_json_obj_content(json.loads(err))
40 |
41 | logzero.json(False)
42 | logzero.logger.info('info')
43 | out, err = capsys.readouterr()
44 | assert "] info" in err
45 |
46 |
47 | def test_json_logfile(capsys):
48 | # Test default logger
49 | logzero.reset_default_logger()
50 | temp = tempfile.NamedTemporaryFile()
51 | try:
52 | logger = logzero.setup_logger(logfile=temp.name, json=True)
53 | logger.info('info')
54 |
55 | with open(temp.name) as f:
56 | content = f.read()
57 | _test_json_obj_content(json.loads(content))
58 |
59 | finally:
60 | temp.close()
61 |
62 |
63 | def test_json_encoding(capsys):
64 | """
65 | see logzero.json(json_ensure_ascii=True)
66 | """
67 | logzero.reset_default_logger()
68 |
69 | # UTF-8 mode
70 | logzero.json(json_ensure_ascii=False)
71 | logzero.logger.info('ß')
72 | out, err = capsys.readouterr()
73 | json.loads(err) # make sure JSON is valid
74 | assert 'ß' in err
75 | assert 'u00df' not in err
76 |
77 | # ASCII mode
78 | logzero.json(json_ensure_ascii=True)
79 | logzero.logger.info('ß')
80 | out, err = capsys.readouterr()
81 | json.loads(err) # make sure JSON is valid
82 | assert 'u00df' in err
83 | assert 'ß' not in err
84 |
85 | # Default JSON mode should be utf-8
86 | logzero.json()
87 | logzero.logger.info('ß')
88 | out, err = capsys.readouterr()
89 | json.loads(err) # make sure JSON is valid
90 | assert 'ß' in err
91 | assert 'u00df' not in err
92 |
--------------------------------------------------------------------------------
/tests/test_logzero.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | test_logzero
5 | ----------------------------------
6 |
7 | Tests for `logzero` module.
8 | """
9 | import os
10 | import tempfile
11 | import logging
12 |
13 | import logzero
14 |
15 |
16 | def test_write_to_logfile_and_stderr(capsys):
17 | """
18 | When using `logfile=`, should by default log to a file and stderr.
19 | """
20 | logzero.reset_default_logger()
21 | temp = tempfile.NamedTemporaryFile()
22 |
23 | try:
24 | logger = logzero.setup_logger('test_write_to_logfile_and_stderr', logfile=temp.name)
25 | logger.info("test log output")
26 |
27 | _out, err = capsys.readouterr()
28 | assert " test_logzero:" in err
29 | assert err.endswith("test log output\n")
30 |
31 | with open(temp.name) as f:
32 | content = f.read()
33 | assert " test_logzero:" in content
34 | assert content.endswith("test log output\n")
35 | finally:
36 | temp.close()
37 |
38 |
39 | def test_custom_formatter():
40 | """
41 | Should work with a custom formatter.
42 | """
43 | logzero.reset_default_logger()
44 | temp = tempfile.NamedTemporaryFile()
45 | try:
46 | log_format = '%(color)s[%(levelname)1.1s %(asctime)s customnametest:%(lineno)d]%(end_color)s %(message)s'
47 | formatter = logzero.LogFormatter(fmt=log_format)
48 | logger = logzero.setup_logger(logfile=temp.name, formatter=formatter)
49 | logger.info("test log output")
50 |
51 | with open(temp.name) as f:
52 | content = f.read()
53 | assert " customnametest:" in content
54 | assert content.endswith("test log output\n")
55 |
56 | finally:
57 | temp.close()
58 |
59 |
60 | def test_loglevel():
61 | """
62 | Should not log any debug messages if minimum level is set to INFO
63 | """
64 | logzero.reset_default_logger()
65 | temp = tempfile.NamedTemporaryFile()
66 | try:
67 | logger = logzero.setup_logger(logfile=temp.name, level=logzero.INFO)
68 | logger.debug("test log output")
69 |
70 | with open(temp.name) as f:
71 | content = f.read()
72 | assert len(content.strip()) == 0
73 |
74 | finally:
75 | temp.close()
76 |
77 |
78 | def test_bytes():
79 | """
80 | Should properly log bytes
81 | """
82 | logzero.reset_default_logger()
83 | temp = tempfile.NamedTemporaryFile()
84 | try:
85 | logger = logzero.setup_logger(logfile=temp.name)
86 |
87 | testbytes = os.urandom(20)
88 | logger.debug(testbytes)
89 | logger.debug(None)
90 |
91 | # with open(temp.name) as f:
92 | # content = f.read()
93 | # # assert str(testbytes) in content
94 |
95 | finally:
96 | temp.close()
97 |
98 |
99 | def test_unicode():
100 | """
101 | Should log unicode
102 | """
103 | logzero.reset_default_logger()
104 | temp = tempfile.NamedTemporaryFile()
105 | try:
106 | logger = logzero.setup_logger(logfile=temp.name)
107 |
108 | logger.debug("😄 😁 😆 😅 😂")
109 |
110 | with open(temp.name, "rb") as f:
111 | content = f.read()
112 | assert "\\xf0\\x9f\\x98\\x84 \\xf0\\x9f\\x98\\x81 \\xf0\\x9f\\x98\\x86 \\xf0\\x9f\\x98\\x85 \\xf0\\x9f\\x98\\x82\\n" in repr(content)
113 |
114 | finally:
115 | temp.close()
116 |
117 |
118 | def test_multiple_loggers_one_logfile():
119 | """
120 | Should properly log bytes
121 | """
122 | logzero.reset_default_logger()
123 | temp = tempfile.NamedTemporaryFile()
124 | try:
125 | logger1 = logzero.setup_logger(name="logger1", logfile=temp.name)
126 | logger2 = logzero.setup_logger(name="logger2", logfile=temp.name)
127 | logger3 = logzero.setup_logger(name="logger3", logfile=temp.name)
128 |
129 | logger1.info("logger1")
130 | logger2.info("logger2")
131 | logger3.info("logger3")
132 |
133 | with open(temp.name) as f:
134 | content = f.read().strip()
135 | assert "logger1" in content
136 | assert "logger2" in content
137 | assert "logger3" in content
138 | assert len(content.split("\n")) == 3
139 |
140 | finally:
141 | temp.close()
142 |
143 |
144 | def test_default_logger(disableStdErrorLogger=False):
145 | """
146 | Default logger should work and be able to be reconfigured.
147 | """
148 | logzero.reset_default_logger()
149 | temp = tempfile.NamedTemporaryFile()
150 | try:
151 | logzero.setup_default_logger(logfile=temp.name, disableStderrLogger=disableStdErrorLogger)
152 | logzero.logger.debug("debug1") # will be logged
153 |
154 | # Reconfigure with loglevel INFO
155 | logzero.setup_default_logger(logfile=temp.name, level=logzero.INFO, disableStderrLogger=disableStdErrorLogger)
156 | logzero.logger.debug("debug2") # will not be logged
157 | logzero.logger.info("info1") # will be logged
158 |
159 | # Reconfigure with a different formatter
160 | log_format = '%(color)s[xxx]%(end_color)s %(message)s'
161 | formatter = logzero.LogFormatter(fmt=log_format)
162 | logzero.setup_default_logger(logfile=temp.name, level=logzero.INFO, formatter=formatter, disableStderrLogger=disableStdErrorLogger)
163 |
164 | logzero.logger.info("info2") # will be logged with new formatter
165 | logzero.logger.debug("debug3") # will not be logged
166 |
167 | with open(temp.name) as f:
168 | content = f.read()
169 | _test_default_logger_output(content)
170 |
171 | finally:
172 | temp.close()
173 |
174 |
175 | def _test_default_logger_output(content):
176 | assert "] debug1" in content
177 | assert "] debug2" not in content
178 | assert "] info1" in content
179 | assert "xxx] info2" in content
180 | assert "] debug3" not in content
181 |
182 |
183 | def test_setup_logger_reconfiguration():
184 | """
185 | Should be able to reconfigure without loosing custom handlers
186 | """
187 | logzero.reset_default_logger()
188 | temp = tempfile.NamedTemporaryFile()
189 | temp2 = tempfile.NamedTemporaryFile()
190 | try:
191 | logzero.setup_default_logger(logfile=temp.name)
192 |
193 | # Add a custom file handler
194 | filehandler = logging.FileHandler(temp2.name)
195 | filehandler.setLevel(logzero.DEBUG)
196 | filehandler.setFormatter(logzero.LogFormatter(color=False))
197 | logzero.logger.addHandler(filehandler)
198 |
199 | # First debug message goes to both files
200 | logzero.logger.debug("debug1")
201 |
202 | # Reconfigure logger to remove logfile
203 | logzero.setup_default_logger()
204 | logzero.logger.debug("debug2")
205 |
206 | # Reconfigure logger to add logfile
207 | logzero.setup_default_logger(logfile=temp.name)
208 | logzero.logger.debug("debug3")
209 |
210 | # Reconfigure logger to set minimum loglevel to INFO
211 | logzero.setup_default_logger(logfile=temp.name, level=logzero.INFO)
212 | logzero.logger.debug("debug4")
213 | logzero.logger.info("info1")
214 |
215 | # Reconfigure logger to set minimum loglevel back to DEBUG
216 | logzero.setup_default_logger(logfile=temp.name, level=logzero.DEBUG)
217 | logzero.logger.debug("debug5")
218 |
219 | with open(temp.name) as f:
220 | content = f.read()
221 | assert "] debug1" in content
222 | assert "] debug2" not in content
223 | assert "] debug3" in content
224 | assert "] debug4" not in content
225 | assert "] info1" in content
226 | assert "] debug5" in content
227 |
228 | with open(temp2.name) as f:
229 | content = f.read()
230 | assert "] debug1" in content
231 | assert "] debug2" in content
232 | assert "] debug3" in content
233 | assert "] debug4" not in content
234 | assert "] info1" in content
235 | assert "] debug5" in content
236 |
237 | finally:
238 | temp.close()
239 |
240 |
241 | def test_setup_logger_logfile_custom_loglevel(capsys):
242 | """
243 | setup_logger(..) with filelogger and custom loglevel
244 | """
245 | logzero.reset_default_logger()
246 | temp = tempfile.NamedTemporaryFile()
247 | try:
248 | logger = logzero.setup_logger(logfile=temp.name, fileLoglevel=logzero.WARN)
249 | logger.info("info1")
250 | logger.warning("warn1")
251 |
252 | with open(temp.name) as f:
253 | content = f.read()
254 | assert "] info1" not in content
255 | assert "] warn1" in content
256 |
257 | finally:
258 | temp.close()
259 |
260 |
261 | def test_log_function_call():
262 | @logzero.log_function_call
263 | def example():
264 | """example doc"""
265 | pass
266 |
267 | assert example.__name__ == "example"
268 | assert example.__doc__ == "example doc"
269 |
270 |
271 | def test_default_logger_logfile_only(capsys):
272 | """
273 | Run the ``test_default_logger`` with ``disableStdErrorLogger`` set to ``True`` and
274 | confirm that no data is written to stderr
275 | """
276 | test_default_logger(disableStdErrorLogger=True)
277 | out, err = capsys.readouterr()
278 | assert err == ''
279 |
280 |
281 | def test_default_logger_stderr_output(capsys):
282 | """
283 | Run the ``test_default_logger`` and confirm that the proper data is written to stderr
284 | """
285 | test_default_logger()
286 | out, err = capsys.readouterr()
287 | _test_default_logger_output(err)
288 |
289 |
290 | def test_default_logger_syslog_only(capsys):
291 | """
292 | Run a test logging to ``syslog`` and confirm that no data is written to stderr.
293 | Note that the output in syslog is not currently being captured or checked.
294 | """
295 | logzero.reset_default_logger()
296 | logzero.syslog()
297 | logzero.logger.error('debug')
298 | out, err = capsys.readouterr()
299 | assert out == '' and err == ''
300 |
301 |
302 | def test_logfile_lower_loglevel(capsys):
303 | """
304 | logzero.logfile(..) should work with a lower loglevel than the StreamHandler
305 | """
306 | logzero.reset_default_logger()
307 | temp = tempfile.NamedTemporaryFile()
308 | try:
309 | logzero.loglevel(level=logzero.INFO)
310 | logzero.logfile(temp.name, loglevel=logzero.DEBUG)
311 |
312 | logzero.logger.debug("debug")
313 | logzero.logger.info("info")
314 |
315 | with open(temp.name) as f:
316 | content = f.read()
317 | assert "] debug" in content
318 | assert "] info" in content
319 |
320 | finally:
321 | temp.close()
322 |
323 |
324 | def test_logfile_lower_loglevel_setup_logger(capsys):
325 | """
326 | logzero.setup_logger(..) should work with a lower loglevel than the StreamHandler
327 | """
328 | temp = tempfile.NamedTemporaryFile()
329 | try:
330 | logger = logzero.setup_logger(level=logzero.INFO, logfile=temp.name, fileLoglevel=logzero.DEBUG)
331 | logger.debug("debug")
332 | logger.info("info")
333 | with open(temp.name) as f:
334 | content = f.read()
335 | assert "] debug" in content
336 | assert "] info" in content
337 | finally:
338 | temp.close()
339 |
340 |
341 | def test_root_logger(capsys):
342 | """
343 | Test creating a root logger
344 | """
345 | logzero.reset_default_logger()
346 | logger1 = logzero.setup_logger()
347 | assert logger1.name == 'logzero'
348 |
349 | logger2 = logzero.setup_logger(isRootLogger=True)
350 | assert logger2.name == 'root'
351 |
352 | logger3 = logzero.setup_logger(name='')
353 | assert logger3.name == 'root'
354 |
--------------------------------------------------------------------------------
/tests/test_new_api.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | """
4 | test_logzero
5 | ----------------------------------
6 |
7 | Tests for `logzero` module.
8 | """
9 | import os
10 | import tempfile
11 | import logzero
12 |
13 |
14 | def test_api_logfile(capsys):
15 | """
16 | logzero.logfile(..) should work as expected
17 | """
18 | logzero.reset_default_logger()
19 | temp = tempfile.NamedTemporaryFile()
20 | try:
21 | logzero.logger.info("info1")
22 |
23 | # Set logfile
24 | logzero.logfile(temp.name)
25 | logzero.logger.info("info2")
26 |
27 | # Remove logfile
28 | logzero.logfile(None)
29 | logzero.logger.info("info3")
30 |
31 | # Set logfile again
32 | logzero.logfile(temp.name)
33 | logzero.logger.info("info4")
34 |
35 | with open(temp.name) as f:
36 | content = f.read()
37 | assert "] info1" not in content
38 | assert "] info2" in content
39 | assert "] info3" not in content
40 | assert "] info4" in content
41 |
42 | finally:
43 | temp.close()
44 |
45 |
46 | def test_api_loglevel(capsys):
47 | """
48 | Should reconfigure the internal logger loglevel
49 | """
50 | logzero.reset_default_logger()
51 | temp = tempfile.NamedTemporaryFile()
52 | try:
53 | logzero.logfile(temp.name)
54 | logzero.logger.info("info1")
55 | logzero.loglevel(logzero.WARN)
56 | logzero.logger.info("info2")
57 | logzero.logger.warning("warn1")
58 |
59 | with open(temp.name) as f:
60 | content = f.read()
61 | assert "] info1" in content
62 | assert "] info2" not in content
63 | assert "] warn1" in content
64 |
65 | finally:
66 | temp.close()
67 |
68 |
69 | def test_api_loglevel_custom_handlers(capsys):
70 | """
71 | Should reconfigure the internal logger loglevel and custom handlers
72 | """
73 | logzero.reset_default_logger()
74 | # TODO
75 | pass
76 | # temp = tempfile.NamedTemporaryFile()
77 | # try:
78 | # logzero.logfile(temp.name)
79 | # logzero.logger.info("info1")
80 | # logzero.loglevel(logzero.WARN)
81 | # logzero.logger.info("info2")
82 | # logzero.logger.warning("warn1")
83 |
84 | # with open(temp.name) as f:
85 | # content = f.read()
86 | # assert "] info1" in content
87 | # assert "] info2" not in content
88 | # assert "] warn1" in content
89 |
90 | # finally:
91 | # temp.close()
92 |
93 |
94 | def test_api_rotating_logfile(capsys):
95 | """
96 | logzero.rotating_logfile(..) should work as expected
97 | """
98 | logzero.reset_default_logger()
99 | temp = tempfile.NamedTemporaryFile()
100 | try:
101 | logzero.logger.info("info1")
102 |
103 | # Set logfile
104 | logzero.logfile(temp.name, maxBytes=10, backupCount=3)
105 | logzero.logger.info("info2")
106 | logzero.logger.info("info3")
107 |
108 | with open(temp.name) as f:
109 | content = f.read()
110 | assert "] info1" not in content # logged before setting up logfile
111 | assert "] info2" not in content # already rotated out
112 | assert "] info3" in content # already rotated out
113 |
114 | fn_rotated = temp.name + ".1"
115 | assert os.path.exists(fn_rotated)
116 | with open(fn_rotated) as f:
117 | content = f.read()
118 | assert "] info2" in content
119 |
120 | finally:
121 | temp.close()
122 |
123 |
124 | def test_api_logfile_custom_loglevel():
125 | """
126 | logzero.logfile(..) should be able to use a custom loglevel
127 | """
128 | logzero.reset_default_logger()
129 | temp = tempfile.NamedTemporaryFile()
130 | try:
131 | # Set logfile with custom loglevel
132 | logzero.logfile(temp.name, loglevel=logzero.WARN)
133 | logzero.logger.info("info1")
134 | logzero.logger.warning("warn1")
135 |
136 | # If setting a loglevel with logzero.loglevel(..) it will not overwrite
137 | # the custom loglevel of the file handler
138 | logzero.loglevel(logzero.INFO)
139 | logzero.logger.info("info2")
140 | logzero.logger.warning("warn2")
141 |
142 | with open(temp.name) as f:
143 | content = f.read()
144 | assert "] info1" not in content
145 | assert "] warn1" in content
146 | assert "] info2" not in content
147 | assert "] warn2" in content
148 |
149 | finally:
150 | temp.close()
151 |
--------------------------------------------------------------------------------