Python logging完全ガイド!ログ出力とログ管理の基本

フリーランスボード

20万件以上の案件から、副業に最適なリモート・週3〜の案件を一括検索できるプラットフォーム。プロフィール登録でAIスカウトが自動的にマッチング案件を提案。市場統計や単価相場、エージェントの口コミも無料で閲覧可能なため、本業を続けながら効率的に高単価の副業案件を探せます。フリーランスボード

ITプロパートナーズ

週2〜3日から働ける柔軟な案件が業界トップクラスの豊富さを誇るフリーランスエージェント。エンド直契約のため高単価で、週3日稼働でも十分な報酬を得られます。リモートや時間フレキシブルな案件も多数。スタートアップ・ベンチャー中心で、トレンド技術を使った魅力的な案件が揃っています。専属エージェントが案件紹介から契約交渉までサポート。利用企業2,000社以上の実績。ITプロパートナーズ

Midworks 10,000件以上の案件を保有し、週3日〜・フルリモートなど柔軟な働き方に対応。高単価案件が豊富で、報酬保障制度(60%)や保険料負担(50%)など正社員並みの手厚い福利厚生が特徴。通勤交通費(月3万円)、スキルアップ費用(月1万円)の支給に加え、リロクラブ・freeeが無料利用可能。非公開案件80%以上、支払いサイト20日で安心して稼働できます。Midworks

Pythonでアプリケーションを開発していて、適切なログ出力方法が分からず困っていませんか?logging(ロギング)は、デバッグ、監視、トラブルシューティングに欠かせない重要な技術です。この記事では、Python標準ライブラリのloggingモジュールの基本から実践的な使い方まで、初心者にも分かりやすく解説します。

目次

Python loggingとは?なぜprint文ではダメなのか

loggingは、アプリケーションの実行状況や問題を記録するためのPython標準機能です。print文と比較して、多くの優れた特徴があります。

loggingのメリット

  • レベル制御: DEBUG、INFO、WARNING、ERROR、CRITICALの5段階
  • 出力先制御: ファイル、コンソール、外部システムへの柔軟な出力
  • フォーマット制御: タイムスタンプ、モジュール名、行番号などの自動付与
  • パフォーマンス: 条件によってログ出力をスキップ
  • 本番環境対応: 設定変更だけでログレベルを調整可能

print文との比較

# ❌ print文(非推奨)
print("ユーザーログイン開始")
print(f"エラー: {error_message}")

# ✅ logging(推奨)
import logging
logging.info("ユーザーログイン開始")
logging.error(f"エラー: {error_message}")

【基本編】Python loggingの基本的な使い方

1. 最も簡単なログ出力

import logging

# 基本設定
logging.basicConfig(level=logging.DEBUG)

# 各レベルでのログ出力
logging.debug("デバッグ情報")
logging.info("一般的な情報")
logging.warning("警告メッセージ")
logging.error("エラーメッセージ")
logging.critical("致命的なエラー")

2. ログレベルの理解

import logging

logging.basicConfig(level=logging.WARNING)

# WARNING以上のみ出力される
logging.debug("表示されない")      # レベル: 10
logging.info("表示されない")       # レベル: 20
logging.warning("表示される")      # レベル: 30
logging.error("表示される")        # レベル: 40
logging.critical("表示される")     # レベル: 50

3. フォーマットの設定

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logging.info("フォーマットされたログメッセージ")
# 出力例: 2025-01-15 10:30:45,123 - root - INFO - フォーマットされたログメッセージ

【設定編】logging設定の詳細

1. ファイルへのログ出力

import logging

# ファイル出力の基本設定
logging.basicConfig(
    filename='app.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    encoding='utf-8'
)

logging.info("ファイルに記録されるメッセージ")

2. コンソールとファイル両方への出力

import logging

# ロガーの作成
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

# フォーマッターの作成
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# ファイルハンドラー
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setFormatter(formatter)

# コンソールハンドラー
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)

# ハンドラーをロガーに追加
logger.addHandler(file_handler)
logger.addHandler(console_handler)

logger.info("コンソールとファイル両方に出力")

3. ローテーティングファイルハンドラー

import logging
from logging.handlers import RotatingFileHandler

# ログファイルのローテーション設定
handler = RotatingFileHandler(
    'app.log',
    maxBytes=1024*1024,  # 1MB
    backupCount=5,       # 最大5個のバックアップ
    encoding='utf-8'
)

