- 技術 💻
ログとは|種類・見方・残し方をnginxとアプリの実物で解説
結論
ログとは、ソフトウェアが「いつ・どこで・何が起きて・結果どうなったか」を1行ずつ時系列で書き残した記録です。Web サーバーのアクセスログ・エラーログ、アプリのログ、OS のシステムログなどの種類があり、どれも「時刻・場所(どのプログラム)・重さ(レベル)・内容」の4つの要素で読めます。
この記事でわかること
- ログの5つの種類と、Linux・Windows でどこにあるか
- nginx と自作アプリで実際に出したログ原文の1行ずつの読み方
- ログレベル・JSON 形式・logrotate・障害時の追い方・個人情報のマスク
ログは、リクエストが通る場所ごとに別のファイルへ書かれます。手元で動かした構成では、1回のアクセスが次のように3つのログに残りました。
flowchart TD
C[ブラウザ・curl] --> N[nginx]
N --> A[access.log<br>何を要求し<br>何番を返したか]
N --> E[error.log<br>nginx 自身の<br>問題]
N --> P[アプリ]
P --> L[app.log<br>処理の中身<br>例外]
A --> R[logrotate<br>日ごとに圧縮・削除]
L --> R
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| ログとは | いつ・どこで・何が起きたかを時系列で残した記録 | ログとは |
| ログの種類 | アクセス・エラー・アプリ・システム・監査の5つ | ログの種類 |
| ログはどこにある | Linux は /var/log と journalctl、Windows はイベントビューアー | ログはどこにあるか |
| アクセスログの見方 | IP・時刻・リクエスト・ステータス・バイト数・UA の順 | アクセスログの見方 |
| ログレベルとは | DEBUG<INFO<WARNING<ERROR<CRITICAL の重さ | ログレベルとは |
| ログの見方(コマンド) | tail -f・grep・awk・journalctl | ログを見るコマンド |
| ログが増え続ける | logrotate で日ごとに分けて圧縮・削除 | ログの残し方 |
| 障害のときどこから見る | 時刻→ステータス→エラーログ→リクエストID の順 | 障害時のログの追い方 |
| ログに書いてはいけないもの | パスワード・カード番号・トークン。メールはマスク | 個人情報のマスク |
確認環境:Ubuntu 24.04.5 LTS の上で、apt で入れた nginx 1.24.0(Ubuntu)を自分の設定ファイルで 127.0.0.1:45714 に起動し、access.log(combined 形式と JSON 形式)と error.log(warn レベル)の原文を取りました。アプリは Python 3.13.18 の標準ライブラリ(http.server・logging)だけで作り、テキスト形式と JSON 形式のログ、リクエスト ID の引き継ぎ、個人情報のマスクを実行しました。ローテーションは logrotate 3.21.0 を作業場所の設定ファイルと状態ファイルで -f 実行し、nginx への USR1 と copytruncate の両方の動きを確かめています。リクエストの送信は curl 8.5.0 です。システムログは /var/log/apt/history.log・/var/log/dpkg.log の実物を読み、journalctl はこの環境にジャーナルが無かったため systemd の公式マニュアルで確かめました。Windows のイベントビューアーは Microsoft の公式ドキュメントで確かめた範囲だけを書いています。図は mermaid で描画を確かめました(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ログとは|種類・見方・残し方をnginxとアプリの実物で解説」
ログとは(意味と役割)
この節では、ログという言葉の意味と、ログが何のために残されるのかが分かります。
ログ(log)とは、プログラムやサーバーが動いた記録を、起きた順に1行ずつ書き残したものです。 もとは船の航海日誌(logbook)を指す言葉で、「何時にどこで何が起きたか」を後から読み返せるようにする、という役割はそのままです。
手元の nginx に curl で1回アクセスしたとき、アクセスログに残った1行がこれです。
127.0.0.1 - - [29/Oct/2026:18:45:42 +0900] "GET /api/orders/1 HTTP/1.1" 200 51 "-" "curl/8.5.0"
「127.0.0.1 から、10月29日 18時45分42秒に、/api/orders/1 が GET で要求され、200(成功)で 51 バイト返した。使ったソフトは curl 8.5.0」と読めます。ログは人がその場で見ていなくても、あとから「何が起きたか」を再現できる唯一の手がかりです。
1障害の原因を調べる
エラーが出た時刻の前後を読み、どのリクエストで何が失敗したかをたどる。
2不正なアクセスに気づく
見覚えのない IP・URL・ログイン失敗の連続を見つける。
3証拠として残す
誰がいつ何を変えたかを、あとから第三者が確かめられる形で保存する(監査)。
4改善の材料にする
遅いページ・多い 404・使われていない機能を数えて、直す順番を決める。
ログの種類(アクセスログ・エラーログ・アプリログ・システムログ・監査ログ)
この節では、ログの主な5種類が「何を」「誰が」書くものかと、手元で取った実物の1行が分かります。
ログは「書く主体」で分けると分かりやすくなります。 Web サーバーが書くのがアクセスログとエラーログ、アプリの開発者が書くのがアプリケーションログ、OS や常駐サービスが書くのがシステムログ、「誰が何をしたか」だけを残す目的で分けたものが監査ログです。
| 種類 | 誰が書く・何を書く | 手元で取った1行(抜粋) |
|---|---|---|
| アクセスログ | Web サーバー。1リクエストにつき1行。誰が何を要求し何番を返したか | "GET /api/orders/99 HTTP/1.1" 404 25 |
| エラーログ | Web サーバー。サーバー自身が困ったこと(ファイルが無い・中継先に繋がらない) | [error] connect() failed (111: Connection refused) while connecting to upstream |
| アプリケーションログ | 開発者が書いたコード。処理の進み・警告・例外のスタックトレース | WARNING [c0e5…] shop: order not found oid=99 |
| システムログ | OS・パッケージ管理・常駐サービス。起動・停止・インストール | 2026-10-29 18:44:55 install nginx-common:all <none> 1.24.0-2ubuntu7.19 |
| 監査ログ | 「誰が・いつ・何を変えたか」を証拠として残す。消さない・書き換えない | Commandline: apt-get install -y --no-install-recommends nginx |
システムログと監査ログの行は、この環境の /var/log/dpkg.log と /var/log/apt/history.log にそのまま残っていたものです。自分が apt でインストールした操作も、頼んでいないのにログに残っています。 これが「ログは勝手に書かれる」という感覚のもとです。
アクセスログ(外から見た記録)
1リクエスト=1行で、正常でも書かれる
集計に向く(件数・ステータス・遅さ)
何が起きたかは分かるが、なぜかは分からない
エラーログ・アプリログ(中から見た記録)
問題や節目のときだけ書かれる
原因(例外・設定・接続先)が書いてある
アクセスログの「500」の理由はこちらにある
ログはどこにあるか(/var/log・journalctl・イベントビューアー)
この節では、Linux と Windows でログがどこに置かれ、どう開くのかが分かります。
Linux のログは、ファイルなら /var/log の下、systemd のジャーナルなら journalctl コマンドで見ます。 この環境の /var/log を ls した結果です。
$ ls -la /var/log
-rw-r--r-- 1 root root 48009 alternatives.log # update-alternatives の記録
drwxr-xr-x 2 root root 4096 apt/ # apt の history.log・term.log
-rw-r--r-- 1 root root 725801 dpkg.log # パッケージの install/remove
-rw-rw---- 1 root utmp 0 btmp # ログイン失敗(last -f で読む)
drwxr-sr-x 2 root systemd-journal journal/ # systemd のジャーナル(バイナリ)
drwxr-xr-x 2 root adm 4096 nginx/ # apt の nginx の既定の置き場
drwxrwxr-t 2 root postgres 4096 postgresql/ # PostgreSQL のログ
-rw-rw-r-- 1 root utmp 0 wtmp # ログイン履歴(last で読む)
| 環境 | 場所・開き方 | 備考 |
|---|---|---|
| Linux(ファイル) | /var/log/(nginx は /var/log/nginx/、apt は /var/log/apt/) |
多くは root か adm グループだけ読める |
| Linux(systemd) | journalctl -u サービス名、journalctl -p err、journalctl -f |
バイナリなので cat では読めない |
| Windows | イベントビューアー。「Windows ログ」にアプリケーション・セキュリティ・システム | Microsoft の公式ドキュメントで確認 |
| コンテナ(Docker) | 標準出力に書き、docker logs で読むのが基本 |
コンテナ内のファイルは消えやすい |
| 自作アプリ | 設定で決める。手元は logs/app.log・logs/app.json.log |
起動時に「どこに書くか」を1行出すと迷わない |
この環境にはジャーナルのファイルが無く、journalctl -n 3 は No journal files were found. と返しました。コンテナや最小構成の環境では、journalctl に何も出ないことがあります。そのときはファイルのログ(/var/log の下)と、アプリ自身の出力を見ます。journalctl の使い方は、関連記事「journalctlの使い方|目的別コマンド逆引き一覧と絞り込み例」で扱っています。
アクセスログの見方(nginx の1行を要素ごとに読む)
この節では、nginx のアクセスログ1行を、欄ごとに何を表すか分解して読めるようになります。
nginx の既定の形式は combined で、9つ(IP・識別子・ユーザー・時刻・リクエスト・ステータス・バイト数・参照元・User-Agent)の欄が空白で区切られています。 nginx の公式ドキュメントにある定義です。
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
手元で、ブラウザを装った User-Agent と参照元を付けて送ったときの1行です。
127.0.0.1 - - [29/Oct/2026:18:45:43 +0900] "GET / HTTP/1.1" 200 15 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/141.0.0.0"
| 欄 | 値(上の行) | 意味 |
|---|---|---|
| $remote_addr | 127.0.0.1 | 接続してきた IP アドレス。ロードバランサー経由だとその IP になる |
| - | - | 識別子(ほぼ常に「-」) |
| $remote_user | - | Basic 認証のユーザー名。認証が無ければ「-」 |
| [$time_local] | [29/Oct/2026:18:45:43 +0900] | 応答を書き終えた時刻とタイムゾーン |
| "$request" | "GET / HTTP/1.1" | メソッド・パス・プロトコル |
| $status | 200 | HTTP ステータスコード(2xx 成功・4xx 要求側の誤り・5xx サーバー側の失敗) |
| $body_bytes_sent | 15 | 返した本文のバイト数(ヘッダーを除く) |
| "$http_referer" | "https://www.google.com/" | どのページから来たか。直接入力なら「-」 |
| "$http_user_agent" | "Mozilla/5.0 … Chrome/141.0.0.0" | ブラウザ・OS・ボットの名乗り。偽れる |
同じ構成で返したステータスごとの行を並べると、アクセスログだけで「何が」「どのくらい」失敗したかは分かります。 ただし「なぜ」は書いてありません。
127.0.0.1 - - [29/Oct/2026:18:45:42 +0900] "GET /api/orders/99 HTTP/1.1" 404 25 "-" "curl/8.5.0"
127.0.0.1 - - [29/Oct/2026:18:45:42 +0900] "GET /api/orders/abc HTTP/1.1" 500 24 "-" "curl/8.5.0"
127.0.0.1 - - [29/Oct/2026:18:45:43 +0900] "GET /down/x HTTP/1.1" 502 166 "-" "curl/8.5.0"
127.0.0.1 - - [29/Oct/2026:18:45:43 +0900] "GET /secret.html HTTP/1.1" 403 162 "-" "curl/8.5.0"
Apache の既定(combined)も同じ並びです。1行の形が同じなので、読み方はそのまま使えます。
エラーログの見方(nginx の error.log の原文)
この節では、Web サーバーのエラーログの1行の構造と、アクセスログの 404・500・502・403 がエラーログでどう書かれるかが分かります。
エラーログの1行は「時刻 [レベル] プロセス番号: 接続番号 メッセージ, client: …, request: …」の形です。 上の4つの失敗に対応して、error.log にはこう残りました。
2026/10/29 18:45:43 [error] 2012#2012: *12 open() "/srv/s4/r17/t14/html/nothing.html" failed (2: No such file or directory), client: 127.0.0.1, server: , request: "GET /nothing.html HTTP/1.1", host: "127.0.0.1:45714"
2026/10/29 18:45:43 [error] 2012#2012: *15 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET /down/x HTTP/1.1", upstream: "http://127.0.0.1:45716/x", host: "127.0.0.1:45714"
2026/10/29 18:45:43 [error] 2012#2012: *18 open() "/srv/s4/r17/t14/html/secret.html" failed (13: Permission denied), client: 127.0.0.1, server: , request: "GET /secret.html HTTP/1.1", host: "127.0.0.1:45714"
| アクセスログの結果 | error.log の文言 | 原因と直し方 |
|---|---|---|
| 404 | open() "…/nothing.html" failed (2: No such file or directory) |
ファイルが無い。パスと root の設定を見る |
| 502 | connect() failed (111: Connection refused) while connecting to upstream |
中継先のアプリが起動していない・ポート違い |
| 403 | open() "…/secret.html" failed (13: Permission denied) |
ファイルの権限。nginx の worker が読める権限にする |
| 500(アプリの例外) | なし | nginx は正常に中継できたので書かない。アプリのログを見る |
- 500 はエラーログに出ませんでした。 アプリが「500 を返す」という正常な応答をしたので、nginx にとっては失敗ではないためです。アプリの例外は次の節のアプリログにあります。
*12のような番号は接続の通し番号で、同じ接続の複数の行をつなぐ手がかりになります。- nginx の公式ドキュメントでは、
error_logのレベルはdebug, info, notice, warn, error, crit, alert, emergの順に重くなり、既定はerrorです。手元はwarnにして、warn 以上を書かせています。
アプリケーションログの見方(レベル・リクエストID・スタックトレース)
この節では、開発者が自分で書くアプリログの1行の形と、例外が出たときの読み方が分かります。
アプリログは「時刻 レベル [リクエストID] 出どころ: メッセージ」の形にそろえると、あとで追いやすくなります。 手元のアプリ(Python の logging)が出した app.log の原文です。
2026-10-29 18:45:41,160 INFO [-] shop: server starting port=45715
2026-10-29 18:45:42,117 DEBUG [a270a64842d9f31c4f534cb4b38656e3] shop: order lookup oid=1
2026-10-29 18:45:42,117 INFO [a270a64842d9f31c4f534cb4b38656e3] shop: order found oid=1 user_id=u-42
2026-10-29 18:45:42,126 DEBUG [c0e5544cb4ad9b527ffe85675da8a5b5] shop: order lookup oid=99
2026-10-29 18:45:42,126 WARNING [c0e5544cb4ad9b527ffe85675da8a5b5] shop: order not found oid=99
2026-10-29 18:45:42,133 ERROR [c04eef2abacc3cd98f95ec6e7e264978] shop: unhandled error
Traceback (most recent call last):
File "/srv/s4/r17/t14/app/app.py", line 64, in do_GET
oid = int(self.path.rsplit("/", 1)[1])
ValueError: invalid literal for int() with base 10: 'abc'
2026-10-29 18:45:43,347 WARNING [257c4d6f6a0cc5d8b1259cc2015c3652] shop: slow response: db query took 1180ms
書いた側のコードは次のとおりで、extra でリクエスト ID を毎行に付け、例外は exc_info=True でスタックトレースごと残しています。
fh.setFormatter(logging.Formatter(
"%(asctime)s %(levelname)-7s [%(request_id)s] %(name)s: %(message)s"))
try:
order = ORDERS[oid] # 無ければ KeyError
text_logger.info("order found oid=%s user_id=u-42", oid, extra=extra)
except KeyError as e:
text_logger.warning("order not found oid=%s", e, extra=extra) # 想定内 → WARNING
except Exception:
text_logger.error("unhandled error", exc_info=True, extra=extra) # 想定外 → ERROR+トレース
時刻を見る
アクセスログの 500 の時刻(18:45:42)と同じ秒の行を探す。ミリ秒まであると複数の行の順番が分かる。
レベルを見る
ERROR の行が「想定外の失敗」。WARNING は想定内の失敗や遅さ。DEBUG は細かい途中経過。
スタックトレースは下から読む
最後の行が例外の種類と内容(ValueError: invalid literal for int() … 'abc')。その上の File 行が、自分のコードのどの行で起きたか(app.py の 64 行目)。
リクエスト ID で同じ処理の行を集める
角かっこの中の ID で grep すると、1つのリクエストの DEBUG から ERROR までが順に並ぶ。
ログレベルとは(DEBUG・INFO・WARNING・ERROR・CRITICAL の使い分け)
この節では、ログレベルの意味と、Python・nginx・syslog・Windows でレベルの呼び方がどう対応するかが分かります。
ログレベルは、その行の「重さ」です。 設定で「このレベル以上だけ書く」と決められるので、普段は INFO 以上、調査のときだけ DEBUG まで出す、という使い方になります。Python の公式ドキュメントでは、DEBUG(10)・INFO(20)・WARNING(30)・ERROR(40)・CRITICAL(50)の順で、数が大きいほど重くなります。
| 意味 | Python / Java 系 | nginx の error_log | syslog・journalctl(番号) | Windows イベント |
|---|---|---|---|---|
| 続行できない致命的な失敗 | CRITICAL / FATAL | emerg・alert・crit | emerg(0)・alert(1)・crit(2) | 重大 |
| その処理が失敗した | ERROR | error | err(3) | エラー |
| 失敗ではないが注意 | WARNING / WARN | warn | warning(4) | 警告 |
| 正常な節目 | INFO | notice・info | notice(5)・info(6) | 情報 |
| 開発時の細かい途中経過 | DEBUG / TRACE | debug | debug(7) | 詳細 |
手元の app.log をレベルごとに数えると、INFO 5・DEBUG 2・WARNING 2・ERROR 2 でした。ERROR は「作った側が想定していなかった失敗」に限ると、ERROR の件数がそのまま「見るべき件数」になります。「注文が無い(404)」のような想定内の失敗を ERROR にすると、本当の失敗が埋もれます。
レベルの付け方
判断の問い:「この行が出たら、人が起きて対応する必要があるか」
レベルごとの判断基準の詳しい一覧は、関連記事「ログレベルの使い分け|ERROR・WARN・INFO・DEBUGの判断基準とログ設計」で扱っています。
構造化ログ(JSON)とリクエストIDで複数のログをつなぐ
この節では、テキスト形式と JSON 形式の違いと、リクエスト ID で nginx とアプリのログを1本につなぐ方法が分かります。
構造化ログとは、1行を「項目名: 値」の組(多くは JSON)で書いたログです。 人が読むには長くなりますが、項目名で正確に検索・集計でき、ログ基盤(CloudWatch Logs・Elasticsearch・Loki など)に入れたときにそのまま項目として扱えます。手元で nginx とアプリの両方を JSON にしました。
log_format json escape=json
'{"time":"$time_iso8601","remote_addr":"$remote_addr","request_id":"$request_id",'
'"method":"$request_method","uri":"$request_uri","status":$status,'
'"bytes":$body_bytes_sent,"request_time":$request_time,'
'"upstream_time":"$upstream_response_time","ua":"$http_user_agent"}';
location /json/ {
access_log /srv/s4/r17/t14/logs/access.json.log json;
proxy_pass http://127.0.0.1:45715/;
proxy_set_header X-Request-ID $request_id; # アプリへ ID を渡す
}
$request_id は nginx が付ける16バイトのランダムな ID で、escape=json は値に含まれる " や改行を JSON として壊れない形に直します(nginx の公式ドキュメント)。1回の失敗したリクエストで、3つのログに同じ ID が残りました。
$ curl -i http://127.0.0.1:45714/json/orders/abc
HTTP/1.1 500 Internal Server Error
X-Request-ID: 1207638d6fa0f087074443ea0b264d69
$ grep -rn 1207638d6fa0f087074443ea0b264d69 logs/
logs/access.json.log:2:{"time":"2026-10-29T18:45:56+09:00","remote_addr":"127.0.0.1","request_id":"1207638d6fa0f087074443ea0b264d69","method":"GET","uri":"/json/orders/abc","status":500,"bytes":24,"request_time":0.007,"upstream_time":"0.006","ua":"curl/8.5.0"}
logs/app.log:15:2026-10-29 18:45:56,674 ERROR [1207638d6fa0f087074443ea0b264d69] shop: unhandled error
logs/app.json.log:8:{"time": "2026-10-29T18:45:56.680+09:00", "level": "INFO", "logger": "shop.json", "request_id": "1207638d6fa0f087074443ea0b264d69", "message": "request completed", "user_id": "u-42", "duration_ms": 6.2, "path": "/orders/abc", "status": 500}
応答ヘッダーの X-Request-ID を問い合わせの人に教えてもらえば、その1件だけを3つのログから一度に取り出せます。 これが、アクセスログ(何が起きた)とアプリログ(なぜ起きた)をつなぐ鍵です。
| テキスト形式 | JSON 形式 | |
|---|---|---|
| 人が目で読む | 読みやすい | 長くて読みにくい(jq で整形する) |
| 検索・集計 | 欄の位置に頼る(awk '{print $9}') | 項目名で正確に取れる(.status) |
| 値に空白・引用符がある | 欄がずれる | エスケープされて壊れない |
| 1行の大きさ(手元の実測) | 185 バイト | 335 バイト |
| 向く場面 | 開発中・1台のサーバー | 複数台・ログ基盤に集める |
必須の項目の決め方と Python・Node での実装は、関連記事「構造化ログ(JSON)設計|必須フィールド一覧とPython・Node実装例」で扱っています。
ログを見るコマンド(tail -f・grep・awk・journalctl)
この節では、ログを「流しながら見る」「絞る」「数える」ための最小のコマンドが、手元の出力つきで分かります。
最初に覚えるのは4つです。 流しながら見る tail -f、絞る grep、数える awk | sort | uniq -c、systemd のログを見る journalctl です。
流しながら見る
tail -f logs/access.log。書かれた行が順に表示される。Ctrl+C で止める。
-n 100 で直前100行から
失敗だけに絞る
grep -E '" 5[0-9]{2} ' access.log。ステータスが 5xx の行だけ。
-n で行番号、-i で大小無視
数える
awk '{print $9}' access.log | sort | uniq -c | sort -rn。9番目の欄=ステータスの件数。
$7 なら URL 別
systemd のログ
journalctl -u nginx -p err --since today。ユニット・重さ・時刻で絞る。
-f で流しながら、-o json で JSON
手元のアクセスログを数えた結果です。
$ awk '{print $9}' access.log-20261029 | sort | uniq -c | sort -rn
6 200
3 404
1 502
1 500
1 403
$ grep -oE '\[(emerg|alert|crit|error|warn|notice|info)\]' error.log-20261029 | sort | uniq -c
4 [error]
grep の詳しい使い方と awk の集計の実例は、関連記事「awkコマンドの使い方|実例50と集計・ログ解析、gawkとmawkの違い」で扱っています。
ログの残し方(ローテーション:logrotate の実物)
この節では、ログが増え続ける問題と、logrotate で日ごとに分けて圧縮・削除する実際の動きが分かります。
ログは放っておくと増え続け、ディスクを使い切ります。 手元の実測では、nginx の combined 形式は1行 185 バイト、JSON 形式は1行 335 バイトでした。
そこで、1日ごと(またはサイズごと)に別のファイルへ切り替え、前のものを圧縮し、一定の数を超えたら消します。これがローテーションで、Linux では logrotate が担当します。作業場所に置いた設定ファイルです。
/srv/s4/r17/t14/logs/access.log /srv/s4/r17/t14/logs/error.log {
daily # 1日ごと
rotate 14 # 14世代残す
missingok # ファイルが無くてもエラーにしない
notifempty # 空なら回さない
compress # gzip で圧縮
delaycompress # 直前の1世代は圧縮を待つ
dateext # access.log-20261029 の形にする
sharedscripts
postrotate
kill -USR1 $(cat /srv/s4/r17/t14/tmp/nginx.pid) # nginx に「開き直せ」と伝える
endscript
}
/srv/s4/r17/t14/logs/app.log {
size 1k # 1KB を超えたら
rotate 3
copytruncate # コピーしてから元を空にする(開き直せないアプリ向け)
compress
}
-f(強制)と -v(詳しく)で実行した結果の要点と、実行後のファイル一覧です。
$ logrotate -f -v -s tmp/logrotate.state logrotate.conf
rotating log /srv/s4/r17/t14/logs/access.log, log->rotateCount is 14
dateext suffix '-20261029'
renaming /srv/s4/r17/t14/logs/access.log to /srv/s4/r17/t14/logs/access.log-20261029
renaming /srv/s4/r17/t14/logs/error.log to /srv/s4/r17/t14/logs/error.log-20261029
running postrotate script
rotating log /srv/s4/r17/t14/logs/app.log, log->rotateCount is 3
copying /srv/s4/r17/t14/logs/app.log to /srv/s4/r17/t14/logs/app.log.1
truncating /srv/s4/r17/t14/logs/app.log
compressing log with: /bin/gzip
$ ls -l logs/
-rw-r--r-- 1 nobody root 0 access.log # nginx が開き直した新しいファイル
-rw-r--r-- 1 root root 1189 access.log-20261029 # 前のファイル(delaycompress で次回に圧縮)
-rw-r--r-- 1 root root 0 app.log
-rw-r--r-- 1 root root 613 app.log.1.gz # コピーしたものを圧縮
-rw-r--r-- 1 nobody root 0 error.log
-rw-r--r-- 1 root root 880 error.log-20261029
ローテーション後に1回アクセスすると、新しい access.log に1行だけ書かれました。ここで大切なのは postrotate の USR1 です。 試しに postrotate 無しで access.log を mv しただけにすると、nginx は名前の変わった前のファイルに書き続け、新しい access.log は作られませんでした。
$ mv access.log access.log.moved; curl -s http://127.0.0.1:45714/after-mv
$ ls access.log
ls: cannot access 'access.log': No such file or directory
$ tail -1 access.log.moved
127.0.0.1 - - [29/Oct/2026:18:46:27 +0900] "GET /after-mv HTTP/1.1" 404 162 "-" "curl/8.5.0"
$ kill -USR1 $(cat tmp/nginx.pid); curl -s http://127.0.0.1:45714/after-usr1
$ cat access.log
127.0.0.1 - - [29/Oct/2026:18:46:27 +0900] "GET /after-usr1 HTTP/1.1" 404 162 "-" "curl/8.5.0"
- プログラムは「ファイル名」ではなく「開いたファイル」に書き続けます。名前を変えても、開き直すまで前のファイルに書かれます。nginx は
USR1、多くのサーバーはHUPや reopen の命令で開き直します。 - 開き直す仕組みが無いアプリには
copytruncateを使います。コピーと空にする間に書かれた行は失われる可能性がある、という点は logrotate のマニュアルにも書かれています。 - app.log は
copytruncateのあと、次のリクエストの行が空になった app.log に正しく書かれました(Python の logging は追記モードで開いているため)。
Ubuntu の nginx は、apt で入れた時点で /etc/logrotate.d/nginx が置かれ、/var/log/nginx の下を毎日回す設定になっています。ディレクティブの一覧とコピーして使える雛形は、関連記事「logrotateの設定方法|コピペ用テンプレートとディレクティブ一覧」で扱っています。
障害時のログの追い方(手順)
この節では、「エラーが出た」「遅い」と言われたときに、どのログをどの順に見ると最短で原因に着くかが分かります。
順番は「時刻 → アクセスログで範囲を絞る → エラーログ・アプリログで理由を読む → リクエスト ID で1件に絞る → 直前の変更を疑う」です。 手元の 500 を、この順で追った例を添えます。
- 1いつ・誰に・何が起きたかを聞く時刻(分まで)・URL・画面の文言・X-Request-ID。タイムゾーンも確かめる
- 2アクセスログをその時刻で絞り、ステータスを数えるgrep '29/Oct/2026:18:45' access.log | awk '{print $9}' | sort | uniq -c → 500 が1件、502 が1件
- 35xx なら error.log とアプリログの同じ時刻を読む502 は error.log の connect() failed(中継先が落ちている)。500 は app.log の ERROR と Traceback
- 4リクエスト ID で1件の全部の行を集めるgrep -rn <ID> logs/ で、nginx の行・アプリの DEBUG から ERROR までが並ぶ
- 5最初に出た ERROR より前を見る後ろの ERROR は連鎖のことが多い。直前の WARNING(遅い・再試行)が前兆
- 6直前に何を変えたかをシステムログで確かめる/var/log/apt/history.log・dpkg.log・デプロイの記録。手元では 18:44:55 に nginx を入れた記録が残っていた
手元の 500 は、手順3で app.log の ValueError: invalid literal for int() with base 10: 'abc' に着き、URL の注文番号に数字以外が来たときの入力チェックが無い、という原因が分かりました。アクセスログだけを何時間眺めても、この1行には着きません。 「何が」はアクセスログ、「なぜ」はアプリログ、と役割を分けて読むのが近道です。
- 1時刻のタイムゾーンをそろえるnginx は +0900、アプリは UTC、という食い違いが「ログが無い」の原因になる。手元は両方を JST にそろえた
- 25xx は「返した側」のログを見る502 は nginx の error.log、500 はアプリのログ。nginx の error.log に 500 は出なかった
- 3最初の1件を探す件数の多いエラーではなく、時刻の早いエラーが原因に近い
- 4ログが無いときは、書く設定・レベル・場所を疑うerror_log のレベルが error だと warn は出ない。ローテーション直後は前のファイルにある
- 5調べた経過を残す見たログの行と時刻を報告書に貼ると、再発防止の材料になる
個人情報のマスクと、ログに書いてはいけないもの
この節では、ログに書いてはいけない情報と、どうしても残す必要のある項目をマスクして書く方法が分かります。
パスワード・カード番号・トークン(セッション ID・API キー)は、どのレベルでもログに書きません。 ログは多くの人が読め、長く保存され、外部のログ基盤に送られることもあるからです。氏名・メールアドレス・電話番号は、追跡に必要なら一部を伏せて(マスクして)書きます。
手元のアプリは、最初はそのまま書いていました。
2026-10-29 18:45:43,356 INFO [bb2fb87c…] shop: signup email=taro.yamada@example.com card=4111-1111-1111-1234
logging の Filter で、書く直前にメールアドレスとカード番号を置き換えるようにしたあとの行です。
EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")
CARD = re.compile(r"\b(\d{4})[- ]?\d{4}[- ]?\d{4}[- ]?(\d{4})\b")
def mask(s: str) -> str:
s = EMAIL.sub(lambda m: m.group(0)[0] + "***@" + m.group(0).split("@")[1], s)
s = CARD.sub(r"\1-****-****-\2", s)
return s
class MaskFilter(logging.Filter):
def filter(self, record):
record.msg = mask(str(record.msg))
return True
text_logger.addFilter(MaskFilter())
2026-10-29 18:45:56,665 INFO [a4cf2802…] shop: signup email=t***@example.com card=4111-****-****-1234
| 情報 | 扱い | 理由 |
|---|---|---|
| パスワード・秘密鍵・API キー | 書かない(マスクもしない) | 一度でも残ると漏えいと同じ |
| セッション ID・アクセストークン | 書かない。必要なら末尾4文字など | 読んだ人がなりすませる |
| カード番号 | 先頭と末尾以外を伏せる | 手元は 4111-****-****-1234 |
| メールアドレス・電話番号 | 一部を伏せる、またはユーザー ID に置き換える | 追跡には ID で足りることが多い |
| ユーザー ID・注文 ID・リクエスト ID | 書く | 追跡の鍵。それ自体は秘密ではない |
| リクエストの本文(JSON 全体) | DEBUG でも丸ごと書かない | 上の全部が混ざって入る |
アクセスログの URL にも注意が要ります。?token=… や ?email=… をクエリ文字列に入れると、nginx の $request にそのまま残ります。秘密はヘッダーか本文で送ります。ログに出す項目の決め方と設計書の形は、関連記事「ログ設計の出力項目一覧|ログ設計書テンプレートとマスク」で扱っています。
よくあるエラー・つまずき(原文つき)
この節では、ログを見ようとしたときに出る文言と、その意味・直し方が分かります。
| 出た文言(原文) | 意味 | 直し方 |
|---|---|---|
No journal files were found. |
systemd のジャーナルがこの環境に無い(コンテナ・最小構成) | /var/log の下のファイルと、アプリの標準出力を見る |
tail: cannot open '/var/log/nginx/error.log' for reading: Permission denied |
root か adm グループだけ読める | sudo を付ける、または adm グループに入る |
open() "…" failed (13: Permission denied) |
nginx の worker がファイルを読めない | ファイルと途中のフォルダーの権限を直す |
connect() failed (111: Connection refused) while connecting to upstream |
中継先のアプリが起動していない・ポート違い | アプリの起動とポートを確かめる |
| ローテーション後、新しいログファイルに何も書かれない | 名前を変えただけで、プロセスは前のファイルに書き続けている | postrotate で開き直す(nginx は USR1)か copytruncate |
| 探している時刻の行が無い | タイムゾーンの違い(UTC と JST で9時間ずれる) | 各ログの時刻の表記を確かめ、9時間ずらして探す |
| ログが数行しか無い・エラーが出ない | レベルの設定が高い(error 以上だけ) | 調査中だけ warn や info に下げる |
| ディスクが一杯で「No space left on device」 | ローテーションや削除の設定が無い | logrotate を設定し、du -sh /var/log/* で大きいものを探す |
どのログを見るかの判断表
| 起きていること | 最初に見るログ | 次に見るログ |
|---|---|---|
| 画面に 500 が出る | アプリログの ERROR | アクセスログで件数と範囲 |
| 画面に 502・504 が出る | nginx の error.log(upstream の行) | アプリが起動しているか・遅いか |
| 404 が多い | アクセスログの URL と参照元 | リンク切れ・ボットの探索 |
| 遅い | アクセスログの $request_time・$upstream_response_time | アプリログの WARNING(遅いクエリ) |
| ログインできない人がいる | アプリログの認証の行(ユーザー ID で絞る) | システムログ(時刻のずれ・証明書) |
| サーバーが落ちた・再起動した | システムログ(journalctl -b -1・dmesg) | 直前の apt・デプロイの記録 |
| 誰が設定を変えたか知りたい | 監査ログ(apt history・アプリの変更履歴) | ログイン履歴(last・wtmp) |
よくある質問
Q. ログとは何ですか? A. プログラムやサーバーが「いつ・どこで・何が起きて・結果どうなったか」を時系列で書き残した記録です。障害の原因調査、不正なアクセスの発見、証拠の保存、改善の材料に使います。
Q. ログの種類にはどんなものがありますか? A. 書く主体で分けると、Web サーバーのアクセスログとエラーログ、開発者が書くアプリケーションログ、OS や常駐サービスのシステムログ、「誰が何を変えたか」を残す監査ログの5つです。ほかに DB のスロークエリログ、ログイン履歴などがあります。
Q. アクセスログとエラーログの違いは? A. アクセスログは正常でも1リクエストごとに1行書かれ、「何が起きたか」を数えるのに向きます。エラーログはサーバーが困ったときだけ書かれ、「なぜ」が書いてあります。手元では 404・502・403 の理由は error.log に、500 の理由はアプリログにありました。
Q. ログはどこにありますか?
A. Linux は /var/log の下(nginx は /var/log/nginx、apt は /var/log/apt)と journalctl、Windows はイベントビューアーの「Windows ログ」(アプリケーション・セキュリティ・システム)です。アプリのログは設定次第なので、起動時のメッセージや設定ファイルで確かめます。
Q. ログの見方が分かりません。どこから読めばよいですか? A. 1行を「時刻・レベル・出どころ・内容」の4つに分けて読みます。障害なら、起きた時刻でアクセスログを絞り、5xx があればエラーログとアプリログの同じ時刻を読み、リクエスト ID で1件に絞ります。
Q. ログレベルとは何ですか? A. その行の重さです。DEBUG(細かい途中経過)・INFO(正常な節目)・WARNING(注意)・ERROR(失敗)・CRITICAL(続行不能)の順に重くなり、設定で「このレベル以上だけ書く」と決めます。普段は INFO 以上、調査のときだけ DEBUG まで出すのが一般的です。
Q. 構造化ログ(JSON)にする利点は? A. 項目名で正確に検索・集計でき、値に空白や引用符があっても欄がずれません。ログ基盤に集めるときにそのまま項目として扱えます。手元の実測では1行が 185 バイトから 335 バイトに増えました。
Q. リクエスト ID とは何ですか?
A. 1回のリクエストに付ける固有の ID です。nginx の $request_id をヘッダーでアプリに渡し、アプリのログにも書くと、nginx のログとアプリのログを grep 1回でつなげます。応答ヘッダーで返しておくと、問い合わせの人から教えてもらえます。
Q. ログはいつまで残しますか? A. 手元の設定は14日です。実際には、障害調査に要る期間(数週間)と、監査・法令・契約で求められる期間(数か月から数年)を分けて考え、長期のものは圧縮して別の保管先に移します。期間は組織の決まりに合わせます。
Q. ログに個人情報を書いてもよいですか? A. パスワード・カード番号・トークンは書きません。氏名・メールアドレス・電話番号は、追跡に必要なら一部を伏せるか、ユーザー ID に置き換えます。手元では logging の Filter で書く直前に置き換えました。
Q. ログファイルが大きくなりすぎました。消してもよいですか?
A. ローテーション済みの圧縮ファイル(.gz や日付つき)は、保存期間を過ぎていれば消せます。今書かれているファイルを rm すると、プロセスは消えたファイルに書き続けて容量も戻りません。: > access.log で空にするか、logrotate を設定します。
Q. print や console.log で出すのとログの違いは? A. 出す内容は同じでも、ログの仕組み(logging・logback など)を使うと、レベルで絞る・書き先を変える・時刻や出どころを自動で付ける・ローテーションする、ができます。開発中の print は、本番では INFO や DEBUG のログに置き換えます。
まとめ
- ログは「いつ・どこで・何が起きて・結果どうなったか」を時系列で残した記録。1行は時刻・レベル・出どころ・内容の4つで読む。
- 種類はアクセス・エラー・アプリ・システム・監査の5つ。アクセスログは「何が」、エラーログとアプリログは「なぜ」が書いてある。手元では 500 の理由は nginx の error.log ではなくアプリログにあった。
- ログレベルは DEBUG<INFO<WARNING<ERROR<CRITICAL。ERROR は想定外の失敗だけにして、件数を監視の対象にする。
- リクエスト ID を nginx からアプリへ渡して両方のログに書くと、
grep1回で1件の全部の行が集まる。JSON 形式は項目名で正確に集計できる(1行 185 → 335 バイト)。 - ログは増え続けるので logrotate で分けて圧縮・削除する。名前を変えるだけでは前のファイルに書き続けるため、postrotate で開き直す(nginx は USR1)か copytruncate を使う。パスワード・カード番号・トークンは書かず、メールアドレスはマスクする。
参考・出典
確認日はいずれも 2026年10月29日です。
- nginx「Core functionality」error_log(レベルの一覧と既定値):https://nginx.org/en/docs/ngx_core_module.html#error_log
- nginx「Module ngx_http_log_module」(combined 形式の定義・escape=json):https://nginx.org/en/docs/http/ngx_http_log_module.html
- nginx「Module ngx_http_core_module」$request_id:https://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_id
- systemd「journalctl(1)」(-u・-p・--since・-f・-b・-o json):https://www.freedesktop.org/software/systemd/man/latest/journalctl.html
- Ubuntu manpages「logrotate(8)」(copytruncate・dateext・postrotate):https://manpages.ubuntu.com/manpages/noble/en/man8/logrotate.8.html
- Python「logging --- Python 用ロギング機能」ロギングレベル:https://docs.python.org/ja/3/library/logging.html#logging-levels
- IETF「RFC 5424 The Syslog Protocol」6.2.1 Severity:https://www.rfc-editor.org/rfc/rfc5424#section-6.2.1
- Microsoft「イベントの種類」(エラー・警告・情報・成功の監査・失敗の監査):https://learn.microsoft.com/ja-jp/windows/win32/eventlog/event-types
- Microsoft「Event Viewer」(Windows ログ:アプリケーション・セキュリティ・システム):https://learn.microsoft.com/ja-jp/shows/inside/event-viewer
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ログとは|種類・見方・残し方をnginxとアプリの実物で解説」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。