Java Servletでログイン試行履歴を日単位のログファイルへ出力する
Java Servletを使ったWebアプリケーションで、ログイン処理を行うたびに試行履歴をログファイルへ残す実装例です。
今回は簡易的な構成として、ログ出力専用クラスを作成せず、LoginAction クラス内だけで完結させます。
ログへ記録する項目は以下の4つです。
- ログイン試行日時
- ログインしようとした従業員番号
- ログイン成功・失敗
- 失敗した場合の原因
また、ログファイルは1日ごとに分割します。
例えば、次のようなファイルを自動的に作成します。
login-2026-09-10.log
login-2026-09-11.log
login-2026-09-12.log
実装前のLoginAction
元々のログイン処理は以下のような構成です。
package web;
import java.util.ArrayList;
import common.QuestionnaireBusinessException;
import common.QuestionnaireSystemException;
import entity.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import logic.LoginLogic;
public class LoginAction implements ActionIF {
@Override
public String execute(HttpServletRequest request) {
String page = "menu.jsp";
try {
String userId = request.getParameter("userId");
String password = request.getParameter("password");
ArrayList errorMessageList = new ArrayList<>();
if (userId == null || userId.equals("")) {
errorMessageList.add("ユーザーIDが未入力です。");
}
if (password == null || password.equals("")) {
errorMessageList.add("パスワードが未入力です。");
}
if (!errorMessageList.isEmpty()) {
throw new QuestionnaireBusinessException(errorMessageList);
}
LoginLogic logic = new LoginLogic();
User user = logic.login(
Integer.parseInt(userId),
password);
HttpSession session = request.getSession(true);
session.setAttribute("user", user);
} catch (QuestionnaireBusinessException e) {
request.setAttribute(
"errorMessage",
e.getMessage());
request.setAttribute(
"errorMessageList",
e.getMessageList());
e.printStackTrace();
page = "login.jsp";
} catch (QuestionnaireSystemException e) {
request.setAttribute(
"errorMessage",
e.getMessage());
e.printStackTrace();
page = "system-error-page.jsp";
}
return page;
}
}
この処理へログ出力機能を追加します。
実装方針
今回のポイントは finally を利用することです。
try {
// ログイン処理
} catch (...) {
// エラー処理
} finally {
// ログ出力
}
finally は、
- ログイン成功時
- ログイン失敗時
どちらの場合でも最後に実行されます。
そのため、「ログインを試行するたびに記録する」という処理に向いています。
完成コード
package web;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import common.QuestionnaireBusinessException;
import common.QuestionnaireSystemException;
import entity.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import logic.LoginLogic;
public class LoginAction implements ActionIF {
@Override
public String execute(HttpServletRequest request) {
String page = "menu.jsp";
// リクエストパラメータの取得
String userId = request.getParameter("userId");
String password = request.getParameter("password");
// ログ出力用
boolean loginSuccess = false;
String failureReason = "";
try {
// エラーメッセージリストの生成
ArrayList errorMessageList = new ArrayList<>();
if (userId == null || userId.equals("")) {
errorMessageList.add("ユーザーIDが未入力です。");
}
if (password == null || password.equals("")) {
errorMessageList.add("パスワードが未入力です。");
}
if (!errorMessageList.isEmpty()) {
throw new QuestionnaireBusinessException(
errorMessageList);
}
// ログイン処理
LoginLogic logic = new LoginLogic();
User user = logic.login(
Integer.parseInt(userId),
password);
// セッションへユーザー情報を保存
HttpSession session =
request.getSession(true);
session.setAttribute("user", user);
// ログイン成功
loginSuccess = true;
} catch (QuestionnaireBusinessException e) {
request.setAttribute(
"errorMessage",
e.getMessage());
request.setAttribute(
"errorMessageList",
e.getMessageList());
// ログに出力する失敗理由
failureReason = e.getMessage();
e.printStackTrace();
page = "login.jsp";
} catch (QuestionnaireSystemException e) {
request.setAttribute(
"errorMessage",
e.getMessage());
// ログに出力する失敗理由
failureReason = "システムエラー";
e.printStackTrace();
page = "system-error-page.jsp";
} finally {
// =========================
// ログファイル出力
// =========================
LocalDateTime now =
LocalDateTime.now();
DateTimeFormatter dateTimeFormatter =
DateTimeFormatter.ofPattern(
"yyyy-MM-dd HH
ss");
DateTimeFormatter dateFormatter =
DateTimeFormatter.ofPattern(
"yyyy-MM-dd");
// 日付ごとにログファイルを分ける
String fileName =
"login-"
+ LocalDate.now().format(dateFormatter)
+ ".log";
// Tomcatのlogsフォルダを指定
String logPath =
System.getProperty("catalina.base")
+ File.separator
+ "logs"
+ File.separator
+ fileName;
try (FileWriter writer =
new FileWriter(logPath, true)) {
// 試行日時
writer.write(
now.format(dateTimeFormatter));
// 従業員番号
writer.write(" | 従業員番号: ");
if (userId == null || userId.equals("")) {
writer.write("未入力");
} else {
writer.write(userId);
}
// 成功・失敗
if (loginSuccess) {
writer.write(" | 成功");
} else {
writer.write(" | 失敗");
writer.write(
" | 原因: "
+ failureReason);
}
// 改行
writer.write(
System.lineSeparator());
} catch (IOException e) {
e.printStackTrace();
}
}
return page;
}
}
ログイン結果を保持する変数
まず、ログ出力用として以下の2つを追加します。
boolean loginSuccess = false;
String failureReason = "";
loginSuccess はログインの成否を保持します。
初期値は false としておきます。
boolean loginSuccess = false;
ログイン処理が最後まで正常に完了した場合のみ、
loginSuccess = true;
とします。
この方法にすることで、
true
→ ログイン成功
false
→ ログイン失敗
という判定ができます。
失敗原因を保持する
業務エラーが発生した場合は、
failureReason = e.getMessage();
として、例外に設定されているメッセージを保存します。
例えば、
ユーザーIDが未入力です。
や、
ユーザーIDまたはパスワードが違います。
といった内容をログへ残せます。
システムエラーについては今回、
failureReason = "システムエラー";
としています。
finallyで必ずログを出力する
今回の実装で特に重要なのが finally です。
finally {
// ログ出力処理
}
ログインが成功した場合でも、
try
の最後に finally が実行されます。
認証エラーなどによって、
catch
へ移動した場合にも、その後 finally が実行されます。
そのため、
ログイン成功
↓
finally
↓
ログ出力
だけでなく、
ログイン失敗
↓
catch
↓
finally
↓
ログ出力
という流れになります。
日付と日時を取得する
今回はJavaの java.time を利用します。
現在日時は、
LocalDateTime now =
LocalDateTime.now();
で取得できます。
表示形式については、
DateTimeFormatter dateTimeFormatter =
DateTimeFormatter.ofPattern(
"yyyy-MM-dd HH
ss");
としています。
そのため、ログには、
2026-09-10 07:15:32
という形式で記録されます。
日付ごとにログファイルを分割する
ログファイル名は以下の処理で生成しています。
String fileName =
"login-"
+ LocalDate.now().format(dateFormatter)
+ ".log";
例えば2026年9月10日なら、
login-2026-09-10.log
となります。
翌日になると、
login-2026-09-11.log
となるため、特別な日付変更処理を記述しなくても、自動的に別ファイルへ保存できます。
Tomcatのlogsフォルダへ保存する
Tomcatが利用しているフォルダは、
System.getProperty("catalina.base")
から取得できます。
今回は、
String logPath =
System.getProperty("catalina.base")
+ File.separator
+ "logs"
+ File.separator
+ fileName;
としています。
そのため、イメージとしては、
Tomcat
└─ logs
├─ login-2026-09-10.log
├─ login-2026-09-11.log
└─ login-2026-09-12.log
という配置になります。
FileWriterのtrueは追記を意味する
ログファイルへの書き込みでは、
new FileWriter(logPath, true)
としています。
第2引数の true が重要です。
new FileWriter(logPath, true)
とすると、既存のファイル内容を残したまま末尾へ追記します。
もし、
new FileWriter(logPath)
としてしまうと、書き込みを行うたびに既存内容が上書きされてしまいます。
ログファイルでは基本的に履歴を残したいため、追記モードを利用します。
try-with-resourcesを利用する
ファイル操作では、
try (FileWriter writer =
new FileWriter(logPath, true)) {
// 書き込み処理
}
という try-with-resources を利用しています。
これによって処理終了時に FileWriter が自動的に閉じられます。
明示的に、
writer.close();
を書く必要がありません。
出力されるログ
ログイン試行を行うと、例えば以下のように記録されます。
2026-09-10 07:15:32 | 従業員番号: 10001 | 成功
2026-09-10 07:16:10 | 従業員番号: 10002 | 失敗 | 原因: ユーザーIDまたはパスワードが違います。
2026-09-10 07:17:03 | 従業員番号: 未入力 | 失敗 | 原因: ユーザーIDが未入力です。
今回必要としていた、
・試行日時
・ログインしようとした従業員番号
・成功か失敗か
・失敗した場合の原因
を1行で確認できます。
パスワードはログへ出力しない
ログイン処理では、
String password =
request.getParameter("password");
としてパスワードも取得しています。
ただし、パスワードそのものはログへ保存しないようにします。
例えば、
従業員番号: 10001
パスワード: password123
のようなログは作成すべきではありません。
ログファイルが閲覧された場合、認証情報がそのまま漏えいしてしまうためです。
今回のように、
従業員番号: 10001 | 失敗 | 原因: パスワードが正しくありません。
程度に留めます。
注意点
今回の実装は、学習用・小規模なシステム向けの簡易実装です。
実務では、ログ処理を LoginAction の中へ直接記述するよりも、
LoginAction
↓
LoginLogic
LoginAction
↓
LoginLogger
のように、ログ出力専用クラスへ分離した方が保守しやすくなります。
また、実際の業務システムでは、
- java.util.logging
- Logback
- Log4j2
- SLF4J
などの仕組みを利用することも多いです。
今回は、
FileWriter
LocalDateTime
finally
といったJavaの基本機能だけを利用することで、ログ出力の仕組みそのものを理解しやすい形にしています。
まとめ
今回の実装では、ログイン試行履歴を以下の流れで記録しました。
ログインボタン押下
↓
LoginAction
↓
入力チェック
↓
LoginLogicで認証
↓
成功 / 失敗
↓
finally
↓
ログファイルへ追記
実装上の主なポイントは以下です。
- boolean でログイン成否を保持する
- 例外メッセージから失敗原因を取得する
- finally で成功・失敗にかかわらずログを出力する
- LocalDateTime で試行日時を取得する
- ファイル名に日付を含めて日単位に分割する
- FileWriter の追記モードを利用する
- パスワードそのものはログへ出力しない
小規模なJava Servletアプリケーションでログ出力の仕組みを学ぶ題材として、比較的シンプルに実装できます。