logging.basicConfig(
    handlers=[handler],
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

logging.info("ローテーションするログファイルへの出力")

【実践編】実際のアプリケーションでの使用例

1. 関数の実行ログ

import logging
import functools

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def log_function_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logger.info(f"{func.__name__} 実行開始")
        try:
            result = func(*args, **kwargs)
            logger.info(f"{func.__name__} 実行完了")
            return result
        except Exception as e:
            logger.error(f"{func.__name__} でエラー: {e}")
            raise
    return wrapper

@log_function_call
def calculate_total(items):
    return sum(items)

2. エラーハンドリングでのログ活用

import logging

logger = logging.getLogger(__name__)

def safe_divide(a, b):
    logger.debug(f"除算処理: {a} / {b}")
    
    try:
        if b == 0:
            logger.warning("ゼロ除算が発生しました")
            return None
        
        result = a / b
        logger.info(f"除算結果: {result}")
        return result
    
    except Exception as e:
        logger.error(f"予期しないエラー: {e}", exc_info=True)
        return None

3. Webアプリケーションでの使用例

from flask import Flask, request
import logging

app = Flask(__name__)

# アプリケーション用ロガー設定
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    logger.info(f"ユーザー取得リクエスト: {user_id}")
    logger.debug(f"リクエストIP: {request.remote_addr}")
    
    try:
        user = get_user_from_db(user_id)
        if user:
            logger.info(f"ユーザー取得成功: {user.name}")
            return {"id": user.id, "name": user.name}
        else:
            logger.warning(f"ユーザーが見つかりません: {user_id}")
            return {"error": "User not found"}, 404
    
    except Exception as e:
        logger.error(f"ユーザー取得エラー: {e}", exc_info=True)
        return {"error": "Internal server error"}, 500

【設定ファイル編】設定ファイルによるログ管理

1. 辞書形式での設定

import logging
import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'standard',
        },
        'file': {
            'class': 'logging.FileHandler',
            'level': 'INFO',
            'formatter': 'standard',
            'filename': 'app.log',
            'encoding': 'utf-8',
        },
    },
    'loggers': {
        '': {  # root logger
            'handlers': ['console', 'file'],
            'level': 'DEBUG',
            'propagate': False
        }
    }
}

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)
logger.info("設定ファイルを使用したログシステム")

2. JSON設定ファイル

# logging_config.json
{
    "version": 1,
    "disable_existing_loggers": false,
    "formatters": {
        "standard": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "standard"
        }
    },
    "root": {
        "level": "INFO",
        "handlers": ["console"]
    }
}
import json
import logging.config

# JSON設定ファイルの読み込み
with open('logging_config.json', 'r') as f:
    config = json.load(f)

logging.config.dictConfig(config)
logger = logging.getLogger(__name__)
logger.info("JSON設定ファイルからロードしたロガー")

【構造化ログ編】JSONログとメタデータ

1. JSON形式でのログ出力

import json
import logging

class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_entry = {
            'timestamp': self.formatTime(record),
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'module': record.module,
            'line': record.lineno
        }
        return json.dumps(log_entry, ensure_ascii=False)

# JSONフォーマッターの適用
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())

logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

logger.info("JSON形式のログメッセージ")

2. コンテキスト情報付きログ

import logging
from contextvars import ContextVar

# リクエストIDのコンテキスト変数
request_id_var = ContextVar('request_id')

class ContextFilter(logging.Filter):
    def filter(self, record):
        record.request_id = request_id_var.get(None)
        return True

logger = logging.getLogger(__name__)
logger.addFilter(ContextFilter())

# フォーマットにrequest_idを追加
handler = logging.StreamHandler()
formatter = logging.Formatter(
    '%(asctime)s - %(request_id)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)

# 使用例
def process_request(request_id):
    request_id_var.set(request_id)
    logger.info("リクエスト処理開始")
    logger.info("リクエスト処理完了")

【パフォーマンス編】ログのパフォーマンス最適化

1. 遅延評価(Lazy Evaluation)

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.WARNING)

# ❌ 悪い例:常に文字列が作成される
expensive_data = get_expensive_data()
logger.debug(f"Debug data: {expensive_data}")

# ✅ 良い例:DEBUGレベルが無効なら実行されない
logger.debug("Debug data: %s", lambda: get_expensive_data())

# ✅ さらに良い例:レベルチェック
if logger.isEnabledFor(logging.DEBUG):
    logger.debug(f"Debug data: {get_expensive_data()}")

2. 非同期ログ処理

import logging
import queue
import threading
from logging.handlers import QueueHandler, QueueListener

# ログキューの作成
log_queue = queue.Queue()

# キューハンドラーの設定
queue_handler = QueueHandler(log_queue)

# ファイルハンドラー(別スレッドで処理)
file_handler = logging.FileHandler('async.log', encoding='utf-8')
file_handler.setFormatter(
    logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
)

# キューリスナーの開始
listener = QueueListener(log_queue, file_handler)
listener.start()

