共通DBアクセスクラス

【Javaの基礎知識】設定地獄はもう嫌!シンプルな共通ログ出力クラスを作ってみた

もう細かい設定や複雑なライブラリに振り回されたくないと思いませんか? Javaでログ出力を行うとき、多機能なOSSの導入や膨大な設定ファイル作成に時間を取られがちです。

そこで今回は、最小限の機能で即使える、自作のシンプルなログ出力クラスを紹介します。初心者から実務者まで使いやすく、開発効率を大幅に改善する実践的な内容です。

共通ログ出力クラスの概要と役割

Javaで開発を行う際にログ出力は必須の仕組みです。しかしプロジェクトによってログの実装方法は統一されておらず、ログ管理が煩雑になることがあります。

特に小規模から中規模プロジェクトではlog4j2やslf4jを導入するには大げさであり、設定の手間や依存管理が負担になることも多いです。

このような課題を解決するために、共通で使えるログ出力クラスを用意することで、ログ管理を簡易化しつつ保守性と可読性を高めることができます。ここでは共通ログ出力クラスの概要と役割を解説します。

共通ログ出力クラスの目的

ログ管理の重要性と課題解決のポイント ログはシステムの稼働状況や障害発生箇所を特定するための重要な情報源です。

しかしプロジェクトごとにログ出力の方法がバラバラになっていると、保守や障害調査の際にログの場所や出力形式が異なり、調査に時間がかかるという問題が発生します。

またlog4j2などの高度なログライブラリは大規模システム向けの機能が豊富である反面、設定ファイル作成や依存ライブラリの追加など、初期設定の負担が大きいという課題があります。

共通ログ出力クラスを導入する目的は以下の通りです。

目的内容
ログ出力の統一化プロジェクト全体でログ出力方法と形式を統一する
即時利用可能導入後すぐにログ出力が可能となり、設定に時間を取られない
依存排除外部ライブラリを使わず標準JDKのみで動作可能
トラブル対応の迅速化障害調査時にログから原因特定を迅速に行える

このように共通ログ出力クラスを利用することで、ログ管理の手間を減らし、保守効率を高めることが可能になります。

共通ログ出力クラスを導入するメリット

導入することで得られるメリットは多岐にわたります。

メリット説明
即時利用可能複雑な設定なしにすぐ使えるため開発がスムーズになります
依存ライブラリ不要標準JDKのみで動作するため環境依存のトラブルが減少します
ログ形式の統一日時・ログレベル・メッセージが一貫した形式で出力されます
保守性向上共通化によりメンテナンスが容易になり品質が安定します
トラブル対応の迅速化統一されたログ情報により原因特定が速くなります

これらのメリットにより、開発効率が向上し運用の信頼性も高まります。特に小規模や検証段階での利用に適しており、設定負荷を抑えつつ必要十分なログ機能を提供します。

共通ログ出力クラスの設計の基本方針

共通ログ出力クラスを設計する際には、実務で使えることを前提とした機能に絞り込み、以下の方針で設計を行いました。

まず依存ライブラリを使用せず、標準JDKのみで動作するようにすることでどの環境でもすぐに導入可能にします。

これにより開発環境による差異を排除し、設定作業を必要最小限に抑えます。 次にログ出力先はコンソールとファイルの両方をサポートし、開発時にはコンソール確認、本番運用時にはファイル出力へ切り替えられるように設計します。

またログレベル管理を実装し、INFO、WARN、ERROR、DEBUGのログレベルで出力内容を切り分けることが可能となっています。 ログの出力形式は以下の通り固定しています。

YYYY-MM-DD HH:MM:SS [LEVEL] message

この形式に統一することでログ解析時にもパターン検索が容易となり、保守の効率が上がります。 ログの呼び出しはシンプルさを重視し、以下のようにstaticメソッドで即座に呼び出せる設計としています。

Logger.init(); // 初期化
Logger.out(Logger.INFO, "アプリケーションを起動します");
Logger.out(Logger.ERROR, "データベース接続エラーが発生しました");

ログ出力時にエラーが発生してもアプリケーションが停止しないよう例外処理を内包しており、ログ出力がアプリケーションの停止原因とならないように設計しています。

さらにログファイルは日付ごとに分割するのではなく単一ファイル追記型とし、小規模プロジェクトや検証ツール、学習用プログラムでログローテーションを考慮する必要がない場面での利用に特化しています。

このように共通ログ出力クラスを設計することで、ログ出力の仕組みを速やかにプロジェクトへ導入できるだけでなく、統一化されたログ管理環境の構築が可能となり、保守性の高い開発基盤を構築することができます。

設計書の構成と設計要素

共通ログ出力クラスをプロジェクトに導入する際には、保守性と再利用性を高めるために設計書を準備することが重要です。

特に複数プロジェクトで横展開する場合やメンテナンスの引き継ぎを想定する場合には、設計の意図を明確化し、コードを読まなくても概要を理解できるようにする必要があります。

ここでは共通ログ出力クラスの設計書の構成と設計要素について具体的に解説します。

クラス設計の前提条件

共通ログ出力クラスを設計する際に前提とした条件は以下の通りです。

前提条件

  • Java SE環境で動作可能であること(依存ライブラリ不要)
  • 即時利用可能であること(複雑な設定不要)
  • コンソールおよびファイルへの同時出力が可能であること
  • ログレベルを指定して出力を管理できること
  • スレッドセーフであること(ログ出力で競合が起きない)
  • 出力形式が統一されていること(日時、レベル、メッセージ)

これらの前提条件を満たすことで、小規模プロジェクトから中規模プロジェクト、学習用ツールに至るまで幅広く使える共通ログ基盤として活用可能となります。

使用する変数一覧

