ログ出力設計を一言でいうと
ログ出力設計は、
「エラーや重要な出来事を、あとから“意味を持って”追いかけられるように記録する設計」 のことです。
ただ console.log(err) を散らばせるのではなく、
どこで、何を、どの粒度で、どんな形式で残すのかを決めておく。
非同期処理(fetch / Promise / async/await)の世界では、
エラーが「別のタイミング」「別の場所」で起きるので、
ログ設計がないと、原因追跡が一気に難しくなります。
ここが重要です。
ログ出力設計は、
「バグが起きたときに、未来の自分を助けるためのメモを、今の自分がどう残すか」 を決める行為です。
その視点で見ていくと、何をログに残すべきかが見えてきます。
なぜ非同期処理ではログ設計が特に重要なのか
エラーが「その場」で起きないから
同期処理なら、
「この関数を呼んだら、その場でエラーが出た」という形で追いやすいです。
しかし非同期処理では、fetch を呼んだ瞬間ではなく、
レスポンスが返ってきたとき、
あるいは then や await の中でエラーが起きます。
例えば、次のようなコードです。
async function loadUsers() {
const res = await fetch("/api/users");
const data = await res.json();
renderUsers(data);
}
JavaScriptもし renderUsers の中でエラーが起きたとしても、
コンソールには「どのリクエストの結果で」「どんなデータが来て」「何をしようとして失敗したのか」が、
何も残っていないかもしれません。
「なんかエラー出てるけど、どの API 呼び出しが原因?」
「ユーザーが何をしているときに起きた?」
こういう疑問に答えるのが、ログ出力設計の役割です。
同じ処理が何度も走るから
非同期処理は、
「ボタンを押すたびに API を叩く」
「スクロールのたびに追加読み込みする」
といった形で、同じ関数が何度も呼ばれます。
ログが設計されていないと、
「どの呼び出しのときに壊れたのか」が分かりません。
逆に、
「いつ」「どの API」「どんなパラメータ」で呼んだのかをログに残しておけば、
再現性の低いバグでも追いやすくなります。
ここが重要です。
非同期処理では、
「時間」と「回数」が絡むので、
ログに“文脈”を残しておかないと、後から何が何だか分からなくなる のです。
何をログに残すべきかの基本
最低限押さえたい情報
非同期処理のログで、最低限押さえたいのは次のような情報です。
どの処理か(関数名や機能名)
いつか(タイムスタンプ)
何をしようとしたか(URL、メソッド、主要なパラメータ)
どうなったか(成功か失敗か、ステータスコード、エラーメッセージ)
これを意識してログを書くと、
「このログは何の話をしているのか」が一目で分かるようになります。
例えば、次のようなログは情報が足りません。
console.error("エラー:", err);
JavaScript一方で、こう書くと一気に意味が出ます。
console.error("[loadUsers] API 呼び出し失敗", {
url: "/api/users",
method: "GET",
error: err,
});
JavaScriptログを「構造化」しておく
文字列だけではなく、オブジェクトでログを出すと、
あとから検索・フィルタしやすくなります。
console.error({
level: "error",
function: "loadUsers",
url: "/api/users",
method: "GET",
message: err.message,
stack: err.stack,
});
JavaScriptブラウザコンソールでも見やすいですし、
将来的にサーバー側に送って集計する場合にも役立ちます。
ここが重要です。
「何をログに残すか」を決めるときは、
“未来の自分がバグを追うときに、何を知りたくなるか” を想像してみてください。
その答えが、そのままログ項目になります。
共通のログ関数を作って「バラバラ log 地獄」を防ぐ
直接 console.log しない習慣をつける
あちこちで console.log や console.error を直接呼ぶと、
ログの形式がバラバラになります。
ある場所では "エラー" とだけ出し、
別の場所では "Error:" + err.message と出し、
さらに別の場所ではオブジェクトを出す、
という状態です。
これを防ぐために、
「ログは必ず共通関数を通す」というルールを作ると、
一気に整理されます。
例えば、こんな関数を用意します。
function logError(context, err, extra = {}) {
console.error({
level: "error",
context,
message: err.message,
stack: err.stack,
...extra,
});
}
JavaScript非同期処理での使い方
これを fetch を使った処理で使ってみます。
async function loadUsers() {
try {
console.log("[loadUsers] API 呼び出し開始");
const res = await fetch("/api/users");
console.log("[loadUsers] レスポンス受信", { status: res.status });
if (!res.ok) {
const err = new Error("HTTP error " + res.status);
logError("loadUsers:responseNotOk", err, {
status: res.status,
url: "/api/users",
});
throw err;
}
const data = await res.json();
console.log("[loadUsers] JSON パース成功");
renderUsers(data);
} catch (err) {
logError("loadUsers:catch", err, {
url: "/api/users",
});
showErrorMessage("ユーザーの取得に失敗しました。時間をおいて再度お試しください。");
}
}
JavaScriptここでは、
「どの段階で」「何が起きたか」を context で表現し、
URL やステータスなどの追加情報を extra に載せています。
ここが重要です。
共通の logError を通すことで、
ログの形式が揃い、
「どの処理のどの段階でのエラーか」が一目で分かるようになります。
これは非同期処理のデバッグで本当に効きます。
エラーと「ユーザー向け表示」との分離
ログは開発者向け、メッセージはユーザー向け
前の話ともつながりますが、
ログ出力設計では、
「ログに何を書くか」と「ユーザーに何を見せるか」を分けて考えます。
例えば、catch の中でこう書きがちです。
catch (err) {
alert(err.message);
}
JavaScriptこれは、
「開発者向けの生メッセージを、そのままユーザーに見せている」状態です。
代わりに、こう分けます。
catch (err) {
logError("loadUsers:catch", err, { url: "/api/users" });
const userMessage = toUserFriendlyMessage(err);
showErrorMessage(userMessage);
}
JavaScriptlogError には、
スタックトレースや内部的な情報を含めて構いません。
一方、ユーザー向けメッセージは、
短く、行動が分かる形に整えます。
ここが重要です。
ログ出力設計の中で、「これは開発者のため」「これはユーザーのため」という線引きをはっきりさせると、
エラー処理全体の設計が一気にクリアになります。
「いつログを出すか」を決める
成功時もログを出すかどうか
ログはエラーのときだけ、と思いがちですが、
非同期処理では「成功時のログ」も役に立つことがあります。
例えば、
「この API はどれくらいの頻度で呼ばれているか」
「どのパラメータで呼ばれたときにエラーが多いか」
といった分析をしたくなるかもしれません。
ただし、何でもかんでもログにするとノイズが増えます。
そこで、次のような方針を決めるとよいです。
重要な API 呼び出し(ログイン、決済など)は成功時もログを出す。
それ以外は、基本はエラー時だけ。
例えば、ログイン API ならこうです。
async function login(email, password) {
const start = Date.now();
try {
const res = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ email, password }),
});
if (!res.ok) {
const err = new Error("HTTP error " + res.status);
logError("login:responseNotOk", err, { status: res.status });
throw err;
}
const data = await res.json();
console.log({
level: "info",
context: "login:success",
durationMs: Date.now() - start,
userId: data.userId,
});
return data;
} catch (err) {
logError("login:catch", err, {
durationMs: Date.now() - start,
});
throw err;
}
}
JavaScriptここでは、
成功時には info レベルでログを出し、
エラー時には error レベルで logError を使っています。
ログレベルのイメージ
本格的なロガーでは、debug, info, warn, error などのレベルを使います。
初心者のうちは、
最低限 info と error の二つを意識するだけでも十分です。
info は「正常な流れの中で、重要な出来事」
error は「想定外 or 失敗した出来事」
というイメージで使い分けると、
ログを見たときに「どこからが異常か」が分かりやすくなります。
ここが重要です。
「いつログを出すか」「どのレベルで出すか」を決めておくと、
ログが“ただの文字の海”ではなく、“意味のあるタイムライン”になります。
非同期処理ならではの工夫:ID やコンテキストを付ける
リクエストごとの「ID」を付ける
同じ API を何度も叩く場合、
「このログとこのログは同じリクエストの話」というのが分かると便利です。
簡易的には、
リクエストごとに ID を振って、それをログに含めます。
let requestIdCounter = 1;
function nextRequestId() {
return "req-" + requestIdCounter++;
}
async function loadUsers() {
const requestId = nextRequestId();
console.log("[loadUsers] start", { requestId });
try {
const res = await fetch("/api/users");
console.log("[loadUsers] response", { requestId, status: res.status });
if (!res.ok) {
const err = new Error("HTTP error " + res.status);
logError("loadUsers:responseNotOk", err, { requestId, status: res.status });
throw err;
}
const data = await res.json();
console.log("[loadUsers] success", { requestId });
renderUsers(data);
} catch (err) {
logError("loadUsers:catch", err, { requestId });
showErrorMessage("ユーザーの取得に失敗しました。");
}
}
JavaScriptこうしておくと、
コンソールで requestId: "req-3" を検索するだけで、
そのリクエストに関するログだけを追いかけられます。
ユーザーや画面のコンテキストも付ける
ログには、
「どのユーザーが」「どの画面で」操作していたか、
といったコンテキストもあると強いです。
例えば、ログイン後にユーザー ID をどこかに保持しておき、
logError の extra に含めるようにします。
let currentUserId = null;
function setCurrentUserId(id) {
currentUserId = id;
}
function logError(context, err, extra = {}) {
console.error({
level: "error",
context,
message: err.message,
stack: err.stack,
userId: currentUserId,
...extra,
});
}
JavaScriptこれで、
「特定のユーザーだけで起きているエラー」
「特定の画面でだけ起きているエラー」
を見つけやすくなります。
ここが重要です。
非同期処理のログには、「時間」だけでなく「誰が」「どのリクエストで」という“文脈”を付けると、
原因追跡の難易度が一気に下がります。
初心者として「ログ出力設計」で本当に押さえてほしいこと
非同期処理では、エラーが「あとで」「別の場所」で起きるので、
ログがないと原因追跡がとても難しくなる。
ログには、
「どの処理か」「いつか」「何をしようとしたか」「どうなったか」を必ず含める。
URL、メソッド、ステータス、エラーメッセージなどを意識して残す。
console.log を直接あちこちで呼ぶのではなく、logError や logInfo のような共通関数を作り、
そこから一貫した形式でログを出す。
ログは「開発者向け」、ユーザー向けメッセージは別で用意する。
catch の中では、
「ログに生の情報を残す」
「ユーザーには整形済みの短いメッセージを見せる」
という二段構えにする。
必要に応じて、
リクエスト ID やユーザー ID などのコンテキストをログに含め、
「どのリクエストの話か」を追いやすくする。
ここが重要です。
コードを書くとき、catch (err) の中で一度立ち止まって、
「未来の自分がこのバグを追うとき、何がログに残っていたら助かるだろう?」
と自分に問いかけてみてください。
その問いに答える形で、
少しずつ logError やログフォーマットを育てていくと、
あなたの非同期エラー処理は、「なんとなく console.log する」段階から、
“意図を持って未来の自分を助けるログ” を書ける段階 に確実に進んでいきます。
