├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── click_log
├── __init__.py
├── core.py
└── options.py
├── docs
├── Makefile
├── conf.py
├── index.rst
└── make.bat
├── setup.cfg
├── setup.py
├── tests
└── test_basic.py
└── tox.ini
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | # Run click-log CI
2 |
3 | name: "Test Suite"
4 |
5 | on:
6 | push:
7 | branches:
8 | - master
9 | pull_request:
10 | workflow_dispatch:
11 |
12 | defaults:
13 | run:
14 | shell: bash
15 |
16 | jobs:
17 | tests:
18 | name: "Python ${{ matrix.python-version }} tests"
19 | runs-on: "ubuntu-latest"
20 |
21 | strategy:
22 | fail-fast: false
23 | matrix:
24 | python-version:
25 | - "3.7"
26 | - "3.8"
27 | - "3.9"
28 | - "3.10"
29 | - "pypy-3.7"
30 |
31 | steps:
32 | - name: "Check out the repo"
33 | uses: "actions/checkout@v3"
34 |
35 | - name: "Set up Python"
36 | uses: "actions/setup-python@v3"
37 | with:
38 | python-version: "${{ matrix.python-version }}"
39 |
40 | - name: "Install tox"
41 | run: |
42 | python -m pip install tox tox-gh-actions
43 |
44 | - name: "Run tox for ${{ matrix.python-version }}"
45 | run: |
46 | python -m tox
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | *.pyo
4 | *.egg-ignore
5 | *.egg-info
6 | dist
7 | build/
8 | docs/_build
9 | click.egg-info
10 | .tox
11 | .cache
12 | .pytest_cache
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2015 Markus Unterwaditzer & contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include Makefile
3 |
4 | recursive-include docs *
5 | recursive-include tests *
6 | prune docs/_build
7 |
8 | global-exclude *.py[cdo] __pycache__ *.so *.pyd
9 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | release:
2 | python setup.py sdist bdist_wheel upload
3 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | click-log
2 | =========
3 |
4 | .. image:: https://travis-ci.org/click-contrib/click-log.svg?branch=master
5 | :target: https://travis-ci.org/click-contrib/click-log
6 |
7 | Integrates logging with click.
8 |
9 | - `Documentation `_
10 | - `Source code `_
11 |
12 | License
13 | =======
14 |
15 | Licensed under the MIT, see ``LICENSE``.
16 |
--------------------------------------------------------------------------------
/click_log/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # flake8: noqa
3 |
4 | import click
5 |
6 | __version__ = '0.4.0'
7 |
8 |
9 | if not hasattr(click, 'get_current_context'):
10 | raise RuntimeError('You need Click 5.0.')
11 |
12 | from .core import (
13 | ClickHandler,
14 | ColorFormatter,
15 | basic_config,
16 | )
17 |
18 | from .options import simple_verbosity_option
19 |
--------------------------------------------------------------------------------
/click_log/core.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import logging
4 | import sys
5 |
6 | import click
7 |
8 | LOGGER_KEY = __name__ + '.logger'
9 | DEFAULT_LEVEL = logging.INFO
10 |
11 |
12 | class ColorFormatter(logging.Formatter):
13 | colors = {
14 | 'error': dict(fg='red'),
15 | 'exception': dict(fg='red'),
16 | 'critical': dict(fg='red'),
17 | 'debug': dict(fg='blue'),
18 | 'warning': dict(fg='yellow')
19 | }
20 |
21 | def format(self, record):
22 | if not record.exc_info:
23 | level = record.levelname.lower()
24 | msg = record.getMessage()
25 | if level in self.colors:
26 | prefix = click.style('{}: '.format(level),
27 | **self.colors[level])
28 | msg = '\n'.join(prefix + x for x in msg.splitlines())
29 | return msg
30 | return logging.Formatter.format(self, record)
31 |
32 |
33 | class ClickHandler(logging.Handler):
34 | _use_stderr = True
35 |
36 | def emit(self, record):
37 | try:
38 | msg = self.format(record)
39 | level = record.levelname.lower()
40 | click.echo(msg, err=self._use_stderr)
41 | except Exception:
42 | self.handleError(record)
43 |
44 |
45 | _default_handler = ClickHandler()
46 | _default_handler.formatter = ColorFormatter()
47 |
48 |
49 | def _normalize_logger(logger):
50 | if not isinstance(logger, logging.Logger):
51 | logger = logging.getLogger(logger)
52 | return logger
53 |
54 |
55 | def basic_config(logger=None):
56 | '''Set up the default handler (:py:class:`ClickHandler`) and formatter
57 | (:py:class:`ColorFormatter`) on the given logger.'''
58 | logger = _normalize_logger(logger)
59 | logger.handlers = [_default_handler]
60 | logger.propagate = False
61 |
62 | return logger
63 |
--------------------------------------------------------------------------------
/click_log/options.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import click
4 | from .core import _normalize_logger
5 |
6 |
7 | def simple_verbosity_option(logger=None, *names, **kwargs):
8 | '''A decorator that adds a `--verbosity, -v` option to the decorated
9 | command.
10 |
11 | Name can be configured through ``*names``. Keyword arguments are passed to
12 | the underlying ``click.option`` decorator.
13 | '''
14 |
15 | if not names:
16 | names = ['--verbosity', '-v']
17 | if isinstance(logger, str) and logger.startswith('-'):
18 | raise ValueError('Since click-log 0.2.0, the first argument must now '
19 | 'be a logger.')
20 |
21 | kwargs.setdefault('default', 'INFO')
22 | kwargs.setdefault('metavar', 'LVL')
23 | kwargs.setdefault('expose_value', False)
24 | kwargs.setdefault('help', 'Either CRITICAL, ERROR, WARNING, INFO or DEBUG')
25 | kwargs.setdefault('is_eager', True)
26 |
27 | logger = _normalize_logger(logger)
28 |
29 | def decorator(f):
30 | def _set_level(ctx, param, value):
31 | x = getattr(logging, value.upper(), None)
32 | if x is None:
33 | raise click.BadParameter(
34 | 'Must be CRITICAL, ERROR, WARNING, INFO or DEBUG, not {}'
35 | .format(value)
36 | )
37 | logger.setLevel(x)
38 |
39 | return click.option(*names, callback=_set_level, **kwargs)(f)
40 | return decorator
41 |
--------------------------------------------------------------------------------
/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
23 | help:
24 | @echo "Please use \`make ' where is one of"
25 | @echo " html to make standalone HTML files"
26 | @echo " dirhtml to make HTML files named index.html in directories"
27 | @echo " singlehtml to make a single large HTML file"
28 | @echo " pickle to make pickle files"
29 | @echo " json to make JSON files"
30 | @echo " htmlhelp to make HTML files and a HTML help project"
31 | @echo " qthelp to make HTML files and a qthelp project"
32 | @echo " applehelp to make an Apple Help Book"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " epub3 to make an epub3"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 | @echo " dummy to check syntax errors of document sources"
51 |
52 | .PHONY: clean
53 | clean:
54 | rm -rf $(BUILDDIR)/*
55 |
56 | .PHONY: html
57 | html:
58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
61 |
62 | .PHONY: dirhtml
63 | dirhtml:
64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
65 | @echo
66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
67 |
68 | .PHONY: singlehtml
69 | singlehtml:
70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
71 | @echo
72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
73 |
74 | .PHONY: pickle
75 | pickle:
76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
77 | @echo
78 | @echo "Build finished; now you can process the pickle files."
79 |
80 | .PHONY: json
81 | json:
82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
83 | @echo
84 | @echo "Build finished; now you can process the JSON files."
85 |
86 | .PHONY: htmlhelp
87 | htmlhelp:
88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
89 | @echo
90 | @echo "Build finished; now you can run HTML Help Workshop with the" \
91 | ".hhp project file in $(BUILDDIR)/htmlhelp."
92 |
93 | .PHONY: qthelp
94 | qthelp:
95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
96 | @echo
97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/click-log.qhcp"
100 | @echo "To view the help file:"
101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/click-log.qhc"
102 |
103 | .PHONY: applehelp
104 | applehelp:
105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
106 | @echo
107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
108 | @echo "N.B. You won't be able to view it unless you put it in" \
109 | "~/Library/Documentation/Help or install it in your application" \
110 | "bundle."
111 |
112 | .PHONY: devhelp
113 | devhelp:
114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
115 | @echo
116 | @echo "Build finished."
117 | @echo "To view the help file:"
118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/click-log"
119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/click-log"
120 | @echo "# devhelp"
121 |
122 | .PHONY: epub
123 | epub:
124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
125 | @echo
126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
127 |
128 | .PHONY: epub3
129 | epub3:
130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
131 | @echo
132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
133 |
134 | .PHONY: latex
135 | latex:
136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
137 | @echo
138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
140 | "(use \`make latexpdf' here to do that automatically)."
141 |
142 | .PHONY: latexpdf
143 | latexpdf:
144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
145 | @echo "Running LaTeX files through pdflatex..."
146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
148 |
149 | .PHONY: latexpdfja
150 | latexpdfja:
151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
152 | @echo "Running LaTeX files through platex and dvipdfmx..."
153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
155 |
156 | .PHONY: text
157 | text:
158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
159 | @echo
160 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
161 |
162 | .PHONY: man
163 | man:
164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
165 | @echo
166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
167 |
168 | .PHONY: texinfo
169 | texinfo:
170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
171 | @echo
172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
173 | @echo "Run \`make' in that directory to run these through makeinfo" \
174 | "(use \`make info' here to do that automatically)."
175 |
176 | .PHONY: info
177 | info:
178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
179 | @echo "Running Texinfo files through makeinfo..."
180 | make -C $(BUILDDIR)/texinfo info
181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
182 |
183 | .PHONY: gettext
184 | gettext:
185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
186 | @echo
187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
188 |
189 | .PHONY: changes
190 | changes:
191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
192 | @echo
193 | @echo "The overview file is in $(BUILDDIR)/changes."
194 |
195 | .PHONY: linkcheck
196 | linkcheck:
197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
198 | @echo
199 | @echo "Link check complete; look for any errors in the above output " \
200 | "or in $(BUILDDIR)/linkcheck/output.txt."
201 |
202 | .PHONY: doctest
203 | doctest:
204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
205 | @echo "Testing of doctests in the sources finished, look at the " \
206 | "results in $(BUILDDIR)/doctest/output.txt."
207 |
208 | .PHONY: coverage
209 | coverage:
210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
211 | @echo "Testing of coverage in the sources finished, look at the " \
212 | "results in $(BUILDDIR)/coverage/python.txt."
213 |
214 | .PHONY: xml
215 | xml:
216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
217 | @echo
218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
219 |
220 | .PHONY: pseudoxml
221 | pseudoxml:
222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
223 | @echo
224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
225 |
226 | .PHONY: dummy
227 | dummy:
228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
229 | @echo
230 | @echo "Build finished. Dummy builder generates no files."
231 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import sys
4 | import os
5 |
6 | import click_log
7 |
8 | extensions = ['sphinx.ext.autodoc']
9 |
10 | templates_path = ['_templates']
11 |
12 | source_suffix = '.rst'
13 | master_doc = 'index'
14 |
15 | project = 'click-log'
16 | copyright = '2016, Markus Unterwaditzer & contributors'
17 | author = 'Markus Unterwaditzer & contributors'
18 |
19 | release = click_log.__version__
20 | version = '.'.join(release.split('.')[:2]) # The short X.Y version.
21 |
22 | exclude_patterns = ['_build']
23 |
24 | pygments_style = 'sphinx'
25 |
26 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
27 |
28 | try:
29 | import sphinx_rtd_theme
30 | html_theme = 'sphinx_rtd_theme'
31 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
32 | except ImportError:
33 | html_theme = 'default'
34 | if not on_rtd:
35 | print('-' * 74)
36 | print('Warning: sphinx-rtd-theme not installed, building with default '
37 | 'theme.')
38 | print('-' * 74)
39 |
40 | html_static_path = ['_static']
41 | htmlhelp_basename = 'click-logdoc'
42 |
43 | latex_elements = {}
44 |
45 | latex_documents = [
46 | (master_doc, 'click-log.tex', 'click-log Documentation',
47 | 'Markus Unterwaditzer \\& contributors', 'manual'),
48 | ]
49 |
50 | man_pages = [
51 | (master_doc, 'click-log', 'click-log Documentation',
52 | [author], 1)
53 | ]
54 |
55 | texinfo_documents = [
56 | (master_doc, 'click-log', 'click-log Documentation',
57 | author, 'click-log', 'One line description of project.',
58 | 'Miscellaneous'),
59 | ]
60 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | ==============================================================
2 | Click-log: Simple and beautiful logging for click applications
3 | ==============================================================
4 |
5 | .. include:: ../README.rst
6 |
7 | .. toctree::
8 | :maxdepth: 2
9 |
10 | .. module:: click_log
11 |
12 | Getting started
13 | ===============
14 |
15 | Assuming you have this Click application::
16 |
17 | @click.command()
18 | def cli():
19 | click.echo("Dividing by zero.")
20 |
21 | try:
22 | 1 / 0
23 | except:
24 | click.echo("ERROR: Failed to divide by zero.")
25 |
26 |
27 | Ignore the application's core functionality for a moment. The much more
28 | pressing question here is: How do we add an option to not print anything on
29 | success? We could try this::
30 |
31 | @click.command()
32 | @click.option('--quiet', default=False, is_flag=True)
33 | def cli(quiet):
34 | if not quiet:
35 | click.echo("Dividing by zero.")
36 |
37 | try:
38 | 1 / 0
39 | except:
40 | click.echo("ERROR: Failed to divide by zero.")
41 |
42 | Wrapping if-statements around each ``echo``-call is cumbersome though. And with
43 | that, we discover logging::
44 |
45 | import logging
46 | logger = logging.getLogger(__name__)
47 | # More setup for logging handlers here
48 |
49 | @click.command()
50 | @click.option('--quiet', default=False, is_flag=True)
51 | def cli(quiet):
52 | if quiet:
53 | logger.setLevel(logging.ERROR)
54 | else:
55 | logger.setLevel(logging.INFO)
56 |
57 | ...
58 |
59 | Logging is a better solution, but partly because Python's logging module aims
60 | to be so generic, it doesn't come with sensible defaults for CLI applications.
61 | At some point you might also want to expose more logging levels through more
62 | options, at which point the boilerplate code grows even more.
63 |
64 | This is where click-log comes in::
65 |
66 | import logging
67 | logger = logging.getLogger(__name__)
68 | click_log.basic_config(logger)
69 |
70 | @click.command()
71 | @click_log.simple_verbosity_option(logger)
72 | def cli():
73 | logger.info("Dividing by zero.")
74 |
75 | try:
76 | 1 / 0
77 | except:
78 | logger.error("Failed to divide by zero.")
79 |
80 | The output will look like this::
81 |
82 | Dividing by zero.
83 | error: Failed to divide by zero.
84 |
85 |
86 | The ``error:``-prefix will be red, unless the output is piped to another
87 | command.
88 |
89 | The :py:func:`simple_verbosity_option` decorator adds a ``--verbosity`` option
90 | (a short option ``-v`` is also available) that takes a (case-insensitive) value
91 | of ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``, or ``CRITICAL``, and calls
92 | ``setLevel`` on the given logger accordingly.
93 | The default logger level is ``INFO``.
94 |
95 | .. note::
96 |
97 | Make sure to define the `simple_verbosity_option` as early as possible.
98 | Otherwise logging setup will not be early enough for some of your other
99 | eager options.
100 |
101 | API
102 | ===
103 |
104 |
105 | .. autofunction:: basic_config
106 |
107 | .. autofunction:: simple_verbosity_option
108 |
109 | Classes
110 | -------
111 |
112 | .. autoclass:: ClickHandler
113 |
114 | .. autoclass:: ColorFormatter
115 |
116 |
117 |
118 | Indices and tables
119 | ==================
120 |
121 | * :ref:`genindex`
122 | * :ref:`modindex`
123 | * :ref:`search`
124 |
125 |
--------------------------------------------------------------------------------
/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. epub3 to make an epub3
31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
32 | echo. text to make text files
33 | echo. man to make manual pages
34 | echo. texinfo to make Texinfo files
35 | echo. gettext to make PO message catalogs
36 | echo. changes to make an overview over all changed/added/deprecated items
37 | echo. xml to make Docutils-native XML files
38 | echo. pseudoxml to make pseudoxml-XML files for display purposes
39 | echo. linkcheck to check all external links for integrity
40 | echo. doctest to run all doctests embedded in the documentation if enabled
41 | echo. coverage to run coverage check of the documentation if enabled
42 | echo. dummy to check syntax errors of document sources
43 | goto end
44 | )
45 |
46 | if "%1" == "clean" (
47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
48 | del /q /s %BUILDDIR%\*
49 | goto end
50 | )
51 |
52 |
53 | REM Check if sphinx-build is available and fallback to Python version if any
54 | %SPHINXBUILD% 1>NUL 2>NUL
55 | if errorlevel 9009 goto sphinx_python
56 | goto sphinx_ok
57 |
58 | :sphinx_python
59 |
60 | set SPHINXBUILD=python -m sphinx.__init__
61 | %SPHINXBUILD% 2> nul
62 | if errorlevel 9009 (
63 | echo.
64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
65 | echo.installed, then set the SPHINXBUILD environment variable to point
66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
67 | echo.may add the Sphinx directory to PATH.
68 | echo.
69 | echo.If you don't have Sphinx installed, grab it from
70 | echo.http://sphinx-doc.org/
71 | exit /b 1
72 | )
73 |
74 | :sphinx_ok
75 |
76 |
77 | if "%1" == "html" (
78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
79 | if errorlevel 1 exit /b 1
80 | echo.
81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
82 | goto end
83 | )
84 |
85 | if "%1" == "dirhtml" (
86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
87 | if errorlevel 1 exit /b 1
88 | echo.
89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
90 | goto end
91 | )
92 |
93 | if "%1" == "singlehtml" (
94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
95 | if errorlevel 1 exit /b 1
96 | echo.
97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
98 | goto end
99 | )
100 |
101 | if "%1" == "pickle" (
102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
103 | if errorlevel 1 exit /b 1
104 | echo.
105 | echo.Build finished; now you can process the pickle files.
106 | goto end
107 | )
108 |
109 | if "%1" == "json" (
110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
111 | if errorlevel 1 exit /b 1
112 | echo.
113 | echo.Build finished; now you can process the JSON files.
114 | goto end
115 | )
116 |
117 | if "%1" == "htmlhelp" (
118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
119 | if errorlevel 1 exit /b 1
120 | echo.
121 | echo.Build finished; now you can run HTML Help Workshop with the ^
122 | .hhp project file in %BUILDDIR%/htmlhelp.
123 | goto end
124 | )
125 |
126 | if "%1" == "qthelp" (
127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
128 | if errorlevel 1 exit /b 1
129 | echo.
130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
131 | .qhcp project file in %BUILDDIR%/qthelp, like this:
132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\click-log.qhcp
133 | echo.To view the help file:
134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\click-log.ghc
135 | goto end
136 | )
137 |
138 | if "%1" == "devhelp" (
139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
140 | if errorlevel 1 exit /b 1
141 | echo.
142 | echo.Build finished.
143 | goto end
144 | )
145 |
146 | if "%1" == "epub" (
147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
148 | if errorlevel 1 exit /b 1
149 | echo.
150 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
151 | goto end
152 | )
153 |
154 | if "%1" == "epub3" (
155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
156 | if errorlevel 1 exit /b 1
157 | echo.
158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
159 | goto end
160 | )
161 |
162 | if "%1" == "latex" (
163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
164 | if errorlevel 1 exit /b 1
165 | echo.
166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdf" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "latexpdfja" (
181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
182 | cd %BUILDDIR%/latex
183 | make all-pdf-ja
184 | cd %~dp0
185 | echo.
186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
187 | goto end
188 | )
189 |
190 | if "%1" == "text" (
191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
192 | if errorlevel 1 exit /b 1
193 | echo.
194 | echo.Build finished. The text files are in %BUILDDIR%/text.
195 | goto end
196 | )
197 |
198 | if "%1" == "man" (
199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
200 | if errorlevel 1 exit /b 1
201 | echo.
202 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
203 | goto end
204 | )
205 |
206 | if "%1" == "texinfo" (
207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
208 | if errorlevel 1 exit /b 1
209 | echo.
210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
211 | goto end
212 | )
213 |
214 | if "%1" == "gettext" (
215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
216 | if errorlevel 1 exit /b 1
217 | echo.
218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
219 | goto end
220 | )
221 |
222 | if "%1" == "changes" (
223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
224 | if errorlevel 1 exit /b 1
225 | echo.
226 | echo.The overview file is in %BUILDDIR%/changes.
227 | goto end
228 | )
229 |
230 | if "%1" == "linkcheck" (
231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
232 | if errorlevel 1 exit /b 1
233 | echo.
234 | echo.Link check complete; look for any errors in the above output ^
235 | or in %BUILDDIR%/linkcheck/output.txt.
236 | goto end
237 | )
238 |
239 | if "%1" == "doctest" (
240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
241 | if errorlevel 1 exit /b 1
242 | echo.
243 | echo.Testing of doctests in the sources finished, look at the ^
244 | results in %BUILDDIR%/doctest/output.txt.
245 | goto end
246 | )
247 |
248 | if "%1" == "coverage" (
249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
250 | if errorlevel 1 exit /b 1
251 | echo.
252 | echo.Testing of coverage in the sources finished, look at the ^
253 | results in %BUILDDIR%/coverage/python.txt.
254 | goto end
255 | )
256 |
257 | if "%1" == "xml" (
258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
259 | if errorlevel 1 exit /b 1
260 | echo.
261 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
262 | goto end
263 | )
264 |
265 | if "%1" == "pseudoxml" (
266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
267 | if errorlevel 1 exit /b 1
268 | echo.
269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
270 | goto end
271 | )
272 |
273 | if "%1" == "dummy" (
274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
275 | if errorlevel 1 exit /b 1
276 | echo.
277 | echo.Build finished. Dummy builder generates no files.
278 | goto end
279 | )
280 |
281 | :end
282 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [wheel]
2 | universal = 1
3 |
4 | [flake8]
5 | # W503: Line break before operator
6 | ignore = W503
7 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import ast
4 | import re
5 |
6 | from setuptools import setup
7 |
8 | _version_re = re.compile(r'__version__\s+=\s+(.*)')
9 |
10 | with open('click_log/__init__.py', 'rb') as f:
11 | version = str(ast.literal_eval(_version_re.search(
12 | f.read().decode('utf-8')).group(1)))
13 |
14 | setup(
15 | name='click-log',
16 | version=version,
17 | description='Logging integration for Click',
18 | author='Markus Unterwaditzer',
19 | author_email='markus@unterwaditzer.net',
20 | url='https://github.com/click-contrib/click-log',
21 | license='MIT',
22 | long_description=open('README.rst').read(),
23 | classifiers=[
24 | # See: https://pypi.python.org/pypi?:action=list_classifiers
25 | 'Development Status :: 4 - Beta',
26 | 'Environment :: Console',
27 | 'Environment :: Plugins',
28 | 'Intended Audience :: Developers',
29 | 'License :: OSI Approved :: MIT License',
30 | # List of python versions and their support status:
31 | # https://en.wikipedia.org/wiki/CPython#Version_history
32 | 'Programming Language :: Python',
33 | 'Programming Language :: Python :: 3',
34 | 'Programming Language :: Python :: 3.7',
35 | 'Programming Language :: Python :: 3.8',
36 | 'Programming Language :: Python :: 3.9',
37 | 'Programming Language :: Python :: 3.10',
38 | 'Topic :: System :: Logging',
39 | ],
40 | packages=['click_log'],
41 | install_requires=[
42 | 'click',
43 | ],
44 | )
45 |
--------------------------------------------------------------------------------
/tests/test_basic.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import logging
4 |
5 | import click
6 | from click.testing import CliRunner
7 |
8 | import click_log
9 |
10 | import pytest
11 |
12 |
13 | test_logger = logging.getLogger(__name__)
14 | click_log.basic_config(test_logger)
15 | test_logger.level = logging.INFO
16 |
17 |
18 | @pytest.fixture
19 | def runner():
20 | return CliRunner()
21 |
22 |
23 | def test_basic(runner):
24 | @click.command()
25 | def cli():
26 | test_logger.info('hey')
27 | test_logger.error('damn')
28 |
29 | result = runner.invoke(cli, catch_exceptions=False)
30 | assert not result.exception
31 | assert result.output == 'hey\nerror: damn\n'
32 |
33 |
34 | def test_multilines(runner):
35 | @click.command()
36 | def cli():
37 | test_logger.warning("""
38 | Lorem ipsum dolor sit amet,
39 | consectetur adipiscing elit,
40 | sed do eiusmod tempor incididunt""")
41 |
42 | result = runner.invoke(cli, catch_exceptions=False)
43 | assert not result.exception
44 | assert result.output == (
45 | 'warning: \n'
46 | 'warning: Lorem ipsum dolor sit amet,\n'
47 | 'warning: consectetur adipiscing elit,\n'
48 | 'warning: sed do eiusmod tempor incididunt\n')
49 |
50 |
51 | def test_unicode(runner):
52 | @click.command()
53 | def cli():
54 | test_logger.error(u"""
55 | ❤️ 💔 💌 💕 💞 💓 💗 💖 💘
56 | 💝 💟 💜 💛 💚 💙""")
57 |
58 | result = runner.invoke(cli, catch_exceptions=False)
59 | assert not result.exception
60 | assert result.output == (
61 | 'error: \n'
62 | u'error: ❤️ 💔 💌 💕 💞 💓 💗 💖 💘\n'
63 | u'error: 💝 💟 💜 💛 💚 💙\n')
64 |
65 |
66 | def test_weird_types_log(runner):
67 | @click.command()
68 | def cli():
69 | test_logger.error(42)
70 | test_logger.error('42')
71 | test_logger.error(b'42')
72 | test_logger.error(u'42')
73 |
74 | result = runner.invoke(cli, catch_exceptions=False)
75 | assert not result.exception
76 | assert set(result.output.splitlines()) <= set(('error: 42', 'error: b\'42\''))
77 |
78 |
79 | def test_early_logging(runner):
80 | i = None
81 |
82 | def callback(context, param, value):
83 | test_logger.debug('catch me {}!'.format(i))
84 |
85 | @click.command()
86 | @click_log.simple_verbosity_option(test_logger)
87 | @click.option('--config', is_eager=True, default=None, expose_value=False,
88 | callback=callback)
89 | def cli():
90 | test_logger.debug('hello')
91 |
92 | for i in range(2):
93 | result = runner.invoke(cli, ['-v', 'debug'], catch_exceptions=False)
94 | assert 'debug: hello' in result.output
95 | assert 'debug: catch me {}!'.format(i) in result.output
96 |
97 |
98 | def test_logging_args(runner):
99 | @click.command()
100 | @click_log.simple_verbosity_option(test_logger)
101 | def cli():
102 | test_logger.debug('hello %s', 'world')
103 |
104 | result = runner.invoke(cli, ['-v', 'debug'])
105 | assert 'debug: hello world' in result.output
106 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py37,py38,py39,py310,pypy3
3 |
4 | [testenv]
5 | passenv = LANG
6 | deps =
7 | pytest
8 | git+https://github.com/pallets/click
9 | commands = python -m pytest {posargs}
10 |
11 | [gh-actions]
12 | python =
13 | 3.7: py37
14 | 3.8: py38
15 | 3.9: py39
16 | 3.10: py310
17 | pypy3: pypy3
18 |
--------------------------------------------------------------------------------