変数は適切に命名し役割がすぐに分かるようにすることが保守性向上につながります。以下は共通ログ出力クラスで使用する主要変数の一覧です。

変数名説明
logLevelStringログ出力レベル(INFO、DEBUG2、DEBUG3、ERRORなど)の設定。デフォルト値はINFO。
logWriterFileWriterログファイルへの書き込みを担当するインスタンス。
logFilePathStringログファイルのパス。デフォルトはlogfile.log。
dateFormatSimpleDateFormatログ出力時の日時フォーマットを管理するインスタンス。
lockObjectObjectスレッド同期用のロックオブジェクト。複数スレッドで同時にログ出力が行われないように制御。
systemInfoSystemInfoシステム設定情報を格納するインスタンス。system.xmlの設定情報を読み込み、ログ出力の設定を管理。
iniReaderIniFileReadsystem.xmlから設定を読み込むためのインスタンス。
diffTimeStringログファイルの切り替えを判断するための時間文字列。

このように変数を明確化し、その用途と型を整理することでメンテナンス時に理解が容易となり、修正ミスの防止にもつながります。

実装されている関数の詳細

共通ログ出力クラスに実装する関数はシンプルでありながら実務で使えることを重視しています。以下が主要関数の概要です。

関数名説明
init(String directory, String fileName, String level)ログ出力準備を行う初期化処理。ディレクトリ、ファイル名、ログレベルを設定。
out(String level, String className, String method, String message)指定したログレベルとメッセージでログを出力する関数。
getLogLevelName(int level)ログレベルの数値から文字列表現を取得する関数。
writeToFile(String formattedMessage)フォーマット済みメッセージをファイルへ出力する関数。
writeToConsole(String formattedMessage)フォーマット済みメッセージをコンソールへ出力する関数。
shouldLog(String level)ログレベルが現在設定されているレベルより低い場合は出力しない。ログレベル制御のためのメソッド。

以下はログ出力時に呼び出すサンプルコードです。

Logger.init();
Logger.out(Logger.INFO, "システム起動処理を開始します");
Logger.out(Logger.ERROR, "ファイルの読み込み中にエラーが発生しました");

これらの関数は用途ごとに役割を分けており、コードの責務が明確となるため保守性が高くなります。

また関数を分けることで将来的に出力形式の変更や出力先の追加対応などが発生しても関数単位で対応できるため拡張性が高くなります。

共通ログ出力クラスの設定手順

共通ログ出力クラスはプロジェクトでのログ管理を簡易化し、保守性を高めるために設計されています。

しかしクラスを使える状態にするためには初期設定を適切に行う必要があります。複雑な設定は不要ですが、基本的な設定手順を理解しておくことでスムーズに導入できるようになります。

ここでは共通ログ出力クラスの設定手順について具体的に解説します。

前提となる実行環境

共通ログ出力クラスを運用するためには以下の実行環境が前提となります。

前提となる実行環境

  • Java SE 8 以降が動作する環境であること
  • ファイル書き込み権限があるユーザで実行すること
  • コンソール出力が可能な環境であること
  • ファイル出力用のディレクトリが存在するか、作成できる環境であること

共通ログ出力クラスは標準JDKのみで動作するため、追加ライブラリのインストールは不要です。

またコンソール出力だけであれば特別な準備は必要ありませんが、ファイルへログを出力する場合には出力先ディレクトリの作成と権限設定が必要になります。

ソースコードの配置場所について

Javaのパッケージ構成に対応する形で、ソースコードは以下のようにディレクトリに格納します。

com.beengineer
 └── common
    ├── IniFileRead.java
    ├── SystemInfo.java
    └── log
        ├── Logger.java
        └── LogWriter.java
※ system.xmlは任意のディレクトリへ格納し、パスをIniFileReadクラス内のgetConfigFilePath() メソッドで指定します。

この構成により、Logger.java と LogWriter.java は ec.common.log パッケージ内に格納され、ログ関連の機能がこのパッケージで管理されます。IniFileRead.java と SystemInfo.java は ec.common パッケージ内で、設定の読み込みやシステム情報を管理します。

System.xmlのパス設定

設定はすべて「system.xml」に記載されており、共通ログ出力クラスアプリはこのファイルを探し出す必要があります。現在、IniFileRead.java の getConfigFilePath() メソッドに ハードコーディング されたパスが使用されていますが、実際には 相対パス で設定する必要があります。

例えば、getConfigFilePath() メソッドで指定されているように、プロジェクトの構成に合わせて適切なパスを指定します。

以下のように設定された場合:

public static String getConfigFilePath() {
// プロジェクトの構成に合わせて適切なパスを指定します
return "src/main/java/com/beengineer/sample/system.xml"; // 実際のパスを指定
}

このようなパス設定は、開発時には便利ですが、実行環境に依存するため、相対パスで指定する方が望ましいです。実行環境で問題なくファイルを参照できるよう、相対パスを使用して system.xml を指定することが推奨されます。

system.xml の設定例と解説

共通ログ出力クラスを正常に動作させるためには、system.xml内にログ設定を正しく記述する必要があります。以下は最低限必要な要素の例です。

<system>
    <log>
        <filepath>/Users/bepro/Work/Java/logs</filepath>  <!-- ログ保存先ディレクトリ -->
        <logfile>20250713.log</logfile>  <!-- ログファイル名 -->
        <level>DEBUG3</level> <!-- ログレベル -->
    </log>
</system>

filepath 属性にはログファイルを保存するディレクトリの絶対パスを指定してください。level 属性はログの出力レベルを示し、ERROR、WARM、INFO、DEBUG0~DEBUG3のいずれかを設定します。

この設定を行うことで、ログクラスは指定されたディレクトリにログを出力し、指定レベル以上のログを記録します。環境に合わせてパスやログレベルを調整してください。

