デバッグ技法とログ出力の基本
2025-11-12はじめに
プログラミングにおいて、バグのない完璧なコードを最初から書くことはほとんど不可能です。実際の開発プロセスでは、コードのデバッグ(バグの発見と修正)が開発時間の大部分を占めます。効果的なデバッグ技法と体系的なログ出力を習得することは、生産性の高い開発者になるために不可欠なスキルです。
本章では、Pythonにおける実践的なデバッグ技法と、プロフェッショナルなログ出力の基本を詳細に解説します。これらのスキルを習得することで、問題の早期発見、迅速な解決、そしてより堅牢なアプリケーションの構築が可能になります。
デバッグ技法の基礎
デバッグの基本プロセス
効果的なデバッグは、体系的なアプローチが必要です。
以下のステップに従うことで、効率的に問題を解決できます。
- 問題の再現: バグを確実に再現する方法を見つける
- 問題の局所化: 問題の発生箇所を特定する
- 原因の特定: 根本原因を突き止める
- 修正の実施: 適切な修正を行う
- 検証: 修正が問題を解決し、新たな問題を生んでいないことを確認する
printデバッグの限界とその先
多くの初学者が最初に学ぶデバッグ方法はprint文を使用した方法です。
def calculate_average(numbers):
print(f"入力: {numbers}") # デバッグ用print
total = sum(numbers)
print(f"合計: {total}") # デバッグ用print
average = total / len(numbers)
print(f"平均: {average}") # デバッグ用print
return average
printデバッグはシンプルで直感的ですが、以下のような限界があります。
- 大量の出力でコンソールが汚れる
- デバッグ終了後のprint文削除の手間
- 詳細な実行コンテキスト情報の欠如
- 本番環境での使用困難
ロギングによるデバッグ
ロギングはprintデバッグの進化形として、より構造化されたアプローチを提供します。Pythonの logging モジュールを用いてリストの平均を計算するcalculate_average_with_logging 関数を定義してます。
import logging
# ロギングの基本設定
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def calculate_average_with_logging(numbers):
logging.debug(f"関数開始: calculate_average({numbers})")
if not numbers:
logging.warning("空のリストが渡されました")
return 0
try:
total = sum(numbers)
logging.debug(f"合計計算: {total}")
average = total / len(numbers)
logging.info(f"平均計算完了: {average}")
return average
except Exception as e:
logging.error(f"平均計算中にエラー: {e}", exc_info=True)
raise
# 使用例
numbers = [10, 20, 30, 40, 50]
result = calculate_average_with_logging(numbers)
関数開始時や合計計算時にはデバッグレベルで情報を記録し、平均計算完了時には情報レベルで結果を出力します。また空リストが渡された場合には警告を出力し、計算中に例外が発生した場合にはエラーレベルで詳細情報を記録して例外を再送出することで、計算処理の流れや問題発生時の原因を追跡しやすくしている設計になっています。
アサーションによる防御的プログラミング
アサーションは、プログラムの特定の時点で真であるべき条件をチェックします。このコードは、process_user_data 関数内で Python の アサーションを使って前提条件と事後条件をチェックし、ユーザーデータの整合性を保証する設計になっています。
def process_user_data(user_data):
# 前提条件のチェック
assert isinstance(user_data, dict), "user_dataは辞書である必要があります"
assert 'name' in user_data, "user_dataにはnameキーが必要です"
assert 'age' in user_data, "user_dataにはageキーが必要です"
assert user_data['age'] >= 0, "年齢は0以上である必要があります"
# メインの処理
name = user_data['name']
age = user_data['age']
# 事後条件のチェック
result = f"{name}は{age}歳です"
assert isinstance(result, str), "結果は文字列である必要があります"
return result
# アサーションを有効にして実行(本番環境では無効化可能)
try:
print(process_user_data({'name': 'Alice', 'age': 25}))
print(process_user_data({'name': 'Bob', 'age': -5})) # アサーションエラー
except AssertionError as e:
print(f"アサーションエラー: {e}")
具体的には、引数 user_data が辞書であることや 'name'・'age' キーが存在すること、年齢が 0 以上であることを前提条件として検証し、処理後には結果が文字列であることを確認してから返却します。アサーション違反が発生すると例外が投げられ、try ブロックで捕捉してエラーメッセージを表示することで、開発中にデータの不正を検出しやすくしています。
pdbによる対話的デバッグ
Python標準のデバッガpdbを使用すると、プログラムの実行を一時停止し、変数の検査やステップ実行が可能です。次のコードは、complex_calculation 関数の実行を対話的に追跡できるようにした例です。
import pdb
def complex_calculation(data):
# デバッガーの開始
pdb.set_trace()
result = 0
for i, item in enumerate(data):
# 各ループで変数を検査可能
processed = item * 2 + i
result += processed
# 条件付きブレークポイント
if result > 100:
pdb.set_trace() # 結果が100を超えた時点で停止
return result
# 使用例(実際の実行時に対話的デバッグが可能)
# data = [10, 20, 30, 40, 50]
# result = complex_calculation(data)
pdb.set_trace() によって処理の途中で実行を一時停止し、変数の値やループの進行状況を確認しながらデバッグできる設計になっています。また、結果が 100 を超えた場合に条件付きでブレークポイントを設定することで、特定の状況下のみ停止させて詳細な解析が可能となっています。
pdbの主要コマンド
l(ist): 現在のコード表示n(ext): 次の行へ進むs(tep): 関数の中へステップインc(ontinue): 実行再開p(rint): 変数の値表示q(uit): デバッガ終了
ログ出力の基本
ロギングの重要性
ログ出力は、単なるデバッグツールではなく、以下の目的で重要な役割を果たします。
- 問題調査: 本番環境での問題原因究明
- 監視: アプリケーションの健全性監視
- 監査: セキュリティイベントや重要な操作の記録
- パフォーマンス分析: 処理時間やリソース使用状況の追跡
ロギングレベル
Pythonのloggingモジュールは、重要度に応じたログレベルを提供します。debug は開発中の詳細情報、info は正常動作の重要なイベント、warning は問題の可能性がある状況、error は重大な問題、critical はプログラム継続が困難な致命的状況をそれぞれ記録する設計になっています。
import logging
# 各レベルの使用例
logging.debug("詳細なデバッグ情報") # レベル10 - 開発中の詳細情報
logging.info("正常な動作情報") # レベル20 - 正常系の重要なイベント
logging.warning("警告情報") # レベル30 - 問題の可能性がある状況
logging.error("エラー情報") # レベル40 - 重大な問題
logging.critical("致命的なエラー情報") # レベル50 - プログラム継続困難な状況
基本的なロギング設定
次のコードは、Python の logging モジュールを用いて包括的なロギング設定を行う例で、アプリケーション専用のロガー my_application を作成し、ログの詳細をフォーマットで整えた上で、標準出力用のコンソールハンドラー(INFO 以上)、すべてのログを記録するファイルハンドラー(DEBUG 以上)、エラーレベルのみを記録する別ファイルハンドラー(ERROR 以上)を追加することで、ログの出力先や重要度に応じた管理を柔軟に行える設計になっています。
import logging
import sys
def setup_logging():
"""包括的なロギング設定"""
# ロガーの作成
logger = logging.getLogger('my_application')
logger.setLevel(logging.DEBUG)
# フォーマッターの作成
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
)
# コンソールハンドラー
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
# ファイルハンドラー
file_handler = logging.FileHandler('application.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# エラーファイルハンドラー(エラーレベルのみ)
error_handler = logging.FileHandler('errors.log', encoding='utf-8')
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
# ハンドラーの追加
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.addHandler(error_handler)
return logger
# ロギングのセットアップ
logger = setup_logging()
コンテキスト情報のロギング
デバッグを容易にするために、関連するコンテキスト情報をログに含めます。次のコードは、関数呼び出しを自動的にログに記録するデコレータ log_function_call を定義し、注文処理を行う process_order 関数に適用した例です。
デコレータは関数の開始時や正常終了時、例外発生時にそれぞれデバッグ・エラーログを記録し、関数内部では注文の開始、空注文の警告、合計金額の計算などの情報をログに出力することで、処理の流れや問題発生時の原因を追跡しやすくする設計になっています。
import logging
from functools import wraps
def log_function_call(func):
"""関数呼び出しをログに記録するデコレータ"""
@wraps(func)
def wrapper(*args, **kwargs):
logger = logging.getLogger(func.__module__)
# 関数開始のログ
logger.debug(f"関数開始: {func.__name__}(args={args}, kwargs={kwargs})")
try:
result = func(*args, **kwargs)
# 関数正常終了のログ
logger.debug(f"関数正常終了: {func.__name__} -> {result}")
return result
except Exception as e:
# 関数異常終了のログ
logger.error(
f"関数異常終了: {func.__name__} - {e}",
exc_info=True,
extra={'args': args, 'kwargs': kwargs}
)
raise
return wrapper
@log_function_call
def process_order(order_id, items, user_id):
"""注文処理関数"""
logger = logging.getLogger(__name__)
logger.info(f"注文処理開始: order_id={order_id}, user_id={user_id}")
# 何らかの処理...
if not items:
logger.warning(f"空の注文: order_id={order_id}")
return None
total = sum(item['price'] * item['quantity'] for item in items)
logger.info(f"注文合計計算: order_id={order_id}, total={total}")
return total
# 使用例
try:
order_items = [
{'name': '商品A', 'price': 1000, 'quantity': 2},
{'name': '商品B', 'price': 500, 'quantity': 1}
]
result = process_order("ORD123", order_items, "USER456")
except Exception as e:
logging.error("注文処理中にエラーが発生しました")
パフォーマンスロギング
次のコードは、Python の contextlib.contextmanager を用いて、処理の実行時間を計測してログに記録するコンテキストマネージャ log_execution_time を定義した例です。
with ブロック内で指定した処理を実行する前後で開始時刻と終了時刻を取得し、処理の開始・終了および実行時間をログに出力する設計になっています。
expensive_operation 関数では、このコンテキストマネージャを利用して高負荷処理の所要時間を記録し、処理結果をデバッグログとして出力することで、性能分析や処理時間の監視を容易に行えるようになっています。
import logging
import time
from contextlib import contextmanager
@contextmanager
def log_execution_time(operation_name):
"""実行時間を計測してログに記録するコンテキストマネージャ"""
start_time = time.time()
logger = logging.getLogger('performance')
try:
logger.info(f"操作開始: {operation_name}")
yield
finally:
end_time = time.time()
execution_time = end_time - start_time
logger.info(f"操作終了: {operation_name} - 実行時間: {execution_time:.3f}秒")
# 使用例
def expensive_operation():
"""時間のかかる処理"""
with log_execution_time("高負荷処理"):
# 何らかの重い処理
time.sleep(2)
result = sum(i * i for i in range(10000))
logging.debug(f"処理結果: {result}")
return result
# 実行
expensive_operation()
デバッグ技法とベストプラクティス
条件付きブレークポイント
次のコードは、条件付きで Python のデバッガ pdb を起動できる debug_conditionally 関数を定義し、process_data_batch 関数内でデータバッチを処理する際に特定の条件下でデバッグを実行する設計になっています。
具体的には、6番目の要素で必ずデバッガを起動し、さらに値が閾値を超えた場合には条件付きでデバッグを開始することで、問題のあるデータや特定の処理状況を対話的に確認しながら処理の挙動を解析できるようになっています。
def debug_conditionally(condition=True):
"""条件付きデバッグ"""
if condition:
import pdb
pdb.set_trace()
def process_data_batch(data_batch, threshold=100):
"""データバッチ処理"""
results = []
for i, data in enumerate(data_batch):
# 特定の条件でデバッガを起動
if i == 5: # 6番目の要素で停止
debug_conditionally()
# 値が閾値を超えた場合にデバッグ
if data > threshold:
debug_conditionally(condition=(data > 1000))
processed = data * 2
results.append(processed)
return results
例外の詳細ロギング
次のコードは、例外発生時に詳細な情報をログに記録する detailed_exception_logging 関数を定義した例で、risky_operation 内で意図的に例外が発生した場合に、例外の種類とメッセージをエラーレベルで記録します。
スタックトレースをデバッグレベルで出力するとともに、発生時点のローカル変数の状態も取得してデバッグログに記録することで、問題発生時の原因究明やデバッグ作業を容易に行える設計になっています。
import logging
import traceback
def detailed_exception_logging():
"""詳細な例外情報のロギング"""
try:
# 何らかの処理
risky_operation()
except Exception as e:
logger = logging.getLogger('exception_tracking')
# 基本例外情報
logger.error(f"例外発生: {type(e).__name__}: {e}")
# スタックトレース
logger.debug("スタックトレース:", exc_info=True)
# ローカル変数の情報(デバッグレベル)
import inspect
frame = inspect.currentframe()
try:
locals_info = frame.f_back.f_locals
logger.debug(f"ローカル変数: {locals_info}")
finally:
del frame
raise
def risky_operation():
"""リスクのある操作"""
data = {"key": "value"}
# 意図的に例外を発生
return data["nonexistent_key"]
ログローテーション
長時間実行されるアプリケーションでは、ログファイルのローテーションが重要です。次のコードは、ログをサイズや時間に応じて自動的にローテーションさせる設定を行う setup_rotating_logging 関数を定義した例です。
サイズベースの RotatingFileHandler により 10MB ごとにログファイルを切り替え最大 3 ファイルを保持し、時間ベースの TimedRotatingFileHandler により日次でログをローテーションして過去 7 日分を保持することで、ログファイルの肥大化を防ぎつつ、重要なログ情報を効率的に管理できる設計になっています。
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
def setup_rotating_logging():
"""ローテーションするロギング設定"""
logger = logging.getLogger('rotating_logger')
logger.setLevel(logging.INFO)
# サイズベースのローテーション(10MBごと、3ファイル保持)
size_handler = RotatingFileHandler(
'app.log', maxBytes=10*1024*1024, backupCount=3, encoding='utf-8'
)
# 時間ベースのローテーション(日次、7日分保持)
time_handler = TimedRotatingFileHandler(
'app_time.log', when='midnight', interval=1, backupCount=7, encoding='utf-8'
)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
size_handler.setFormatter(formatter)
time_handler.setFormatter(formatter)
logger.addHandler(size_handler)
logger.addHandler(time_handler)
return logger
構造化ロギング
次のコードは、ログを JSON 形式などで構造化して出力する StructuredFormatter クラスを定義し、setup_structured_logging 関数で構造化ログ用のロガーを設定した例です。
タイムスタンプ、ログレベル、ロガー名、メッセージ、モジュール名、行番号などの基本情報に加え、extra で渡された追加情報も JSON に含めて出力することで、ログ解析や外部システムへの連携に適した形式でログを管理できる設計になっています。
import logging
import json
class StructuredFormatter(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
}
# 追加のextra情報がある場合
if hasattr(record, 'extra_data'):
log_entry.update(record.extra_data)
return json.dumps(log_entry, ensure_ascii=False)
def setup_structured_logging():
"""構造化ロギングの設定"""
logger = logging.getLogger('structured')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = StructuredFormatter()
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
# 使用例
structured_logger = setup_structured_logging()
structured_logger.info(
"ユーザーアクション",
extra={'extra_data': {'user_id': '123', 'action': 'login', 'ip': '192.168.1.1'}}
)
まとめ
効果的なデバッグ技法と体系的なログ出力は、現代のソフトウェア開発において不可欠なスキルです。printデバッグから始まり、ロギング、対話的デバッガの使用、そして構造化ロギングへと進化することで、より効率的な問題解決と、本番環境での効果的な監視が可能になります。
これらの技術を組み合わせることで、開発時のデバッグ効率が向上するだけでなく、本番環境での問題追跡やパフォーマンス分析も容易になります。実際のプロジェクトでは、プロジェクトの規模や要件に応じて、これらの技法を適切に組み合わせて使用することが重要です。
演習問題
初級問題
問題1: 基本的なロギング設定
loggingモジュールを使用して、コンソールにログを出力する基本的な設定を行ってください。
実行結果:
INFO:root:プログラムを開始します
INFO:root:処理が完了しました
問題2: 基本的なログ
loggingモジュールを使用して、DEBUGレベル以上をファイル出力にしてください。
実行結果:
DEBUG:root:デバッグメッセージ
INFO:root:情報メッセージ
問題3: 簡単なデバッグ用print関数
デバッグモードが有効なときだけメッセージを表示する debug_print() 関数を以下の仕様で作成してください。
DEBUG_MODE が True のときはメッセージを表示する。
DEBUG_MODE が False のときは何も表示しない。
中級問題
問題4: 条件付きブレークポイント
リスト内の数値を順番に処理するプログラムを作成してください。処理中に、値が 100以上 の要素が見つかった場合は、pdb を使ってデバッガを起動してください。data = [10, 20, 150, 30] を利用して検証してください。
以下の内容を使用します。
import pdb
pdb.set_trace()
問題5: 例外の詳細ロギング
リストの数値を割り算する関数を作成してください。処理中に例外が発生した場合は、エラーメッセージを表示して、かつlogging を使ってログを出力するようにしてください。
実行例:
numbers = [10, 20, 30]
result = divide_numbers(numbers, 2)
print(result)
result = divide_numbers(numbers, 0)
実行結果:
[5.0, 10.0, 15.0]
ERROR:root:division by zero
エラーが発生しました: division by zero
問題6: ログローテーションの設定
logging モジュールを使用して、ログをファイルに出力してください。また、ログファイルのサイズが 10KB を超えたら新しいログファイルを作成する設定を行ってください。
ログローテーションの設定:
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
"app.log",
maxBytes=10240,
backupCount=1
)
問題7: コンテキスト情報付きロギング
関数呼び出しの開始、終了、引数、戻り値を自動的にログに記録するデコレータを作成してください。wraps をインポートして、log_call() デコレータを作成します。関数実行前には「関数開始: 関数名」関数の実行後に「関数終了: 関数名」と出力します。
デコレータの基本構造:
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
問題8: パフォーマンスモニタリング
複数の関数の実行時間を計測し、統計情報(平均、最大、最小時間)をログに出力するシステムを作成してください。
# 実行時間の計測
start = time.time()
# 処理
end = time.time()
elapsed = end - start
# 平均値
average = sum(times) / len(times)
# 最大値と最小値
max_time = max(times)
min_time = min(times)
実行例:
関数: fast_task
実行回数: 3
平均時間: 0.100秒
最大時間: 0.101秒
最小時間: 0.099秒
関数: slow_task
実行回数: 3
平均時間: 0.500秒
最大時間: 0.501秒
最小時間: 0.499秒
問題9: 構造化ログ出力
JSON形式で構造化されたログを出力するフォーマッタを作成してください。タイムスタンプ、ログレベル、メッセージ、カスタムフィールドを含めるようにしてください。
JSON形式のログを出力するために、logging.Formatter を継承した JsonFormatter クラスを作成してください。
クラス内に format() メソッドを定義し、引数として受け取ったログレコード (record) から、タイムスタンプ、ログレベル、メッセージ、ユーザー名を取得してください。
取得した情報を辞書にまとめ、それぞれの値を適切なキーに保存してください。その後、json.dumps() を使用して辞書をJSON文字列へ変換し、その文字列を返すようにしてください。
実行例:
{"timestamp":"2026-06-04T12:00:00","level":"INFO","message":"ログイン成功","user":"tanaka"}
{"timestamp":"2026-06-04T12:00:01","level":"INFO","message":"商品購入","user":"suzuki"}
上級問題
問題10: 分散システムでのトレーシング
複数の関数呼び出しを跨いで一意のトレースIDを伝播させ、関連するログを同じトレースIDで記録するシステムを作成してください。次のライブラリをインポートします。
logginguuidwraps
トレースIDは複数の関数呼び出しを通じて共有されるため、グローバル変数やコンテキスト管理用のオブジェクトを利用して保持できるようにしてください。ランダムなトレースIDを生成する関数を作成してください。この関数は新しい処理が開始されるたびに呼び出され、一意な文字列を返すようにしてください。現在のトレースIDを取得する機能と、新しいトレースIDを設定する機能を実装します。処理終了後にトレース情報を削除する機能も用意してください。
トレースIDを含んだログを出力できるロガークラスを作成してください。このクラスでは通常のログメッセージに加えて、現在のトレースIDを自動的に付与して出力できるようにしてください。
トレースを開始するためのメソッドを実装してください。このメソッドでは新しいトレースIDを生成し、現在のコンテキストへ保存した後、トレース開始ログを出力してください。また、トレースを終了するためのメソッドも実装してください。このメソッドではトレース終了ログを出力し、保存されていたトレース情報を削除してください。
メイン処理では最初にトレースを開始し、その後に親関数を呼び出してください。親関数の内部からさらに複数の子関数を呼び出すようにしてください。
各関数内ではログを出力し、その際に現在のトレースIDが自動的に付与されることを確認してください。
最後に処理を終了し、トレースをクリーンアップしてください。出力されたログを確認し、親関数と子関数のログがすべて同じトレースIDで記録されていることを確認してください。また、別の処理を実行した場合には新しいトレースIDが発行されることも確認してください。
問題11: 動的ログレベル制御
アプリケーションの実行中にログレベルを動的に変更できるようにする仕組みを実装してください。設定ファイルの変更やシグナル受信でログレベルを変更できるようにしてください。ヒントは以下の通りです。
- JSON設定を読み込む場合は
json.load()を使用します。 - ログ設定を反映する場合は
logging.config.dictConfig()を使用します。 - ロガー取得は
logging.getLogger()を使用します。 - ログレベル変更は
logger.setLevel()を使用します。 - シグナル処理の登録は
signal.signal()を使用します。 - ファイル変更監視には
watchdogライブラリのObserverを利用します。 - 設定ファイルを変更したら再起動せずにログレベルを切り替えられることが目標です。
- 動作確認では
INFOとDEBUGを切り替えると変化が分かりやすくなります。
実行結果:
=== 問題11テスト ===
使用方法:
1. 別のターミナルで以下のコマンドを実行:
# 設定を再読み込み
kill -USR1 <プロセスID>
# 現在のログレベルを表示
kill -USR2 <プロセスID>
# 設定ファイルを編集して自動リロードをテスト
nano logging_config.json
2. 設定ファイル例 (logging_config.json):
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {...},
"handlers": {...},
"loggers": {
"main": {
"handlers": ["console"],
"level": "DEBUG", # ここを変更してテスト
"propagate": false
}
}
}
設定ファイルが存在しません: logging_config.json
デフォルト設定ファイルを作成: logging_config.json
設定ファイルの変更監視を開始: logging_config.json
問題12: 包括的なデバッグ環境
対話的デバッグ、詳細なロギング、パフォーマンス計測、カスタム例外処理を統合した包括的なデバッグ環境をクラスとして設計してください。
- ロガー作成には logging.getLogger() を使用します。
- デコレータを作成する場合は @wraps() を使用します。
- 実行時間は time.time() の差分で計測できます。
- 呼び出し元情報は inspect.currentframe() から取得できます。
- ブレークポイントは pdb.set_trace() で起動できます。
- コンテキストマネージャは @contextmanager を利用すると実装しやすくなります。
- 関数の開始・終了・例外発生をすべて記録するとデバッグしやすくなります。
- パフォーマンス監視とデバッグ機能を組み合わせることで、処理の遅延原因やエラー箇所を効率よく特定できます。
=== 問題12テスト ===
包括的なデバッグ環境のデモを実行します
12:40:01.883 - TestApp - INFO - test.py:49 - setup_logging - デバッグ環境を初期化: TestApp
12:40:01.884 - TestApp - INFO - test.py:66 - info - アプリケーションを開始
12:40:01.884 - TestApp - INFO - test.py:66 - info - データセット 1 を処理 [data=[1, 2, 3, 4, 5]]
12:40:01.884 - TestApp - DEBUG - test.py:60 - debug - 関数開始: process_data [args=([1, 2, 3, 4, 5],) kwargs={} caller=run]
12:40:01.886 - TestApp - DEBUG - test.py:60 - debug - データ処理を開始 [data_length=5]
12:40:01.887 - TestApp - DEBUG - test.py:60 - debug - 関数開始: validate_input [args=([1, 2, 3, 4, 5],) kwargs={} caller=process_data]
12:40:01.887 - TestApp - DEBUG - test.py:60 - debug - 関数終了: validate_input [result=True execution_time=0.000s]
12:40:01.888 - TestApp - DEBUG - test.py:60 - debug - 関数開始: complex_calculation [args=([1, 2, 3, 4, 5],) kwargs={} caller=wrapper]
12:40:01.951 - TestApp - DEBUG - test.py:60 - debug - 関数終了: complex_calculation [result=65 execution_time=0.061s]
12:40:01.952 - TestApp - INFO - test.py:66 - info - データ処理完了 [result=65]
12:40:01.952 - TestApp - DEBUG - test.py:60 - debug - 関数終了: process_data [result=65 execution_time=0.066s]
12:40:01.952 - TestApp - INFO - test.py:66 - info - 処理結果: 65
--------------------------------------------------
12:40:01.952 - TestApp - INFO - test.py:66 - info - データセット 2 を処理 [data=[10, -5, 20, 999, 30]]
12:40:01.952 - TestApp - DEBUG - test.py:60 - debug - 関数開始: process_data [args=([10, -5, 20, 999, 30],) kwargs={} caller=run]
12:40:01.952 - TestApp - DEBUG - test.py:60 - debug - データ処理を開始 [data_length=5]
12:40:01.953 - TestApp - DEBUG - test.py:60 - debug - 関数開始: validate_input [args=([10, -5, 20, 999, 30],) kwargs={} caller=process_data]
12:40:01.953 - TestApp - WARNING - test.py:72 - warning - 負の値が含まれています
12:40:01.953 - TestApp - DEBUG - test.py:60 - debug - 関数終了: validate_input [result=True execution_time=0.000s]
12:40:01.953 - TestApp - DEBUG - test.py:60 - debug - 関数開始: complex_calculation [args=([10, -5, 20, 999, 30],) kwargs={} caller=wrapper]
12:40:01.987 - TestApp - INFO - test.py:66 - info - ブレークポイントに到達: special_value
ブレークポイント: special_value
場所: test.py
コンテキスト: complex_calculation
初級問題
問題1: 基本的なロギング設定
import logging
# ログの基本設定
logging.basicConfig(level=logging.INFO)
# ログを出力
logging.info("プログラムを開始します")
logging.info("処理が完了しました")
問題2: 関数の実行時間計測
import logging
# ログをファイルに出力する設定
logging.basicConfig(
filename="debug.log",
level=logging.DEBUG
)
# ログを出力
logging.debug("デバッグメッセージ")
logging.info("情報メッセージ")
問題3: 簡単なデバッグ用print関数
DEBUG_MODE = True
def debug_print(message):
if DEBUG_MODE:
print("[DEBUG]", message)
# テスト
debug_print("処理を開始します")
DEBUG_MODE = False
debug_print("このメッセージは表示されません")
中級問題
問題4: 条件付きブレークポイント
import pdb
def check_breakpoint(value):
if value >= 100:
print(f"値 {value} が見つかりました")
print("デバッガを起動します")
pdb.set_trace()
def process_list(data_list):
results = []
for value in data_list:
check_breakpoint(value)
processed = value * 2
results.append(processed)
return results
data = [10, 20, 150, 30]
result = process_list(data)
print("結果:", result)
問題5: 例外の詳細ロギング
import logging
logging.basicConfig(level=logging.ERROR)
def divide_numbers(numbers, divisor):
results = []
try:
for number in numbers:
result = number / divisor
results.append(result)
return results
except Exception as e:
logging.error(e)
print(f"エラーが発生しました: {e}")
numbers = [10, 20, 30]
print("正常系")
result = divide_numbers(numbers, 2)
print(result)
print("\n異常系")
divide_numbers(numbers, 0)
問題6: ログローテーションの設定
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("sample")
logger.setLevel(logging.INFO)
handler = RotatingFileHandler(
"app.log",
maxBytes=10240, # 10KB
backupCount=1
)
logger.addHandler(handler)
for i in range(20):
logger.info(f"ログメッセージ {i + 1}")
print("ログ出力完了")
問題7: コンテキスト情報付きロギング
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"関数開始: {func.__name__}")
result = func(*args, **kwargs)
print(f"関数終了: {func.__name__}")
return result
return wrapper
@log_call
def greet(name):
print(f"こんにちは、{name}さん")
greet("佐藤")
問題8: パフォーマンスモニタリング
import logging
import time
logging.basicConfig(level=logging.INFO)
def measure_time(func):
start = time.time()
func()
end = time.time()
return end - start
def fast_task():
time.sleep(0.1)
def slow_task():
time.sleep(0.5)
results = {
"fast_task": [],
"slow_task": []
}
for _ in range(3):
elapsed = measure_time(fast_task)
results["fast_task"].append(elapsed)
for _ in range(3):
elapsed = measure_time(slow_task)
results["slow_task"].append(elapsed)
for name, times in results.items():
average = sum(times) / len(times)
maximum = max(times)
minimum = min(times)
logging.info(f"関数: {name}")
logging.info(f"実行回数: {len(times)}")
logging.info(f"平均時間: {average:.3f}秒")
logging.info(f"最大時間: {maximum:.3f}秒")
logging.info(f"最小時間: {minimum:.3f}秒")
logging.info("-" * 20)
問題9: 構造化ログ出力
import logging
import json
from datetime import datetime
class JsonFormatter(logging.Formatter):
def format(self, record):
log_data = {
"timestamp": datetime.now().isoformat(),
"level": record.levelname,
"message": record.getMessage(),
"user": getattr(record, "user", "unknown")
}
return json.dumps(log_data, ensure_ascii=False)
logger = logging.getLogger("app")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = JsonFormatter()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info(
"ログイン成功",
extra={"user": "tanaka"}
)
logger.info(
"商品購入",
extra={"user": "suzuki"}
)
上級問題
問題10: 分散システムでのトレーシング
import logging
import uuid
from functools import wraps
# 現在のトレースIDを保持
current_trace_id = None
def generate_trace_id():
"""一意なトレースIDを生成"""
return str(uuid.uuid4())
def set_trace_id(trace_id):
"""トレースIDを設定"""
global current_trace_id
current_trace_id = trace_id
def get_trace_id():
"""現在のトレースIDを取得"""
return current_trace_id
def clear_trace_id():
"""トレースIDを削除"""
global current_trace_id
current_trace_id = None
class TraceLogger:
"""トレースID付きログ出力"""
def __init__(self):
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
self.logger = logging.getLogger(__name__)
def log(self, message):
trace_id = get_trace_id()
self.logger.info(
f"[trace_id={trace_id}] {message}"
)
def start_trace(self):
trace_id = generate_trace_id()
set_trace_id(trace_id)
self.log("トレース開始")
return trace_id
def end_trace(self):
self.log("トレース終了")
clear_trace_id()
logger = TraceLogger()
def trace_function(func):
"""関数呼び出しをログ出力するデコレータ"""
@wraps(func)
def wrapper(*args, **kwargs):
logger.log(f"関数開始: {func.__name__}")
result = func(*args, **kwargs)
logger.log(f"関数終了: {func.__name__}")
return result
return wrapper
@trace_function
def child_function_a():
logger.log("子関数Aを実行")
@trace_function
def child_function_b():
logger.log("子関数Bを実行")
@trace_function
def parent_function():
logger.log("親関数を実行")
child_function_a()
child_function_b()
print("=== 1回目の処理 ===")
logger.start_trace()
try:
parent_function()
finally:
logger.end_trace()
print()
print("=== 2回目の処理 ===")
logger.start_trace()
try:
parent_function()
finally:
logger.end_trace()
問題11: 動的ログレベル制御
import logging
import logging.config
import threading
import time
import signal
import json
import os
try:
# Import watchdog dynamically to avoid static-analysis import errors in environments
# where watchdog is not installed. Use importlib to perform runtime import.
import importlib
_wd_observers = importlib.import_module('watchdog.observers')
_wd_events = importlib.import_module('watchdog.events')
Observer = getattr(_wd_observers, 'Observer')
FileSystemEventHandler = getattr(_wd_events, 'FileSystemEventHandler')
_HAS_WATCHDOG = True
except Exception:
# Fallback if watchdog is not installed: simple polling-based watcher
_HAS_WATCHDOG = False
class FileSystemEventHandler:
def on_modified(self, event):
pass
class _PollingObserver:
def __init__(self):
self._thread = None
self._stop_event = threading.Event()
self._watches = [] # tuples of (path, handler, recursive, last_mtime)
def schedule(self, handler, path='.', recursive=False):
# store handler and initial mtime for the config file
try:
mtime = os.path.getmtime(path)
except Exception:
mtime = None
self._watches.append((path, handler, recursive, mtime))
def _poll_loop(self):
while not self._stop_event.is_set():
for i, (path, handler, recursive, last_mtime) in enumerate(self._watches):
try:
current_mtime = os.path.getmtime(path)
except Exception:
current_mtime = None
if last_mtime is not None and current_mtime is not None and current_mtime != last_mtime:
# create a simple event object with src_path attribute
class Event:
def __init__(self, src_path):
self.src_path = src_path
handler.on_modified(Event(path))
self._watches[i] = (path, handler, recursive, current_mtime)
else:
# update stored mtime
self._watches[i] = (path, handler, recursive, current_mtime)
time.sleep(1)
def start(self):
if self._thread and self._thread.is_alive():
return
self._stop_event.clear()
self._thread = threading.Thread(target=self._poll_loop, daemon=True)
self._thread.start()
def stop(self):
self._stop_event.set()
def join(self):
if self._thread:
self._thread.join()
# Use the polling observer as Observer if watchdog unavailable
Observer = _PollingObserver
class DynamicLogLevelManager:
"""
動的ログレベル管理クラス
実行中にログレベルを変更可能
"""
def __init__(self, config_file='logging_config.json'):
self.config_file = config_file
self.observer = None
self.loggers = {}
# 初期設定の読み込み
self.load_config()
# シグナルハンドラの設定
self.setup_signal_handlers()
def load_config(self):
"""設定ファイルからログ設定を読み込み"""
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
logging.config.dictConfig(config)
print(f" ログ設定を読み込みました: {self.config_file}")
return True
except Exception as e:
print(f" 設定ファイルの読み込みに失敗: {e}")
self.setup_default_logging()
return False
else:
print(f" 設定ファイルが存在しません: {self.config_file}")
self.setup_default_logging()
self.create_default_config()
return False
def setup_default_logging(self):
"""デフォルトのログ設定"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def create_default_config(self):
"""デフォルト設定ファイルを作成"""
default_config = {
'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',
'stream': 'ext://sys.stdout'
},
'file': {
'class': 'logging.FileHandler',
'level': 'DEBUG',
'formatter': 'standard',
'filename': 'application.log',
'encoding': 'utf-8'
}
},
'loggers': {
'': { # ルートロガー
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True
},
'security': {
'handlers': ['file'],
'level': 'INFO',
'propagate': False
},
'performance': {
'handlers': ['console'],
'level': 'WARNING',
'propagate': False
}
}
}
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(default_config, f, indent=2, ensure_ascii=False)
print(f" デフォルト設定ファイルを作成: {self.config_file}")
except Exception as e:
print(f" 設定ファイルの作成に失敗: {e}")
def change_log_level(self, logger_name, new_level):
"""
特定ロガーのログレベルを変更
"""
try:
if logger_name == 'root':
logger = logging.getLogger()
else:
logger = logging.getLogger(logger_name)
# ログレベルの文字列をレベル値に変換
if isinstance(new_level, str):
level_value = getattr(logging, new_level.upper())
else:
level_value = new_level
logger.setLevel(level_value)
# ハンドラーのレベルも更新
for handler in logger.handlers:
handler.setLevel(level_value)
print(f" ロガー '{logger_name}' のレベルを {new_level} に変更")
return True
except Exception as e:
print(f" ログレベル変更失敗: {e}")
return False
def get_current_levels(self):
"""現在のログレベルを取得"""
levels = {}
loggers = [logging.getLogger()] # ルートロガー
loggers.extend(logging.Logger.manager.loggerDict.values())
for logger in loggers:
if isinstance(logger, logging.Logger):
levels[logger.name] = logging.getLevelName(logger.getEffectiveLevel())
return levels
def setup_signal_handlers(self):
"""シグナルハンドラを設定"""
def handle_reload_signal(signum, frame):
print(f"\n シグナル {signum} を受信: ログ設定を再読み込み")
self.load_config()
def handle_level_change_signal(signum, frame):
print(f"\n 現在のログレベル:")
levels = self.get_current_levels()
for logger_name, level in sorted(levels.items()):
if logger_name: # 空でないロガー名のみ表示
print(f" {logger_name}: {level}")
# シグナルハンドラを登録
signal.signal(signal.SIGUSR1, handle_reload_signal) # 設定再読み込み
signal.signal(signal.SIGUSR2, handle_level_change_signal) # レベル表示
def start_config_watcher(self):
"""設定ファイルの変更監視を開始"""
class ConfigFileHandler(FileSystemEventHandler):
def __init__(self, manager):
self.manager = manager
def on_modified(self, event):
if event.src_path.endswith(self.manager.config_file):
print(f"\n 設定ファイルが変更されました: {event.src_path}")
self.manager.load_config()
event_handler = ConfigFileHandler(self)
self.observer = Observer()
self.observer.schedule(event_handler, path='.', recursive=False)
self.observer.start()
print(f" 設定ファイルの変更監視を開始: {self.config_file}")
def stop(self):
"""監視を停止"""
if self.observer:
self.observer.stop()
self.observer.join()
# テストアプリケーション
class TestApplication:
"""テスト用アプリケーション"""
def __init__(self):
self.log_manager = DynamicLogLevelManager()
self.setup_loggers()
# 設定ファイル監視を開始
self.log_manager.start_config_watcher()
def setup_loggers(self):
"""アプリケーションロガーを設定"""
self.main_logger = logging.getLogger('main')
self.security_logger = logging.getLogger('security')
self.performance_logger = logging.getLogger('performance')
self.database_logger = logging.getLogger('database')
def run(self):
"""アプリケーションを実行"""
self.main_logger.info("アプリケーションを開始します")
try:
while True:
# 様々なログメッセージを出力
self.main_logger.debug("詳細なデバッグ情報")
self.main_logger.info("通常の情報メッセージ")
self.security_logger.warning("セキュリティ警告")
self.performance_logger.info("パフォーマンス情報")
self.database_logger.debug("データベースクエリ詳細")
# エラーログのテスト
if int(time.time()) % 10 == 0:
self.main_logger.error("定期的なエラーログ")
time.sleep(2)
except KeyboardInterrupt:
self.main_logger.info("アプリケーションを終了します")
self.log_manager.stop()
# テスト
print("=== 問題11テスト ===")
# 使用方法の説明
print("""
使用方法:
1. 別のターミナルで以下のコマンドを実行:
# 設定を再読み込み
kill -USR1 <プロセスID>
# 現在のログレベルを表示
kill -USR2 <プロセスID>
# 設定ファイルを編集して自動リロードをテスト
nano logging_config.json
2. 設定ファイル例 (logging_config.json):
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {...},
"handlers": {...},
"loggers": {
"main": {
"handlers": ["console"],
"level": "DEBUG", # ここを変更してテスト
"propagate": false
}
}
}
""")
# アプリケーションの実行(Ctrl+Cで終了)
app = TestApplication()
# 実際の実行はコメントアウト(テスト時は有効化)
# print("アプリケーションを起動しています...")
# print("プロセスID:", os.getpid())
# app.run()
問題12: 包括的なデバッグ環境
import logging
import pdb
import time
import inspect
from functools import wraps
from contextlib import contextmanager
class ComprehensiveDebugEnvironment:
"""
包括的なデバッグ環境
対話的デバッグ、ロギング、パフォーマンス計測、例外処理を統合
"""
def __init__(self, app_name="DebugApp", log_level=logging.DEBUG):
self.app_name = app_name
self.log_level = log_level
self.performance_data = {}
self.setup_logging()
self.setup_debug_tools()
def setup_logging(self):
"""包括的なロギング設定"""
self.logger = logging.getLogger(self.app_name)
self.logger.setLevel(self.log_level)
# 既存のハンドラーをクリア
self.logger.handlers.clear()
# 詳細なフォーマッタ
formatter = logging.Formatter(
'%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - '
'%(filename)s:%(lineno)d - %(funcName)s - %(message)s',
datefmt='%H:%M:%S'
)
# コンソールハンドラー
console_handler = logging.StreamHandler()
console_handler.setLevel(self.log_level)
console_handler.setFormatter(formatter)
# ファイルハンドラー
file_handler = logging.FileHandler(f'{self.app_name}.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler)
self.logger.info(f"🔧 デバッグ環境を初期化: {self.app_name}")
def setup_debug_tools(self):
"""デバッグツールの設定"""
self.debug_enabled = True
self.breakpoints = {}
def debug(self, message, **context):
"""デバッグログを出力"""
if context:
message = f"{message} [{' '.join(f'{k}={v}' for k, v in context.items())}]"
self.logger.debug(f"🐛 {message}")
def info(self, message, **context):
"""情報ログを出力"""
if context:
message = f"{message} [{' '.join(f'{k}={v}' for k, v in context.items())}]"
self.logger.info(f"ℹ️ {message}")
def warning(self, message, **context):
"""警告ログを出力"""
if context:
message = f"{message} [{' '.join(f'{k}={v}' for k, v in context.items())}]"
self.logger.warning(f"⚠️ {message}")
def error(self, message, **context):
"""エラーログを出力"""
if context:
message = f"{message} [{' '.join(f'{k}={v}' for k, v in context.items())}]"
self.logger.error(f"❌ {message}")
def trace(self, func):
"""
関数の実行をトレースするデコレータ
"""
@wraps(func)
def wrapper(*args, **kwargs):
# 関数開始情報
frame = inspect.currentframe()
self.debug(
f"関数開始: {func.__name__}",
args=args,
kwargs=kwargs,
caller=frame.f_back.f_code.co_name
)
start_time = time.time()
try:
result = func(*args, **kwargs)
execution_time = time.time() - start_time
# 関数終了情報
self.debug(
f"関数終了: {func.__name__}",
result=result,
execution_time=f"{execution_time:.3f}s"
)
# パフォーマンスデータの記録
self.record_performance(func.__name__, execution_time)
return result
except Exception as e:
execution_time = time.time() - start_time
# エラー情報
self.error(
f"関数エラー: {func.__name__}",
error_type=type(e).__name__,
error_message=str(e),
execution_time=f"{execution_time:.3f}s"
)
# 対話的デバッグが有効な場合
if self.debug_enabled:
self.interactive_debug(e, func.__name__, args, kwargs)
raise
return wrapper
def record_performance(self, func_name, execution_time):
"""パフォーマンスデータを記録"""
if func_name not in self.performance_data:
self.performance_data[func_name] = []
self.performance_data[func_name].append(execution_time)
# 定期的にパフォーマンスレポートを出力
if len(self.performance_data[func_name]) % 10 == 0:
self.log_performance_report(func_name)
def log_performance_report(self, func_name=None):
"""パフォーマンスレポートを出力"""
if func_name:
times = self.performance_data.get(func_name, [])
if times:
avg_time = sum(times) / len(times)
max_time = max(times)
self.info(
f"パフォーマンスレポート: {func_name}",
calls=len(times),
average_time=f"{avg_time:.3f}s",
max_time=f"{max_time:.3f}s"
)
else:
for name, times in self.performance_data.items():
if times:
avg_time = sum(times) / len(times)
self.debug(
f"パフォーマンスサマリー: {name}",
calls=len(times),
average_time=f"{avg_time:.3f}s"
)
def interactive_debug(self, exception, func_name, args, kwargs):
"""
対話的デバッグセッションを開始
"""
if not self.debug_enabled:
return
self.info(
"対話的デバッグセッションを開始",
function=func_name,
exception=type(exception).__name__
)
print(f"\n{'='*60}")
print(f"🔧 対話的デバッグセッション")
print(f"{'='*60}")
print(f"関数: {func_name}")
print(f"例外: {type(exception).__name__}: {exception}")
print(f"引数: args={args}, kwargs={kwargs}")
print(f"\n利用可能なコマンド:")
print(f" pdb - pdbデバッガを起動")
print(f" vars - ローカル変数を表示")
print(f" continue - デバッグを続行")
print(f" quit - プログラムを終了")
print(f"{'='*60}")
while True:
try:
command = input("\nデバッグコマンド> ").strip().lower()
if command == 'pdb':
print("pdbデバッガを起動します...")
pdb.set_trace()
break
elif command == 'vars':
frame = inspect.currentframe().f_back
print("ローカル変数:")
for var_name, var_value in frame.f_locals.items():
print(f" {var_name} = {repr(var_value)}")
elif command == 'continue':
print("デバッグを続行します")
break
elif command == 'quit':
print("プログラムを終了します")
exit(1)
else:
print("未知のコマンドです")
except KeyboardInterrupt:
print("\nデバッグセッションを終了します")
break
@contextmanager
def breakpoint(self, condition=True, name="default"):
"""
コンテキストマネージャとしてのブレークポイント
"""
if condition and self.debug_enabled:
self.info(f"ブレークポイントに到達: {name}")
frame = inspect.currentframe()
caller_frame = frame.f_back.f_back
caller_info = f"{caller_frame.f_code.co_filename}:{caller_frame.f_lineno}"
print(f"\n⏸️ ブレークポイント: {name}")
print(f"場所: {caller_info}")
print(f"コンテキスト: {caller_frame.f_code.co_name}")
response = input("デバッガを起動しますか? (y/n): ").strip().lower()
if response == 'y':
pdb.set_trace()
try:
yield
finally:
pass
def performance_monitor(self, func):
"""
パフォーマンス監視デコレータ
"""
@wraps(func)
def wrapper(*args, **kwargs):
with self.measure_performance(func.__name__):
return func(*args, **kwargs)
return wrapper
@contextmanager
def measure_performance(self, operation_name):
"""
パフォーマンス計測コンテキストマネージャ
"""
start_time = time.time()
try:
yield
finally:
end_time = time.time()
execution_time = end_time - start_time
self.record_performance(operation_name, execution_time)
if execution_time > 1.0:
self.warning(
f"処理時間が長い: {operation_name}",
execution_time=f"{execution_time:.3f}s"
)
def enable_debug(self):
"""デバッグを有効化"""
self.debug_enabled = True
self.logger.setLevel(logging.DEBUG)
self.info("デバッグモードを有効化")
def disable_debug(self):
"""デバッグを無効化"""
self.debug_enabled = False
self.logger.setLevel(logging.INFO)
self.info("デバッグモードを無効化")
# テストアプリケーション
class TestApp:
"""テスト用アプリケーション"""
def __init__(self):
self.debug_env = ComprehensiveDebugEnvironment("TestApp")
# デバッグ環境で関数を装飾
self.process_data = self.debug_env.trace(self.process_data)
self.validate_input = self.debug_env.trace(self.validate_input)
self.complex_calculation = self.debug_env.performance_monitor(
self.debug_env.trace(self.complex_calculation)
)
def process_data(self, data):
"""データ処理関数"""
self.debug_env.debug("データ処理を開始", data_length=len(data))
with self.debug_env.breakpoint(len(data) > 5, "large_dataset"):
# データ検証
self.validate_input(data)
# 複雑な計算
result = self.complex_calculation(data)
self.debug_env.info("データ処理完了", result=result)
return result
def validate_input(self, data):
"""入力検証関数"""
if not data:
raise ValueError("データが空です")
if any(x < 0 for x in data):
self.debug_env.warning("負の値が含まれています")
return True
def complex_calculation(self, data):
"""複雑な計算関数"""
total = 0
for i, value in enumerate(data):
# 特定の条件でブレークポイント
with self.debug_env.breakpoint(value == 999, "special_value"):
calculated = value ** 2 + i
total += calculated
# 人工的な遅延
time.sleep(0.01)
return total
def run(self):
"""アプリケーションを実行"""
self.debug_env.info("アプリケーションを開始")
test_datasets = [
[1, 2, 3, 4, 5],
[10, -5, 20, 999, 30], # 負の値と特殊値を含む
[], # 空のデータ(エラーを発生)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 大きなデータセット
]
for i, data in enumerate(test_datasets):
self.debug_env.info(f"データセット {i+1} を処理", data=data)
try:
result = self.process_data(data)
self.debug_env.info(f"処理結果: {result}")
except Exception as e:
self.debug_env.error(f"処理に失敗: {e}")
print("-" * 50)
# パフォーマンスレポートを表示
self.debug_env.log_performance_report()
self.debug_env.info("アプリケーションを終了")
# テスト
print("=== 問題12テスト ===")
print("包括的なデバッグ環境のデモを実行します")
app = TestApp()
app.run()
print("\nデバッグ環境の機能:")
print("✅ 詳細なロギング")
print("✅ 関数トレーシング")
print("✅ パフォーマンス監視")
print("✅ 対話的デバッグ")
print("✅ 条件付きブレークポイント")
print("✅ パフォーマンスレポート")