2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonのloggingを日本時間(JST)にする3つの方法と、ログが黙って消える落とし穴

2
Posted at

はじめに

UTC で動いているサーバーで、Python スクリプトのログ時刻(%(asctime)s)を日本時間にそろえたい、という場面は多いと思います。サーバーのタイムゾーン自体は変えられない(変えたくない)前提です。

AI(Claude)に十数本のスクリプトの修正を任せたところ、最初に入った「よく見る1行」が、ログを日本時間にするどころかログを1行も出さなくしていました。しかも終了コードは 0。この記事では、そのとき分かった書き方の選び方と落とし穴をまとめます。出力はすべて Windows の Python 3.13 で再現確認したものです。

結論:どれを使うか

方法 向いている場面 注意点
Formatter.converter を差し替える 既存スクリプトに1行足したい staticmethod で包む/%z は信用しない
Formatter を継承して formatTime を書く 新規コード・%z も正しく出したい 数行増える
環境変数 TZ を変える プロセス全体を JST 前提で書いている ログ以外の日付計算も変わる

既存コードは1つ目、新規コードは2つ目、で迷いません。

方法1:converter を差し替える(1行で済む)

logging.Formatter は record.created(UNIX 秒)を converter に渡して時刻を作ります。既定は time.localtime なので、UTC サーバーなら UTC で出ます。ここに「UTC + 9時間」を返す関数を入れます。

import logging
import time

logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
logging.Formatter.converter = staticmethod(
    lambda secs: time.gmtime(secs + 9 * 3600)
)
logging.info("hello")

localtime ではなく gmtime を起点にしているので、サーバーが JST 設定だった場合でも 18 時間ずれることがありません。

落とし穴1:staticmethod を外すとログが消える

logging.Formatter.converter = lambda secs: time.gmtime(secs + 9 * 3600)  # NG

これだと標準エラーにこう出て、本来のログは残りません。

--- Logging error ---
Traceback (most recent call last):
  ...
    ct = self.converter(record.created)
TypeError: <lambda>() takes 1 positional argument but 2 were given

クラス属性に入れた Python 関数はメソッドとして束縛され、self が第1引数に入るためです。既定の time.localtime や公式ドキュメント例の time.gmtime は C 実装の組み込み関数なので束縛されず、問題になりません。自作の lambda / def だけがメソッドに化けます。

なお、Formatter のインスタンス属性に入れる場合は束縛されないので動きます。

formatter = logging.Formatter("%(asctime)s %(message)s")
formatter.converter = lambda secs: time.gmtime(secs + 9 * 3600)  # これは動く

クラスかインスタンスかで挙動が変わるので、どちらでも staticmethod で包んでおくのが無難です。

厄介なのは、logging が出力時の例外を握りつぶす設計のため、スクリプトは止まらず終了コードも 0 という点です。cron などで標準エラーを見ていないと「処理は成功、ログファイルは空」という状態だけが残ります。

落とし穴2:同じ関数内の import time で NameError

def setup():
    logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
    logging.Formatter.converter = staticmethod(
        lambda secs: time.gmtime(secs + 9 * 3600)
    )
    logging.info("inside setup")
    import time   # 関数の後半にある import
NameError: cannot access free variable 'time' where it is not associated with a value in enclosing scope. Did you forget to import 'time'?

関数内のどこかに import time があると、その関数全体で time はローカル変数扱いになり、lambda もそれを参照します。ファイル冒頭で import time していても防げません。継ぎ足しを重ねたスクリプトほど関数の途中に import が紛れているので、converter を足す前に同じ関数内を検索しておくと安全です。

落とし穴3:%z は当てにならない

converter 方式は「9時間足した UTC」を作っているだけで、タイムゾーン情報を持ちません。datefmt に %z を入れて +0900 と出ても、それは実行マシンのローカル設定を拾っただけの可能性があります。時差表記まで正しく出したいなら方法2を使います。

方法2:formatTime を書き換える(依存なしで確実)

import logging
from datetime import datetime, timedelta, timezone

JST = timezone(timedelta(hours=9), "JST")

class JSTFormatter(logging.Formatter):
    def formatTime(self, record, datefmt=None):
        dt = datetime.fromtimestamp(record.created, JST)
        if datefmt:
            return dt.strftime(datefmt)
        return dt.strftime("%Y-%m-%d %H:%M:%S") + f",{int(record.msecs):03d}"

handler = logging.StreamHandler()
handler.setFormatter(JSTFormatter("%(asctime)s %(levelname)s %(message)s"))
logging.getLogger().addHandler(handler)

UTC 20:12 に実行した結果:

2026-09-29 05:12:24,788 INFO default datefmt
2026-09-29 05:12:24 +0900 with %z

ZoneInfo を使う場合の注意(Windows)

zoneinfo.ZoneInfo("Asia/Tokyo") も使えますが、Windows では tzdata が無いと失敗し、ここでもログが出なくなります。

zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key Asia/Tokyo'

pip install tzdata するか、日本には夏時間が無いので timezone(timedelta(hours=9)) 固定で十分です。

方法3:TZ 環境変数を使わなかった理由

TZ=Asia/Tokyo で起動すればコード変更ゼロで済みます。ただし影響はプロセス全体に及び、datetime.now() や time.localtime() で「今日」を決めている集計処理まで JST に変わります。JST 0〜9 時の間は UTC と日付が食い違うため、「昨日分を集計するつもりが今日分になる」といったずれが起こりえます。ログの見た目だけ変えたいなら、変更範囲も Formatter に閉じておく方が安全です。

まとめ

症状・目的 原因 対処
ログ時刻を JST にしたい(既存コード) converter の既定が time.localtime staticmethod(lambda secs: time.gmtime(secs + 9 * 3600))
takes 1 positional argument but 2 were given クラス属性の lambda がメソッド扱い staticmethod で包む
cannot access free variable 'time' 同じ関数内の import time 関数内 import を消してファイル冒頭へ
%z が信用できない converter 方式は TZ 情報を持たない formatTime を書き換える
ZoneInfoNotFoundError(Windows) tzdata 未導入 pip install tzdata か固定オフセット
TZ で集計日付までずれた TZ はプロセス全体に効く Formatter 側で対応

どれも失敗してもスクリプトは止まらず終了コード 0 です。ログ設定を変えたら、実際に1行出ることを目で確かめてから本番に入れるのがおすすめです。

元になった記事(AI側の視点で書いたもの): https://kujiragames.com/2026/09/python-logging-jst/

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?