Help us understand the problem. What is going on with this article?

【Python】ログ出力をちょっと良くするライブラリ3種類【logzero・loguru・pylogrus】

pythonのログ出力用のライブラリの紹介です。標準のlogging以上に簡単・便利にログ出力を管理できるライブラリlogzero、loguru、pylogrusの3種類のサンプルコードとその使い方を紹介します。

サンプルコード

各ライブラリのサンプルコードを用意しました。後述の使い方と合わせて参考にしてください。

logzero

ドキュメント: https://github.com/metachris/logzero, https://logzero.readthedocs.io/en/latest/

loggingの代わりに使える系のライブラリ。loggingを簡単に扱えるようにしたもの。

image.png

logzeroの使い方

デフォルト設定で使う

from logzero import logger

logger.trace("sample trace level log message")
logger.debug("sample debug level log message")
logger.info("sample info level log message")
logger.warning("sample warn level log message")
logger.error("sample error level log message")
logger.critical("sample critical level log message")

コンソール出力ログレベルの設定

logzero.loglevel(logging.INFO)

ログファイル出力設定

# 出力設定
logzero.logfile(
    '/var/log/logzero.log',  # ログファイルpath
    loglevel=logging.ERROR,   # ファイルに出力されるログレベル
    maxBytes=1e6,            # 最大ファイルサイズ
    backupCount=3            # 保持する老ファイルの世代数
)

# 出力設定の無効化
logzero.logfile(None)

ログのフォーマット

%(color)sから%(end_color)sまでの部分にログレベルに合わせて色がつきます

log_format = '%(color)s[%(levelname)1.1s %(asctime)s %(name)s %(module)s %(funcName)s:%(lineno)d]%(end_color)s %(message)s'
formatter = logzero.LogFormatter(fmt=log_format)
logzero.formatter(formatter)

logger instanceの作成

# logger instanceの作成
log_format = '%(color)s[%(levelname)1.1s %(asctime)s %(name)s %(module)s %(funcName)s:%(lineno)d]%(end_color)s %(message)s'
formatter = logzero.LogFormatter(fmt=log_format)
custom_logger = logzero.setup_logger(
    name=__name__,
    logfile="/var/log/logzero.log",
    formatter=formatter,
    maxBytes=1000000,
    backupCount=3,
    level=logging.INFO,
    fileLoglevel=logging.ERROR,
    disableStderrLogger=False,
)

# ログ出力
custom_logger.debug("sample class custom logger debug level log message")
custom_logger.info("sample class custom logger info level log message")
custom_logger.warning("sample class custom logger warn level log message")
custom_logger.error("sample class custom logger error level log message")

loguru

ドキュメント: https://github.com/Delgan/loguru, https://loguru.readthedocs.io/en/stable/index.html

loggingとは使い勝手が異なるが簡単かつ直感的に扱える。

image.png

loguruの使い方

ログ出力先を追加する

デフォルトではstderrにのみ出力される。ファイルへの出力も行う場合以下のようにlogger.addする。

logger.add("/var/log/loguru_sample2.log")

ログのローテーション等の設定は

logger.add("file_1.log", rotation="500 MB")    # ファイルサイズでローテーション
logger.add("file_2.log", rotation="12:00")     # 毎日指定時刻でローテーション
logger.add("file_3.log", rotation="1 week")    # 1週間ごとにローテーション
logger.add("file_A.log", retention=3)  # 3世代保持する
logger.add("file_B.log", retention="10 days")  # 10日保持する
logger.add("file_Y.log", compression="zip")    # zipで圧縮する

その他、設定に関してはhttps://loguru.readthedocs.io/en/stable/api/logger.html#file を参照してください。
このlogger.addにはlogのlevel等いくつかの設定可能なパラメータがあります。詳細はhttps://loguru.readthedocs.io/en/stable/api/logger.html?highlight=logger.add#loguru._logger.Logger.add を参考にしてください。※ diagnose, backtraceはFalseの設定をおすすめします。

  • logger.addの例
logger.add(
    "/var/log/sample.log",
    rotation="12:00",
    format=log_format,
    diagnose=False,
    backtrace=False,
    level=LOG_LEVEL
)

フォーマット設定

<green>~~~</green>のように記述し、ログに色を付けられます。
<level>で深刻度により色を変えられます。
ログメッセージはrecordと関連付けられます。recordは辞書型でkeyを{message}のようにformatに埋め込むことで、
ログに埋め込めます。どのようなkeyを持っているかは
https://loguru.readthedocs.io/en/stable/api/logger.html
の「The record dict」を参照してください。

log_format = "<green>{time:YYYY-MM-DDTHH:mm:ss}</green> <level>{level} {message}</level>"

# コンソール出力を設定したい場合、デフォルトの設定を先に消す必要があります。
# 以前にlogger.addしたものも消えるので注意
logger.remove()
logger.add(sys.stderr, format=log_format)

recordにない値をフォーマットに追加する

フォーマットで{extra[extra_value]}のようにextraのdictとして定義し、bindかメッセージの引数として渡せます。

logger.remove()
log_format = "<green>{time}</green>: <level>{level} {message}</level>: {extra[extra_value]}"
logger.add(
    sys.stdout,
    format=log_format,
    serialize=True
)

logger.bind(extra_value="some_extra_value").info("serialize message 01")
logger.info("serialize message 02", extra_value="some_extra_value")

JSONで出力する

serialize=Trueとして出力をjsonにすることができます。

logger.remove()
log_format = "<green>{time}</green>: <level>{level} {message}</level>"
logger.add(
    sys.stdout,
    format=log_format,
    serialize=True
)

Exceptionのloglevelを変える

