最近はPythonでいろいろなプログラムを書くことが増えてきました。デバッグにprint文をよく使っていたのですが、本番環境でのログ管理となると、もう少しちゃんとした方法を使わないといけないのではと度々思っていました。ただ、変更するのはかなり億劫でもあります😫
2025年となり、そういえばloggingモジュールを使えば、ログレベルとか出力先とかいろいろ制御できるよな~🙄と一念発起し、高機能なログ出力ができるPythonのライブラリを導入してみることにしました。
正直、最初は設定が多くて「やっぱりprintでいいんじゃない?🤔」と思っていましたが、使ってみると結構便利で、特に本番環境でのデバッグやトラブルシューティングが格段にやりやすくなりました!
loggingモジュールってどんなもの?
初心者の自分が使ってみて感じたloggingモジュールの特徴を簡単にまとめてみました:
- ログレベル(
DEBUG、INFO、WARNING、ERROR、CRITICAL)で出力を制御できる(本番環境ではERRORだけ出力とか) - ファイルとコンソールに同時出力できる(ログファイルは残しつつ、画面でも確認できる😊)
- 時刻やファイル名、行番号なども自動で記録してくれる(デバッグが捗る!)
- マルチスレッド環境でも安全(printだと出力が混ざることも😱)
- プロジェクト全体で統一したログ形式が使える(チーム開発でめっちゃ重要…チーム開発はしたことないけど🙄)
導入と基本的な使い方
以下のように導入を行っていきます。
1. 基本的な使い方
まずは一番シンプルな使い方から。これならprintとそんなに変わらない感じで使えます:
import logging# 基本設定logging.basicConfig(level=logging.INFO)# ログの出力logging.info('プログラム開始')logging.warning('警告が発生しました')logging.error('エラーが発生しました')
個々までの作業としてはprintよりキータイプする量は多い程度ですが、ログレベルでの分類ができる点はいいかな~って程度です。
2. ファイルにログを出力する
続いては、ログをファイルに保存する設定です。日付ごとにログファイルを作る方法は良い機能かなと思います。
import loggingimport osfrom datetime import datetime# ログ用のディレクトリを作成log_directory = 'logs'if not os.path.exists(log_directory):os.makedirs(log_directory)# 日付付きログファイル名生成log_file = f'{log_directory}/app_{datetime.now().strftime("%Y%m%d")}.log'# loggingの基本設定logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s] %(name)s - %(message)s',datefmt='%Y-%m-%d %H:%M:%S',handlers=[logging.FileHandler(log_file, 'a', 'utf-8'),logging.StreamHandler() #コンソールにも出力])logger = logging.getLogger(__name__)# ログの出力logging.info('プログラム開始')logging.warning('警告が発生しました')logging.error('エラーが発生しました')
これを実行することで、プログラム実行中の標準出力にログ表示できることに加えて、logsディレクトリ(ない場合には作成)に名前に日付のついたログファイル(app_YYYYMMDD.log)が作成され、ログが追記されます。
1つのログ出力でコンソールへの出力とログファイルへ追記ができ、非常に便利ですが、設定は結構大変ですね。もし使うならもっと簡単できる、再利用方法がほしいところです。
(注意)logging.basicConfig()は設定は初回のものだけが有効となり、2回目以降の呼び出しは無視されます。。複数の設定を入れる場合には、この関数内の引数設定を細かくすることになります。
3. 効率的な設定方法(logging.conf編)
先程のコードをプログラム毎に書くというのはさすがにキツイです。そこで、以下のような設定ファイルを使う方法がおすすめですのようです。以下のように記載ができます。※日付付きのファイル名の設定が難しいので省いています。
設定をlogging.confというファイルに記述して呼び出します。(YAML形式でも記述できる 後述)
# logging.conf[loggers]keys=root[handlers]keys=fileHandler,consoleHandler[formatters]keys=simpleFormatter[logger_root]level=INFOhandlers=fileHandler,consoleHandler[handler_fileHandler]class=FileHandlerformatter=simpleFormatterargs=('logs/app.log', 'a', 'utf-8')[handler_consoleHandler]class=StreamHandlerformatter=simpleFormatterargs=(sys.stdout,)[formatter_simpleFormatter]format=%(asctime)s [%(levelname)s] %(name)s - %(message)sdatefmt=%Y-%m-%d %H:%M:%S
そして、Pythonファイルではこれだけで OK!
import loggingimport logging.configlogging.config.fileConfig('logging.conf')logger = logging.getLogger(__name__)logger.info('プログラム開始') # これだけでファイルとコンソールに出力されます
この方法の利点は以下になります。
- 設定がコードから分離される
- 環境による設定変更が容易
- チーム開発で設定の共有がしやすい
確かに一度設定ファイルを作成すればあとの運用はかなり楽になりますね。
4. 効率的な設定方法(YAML編)
今度はYAMLファイルで設定を行ってみます。
YAMLファイルを扱う場合には以下のコマンドを実行してライブラリをインストールしてください。
$ pip install pyyaml
logging設定ファイル(logging_config.yaml)
version: 1formatters:simple:format: '%(asctime)s [%(levelname)s] %(name)s - %(message)s'datefmt: '%Y-%m-%d %H:%M:%S'handlers:console:class: logging.StreamHandlerlevel: INFOformatter: simplestream: ext://sys.stdoutfile:class: logging.FileHandlerlevel: INFOformatter: simple# ここは実行時に動的に設定しますfilename: __dynamic_log_path__encoding: utf-8mode: aloggers:'': # root loggerlevel: INFOhandlers: [console, file]propagate: false
YAMLファイルを使用すれば工夫をして日付付きファイルへの対応もできます。以下のようにユーティリティの関数を作成し、importします。
日付付きログファイル生成用ユーティリティ(logging_utils.py)
import osfrom datetime import datetimedef get_log_filename():log_dir = 'logs'if not os.path.exists(log_dir):os.makedirs(log_dir)return f'logs/app_{datetime.now().strftime("%Y%m%d")}.log'
実際に使用するには以下のように記述します。
main.py
import yamlimport logging.configfrom logging_utils import get_log_filenamedef setup_logging():with open('logging_config.yaml', 'r') as f:config = yaml.safe_load(f)# 動的にログファイル名を設定config['handlers']['file']['filename'] = get_log_filename()logging.config.dictConfig(config)# ロギングの設定setup_logging()logger = logging.getLogger(__name__)# 使用例logger.info('プログラム開始')logger.warning('警告メッセージ')logger.error('エラーメッセージ')
YAMLを使用する利点はconf形式の利点に加えて以下でしょうか。
- 可読性が高い
- ネストされた構造が分かりやすい
- コメントが書きやすい
ただし、pyyamlをインストールすることには注意が必要です。
おわりに
最初は「printで十分じゃない?」と思っていたloggingモジュールですが、使ってみると結構便利で手放せなくなってきました。特にチーム開発や本番環境での運用を考慮するなら、きちんとしたログ管理は必須なんだろうなと考えてしまいます。
loggingの設定は最初は面倒ですが、一度設定ファイルを作ってしまえば、あとは簡単になりますね。print文だらけのコードとはさようなら👋 今年からはちゃんとしたログ管理を目指します!
トラブったときのチェックポイント
私がハマったポイントはこんなところでした。
- ログが出力されない → ログレベルの設定を確認(DEBUGレベルの出力なのにINFO以上の設定になってたり)
- 日本語が文字化けする → エンコーディング指定を忘れてない?
- ログが追記されない → FileHandlerのモードが'w'になってない?正解は'a'
- 設定が反映されない → basicConfigは最初の1回しか効かないので注意
参考リンク