├── .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 | --------------------------------------------------------------------------------