JavaEEサーバーでLog4j2を使ってみた

# 環境について
環境は、以前書いた記事のものと同一です。
[IntelliJ Community で Tomcat の実行環境を整備する](https://qiita.com/Kakky/items/27fa8940016428ee71f6)

一応雑にまとめると以下のような感じになります。

|種類|ソフトウェア|
|---|---|
|言語|Java SE 8|
|サーバーAPI|JavaEE|
|Log API|Log4j2|
|IDE|IntelliJ Community|
|ビルドツール|Gradle|
|コンテナ|Tomcat|

# webappフォルダを作成する
JavaEEプロジェクトであれば既に作成済みかもしれませんが、以下のようにディレクトリを作成します。
<プロジェクト> -> src -> main -> webapp (※) -> WEB-INF (※)
(※) 作成するディレクトリ
また、ディレクトリはちゃんと"webapp"、"WEB-INF"としましょう。

そして、WEB-INF内に"classes"、"lib"というディレクトリも作成しておきましょう。

# プロジェクトにlog4j2を追加
build.gradleのdependancesに以下のように追加します。

```groovy
dependencies {
    // log4jを使えるようにする
    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1'
    compile group: 'org.apache.logging.log4j', name: 'log4j-web', version: '2.11.1'
}
```

# Loggerクラスを作成する
Logを書き込む際、毎回前準備のコードを書くのは面倒であるため、以下のようにラップクラスを作成します。
コードは、以下のリンクを参考にしました。
[SpringMVC の小径　ちょっと寄り道　ロギングの小径](http://arimodoki.dip.jp/promenade/l_log4jplan.html)

```java
import org.apache.logging.log4j.LogManager;

import java.io.File;

/**
 * ロギング処理を行う
 */
public class Log4j {

    private static Log4j logger = null;

    private Log4j() {
    }

    public static synchronized Log4j getInstance() {
        if (logger == null) {
            logger = new Log4j();
        }
        return logger;
    }

    private String getMessage(final String msg) {
        String thisClassName = this.getClass().getName();
        //クラス名を取得
        String SuperClassName = Logger.class.getName();
        Thread t = Thread.currentThread();
        StackTraceElement[] stackTraceElements = t.getStackTrace();
        int pos = 0;
        for (StackTraceElement stackTraceElement : stackTraceElements) {
            String steClassName = stackTraceElement.getClassName();
            if (thisClassName.equals(steClassName) || SuperClassName.equals(steClassName)){
                break;
            }
            pos++;
        }
        pos += 2;
        StackTraceElement m = stackTraceElements[pos];
        return m.getClassName() + ":" + m.getMethodName() + "() \n" + msg;
    }

    private String getErrorMessage(final Exception e) {
        StringBuilder sb = new StringBuilder();
        StackTraceElement[] st = e.getStackTrace();
        if (st != null && st.length > 0) {
            sb.append("Class:")
                    .append(e.getClass().getName())
                    .append("¥n")
                    .append("Detail:")
                    .append(e.getMessage())
                    .append("¥n");
            for (StackTraceElement s : st) {
                sb.append(s.toString())
                        .append("¥n");
            }
        }
        return sb.toString();
    }

    public void debug(final String msg) {
        LogManager.getLogger(this.getClass()).debug(getMessage(msg));
    }

    public void info(final String msg) {
        LogManager.getLogger(this.getClass()).info(getMessage(msg));
    }

    public void info(final Object obj, final String msg) {
        LogManager.getLogger(obj.getClass()).info(msg);
    }
    public void warn(final String msg) {
        LogManager.getLogger(this.getClass()).warn(getMessage(msg));
    }

    public void error(final Exception e) {
        LogManager.getLogger(e.getClass()).error(getErrorMessage(e));
    }

    public void trace(final String msg) {
        LogManager.getLogger(this.getClass()).trace(getMessage(msg));
    }

    public void fatal(final String msg) {
        LogManager.getLogger(this.getClass()).fatal(getMessage(msg));
    }
}
```

EndPoint内では、以下のように呼び出して利用します。

```java
Log4j logger = Log4j.getInstance();
logger.info("ここにログ内容を書く");
```

log4jには、以下のようなレベルのログが用意されており、それに合わせてラップメソッドを定義してあります。

fatal > error > warn > info > debug > trace

[Log4J徹底解説](http://www.nurs.or.jp/~sug/soft/log4j/log4j2.htm)

# log4j2設定ファイルを作成する
log4j2の設定ファイルは、"log4j2.xml"という名前のxmlファイルに記述します。
また、Tomcatでは、webapp -> WEB-INF -> classesフォルダに置くことで、自動で読み込むことが出来ます。
log4j2.xmlの記入例を示します。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="off">

    <Properties>
        <Property name="loglayout">[%d{yyyy-MM-dd HH:mm:ss.SSS}], %-5p, %t, %c, %m%n</Property>
    </Properties>

    <Appenders>
        <!--
            fileName:ログを出力するファイル名
            filePattern:ログが溜まってきたときに圧縮するファイルのパターン
        -->
        <RollingFile name="rolling" fileName="./log/webapp.log" filePattern="./log/webapp-%d{yyyy-MM-dd}-%i.zip">
            <PatternLayout pattern="${loglayout}"/>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="20 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <DefaultRolloverStrategy max="10"/>
        </RollingFile>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="${ptn}" />
        </Console>
    </Appenders>

    <Loggers>
        <!--
            name:ログ出力を行うパッケージ名
            level:ログの出力レベル
            additivity:上位のログを出力するかどうか
        -->
        <Logger name="Logger" level="info" additivity="true">
            <AppenderRef ref="rolling"/>
        </Logger>
    </Loggers>
</Configuration>
```

詳しい説明については、以下のリンクに記載されています。

[Log4j2でログ出力](https://tokkan.net/jspservlet/log4j2.html)

# war化した時にファイルパスを変更する
Gradleプロジェクトでは、相対パスがプロジェクト直下であるため、ファイルのディレクトリを
. -> log
としていましたが、Tomcatでは、相対パスのルートがtomcatインストールフォルダになっているので、
. -> webapps -> <Warファイル名> -> WEB-INF -> log
にする必要があります。
xml内で指定する方法もあるようでしたが、
log4j2.xml      //開発用
log4j2_exe.xml  //本番用
これを、war化するときだけ
log4j2_test.xml //開発用
log4j2.xml      //本番用
に名前変更することで対応しました。

実際には、build.gradleに、以下のコードを追加します。

```groovy
// warタスク実行前に実行
war.doFirst {
    //子ファイルを取得
    java.io.File[] flist = (new java.io.File("./src/main/webapp/WEB-INF/classes")).listFiles();
    for(i in flist) {
        //ファイル名判定
        switch (i.getName()) {
            case "log4j2.xml":
                //リネーム
                i.renameTo(new java.io.File("./src/main/webapp/WEB-INF/classes/log4j2_test.xml"));
                break;

            case "log4j2_exe.xml":
                i.renameTo(new java.io.File("./src/main/webapp/WEB-INF/classes/log4j2.xml"));
                break;
        }
    }
}

//warタスク実行後に実行
war.doLast {
    java.io.File[] flist = (new java.io.File("./src/main/webapp/WEB-INF/classes")).listFiles();
    for(i in flist) {
        switch (i.getName()) {
            case "log4j2_test.xml":
                i.renameTo(new java.io.File("./src/main/webapp/WEB-INF/classes/log4j2.xml"));
                break;

            case "log4j2.xml":
                i.renameTo(new java.io.File("./src/main/webapp/WEB-INF/classes/log4j2_exe.xml"));
                break;
        }
    }
}
```

# 問題点
warタスク実行ごとにファイルを作成すると、gitに管理ファイルとして認識されない
-> 設定をほにゃほにゃして1つのxmlでできるようにするべき