clickと連携してロギングをしてみよう
click-log の使用例
click-log は click を使ったコマンドラインスクリプトにロギング機能を簡単についかすることができます。
インストール
拡張モジュールなので次のようにインストールします。
code: bash condaの場合
$ conda install click-log
code: bash pipの場合
$ pip install click-log
まず、次のような clickのスクリプトを使って、click-logの使い方を説明しましょう。
code: click_log_demo1.py
import click
@click.command()
@click.option('--quiet', default=False, is_flag=True)
def cli(quiet):
click.echo("Dividing by zero.")
try:
1 / 0
except:
click.echo("ERROR: Failed to divide by zero.")
if __name__ == '__main__':
cli()
このスクリプトはオプション--quiet を受け付けています。このフラグが与えられた時に、何も表示したくないとすれば、次のようになるはずです。
code: click_log_demo2.py
import click
@click.command()
@click.option('--quiet', default=False, is_flag=True)
def cli(quiet):
if not quiet: # フラグが指定されていないときは...
click.echo("Dividing by zero.")
try:
1 / 0
except:
click.echo("ERROR: Failed to divide by zero.")
if __name__ == '__main__':
cli()
これは1行の修正ですみましたが、プログラム中にあるclick.echo() およびprint() の箇所を都度 if文で処理しないといけなくなります。以前に説明したように、loggingを使ってログレベルでコントロールする方がスマートです。
code: click_log_demo3.py
import click
import logging
logger = logging.getLogger(__name__)
@click.command()
@click.option('--quiet', default=False, is_flag=True)
def cli(quiet):
if quiet:
logger.setLevel(logging.ERROR)
else:
logger.setLevel(logging.INFO)
if __name__ == '__main__':
cli()
しかし、loggingモジュールは汎用的であることを目的としているため、CLIアプリケーションでログレベルの処理を行う必要でてきます。
ここで、click-logの出番となります。
click-log を使うとログレベルを受け付けるオプション解析や設定などを自動的に処理してくれます。
code: click_log_demo4.py
import click
import click_log
import logging
logger = logging.getLogger(__name__)
click_log.basic_config(logger)
@click.command()
@click_log.simple_verbosity_option(logger)
def cli():
logger.info("Dividing by zero.")
try:
1 / 0
except:
logger.error("Failed to divide by zero.")
if __name__ == '__main__':
cli()
code: bash
$ python click_log_demo4.py --help
Options:
-v, --verbosity LVL Either CRITICAL, ERROR, WARNING, INFO or DEBUG
--help Show this message and exit.
$ python click_log_demo4.py
Dividing by zero.
error: Failed to divide by zero.
$ python click_log_demo4.py -v
Error: -v option requires an argument
$ python click_log_demo4.py -v INFO
Dividing by zero.
error: Failed to divide by zero.
$ python click_log_demo4.py -v ERROR
error: Failed to divide by zero.
もし、ログレベルを指定するためのオプションをデフォルトの--verbosity から変えたい場合は、次のように@click_log.simple_verbosity_option()を呼び出します。
code: click_log_rename.py
import click
import click_log
import logging
logger = logging.getLogger(__name__)
click_log.basic_config(logger)
@click.command()
@click_log.simple_verbosity_option(logger,
"-L", "--log-level",
help="Verbosity. Either CRITICAL, ERROR, WARNING, INFO or DEBUG")
def cli():
logger.info("Dividing by zero.")
try:
1 / 0
except:
logger.error("Failed to divide by zero.")
if __name__ == '__main__':
cli()
code: bash
$ python click_log_rename.py --help
Options:
-l, --log-level LVL Verbosity. Either CRITICAL, ERROR, WARNING, INFO
or DEBUG
--help Show this message and exit.
@click_log.simple_verbosity_option() の第2引数以降は、click_logが内部で@click.option() に渡たしています。
click-loguru の使用例
click-loguru は click を使ったコマンドラインスクリプトに、loguru の機能を使って標準エラー出力あるいはファイルへのロギングさせることができます。
制限としては、python 3.6 以降が必要になります。
インストール
click-loguru は pip でインストールします。
code: bash
$ pip install click-loguru
code: python
import click
from loguru import logger
# module imports
from click_loguru import ClickLoguru
VERSION = "0.2.0"
NAME = "demo"
click_loguru = ClickLoguru(
NAME,
VERSION,
retention=3,
log_dir_parent="tests/data/"
)
@click_loguru.logging_options
@click.group()
@click_loguru.stash_subcommand()
@click.version_option(version=VERSION, prog_name=NAME)
def cli(verbose, quiet, logfile):
"""demo -- a simple cli function with logging by loguru."""
unused_str = f"verbose: {verbose} quiet: {quiet} logfile: {logfile}"
@cli.command()
@click_loguru.init_logger()
@click_loguru.log_elapsed_time()
def test_logging():
"""Log at different severity levels."""
logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
@cli.command()
@click_loguru.init_logger(logfile=False)
def show_context():
"Print value of global quiet option."
options = click_loguru.get_global_options()
print(f"{options}")
@cli.command()
@click_loguru.init_logger(logfile=False)
def quiet_value():
"Print value of global quiet option."
options = click_loguru.get_global_options()
print(f"{options.quiet:d}", end="")
Click にはコマンドライン引数をリストで与えて実行させることができる CliRunnerというクラスが提供されています。
code: python
from click.testing import CliRunner
runner = CliRunner()
runner.invoke(テストする関数, 引数のリスト)
これを使うと設計した通りに動作するかどうかを簡単に繰り返しテストすることができるようになります。コードを改版したときなどで既存の機能に不具合がないかどうかをテストするときに便利になります。
code: python
import os
from pathlib import Path
from click.testing import CliRunner
from click_loguru_demo1 import cli
def test_help():
"""Test help command."""
runner = CliRunner()
result = runner.invoke(cli, [])
print(result.output)
assert result.exit_code == 0
def test_version():
"""Test show_context command."""
runner = CliRunner()
print(result.output)
assert result.exit_code == 0
def test_show_context():
"""Test show_context command."""
runner = CliRunner()
print(result.output)
assert result.exit_code == 0
def test_logging(tmp_path):
"""Test logging to a data file."""
runner = CliRunner()
os.chdir(tmp_path)
assert result.exit_code == 0
assert len(list(Path("tests/data/logs").glob("*"))) == 1
def test_quiet(tmp_path):
"""Test query of global quiet option."""
runner = CliRunner()
os.chdir(tmp_path)
assert result.exit_code == 0
assert not bool(int(result.output))
assert result.exit_code == 0
assert bool(int(result.output))
def test_retention(tmp_path):
"""Test keeping copies of the log file."""
runner = CliRunner()
os.chdir(tmp_path)
log_count = 0
while log_count < 10:
assert result.exit_code == 0
log_count += 1
assert len(list(Path("tests/data/logs").glob("*"))) == 4
if __name__ == '__main__':
test_help()
test_version()
test_show_context()
test_logging('/tmp')
test_quiet('/tmp')
test_retention('/tmp')