# メインロガーの設定
logger = logging.getLogger(__name__)
logger.addHandler(queue_handler)
logger.setLevel(logging.INFO)

# 使用例(メインスレッドをブロックしない)
for i in range(1000):
    logger.info(f"非同期ログメッセージ {i}")

【監視・運用編】本番環境でのログ管理

1. ログレベルの動的変更

import logging
import signal
import sys

logger = logging.getLogger(__name__)

def toggle_debug_level(signum, frame):
    """シグナルでログレベルを切り替え"""
    current_level = logger.getEffectiveLevel()
    if current_level == logging.DEBUG:
        logger.setLevel(logging.INFO)
        logger.info("ログレベルをINFOに変更")
    else:
        logger.setLevel(logging.DEBUG)
        logger.info("ログレベルをDEBUGに変更")

# SIGUSR1でログレベル切り替え
signal.signal(signal.SIGUSR1, toggle_debug_level)

2. システムメトリクス付きログ

import logging
import psutil
import os

class SystemMetricsFilter(logging.Filter):
    def filter(self, record):
        process = psutil.Process(os.getpid())
        record.cpu_percent = process.cpu_percent()
        record.memory_mb = process.memory_info().rss / 1024 / 1024
        return True

logger = logging.getLogger(__name__)
logger.addFilter(SystemMetricsFilter())