共通ログ出力クラスの設定方法

共通ログ出力クラスの設定は以下の手順で行います。

プロジェクトのディレクトリ構成に Logger.java と LogWriter.java を配置します。そして、ログを出力したいクラスで Logger クラスをインポートします。メイン処理が始まる前に、まず Logger.init() を呼び出して初期化処理を行い、その後ログを出力したい箇所で Logger.out(level, message) を呼び出します。以下はその呼び出し例です。

Logger.init();
Logger.out(Logger.INFO, "アプリケーションの初期化を開始します");
Logger.out(Logger.ERROR, "設定ファイルの読み込みに失敗しました");

初期化処理ではログ出力先ファイルの生成およびログファイル出力の準備を行います。エラーが発生した場合でもログ出力処理が原因でアプリケーションが停止しないように設計されています。

ログ保存先ディレクトリの設定

共通ログ出力クラスはファイルへログを保存する際、出力先ディレクトリを設定する必要があります。

設定は Logger.init() 内で自動的に実行される仕様となっていますが、必要に応じてログディレクトリをカスタマイズすることも可能です。初期設定ではプロジェクトのルートディレクトリに「log」ディレクトリを作成し、その中にログファイルを出力します。

出力ファイル名は「application.log」とし、すべてのログがこのファイルに追記される形となっています。出力先ディレクトリを変更したい場合は Logger クラスの以下の部分でパスを変更します。

private static final String LOG_DIRECTORY = "log/";
private static final String LOG_FILE_NAME = "application.log";

これを例えば「/var/logs/myapp/」へ変更したい場合は以下のように設定します。

private static final String LOG_DIRECTORY = "/var/logs/myapp/";
private static final String LOG_FILE_NAME = "application.log";

注意点として、指定したディレクトリが存在しない場合には Logger.init() 内で自動的にディレクトリ作成を試みますが、権限不足などで作成できない場合には例外が発生する可能性があります。

そのため事前に手動でディレクトリを作成するか、適切な権限で実行できる環境で運用することを推奨します。

ログ保存先ディレクトリの設定を行うことで運用環境に合わせた柔軟なログ管理が可能となり、運用監視ツールとの連携やログ解析ツールによる管理も容易になります。

実践的な利用方法とサンプルコード

共通ログ出力クラスは開発現場で即戦力として活用できることを目指して設計されています。ここでは実際の利用シーンに沿って基本的な使い方やよくある利用ケースについて解説します。

適切に使いこなすことでログ管理の効率化とトラブル対応の迅速化が可能です。

基本的な使い方

このログクラスは初期化後、ログレベルとメッセージを指定して簡単にログを出力できます。

ログ出力テスト用のサンプルクラス

まずアプリケーション起動時に必ず初期化処理を行い、その後ログを出力します。

package com.beengineer.sample;

import ec.common.log.Logger;

public class LoggerTest {

    public static void main(String[] args) {
        // `Logger.init()` でログの初期化
        // 初期化が完了した後、ログ出力を行えるようになります。
        Logger.init();
        
        // ログ出力のテストを行うメソッドを呼び出し
        testLogging();
    }

    // ログ出力テスト
    private static void testLogging() {
        // 各ログレベルに対応するテストログメッセージを出力します。
        // それぞれのレベルのログが設定されたログファイルに出力されるか確認することが目的です。

        // INFOレベルのテストログメッセージを出力
        Logger.out(Logger.INFO, "LoggerTest", "main", "INFOレベルのテストログメッセージです。");
        
        // DEBUG2レベルのテストログメッセージを出力
        Logger.out(Logger.DEBUG2, "LoggerTest", "main", "DEBUG2レベルのテストログメッセージです。");
        
        // DEBUG3レベルのテストログメッセージを出力
        Logger.out(Logger.DEBUG3, "LoggerTest", "main", "DEBUG3レベルのテストログメッセージです。");
        
        // ERRORレベルのテストログメッセージを出力
        Logger.out(Logger.ERROR, "LoggerTest", "main", "ERRORレベルのテストログメッセージです。");

        // INFOレベルでテストした場合、このメッセージは出力されるはず
        // INFOレベル以上のログが出力される設定にしている場合、このメッセージがログに書き込まれます。
        Logger.out(Logger.INFO, "LoggerTest", "main", "このメッセージはINFOレベルで出力されます。");
    }
}

ログ初期化の設定方法

LoggerTest クラスでは、ログの初期化を行うために Logger.init() メソッドを使用しています。このメソッドを呼び出すことで、ログの初期化が行われ、ログファイルの保存先やログレベルが設定されます。

  1. Logger.init() メソッドによる初期化
    Logger.init() メソッドは、ログファイルのパス、ファイル名、ログレベルなどを設定するために使用されます。このメソッドが呼び出されることで、ログ出力が可能となり、ログメッセージは指定されたログファイルに出力されるようになります。

    Logger.init(); // ログの初期化

  2. ログ書き込み方法
    ログメッセージを書き込むには、 Logger.out() メソッドを使用します。このメソッドは、ログレベル(例えば INFOERROR)、クラス名、メソッド名、メッセージを引数として受け取り、設定されたログファイルに出力します。

    Logger.out(Logger.INFO, "LoggerTest", "main", "INFOレベルのテストログメッセージです。");

    このように、ログレベルに応じて、適切なログメッセージを出力できます。
  3. ログレベルの制御
    ログ出力は、設定されたログレベルに基づいて制御されます。例えば、設定したレベルが "INFO" の場合、 INFOレベル以上のログメッセージ( INFO, ERROR, WARN)のみが出力され、低いレベル( DEBUG2, DEBUG3)のメッセージは出力されません。
  4. ログファイルの切り替え
    日付が変わると、 Logger クラスは新しいログファイルを作成します。これにより、毎日異なるログファイルにログを記録でき、ファイルの管理が容易になります。
  5. エラー処理
    ログ出力中にエラーが発生した場合、 Logger クラスはエラーメッセージを表示することで問題を通知します。例えば、ファイルの書き込みに失敗した場合、エラーメッセージがコンソールに表示されます。

Logger クラスは、アプリケーションの状態やエラーを簡単にログファイルに出力でき、問題をトラブルシューティングしやすくします。ログの初期化を行い、適切なログレベルを設定することで、重要な情報を効率的に追跡できます。

共通ログ出力クラスの処理の流れ

これにより開発段階から運用まで一貫したログ管理が可能となり、問題発生時に迅速な原因特定ができます。

ログ情報作成クラス

package com.beengineer.common.log;

/********************************************************************************
 * ログ出力クラス
 *
 * このクラスはアプリケーション全体で使用可能な共通ログ出力機能を提供します。
 *
 * 使用例:
 *   Logger.init();  // 必ず最初に初期化を行う(LogWriterなどの準備)
 *   Logger.out(Logger.INFO, "InitInfo#getParam()", "user0001", "システム設定情報を取得します。");
 *
 * 引数の説明:
 *   - level     : 出力したいログレベル(INFO, DEBUG2, DEBUG3, WARN, ERROR)
 *   - className : クラス名(例:"InitInfo#getParam()")
 *   - method    : ユーザー名や識別情報など(任意の識別用文字列)
 *   - message   : 出力するログメッセージ
 *
 * 履歴:
 *   V1.0  R00  2025/07/15  Bepro  新規開発
 ********************************************************************************/

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

import com.beengineer.common.SystemInfo;

public class Logger {

    // ログレベルの定数定義(出力制御に使用)
    public static final String INFO = "INFO";
    public static final String DEBUG2 = "DEBUG2";
    public static final String DEBUG3 = "DEBUG3";
    public static final String ERROR = "ERROR";
    public static final String WARN = "WARN";

    // ログ設定値(現在のログレベル/出力先/ファイル名)
    private static String logLevel = INFO;
    private static LogWriter logWriter;
    private static String logFilePath = "logfile.log";
    private static String logFileName = "logfile.log";

    private static SimpleDateFormat LOGTIME_FORMAT = null;

    /**
     * ロガーの初期化処理を行います。
     * システム設定ファイル(system.xml)からログレベルを取得し、
     * 出力先のログファイルパスを確定させます。
     */
    public static void init() {
        System.out.println("[INIT] Logger.init() called");

        Map<String, String> log_settings = SystemInfo.getKeyValueHash("log");
        if (log_settings != null && log_settings.get("level") != null) {
            logLevel = log_settings.get("level").toUpperCase();
        } else {
            logLevel = INFO;
        }

        System.out.println("DEBUG: logLevel=" + logLevel);

        logWriter = LogWriter.getInstance();
        checkLogFile();
    }

    public static String getLogLevel() {
        return logLevel;
    }

    /**
     * 指定された条件に従ってログを出力します。
     * 出力条件を満たす場合のみ、ログファイルへ書き込みます。
     *
     * @param level     ログレベル(例:Logger.INFO)
     * @param className 呼び出し元のクラス名
     * @param method    メソッド名または任意識別子
     * @param message   出力メッセージ
     */
    public static void out(String level, String className, String method, String message) {
        System.out.println("[TRACE] Logger.out() CALLED: level=" + level + " / class=" + className + " / method=" + method);
        System.out.println("[LOGGER OUT] level=" + level + " / logLevel=" + logLevel);

        // ログ出力制御
        if (!shouldLog(level)) {
            System.out.println("[SKIP] Logger.out(): level=" + level + " is below logLevel=" + logLevel);
            return;
        }

        String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        logWriter.write("[" + timeStamp + "] [" + level + "] " + className + "." + method + ": " + message + "\n");
        logWriter.flush();
        System.out.println("[WRITE] logWriter = " + (logWriter != null));
    }

    /**
     * 指定されたログレベルで出力を行うかどうかを判定します。
     *
     * @param level 出力しようとしているログレベル
     * @return 出力可否(true: 出力する/false: 出力しない)
     */
    private static boolean shouldLog(String level) {
        if (logLevel == null || level == null) return false;

        int current = getLogRank(level);
        int threshold = getLogRank(logLevel);

        System.out.println("[CHECK] shouldLog(): level=" + level + " (" + current + ") vs logLevel=" + logLevel + " (" + threshold + ")");
        return current <= threshold;
    }

    /**
     * ログレベルの重要度を数値として返します(低い値ほど重大)。
     * 例:ERROR=1, DEBUG3=5
     *
     * @param lv ログレベル
     * @return 数値化された優先順位(重大度)
     */
    private static int getLogRank(String lv) {
        switch (lv) {
            case DEBUG3: return 5;
            case DEBUG2: return 4;
            case INFO:   return 3;
            case WARN:   return 2;
            case ERROR:  return 1;
            default:     return 0;
        }
    }

    /**
     * ログファイルの出力パスとファイル名を設定し、
     * ファイルの存在確認およびディレクトリの作成を行います。
     */
    private static void checkLogFile() {
        Map<String, String> sysinfo = SystemInfo.getKeyValueHash("log");
        logFilePath = (String) sysinfo.get("filepath");
        String logFileBaseName = (String) sysinfo.get("logfile");

        LOGTIME_FORMAT = new SimpleDateFormat("yyyyMMdd");
        String nowDate = LOGTIME_FORMAT.format(new Date());
        logFileName = logFileBaseName.replace(".log", "_" + nowDate + ".log");

        String logFilePathName = logFilePath + File.separator + logFileName;

        File logDir = new File(logFilePath);
        if (!logDir.exists()) {
            logDir.mkdirs();
        }

        File logFile = new File(logFilePath + "/" + logFileName);
        if (logFile.exists()) {
            System.out.println("ログファイルは正常に作成されました: " + logFile.getAbsolutePath());
        } else {
            System.out.println("ログファイルが作成されませんでした。");
        }
    }

    /**
     * 現在のログレベルを手動で上書き設定します。
     *
     * @param level 新しいログレベル(例:"DEBUG3")
     */
    public static void setLogLevel(String level) {
        logLevel = level;
    }

    /**
     * ログファイルの出力パスを手動で設定します。
     *
     * @param path ログ出力先パス
     */
    public static void setLogFilePath(String path) {
        logFilePath = path;
    }
}

Logger.java クラスの役割と使用方法

Logger.java クラスは、アプリケーションのログ出力を管理するためのクラスです。本クラスを使用することで、ログメッセージを簡単にファイルに出力し、エラーや情報を追跡することができます。

  • ログの出力には初期化が必要
    ログ出力を行うには、まず初期化が必要です。初期化では、ログファイルの保存先、ファイル名、ログレベル(INFO, DEBUG, ERROR など)を設定します。この設定は通常、設定ファイル(例:system.xml)から読み込まれます。
  • 初期化手順
    ログを出力する前に、 Logger.init() メソッドを呼び出して初期化を行います。このメソッドでは、設定ファイルからログファイルのパスやログレベルを取得し、ログ出力の準備をします。

    Logger.init(); // ログの初期化

  • ログ出力方法
    ログを出力するためには、 Logger.out() メソッドを使用します。このメソッドは、ログレベル(例えば INFO や ERROR)、クラス名、メソッド名、メッセージを引数として受け取り、設定されたログファイルに出力します。

    Logger.out(Logger.INFO, "MainClass", "main", "アプリケーション開始"); // ログ出力

  • ログレベル
    ログ出力は、設定されたログレベルに基づいて制御されます。例えば、設定したレベルが "INFO" の場合、INFOレベル以上のログメッセージ(INFO, ERROR, WARN など)のみが出力され、低いレベル(DEBUG2 や DEBUG3 など)のメッセージは出力されません。
  • ログファイルの切り替え
    Logger クラスは、日付が変わると新しいログファイルを作成します。これにより、毎日異なるログファイルにログを記録でき、ファイルの管理が容易になります。例えば、ファイル名には日付が含まれ、 "application_20230715.log" のように日付付きのログファイルが生成されます。
  • エラー処理
    ログ出力中にエラーが発生した場合、 Logger クラスはエラーメッセージを表示することで問題を通知します。例えば、ファイルの書き込みに失敗した場合、エラーメッセージがコンソールに表示されます。

    // エラーが発生した場合のエラーメッセージ例
    System.out.println("ログファイルへの書き込みに失敗しました。");

Logger クラスを使用することで、アプリケーションの状態やエラーを簡単にログファイルに出力でき、問題をトラブルシューティングしやすくなります。初期化を行い、適切なログレベルを設定することで、重要な情報を効率的に追跡できます。また、ログファイルは日付ごとに切り替わるため、ログの管理がしやすくなります。

ログ出力制御クラス

package com.beengineer.common.log;
/********************************************************************************
 * ログ出力制御クラス
 *
 * <PRE>
 *   このクラスは、ログファイルへの書き込みを制御するためのクラスです。
 *   デフォルトの文字エンコーディングを使用して、ログを書き込みます。
 *
 * 履歴:
 *  V1.0  R01  2025/07/15  Bepro  新規開発
 * </PRE>
 *******************************************************************************/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

import com.beengineer.common.SystemInfo;

public class LogWriter {

    private static BufferedWriter logWriter = null;  // BufferedWriter のみを使用

    private static String diffTime = new String();  // 最後に使用した日付を保持
    private static SimpleDateFormat LOGTIME_FORMAT = null;  // 日付フォーマットを管理
    private static final String FILE_SEP = System.getProperty("file.separator");  // OSによるファイルセパレータの取得
    private static String logFilePath = new String();  // ログファイルのパス
    private static String logFileName = new String();  // ログファイルの名前
    private static boolean logStat = true;  // ログの状態管理(ログが有効かどうか)

    private static LogWriter instance;  // LogWriterのシングルトンインスタンス

    /**
     * LogWriter クラスのインスタンスを取得します。
     * シングルトンパターンでインスタンスを生成します。
     *
     * @return LogWriterのインスタンス
     */
    private LogWriter() {
        logWriterInit();  // ログライターの初期化
    }

    public static synchronized LogWriter getInstance() {
        if (instance == null) {
            instance = new LogWriter();  // インスタンスが存在しない場合、新規作成
        }
        return instance;
    }

    /**
     * ログファイルの初期化を行います。
     * 設定ファイルからログファイルのパスやファイル名を取得し、ログ書き込みの準備を行います。
     */
    private void logWriterInit() {
        try {
            // 設定ファイルからログファイルパスを取得
            Map<String, String> sysinfo = SystemInfo.getKeyValueHash("log");
            logFilePath = (String) sysinfo.get("filepath");  // ログファイルの保存先パス
            String logFileBaseName = (String) sysinfo.get("logfile"); // system.xmlから取得したファイル名(例: "application.log")

            // 日付フォーマットの設定
            LOGTIME_FORMAT = new SimpleDateFormat("yyyyMMdd");

            // 現在の日付を取得し、ログファイル名を生成
            String nowDate = LOGTIME_FORMAT.format(new Date());
            logFileName = logFileBaseName.replace(".log", "_" + nowDate + ".log");

            // ログファイルのパスを組み立て
            String logFilePathName = logFilePath + FILE_SEP + logFileName;

            // ログディレクトリが存在しない場合は作成
            File logDir = new File(logFilePath);
            if (!logDir.exists()) {
                logDir.mkdirs();  // ディレクトリ作成
            }

            // ログファイルのパスがディレクトリでないことを確認
            File logFile = new File(logFilePathName);
            if (logFile.exists() && logFile.isDirectory()) {
                throw new IOException("指定されたパスはディレクトリです: " + logFilePathName);
            }

            // ログファイルの書き込み準備
            logWriter = new BufferedWriter(new FileWriter(logFilePathName, true)); // 追記モードでファイルを開く
        } catch (IOException e) {
            e.printStackTrace();  // エラー発生時にはスタックトレースを出力
        }
    }

    /**
     * ログメッセージをファイルに書き込みます。
     *
     * @param message 書き込むログメッセージ
     */
    public void write(String message) {
        try {
            if (logWriter != null) {
                logWriter.write(message);  // ログメッセージを書き込む
                logWriter.newLine();  // 改行を追加
                logWriter.flush();  // メッセージを書き込んだ後、フラッシュして保存
            }
        } catch (IOException e) {
            e.printStackTrace();  // エラー発生時にはスタックトレースを出力
        }
    }

    /**
     * 同期化されたログ書き込み処理を行います。
     * ログファイルが日付変更されているかを確認し、必要に応じて新しいファイルに切り替えます。
     *
     * @param logs 書き込むログメッセージ
     */
    public synchronized void doWrite(String logs) {
        checkTime();  // ログファイルの切り替えを確認

        try {
            if (logWriter != null) {
                logWriter.write(logs);  // ログメッセージを書き込む
                logWriter.newLine();  // 改行を追加
                logWriter.flush();  // 即座にフラッシュして保存
            }
        } catch (IOException e) {
            e.printStackTrace();  // エラー発生時にはスタックトレースを出力
        }
    }

    /**
     * ログファイルの切り替えを確認し、日付が異なっていれば新しいログファイルを開きます。
     */
    private synchronized void checkTime() {
        String nowTime = LOGTIME_FORMAT.format(new Date());  // 現在の日付を取得

        // 前回と日付が異なる場合、新しいログファイルを生成
        if (!diffTime.equals(nowTime)) {
            String logFileName = nowTime + ".log";  // 新しいログファイル名
            String logFilePathName = logFilePath + FILE_SEP + logFileName;

            try {
                logWriter = new BufferedWriter(new FileWriter(logFilePathName, true));  // 新しいファイルを開く
            } catch (IOException e) {
                e.printStackTrace();  // エラー発生時にはスタックトレースを出力
            }
            diffTime = nowTime;  // 日付情報を更新
        }
    }

    /**
     * ログバッファをクリアし、ファイルを閉じます。
     */
    public void clear() {
        try {
            if (logWriter != null) {
                logWriter.flush();  // バッファ内の内容をフラッシュ
                logWriter.close();  // ファイルを閉じる
            }
        } catch (IOException e) {
            e.printStackTrace();  // エラー発生時にはスタックトレースを出力
        }
    }

    /**
     * バッファ内の内容を即座にフラッシュしてファイルに保存します。
     */
    public void flush() {
        try {
            if (logWriter != null) {
                logWriter.flush();  // バッファをフラッシュ
            }
        } catch (IOException e) {
            e.printStackTrace();  // エラー発生時にはスタックトレースを出力
        }
    }
}

LogWriter.java クラスの役割と使用方法

LogWriter.java クラスは、実際のログ出力を管理するためのクラスです。このクラスは、ログメッセージを指定されたログファイルに追記する機能を提供します。`BufferedWriter` を使用してログファイルにデータを効率的に書き込む役割を担います。ログ初期化の設定方法

LogWriter クラスでは、ログの初期化を行うために logWriterInit() メソッドを使用しています。このメソッドでは、設定ファイル(通常は system.xml)からログの保存先、ファイル名、日付などを取得し、ログの出力準備を行います。

  • LogWriter のインスタンスの取得
    LogWriter クラスはシングルトンパターンを採用しており、 getInstance() メソッドでインスタンスを取得します。このインスタンスを通じて、ログ出力処理が管理されます。

    LogWriter logWriter = LogWriter.getInstance();

  • ログファイルの初期化
    logWriterInit() メソッドは、ログファイルのパスやファイル名を設定ファイルから読み込みます。また、日付を元に新しいログファイルを作成するための処理を行います。ログファイル名には、日付が追加され、毎日異なるファイルが生成されます。
  • ログメッセージの書き込み
    ログメッセージは、 write() または doWrite() メソッドを使用して書き込みます。 doWrite() メソッドは同期化されており、複数スレッドから安全に呼び出すことができます。

    logWriter.doWrite("ログメッセージ"); // ログの書き込み

  • 日付ごとのログファイルの切り替え
    日付が変更された場合、 LogWriter クラスは自動的に新しいログファイルに切り替えます。これにより、毎日のログが個別のファイルに保存され、ログファイルの管理がしやすくなります。
  • ログのフラッシュとクリア
    ログ書き込み後は、 flush() メソッドを使用してバッファをフラッシュし、ログを即座にファイルに反映させます。また、 clear() メソッドを使用して、ファイルを閉じることもできます。
  • エラーハンドリング
    ログ書き込み中にエラーが発生した場合、IOException がキャッチされ、エラーメッセージが表示されます。これにより、ログファイルへの書き込み失敗が通知されます。

    // エラーが発生した場合のエラーメッセージ例
    System.out.println("ログファイルへの書き込みに失敗しました。");

  • BufferedWriter の使用
    LogWriter クラスでは、ログファイルへの書き込みには BufferedWriter のみを使用します。これにより、ログは追記モードでファイルに書き込まれ、出力された内容は即座にファイルに反映されます。 PrintWriter は使用されていません。

LogWriter クラスは、ログファイルへの効率的な書き込み処理を管理します。同期化された書き込みメソッド、日付ごとのファイル切り替え、エラーハンドリングなど、ログの信頼性を確保するための機能が提供されています。

SystremInfo クラス

SystemInfo.java クラスは、設定ファイルから必要な設定情報を動的に取得するためのクラスです。このクラスは、設定ファイル(通常は XML フォーマット)を解析し、指定されたキーに基づいて適切な設定情報を Hashtable 形式で返します。

package com.beengineer.common;

import java.util.HashMap;
import java.util.Map;

/**
 * システム全体設定(system.xml)を読み込むユーティリティクラス
 */
public class SystemInfo {

    // 設定キャッシュ(最初の1回だけ読み込み)を保証するため volatile を使用
    private static volatile Map<String, String> cachedSettings = null;

    /**
     * 指定セクション(例: "log", "database")に該当する設定情報を取得するメソッド
     * 形式: key="log_level", value="DEBUG3" → section="log" の場合、"level" をキーにしたMapを返す
     *
     * @param section system.xml内のセクション名(例:"log", "database")
     * @return 指定されたセクションに該当する設定キーと値のMap(例:"level" → "DEBUG3")
     */
    public static Map<String, String> getKeyValueHash(String section) {

        // 初回のみ設定を読み込む(ダブルチェックロッキングによるスレッドセーフ実装)
        if (cachedSettings == null) {
            synchronized (SystemInfo.class) {
                if (cachedSettings == null) {
                    // system.xml から全設定を一括読み込み("セクション_キー" → 値の形式で格納されている想定)
                    cachedSettings = IniFileRead.readSettings();
                }
            }
        }

        // sectionに該当する設定だけを抽出して返す(例: "log_level" → "level")
        Map<String, String> result = new HashMap<>();
        for (String key : cachedSettings.keySet()) {
            if (key.startsWith(section + "_")) {
                // セクション名のプレフィックスを除去(例: "log_level" → "level")
                String shortKey = key.substring(section.length() + 1);
                result.put(shortKey, cachedSettings.get(key));
            }
        }

        return result;
    }
}

主に使用されるキーとしては、ログ関連の設定(log)やデータベース接続設定(database)などがあります。SystemInfo クラスは、これらの設定情報を取得するために、IniFileRead クラスを内部で利用しています。取得した設定情報は、他のクラスで利用できるように返され、アプリケーション全体での設定管理を簡素化します。 

SystemInfo.java クラスの役割

SystemInfo.javaの処理の流れ

  1. User が SystemInfo.getKeyValueHash("log") を呼び出す。
  2. SystemInfo は IniFileRead.readSettings() を呼び出して設定を取得。
  3. IniFileRead が設定を返す。
  4. SystemInfo が設定から "log" の情報を取得。
  5. キーが "log" の場合、次の設定を Hashtable に格納:
  6. logSettings を返す。
  7. キーが "database" の場合、次の設定を Hashtable に格納:
  8. dbSettings を返す。
  9. それ以外の場合、全ての設定をそのまま返す。

IniFileRead クラス

IniFileRead クラスは、設定ファイル(通常は XML フォーマット)から設定情報を動的に読み込み、必要なキーに基づいて設定情報を取得するためのクラスです。このクラスは、設定ファイルからログ設定やデータベース設定などの情報を抽出し、Hashtable 形式で返します。

package com.beengineer.common;

import java.io.File;
import java.util.Hashtable;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class IniFileRead {

	public static Hashtable<String, String> readSettings() {
	    // 設定項目を格納するHashtableを生成
	    Hashtable<String, String> settings = new Hashtable<>();

	    try {
	        // XMLファイルのパスを取得
	        String xmlFilePath = getConfigFilePath();

	        // XMLファイルオブジェクトを生成
	        File xmlFile = new File(xmlFilePath);

	        // DOMパーサーを初期化
	        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	        DocumentBuilder builder = factory.newDocumentBuilder();

	        // XMLファイルを読み込み、DOMオブジェクトとして構築
	        Document doc = builder.parse(xmlFile);

	        // XMLのルート要素(例:<system>)を取得
	        Element root = doc.getDocumentElement();

	        // ルート直下の子ノード一覧(log, databaseなどのセクション)を取得
	        NodeList childNodes = root.getChildNodes();

	        // 各セクションをループ処理
	        for (int i = 0; i < childNodes.getLength(); i++) {
	            Node section = childNodes.item(i);

	            // セクションが要素ノードである場合のみ処理(コメントや空白を無視)
	            if (section.getNodeType() == Node.ELEMENT_NODE) {
	                // セクションのタグ名(log, databaseなど)を取得
	                Element sectionElement = (Element) section;
	                String sectionName = sectionElement.getTagName();

	                // セクション内の項目一覧を取得
	                NodeList items = sectionElement.getChildNodes();

	                // 各項目(level, filepath, urlなど)をループ処理
	                for (int j = 0; j < items.getLength(); j++) {
	                    Node item = items.item(j);

	                    // 要素ノードでない場合(空白や改行など)はスキップ
	                    if (item.getNodeType() == Node.ELEMENT_NODE) {
	                        // キーは「セクション名_項目名」(例:log_level, database_url)
	                        String key = sectionName + "_" + item.getNodeName();
	                        String value = item.getTextContent();
	                        // 🔽 この行を追加(ここなら key/value の両方が使える)
	                        System.out.println("[IniFileRead] key=" + key + " / val=" + value);
	                        // 設定情報にキーと値を格納
	                        settings.put(key, value);
	                    }
	                }
	            }
	        }
        
	    } catch (Exception e) {
	        // 解析・読み込み中にエラーが発生した場合は標準エラーに出力
	        System.err.println("設定の読み込み中にエラーが発生しました: " + e.getMessage());
	    }

	    // 設定情報を返す
	    return settings;
	}
    
    // 設定ファイルのパスを動的に取得するメソッド
    public static String getConfigFilePath() {
        // 設定ファイルのパスを取得する処理(仮に設定)
        // 実際のパスを指定する必要がある
        return "src/main/java/com/beengineer/sample/system.xml";  // 設定ファイルのパス(仮設定)
    }
}