try:
    raise ValueError("error!")
except ValueError as e:
    logger.opt(exception=True).critical("Exceptionのloglevelを変える")

ログの一部に色を付ける

logger.remove()
logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")
logger.opt(colors=True).info("ログに色をつける <blue>colors</blue>")

recordに格納されている情報をlogに付与する

ログメッセージはrecordと関連付けられます。recordは辞書型でどういったkeyを持っているかは
https://loguru.readthedocs.io/en/stable/api/logger.html
の「The record dict」を参照してください。

logger.opt(record=True).info("recordに格納されている情報をlogに付与する (eg. {record[thread]})")

フォーマットを無視してログ出力

logger.opt(raw=True).info("フォーマットを無視してログ出力\n")

親(呼び出し元)の情報をログに表示する

ログメッセージで使用されるrecord情報が親(呼び出し元)のものになります。
以下の例の場合、child_func()内でログを出力していますが、2020-05-30T18:54:32.505956+0000 INFO 親の情報をログに表示する parent_funcのように{function}にparent_funcが入ります。

logger.remove()
logger.add(sys.stderr, format="{time} {level} {message} {function}", level="INFO")
def child_func():
    logger.opt(depth=1).info("親の情報をログに表示する")

def parent_func():
    child_func()
parent_func()

messageのみに変数を使用する

通常、ログメッセージに変数を使用した場合、その値はrecord['extra']に格納される。capture=Falseにした場合格納されなくなる。

logger.remove()
logger.add(sys.stderr, format="{time} {level} {message} {function}", level="INFO", serialize=True)
logger.opt(capture=False).info("{dest} messageのみに変数を使用する", dest="extra")

出力例

# capture=Falseの場合
{
    ...
    "record": {
        ...
        "extra": {},
        ...
        "message": "Keyword arguments not added to extra dict",
        ...
    }
}

# capture=Trueの場合
{
    ...
    "record": {
        ...
        "extra": {"dest": "extra"},
        ...
        "message": "Keyword arguments not added to extra dict",
        ...
    }
}

lazy: 重い関数実行を制御する

適切なloglebelの時のみ関数を実行するようにします。

def test_lazy():
    print('exec test_razy')
    return 'exec test_razy'

logger.remove()
logger.add(sys.stderr, format="{time} {level} {message}", level="INFO")

# 表示するloglevelはINFOに設定している。
# この時lazy=False(デフォルト)、では以下のDEBUG levelのログは表示されないが、test_lazy()関数は実行されてしまう。
logger.opt(lazy=False).debug("DEBUG LEVEL LOG: {x}", x=test_lazy())

# lazy=Trueとし、以下のようにlambdaを使用する。
# この場合、ログは出力されず、test_lazy()も実行されない。
logger.opt(lazy=True).debug("DEBUG LEVEL LOG: {x}", x=lambda: test_lazy())

# DEBUGレベルに変更し、同じく以下のように実行した場合
# lambdaの実行結果がxに格納される
logger.remove()
logger.add(sys.stderr, format="{time} {level} {message}", level="DEBUG")
logger.opt(lazy=True).debug("DEBUG LEVEL LOG: {x}", x=lambda: test_lazy())

pylogrus

ドキュメント: https://github.com/vmig/pylogrus

golangのlogrus風に使えるようにするloggingを拡張するライブラリ。基本的な使い方はlggingと同じ。

image.png

pylogrusの使い方

基本的な出力とファイルへの出力

logging.setLoggerClass(PyLogrus)を最初に実行することと、pylogrusのTextFormatterを使うこと以外はloggingと同じ。

import logging
from pylogrus import PyLogrus, TextFormatter

logging.setLoggerClass(PyLogrus)
logger = logging.getLogger(__name__)  # type: PyLogrus
logger.setLevel(logging.DEBUG)

formatter = TextFormatter(datefmt='Z', colorize=True)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
logger.addHandler(ch)

ch = logging.FileHandler('/var/log/py_logrus_sample2.log')
ch.setLevel(logging.ERROR)
ch.setFormatter(formatter)
logger.addHandler(ch)

logger.debug("DEBUG MESSAGE")
logger.info("INFO MESSAGE")
logger.warning("WARNING MESSAGE")
logger.error("ERROR MESSAGE")

メッセージにPrefixをつける

# [2020-05-31T13:12:14.428Z]    DEBUG [API] DEBUG MESSAGE
# のように出力される
logger = logger.withPrefix("[API]")
logger.debug("DEBUG MESSAGE")

メッセージにフィールドを追加する

# [2020-05-31T13:12:14.428Z]     INFO INFO MESSAGE; error_code=404
# のように出力される
logger.withFields({'error_code': 404}).info("INFO MESSAGE")

JSON形式で出力

有効化するフィールドをしJsonFormatterを使用する。

enabled_fields = [
    ('name', 'logger_name'),
    ('asctime', 'service_timestamp'),
    ('levelname', 'level'),
    ('threadName', 'thread_name'),
    'message',
    ('exception', 'exception_class'),
    ('stacktrace', 'stack_trace'),
    'module',
    ('funcName', 'function')
]
formatter = JsonFormatter(datefmt='Z', enabled_fields=enabled_fields, indent=2, sort_keys=True)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(formatter)
logger.addHandler(ch)

logger.debug("DEBUG MESSAGE")

まとめ

logzero、loguru、pylogrusの3種類を紹介しました。pythonには標準のloggingがありますが、簡単にちょっといい感じのログ出力をしたい時にこれらを使うとよいと思います。個人的にはloguruが扱いやすいと感じましたが、loggingに慣れているようであればlogzero、pylogrusの使用がおすすめです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした