CHAPTER 3 ・ エラーに強いコード / Lesson 12
ログを残す(logging)
print の代わりに、記録として残す。何件処理して、何件スキップしたかを、あとから確認できる形にします。
このレッスンが終わったら
「logging は、処理の記録を重要度つきで残す仕組み。ログがあれば、あとから『何が起きたか』を確かめられる」と言える。
こんな場面、ありませんか?
Lesson 09 では、読めない行を飛ばしながら、messy.csv を最後まで処理しました。進み具合は、print で表示するのが、いちばん手軽です。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
画面で見るぶんには、これで十分です。ただ、毎晩、自動で動かすプログラムだと、困ることが出てきます。
- 画面を閉じると、表示は消えます。翌朝、「昨夜は何があったか」を確かめられません
- 「ただの進行報告」と「見逃せない問題」が、同じ見た目で並びます
これを解決するのが、処理の記録を残す仕組み、ログ(logging)です。
logging を動かしてみる
標準ライブラリの logging を使います。まず、重要度ごとに、4種類のログを出してみます。
▶ 実行を押すと、ここに結果が出ます
行の先頭に、INFO WARNING ERROR と、重要度の名前が付きました。そして、debug のログは、表示されていません。理由は、次のStepで説明します。
では、Step 1 の処理を、ログで書き直します。最後に、何件成功して、何件スキップしたかの要約も残します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
「問題の行」は WARNING、「始まりと終わりの件数」は INFO と、見た目で区別できます。
仕組みを読む
ログには、重要度の段階があります。これをログレベルと呼びます。
- DEBUG開発中の詳しい確認用
- INFO通常の進行(開始・終了・件数)
- WARNING注意(行を飛ばした、など)
- ERROR失敗(ファイルが読めない)
- CRITICAL続けられない重大な問題
Step 2 の設定は level=logging.INFO でした。だから、INFO 以上が出て、いちばん下の DEBUG は出なかったのです。設定の部分を、1行ずつ日本語にします。
logging.basicConfig(ログの出し方を、次のように決める stream=sys.stdout,出す先は、画面(標準出力)にする level=logging.INFO,INFO 以上のログだけを、出す format="%(levelname)s %(message)s",「重要度の名前」と「本文」を、1行にして出す force=True,前の設定が残っていても、今回の設定で上書きする%(levelname)s重要度の名前(INFO など)%(message)slogging.info(...) に渡した本文この書式には、時刻を表す %(asctime)s も使えます。自分のパソコンで使うときは、時刻を必ず入れます。「いつ起きたか」が分からないログは、役に立たないからです。このサイトでは、実行するたびに時刻が変わってしまうため、使いません。
変えて、試す
① 表示するレベルを変える。 同じログのまま、level だけを WARNING に変えてみます。
▶ 実行を押すと、ここに結果が出ます
INFO が消え、WARNING と ERROR だけが残りました。確認中は DEBUG、普段は INFO か WARNINGと、コードを書き換えずに出す量を調整できます。これが、print との大きな違いです。
② ファイルに残す。 stream= の代わりに filename= を指定すると、ログはファイルに書かれます。書き終えたあと、読み返します。
▶ 実行を押すと、ここに結果が出ます
画面ではなく run.log に書かれ、open で読み返すと、中身が見えました。自分のパソコンでは、このファイルが残るので、翌朝に開いて確認できます。ファイルは、動かすたびに追記されます。毎回作り直したいときは、filemode="w" を足します。
実際の業務用のログは、こんな形をしています(サンプルの app.log です)。1行に、時刻・重要度・本文が入っています。
2025-10-01 09:01:18 WARNING 金額が空欄の行があります(1件目) 2025-10-01 09:01:53 INFO 集計を終了しました(2件目) 2025-10-01 09:02:39 ERROR 日付を読み取れませんでした(3件目) 2025-10-01 09:03:19 INFO 集計を開始しました(4件目) 2025-10-01 09:03:48 INFO 集計を開始しました(5件目) 2025-10-01 09:04:08 WARNING 金額が空欄の行があります(6件目) 2025-10-01 09:05:31 ERROR 日付を読み取れませんでした(7件目) 2025-10-01 09:05:49 INFO 集計を終了しました(8件目) 2025-10-01 09:06:15 INFO 集計を終了しました(9件目) 2025-10-01 09:07:43 ERROR 日付を読み取れませんでした(10件目) 2025-10-01 09:08:06 INFO ファイルを読み込みました(11件目) 2025-10-01 09:08:16 ERROR 日付を読み取れませんでした(12件目) 2025-10-01 09:09:19 INFO ファイルを読み込みました(13件目) 2025-10-01 09:09:56 INFO 集計を開始しました(14件目) 2025-10-01 09:10:46 ERROR 日付を読み取れませんでした(15件目) 2025-10-01 09:12:11 INFO ファイルを読み込みました(16件目) 2025-10-01 09:12:48 ERROR 日付を読み取れませんでした(17件目) 2025-10-01 09:13:59 WARNING 金額が空欄の行があります(18件目) 2025-10-01 09:14:52 INFO ファイルを読み込みました(19件目) 2025-10-01 09:16:00 INFO 集計を開始しました(20件目) 2025-10-01 09:16:57 WARNING 金額が空欄の行があります(21件目) 2025-10-01 09:17:16 WARNING 金額が空欄の行があります(22件目) 2025-10-01 09:17:54 INFO 集計を終了しました(23件目) 2025-10-01 09:18:22 INFO 集計を開始しました(24件目) 2025-10-01 09:19:29 INFO 集計を開始しました(25件目) 2025-10-01 09:20:04 ERROR 日付を読み取れませんでした(26件目) 2025-10-01 09:20:17 INFO 集計を開始しました(27件目) 2025-10-01 09:21:45 INFO 集計を終了しました(28件目) 2025-10-01 09:22:09 INFO ファイルを読み込みました(29件目) 2025-10-01 09:22:55 INFO 集計を開始しました(30件目) 2025-10-01 09:23:01 INFO ファイルを読み込みました(31件目) 2025-10-01 09:23:47 ERROR 日付を読み取れませんでした(32件目) 2025-10-01 09:24:56 WARNING 金額が空欄の行があります(33件目) 2025-10-01 09:26:08 INFO ファイルを読み込みました(34件目) 2025-10-01 09:26:43 INFO 集計を開始しました(35件目) 2025-10-01 09:28:02 ERROR 日付を読み取れませんでした(36件目) 2025-10-01 09:28:51 ERROR 日付を読み取れませんでした(37件目) 2025-10-01 09:29:46 INFO 集計を開始しました(38件目) 2025-10-01 09:30:59 INFO 集計を開始しました(39件目) 2025-10-01 09:32:28 INFO ファイルを読み込みました(40件目)
③ ログに書いてはいけないもの。 ログは、ファイルに残り、あとから複数の人が開くかもしれません。
書いてはいけない
logging.info(f"口座番号 {account_no} への振込を確認")
logging.info(f"ログイン: {user} / パスワード {password}")こう書く
logging.info(f"取引先コード {code} の振込を確認")
logging.info(f"ログイン: {user}")自分で書く
どのお題も、ログの設定は書いてあります(format に時刻は入れていません)。
お題① sales.csv を読み、読み込んだ件数と、売上の合計を、INFO のログに残してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
logging.info(f"{len(rows)}件を読み込みました") とします。合計は sum(int(row["売上"]) for row in rows) で求め、f文字列の {total:,} で桁区切りにします。
答えの例を見る
import csv
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="%(levelname)s %(message)s", force=True)
with open("sales.csv", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
logging.info(f"{len(rows)}件を読み込みました")
total = sum(int(row["売上"]) for row in rows)
logging.info(f"売上の合計は {total:,} 円です")お題② messy.csv の「数量」が空欄の行に、WARNING のログを出してください。最後に、空欄の件数を INFO で残します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
enumerate(rows, start=2) で行番号を付けて回し、row["数量"] == "" のときに logging.warning(...) を出して、件数を1つ増やします。ループのあとで logging.info(...) です。
答えの例を見る
import csv
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="%(levelname)s %(message)s", force=True)
with open("messy.csv", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
empty = 0
for line_no, row in enumerate(rows, start=2):
if row["数量"] == "":
empty += 1
logging.warning(f"{line_no}行目: 数量が空欄です")
logging.info(f"空欄の行は {empty} 件でした")お題③ ログを task.log に書く設定を完成させ、書き終えたあと、ERROR で始まる行が何行あるかを表示してください。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
logging.basicConfig(filename="task.log", encoding="utf-8", level=logging.INFO, format="%(levelname)s %(message)s", force=True) と書きます。
答えの例を見る
import logging
logging.basicConfig(filename="task.log", encoding="utf-8", level=logging.INFO, format="%(levelname)s %(message)s", force=True)
logging.info("処理を開始します")
logging.error("ファイルを読めません")
logging.error("保存できません")
with open("task.log", encoding="utf-8") as f:
lines = f.read().splitlines()
print(len([line for line in lines if line.startswith("ERROR")]))仕事ならどう使う?
理解チェック「金額が空欄の行を飛ばして、処理は続けた」ことを残すとき、ふさわしいレベルはどれでしょう?
理解チェックログに書いてはいけないものは、どれでしょう?