主な処理

  • 設定ファイルの読み込み:
    getConfigFilePath() メソッドで設定ファイルのパスを動的に取得し、DocumentBuilder を使って XML ファイルを解析します。

  • 設定情報の取得:
    readSettings() メソッドで、設定ファイル内の特定のタグ(例えば、log や database)を抽出し、それに対応する設定を Hashtable に格納して返します。

  • エラーハンドリング:
    設定ファイル内のタグが見つからない場合や読み込みエラーが発生した場合は、適切な警告メッセージやエラーメッセージを出力します。 

IniFileRead.javaの役割

SystemInfo.javaの処理の流れ

  1. User が IniFileRead.readSettings() を呼び出す。
  2. getConfigFilePath() メソッドを呼び出して設定ファイルのパスを取得。
  3. new File(xmlFilePath) で設定ファイルのファイルオブジェクトを作成。
  4. DocumentBuilderFactory.newInstance() で新しいインスタンスを作成。
  5. newDocumentBuilder() を呼び出して、XML ドキュメントを解析する準備をする。
  6. builder.parse(xmlFile) を使って XML ファイルを解析し、DOM ツリーを作成。
  7. doc.getElementsByTagName("log") で "log" タグを取得。
  8. もし "log" タグが存在すれば、次の処理を行う:
  9. もし "log" タグが見つからなかった場合、警告メッセージを表示。
  10. もし "database" キーが指定されている場合、次の処理を行う:
  11. それ以外の場合、全ての設定をそのまま返す。

よくある利用ケース

共通ログ出力クラスは様々な用途で使われています。例えば以下のようなケースがあります。

利用用途

  • ユーザー操作の記録
  • 定期バッチ処理の実行ログ
  • 外部APIとの通信結果記録
  • 例外発生時のエラーログ記録
  • パフォーマンス計測ログ

これらの用途で共通ログ出力クラスを使うことで、ログの形式や出力先が統一されるため分析やトラブルシューティングが容易になります。また、小規模なツールや検証プログラムにおいても追加の依存なくすぐにログを出力できる利点があります。

DB接続クラスでのログ出力活用例

この例はSQL実行前後や重要な処理ステップでログを出力し、処理の流れと問題発生時の解析に役立てています。ログレベルは詳細なデバッグ用のDEBUG3や注意喚起のWARNを使い分けています。

public class LoggerTest {
public static void main(String[] args) {
// `Logger.init()` でログの初期化
Logger.init();
}

// ログ出力テスト
private static void testLogging() {
// ログレベル別にテストログメッセージを出力
Logger.out(Logger.INFO, "LoggerTest", "main", "INFOレベルのテストログメッセージです。");
Logger.out(Logger.DEBUG2, "LoggerTest", "main", "DEBUG2レベルのテストログメッセージです。");
Logger.out(Logger.DEBUG3, "LoggerTest", "main", "DEBUG3レベルのテストログメッセージです。");
Logger.out(Logger.ERROR, "LoggerTest", "main", "ERRORレベルのテストログメッセージです。");
// INFOレベルでテストした場合、このメッセージは出力されるはず
Logger.out(Logger.INFO, "LoggerTest", "main", "このメッセージはINFOレベルで出力されます。");
}

出力例:

[2025-07-13 20:53:50] [INFO] LoggerTest.main: INFOレベルのテストログメッセージです。
[2025-07-13 20:53:50] [DEBUG2] LoggerTest.main: DEBUG2レベルのテストログメッセージです。
[2025-07-13 20:53:50] [DEBUG3] LoggerTest.main: DEBUG3レベルのテストログメッセージです。
[2025-07-13 20:53:50] [ERROR] LoggerTest.main: ERRORレベルのテストログメッセージです。
[2025-07-13 20:53:50] [INFO] LoggerTest.main: このメッセージはINFOレベルで出力されます

よく読まれている記事

1

「私たちが日々利用しているスマートフォンやインターネット、そしてスーパーコンピュータやクラウドサービス――これらの多くがLinuxの力で動いていることをご存じですか? 無料で使えるだけでなく、高い柔軟 ...

2

Linux環境でよく目にする「Vim」という名前。サーバーにログインしたら突然Vimが開いてしまい、「どうやって入力するの?」「保存や終了ができない!」と困った経験をした人も多いのではないでしょうか。 ...

3

ネットワーク技術は現代のITインフラにおいて不可欠な要素となっています。しかし、ネットワークを深く理解するためには、その基本となる「プロトコル」と「レイヤ」の概念をしっかり把握することが重要です。 こ ...

4

この記事は、Linuxについて勉強している初心者の方向けに「Shellスクリプト」について解説します。最後まで読んで頂けましたら、Shellスクリプトはどのような役割を担っているのか?を理解出来るよう ...

5

Javaは世界中で広く使われているプログラミング言語であり、特に業務システムやWebアプリケーションの開発において欠かせない存在です。本記事では、初心者向けにJavaの基礎知識を網羅し、環境構築から基 ...

-共通DBアクセスクラス