一覧SD-13-1 · エラー・ログ・機密情報

エラーレスポンスとログの設計

調査に使う識別子を残しながら、例外の詳細や秘密値を外へ出さない方法を確認できます。

説明

処理が失敗したとき、利用者への応答と運用者の調査ログには異なる情報が必要です。内部の例外文をそのまま返すと、接続情報や入力値が漏れる場合があります。

注文更新を途中で失敗させ、応答とログを同じrequest_idで照合します。改行入りの利用者名と公開の秘密値試料を渡し、ログの行構造とロールバック後の保存額を調べます。

調査に使う識別子を残しながら、例外の詳細や秘密値を外へ出さない方法を確認できます。

1元の注文と失敗条件を用意する

注文7の金額は100です。失敗時は内部例外を発生させる固定処理を使います。

from sd13 import * lab = Errors() secret = "PUBLIC-DEMO-SECRET" print(lab.db.execute("SELECT * FROM orders失敗前の金額を読む").fetchall())

注文は1件です。次の操作では更新後に失敗を起こし、応答だけでなく保存値を追います。

2失敗応答へ内部情報を含めない

例外文には公開秘密値が含まれますが、応答には種別とrequest_idだけを返します。

status,body = lab.request固定の更新失敗を発生させる("r-7","alice",secret,True) print(status,body) print(secret in応答への秘密値混入を調べる json.dumps(body))

500、processing_failed、r-7です。秘密値の包含はFalse。利用者は照会用のIDを伝えられます。

3同じIDでログと応答を結び付ける

ログは例外全文ではなく、調査用の項目を選んで記録します。

event = json.loads構造化ログを解析する(lab.lines[0]) print(event) print(event["request_id"]==body["request_id"]応答との対応を確認する)

同じr-7を持ち、例外の型はRuntimeErrorです。原因調査に追加情報が必要なら、アクセス制限した記録へ必要な範囲で残します。

4改行を含む値でログの行数を調べる

利用者名に改行を含む固定文字列を渡します。

lab.request("r-8","alice"+chr(10)値の内部に改行を含める+"forged success",secret,True) line = lab.lines[-1] print(line) print(len(line.splitlines())偽の独立ログ行が生まれていないか調べる)

JSON中の改行はエスケープされ、物理行は1行です。後から解析した文字列には改行が戻るため、表示先でもデータとして扱います。

5調査ログにも秘密値を残さない

ログ全体を検索し、内部の例外詳細が混ざっていないか調べます。

print(any(secret in line記録全体の秘密値混入を調べる for line in lab.lines)) print([json.loads(line)["error_type"] for line in lab.lines])

秘密値はなく、例外型だけが2件あります。何でも記録して後でマスクする方法に頼らず、出力項目を選択しています。

6失敗後の保存値を確認する

二回とも更新途中で失敗しました。

print(lab.db.execute("SELECT amount失敗後に残った金額を読む FROM orders WHERE id=7").fetchone())

100のままです。例外を捕捉して一般的な応答を返すだけでなく、途中更新がロールバックされることも確かめます。

7成功した処理と失敗記録を分ける

同じ処理を失敗なしで実行します。

print(lab.request("r-9","alice",secret,False失敗を起こさず確定させる)) print(lab.db.execute("SELECT amount FROM orders").fetchone(),len(lab.lines))

200で金額90へ更新し、失敗ログは2件のままです。成功・失敗を同じラベルで記録して原因を曖昧にしません。

まとめ

応答とログを関連IDで結び付けます。

構造化と出力項目の選択でログを保護します。

エラー処理後の保存状態も照合します。

操作履歴と解説を振り返れます。

Python 3.15 / 固定の公開試料

1
from sd13 import * lab = Errors() secret = "PUBLIC-DEMO-SECRET" print(lab.db.execute("SELECT * FROM orders").fetchall())
[(7, 100)]
2
status,body = lab.request("r-7","alice",secret,True) print(status,body) print(secret in json.dumps(body))
500 {'request_id': 'r-7', 'error': 'processing_failed'}
False
3
event = json.loads(lab.lines[0]) print(event) print(event["request_id"]==body["request_id"])
{'request_id': 'r-7', 'event': 'order_failed', 'user': 'alice', 'error_type': 'RuntimeError'}
True
4
lab.request("r-8","alice"+chr(10)+"forged success",secret,True) line = lab.lines[-1] print(line) print(len(line.splitlines()))
{"request_id": "r-8", "event": "order_failed", "user": "alice\nforged success", "error_type": "RuntimeError"}
1
5
print(any(secret in line for line in lab.lines)) print([json.loads(line)["error_type"] for line in lab.lines])
False
['RuntimeError', 'RuntimeError']
6
print(lab.db.execute("SELECT amount FROM orders WHERE id=7").fetchone())
(100,)
7
print(lab.request("r-9","alice",secret,False)) print(lab.db.execute("SELECT amount FROM orders").fetchone(),len(lab.lines))
(200, {'request_id': 'r-9', 'amount': 90})
(90,) 2

演習完了