# フォーマットにシステムメトリクスを含める
handler = logging.StreamHandler()
formatter = logging.Formatter(
    '%(asctime)s - %(levelname)s - CPU:%(cpu_percent).1f%% MEM:%(memory_mb).1fMB - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("システムメトリクス付きログ")

3. エラー通知システム

import logging
import smtplib
from email.mime.text import MIMEText

class EmailHandler(logging.Handler):
    def __init__(self, mail_host, from_addr, to_addrs, subject):
        super().__init__()
        self.mail_host = mail_host
        self.from_addr = from_addr
        self.to_addrs = to_addrs
        self.subject = subject
    
    def emit(self, record):
        try:
            msg = MIMEText(self.format(record))
            msg['Subject'] = self.subject
            msg['From'] = self.from_addr
            msg['To'] = ', '.join(self.to_addrs)
            
            server = smtplib.SMTP(self.mail_host)
            server.send_message(msg)
            server.quit()
        except Exception:
            self.handleError(record)

# エラーレベル以上でメール通知
email_handler = EmailHandler(
    'localhost',
    '[email protected]',
    ['[email protected]'],
    'Application Error'
)
email_handler.setLevel(logging.ERROR)

logger = logging.getLogger(__name__)
logger.addHandler(email_handler)

【セキュリティ編】ログのセキュリティ対策

1. 機密情報のマスキング

import logging
import re

class SensitiveDataFilter(logging.Filter):
    def __init__(self):
        super().__init__()
        self.patterns = [
            (re.compile(r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b'), '****-****-****-****'),  # クレジットカード
            (re.compile(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'), '***@***.***'),  # メール
            (re.compile(r'\b\d{3}-\d{4}-\d{4}\b'), '***-****-****'),  # 電話番号
        ]
    
    def filter(self, record):
        if hasattr(record, 'msg'):
            for pattern, replacement in self.patterns:
                record.msg = pattern.sub(replacement, str(record.msg))
        return True

logger = logging.getLogger(__name__)
logger.addFilter(SensitiveDataFilter())

# 機密情報が自動でマスキングされる
logger.info("ユーザー情報: [email protected], card=1234-5678-9012-3456")

2. ログインジェクション対策


import logging class LogInjectionFilter(logging.Filter): def filter(self, record): if hasattr(record, 'msg'): # 改行文字やタブ文字を除去 sanitized = str(record.msg).replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t') record.msg = sanitized return True logger = logging.getLogger(__name__) logger.addFilter(LogInjectionFilter()) # 悪意のある入力がサニタイズされる user_input = "normal input\nFAKE LOG ENTRY" logger.info(f"ユーザー入力: {user_input}")

【フレームワーク別】各種フレームワークでのlogging設定

1. Django設定

# settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': 'django.log',
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['file'],
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
}

2. FastAPI設定

from fastapi import FastAPI, Request
import logging
import time

# ログ設定
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)

app = FastAPI()

@app.middleware("http")
async def log_requests(request: Request, call_next):
    start_time = time.time()
    
    logger.info(f"リクエスト開始: {request.method} {request.url}")
    
    response = await call_next(request)
    
    process_time = time.time() - start_time
    logger.info(f"リクエスト完了: {response.status_code} - {process_time:.3f}s")
    
    return response

【トラブルシューティング編】よくある問題と解決方法

1. ログが出力されない問題

import logging

# 問題:ログが表示されない
logger = logging.getLogger(__name__)
logger.info("このメッセージは表示されない")

# 解決:レベル設定とハンドラー追加
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(levelname)s - %(message)s'))
logger.addHandler(handler)

logger.info("このメッセージは表示される")

2. 重複ログの解決

import logging

# 問題:ログが重複して出力される
logger = logging.getLogger(__name__)

# 解決:propagateを無効化またはハンドラーの重複を避ける
logger.propagate = False  # 親ロガーへの伝播を停止

# または既存ハンドラーをクリア
logger.handlers.clear()
handler = logging.StreamHandler()
logger.addHandler(handler)

3. 文字化け対策

import logging
import codecs

# 文字化け対策
handler = logging.FileHandler('japanese.log', encoding='utf-8')
formatter = logging.Formatter('%(asctime)s - %(message)s')
handler.setFormatter(formatter)

logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

logger.info("日本語のログメッセージも正しく出力される")

ログ解析とモニタリング

1. ログ解析用のヘルパー関数

import logging
import json
from datetime import datetime

def create_structured_log(level, message, **kwargs):
    """構造化ログエントリの作成"""
    log_entry = {
        'timestamp': datetime.utcnow().isoformat(),
        'level': level,
        'message': message,
        **kwargs
    }
    return json.dumps(log_entry, ensure_ascii=False)

# 使用例
logger = logging.getLogger(__name__)

def log_user_action(user_id, action, **metadata):
    structured_log = create_structured_log(
        'INFO',
        f'User action: {action}',
        user_id=user_id,
        action=action,
        **metadata
    )
    logger.info(structured_log)

log_user_action(123, 'login', ip_address='192.168.1.1', user_agent='Chrome/91.0')

2. パフォーマンス監視用ログ

import logging
import time
from functools import wraps

def performance_log(func):
    """関数の実行時間をログに記録"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        try:
            result = func(*args, **kwargs)
            duration = time.time() - start
            logging.info(f"PERF: {func.__name__} completed in {duration:.3f}s")
            return result
        except Exception as e:
            duration = time.time() - start
            logging.error(f"PERF: {func.__name__} failed after {duration:.3f}s: {e}")
            raise
    return wrapper

@performance_log
def slow_database_query():
    time.sleep(0.1)  # データベースクエリのシミュレーション
    return "query result"

まとめ:効果的なPython loggingの活用

Python loggingは、アプリケーションの品質向上と運用効率化に欠かせない重要な技術です。この記事で紹介した基本的な使い方から高度なテクニックまでを段階的に導入することで、保守性の高いログシステムを構築できます。

重要なポイント

  • 段階的導入: print文からloggingへの移行を段階的に実施
  • 適切なレベル設定: 開発・本番環境に応じたログレベルの調整
  • 構造化ログ: JSON形式での構造化によるログ解析の効率化
  • セキュリティ対策: 機密情報のマスキングとログインジェクション対策
  • パフォーマンス考慮: 非同期処理や遅延評価による性能最適化

まずは基本的なlogging.basicConfig()から始めて、徐々にハンドラーやフォーマッターなどの高度な機能を取り入れてみてください。効果的なログシステムの構築により、より信頼性の高いPythonアプリケーションを開発できるようになります。


この記事がお役に立ちましたら、ぜひシェアしてください。Python loggingやログ管理に関するご質問がございましたら、お気軽にコメントでお知らせください。

「らくらくPython塾」が切り開く「呪文コーディング」とは?

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座

フリーランスボード

20万件以上の案件から、副業に最適なリモート・週3〜の案件を一括検索できるプラットフォーム。プロフィール登録でAIスカウトが自動的にマッチング案件を提案。市場統計や単価相場、エージェントの口コミも無料で閲覧可能なため、本業を続けながら効率的に高単価の副業案件を探せます。フリーランスボード

ITプロパートナーズ

週2〜3日から働ける柔軟な案件が業界トップクラスの豊富さを誇るフリーランスエージェント。エンド直契約のため高単価で、週3日稼働でも十分な報酬を得られます。リモートや時間フレキシブルな案件も多数。スタートアップ・ベンチャー中心で、トレンド技術を使った魅力的な案件が揃っています。専属エージェントが案件紹介から契約交渉までサポート。利用企業2,000社以上の実績。ITプロパートナーズ

Midworks 10,000件以上の案件を保有し、週3日〜・フルリモートなど柔軟な働き方に対応。高単価案件が豊富で、報酬保障制度(60%)や保険料負担(50%)など正社員並みの手厚い福利厚生が特徴。通勤交通費(月3万円)、スキルアップ費用(月1万円)の支給に加え、リロクラブ・freeeが無料利用可能。非公開案件80%以上、支払いサイト20日で安心して稼働できます。Midworks