【JAVA】SPRING BOOTでWEBアプリを作成してみる 4日目 ログ出力

2021/9/4



こんにちは。前回はDBからデータ取得して一覧画面に表示させるとこまでできましたので、今回はログの出力を追加したいと思います。



前準備


ログを出力する上で必要なものをネットからMavenを使って取得しましょう。

今回はAOPを使用してメソッド実行前後でログを出力したいと思いますので、
spring-boot-starter-aopを使用します。

pom.xmlのdependenciesタグに以下を追加してください。
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

/demo/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>demo</name>
  <description>Demo project for Spring Boot</description>

  <properties>
    <java.version>1.8</java.version>
    <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>


ログ機能の追加


まず、開発用のプロファイルを追加し、開発環境に合わせてログ情報を出力できるようにします。application.propertiesに"spring.profiles.active=dev"を追記すると、開発(dev)用のプロファイルとしてapplication-dev.propertiesを読み込めるようになります。

/demo/src/main/resources/application.properties
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/web?web_schema
spring.datasource.username=dev
spring.datasource.password=xxxx

#開発(dev)プロファイルの読み込み
spring.profiles.active=dev

開発(dev)プロファイル用の設定としてapplication-dev.propertiesを新規に作成します。名前に-devを追加したファイルを作成してください。

/demo/src/main/resources/application-dev.properties
logging.level.org.springframework.web=ERROR
# mybatisのSQLを取得する
logging.level.com.example.demo.mapper=DEBUG

開発環境ではspringframework.webのログのレベルをERRORに、mybatisの実行SQLを取得するため、com.example.demo.mapperのDEBUGに設定しました。
ログのレベルにはTRACE、DEBUG、INFO、WARN、ERRORがあり、エラーログ情報のみ欲しいときはERRORを、詳細なログが欲しいときはDEBUGを指定することができます。

次に、ログをコンソールの標準出力だけでなく、ファイル出力したいのでSpring Bootに標準でついているlogbackの設定を加えましょう。

/demo/src/main/resources/logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>


<configuration>
    <!--デフォルト設定読み込み -->
    <include resource="org/springframework/boot/logging/logback/base.xml" />

    <!--開発(dev)プロファイル 環境用設定 -->
    <springProfile name="dev">
        <!--変数の設定 -->
        <property name="logFilePath" value="C:/log/" />
        <property name="logFileName" value="app" />
    </springProfile>

    <!--標準出力 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <encoder>
            <charset>UTF-8</charset>
            <!--level   ロギングイベントのレベル -->
            <!--message ロギングイベントに関連付けられたメッセージ -->
            <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %message%n</pattern>
        </encoder>
    </appender>

    <!-- アプリケーションログ -->
    <appender name="APPLIATION_LOG"
        class="ch.qos.logback.core.rolling.RollingFileAppender">

        <!-- 出力先ファイルパス -->
        <file>${logFilePath}${logFileName}.log</file>

        <!-- ログのローテーション設定 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日付けが変わったら年月フォルダ内にzipファイル作成 -->
            <fileNamePattern>${logFilePath}/%d{yyyyMM,aux}/${logFileName}-%d{yyyy-MM-dd}.log.zip</fileNamePattern>
            <!--最大30日間保存 -->
            <maxHistory>30</maxHistory>
        </rollingPolicy>

        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %message%n</pattern>
        </encoder>
    </appender>

    <!--rootロガー、コンソールとアプリケーションログに出力する -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="APPLIATION_LOG" />
    </root>

</configuration>

appenderは、ログの出力先を指定するものでch.qos.logback.core.ConsoleAppenderクラスを設定しているSTDOUTの方が標準出力のコンソールへ出力します。patternタグのlevelはログのレベル、messageはロギングイベントに関連付けられたメッセージを表示します。
一方、RollingFileAppenderクラスを設定しているAPPLIATION_LOGはファイルへの出力を行います。

それでは、確認のため前回作成したUserControllerに、usersテーブルで取得したデータをログ出力できるようにしてみます。25行目のLoggerFactory.getLogger(this.getClass())でロガーを呼び出し、44行目でmodelに設定されたusersテーブルの情報を表示しています。
logger.infoはログレベルINFOでログ出力します。エラーならlogger.errorになります。

/demo/src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.service.UserService;

/**
 *
 * Spring Boot 勉強用 コントローラ
 *
 * @author    otoku-se
 * @version   1.0.0
 */

@Controller
@RequestMapping("/users")
public class UserController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserService userService;

    /**
     *
     * usersテーブルからデータを取得する -> モデル
     * users.htmlに返す -> ビュー
     *
     * @param   model  users.htmlへデータを持っていく
     * @return  users.htmlへ遷移
     */
    @GetMapping
    public String findAll(Model model) {

        model.addAttribute("users", userService.findAll());

        // usersテーブルのデータをログに出力
        logger.info("[usersテーブルの中身] " + model.getAttribute("users").toString());

        return "users";

    }

}


AOP


AOP(Aspect Oriented Programming):アスペクト指向プログラミングは、ログやキャッシュなど共通的に使う処理(横断的な関心事)を、主要な処理と分離させて記述する開発手法です。分離した共通処理は実行した処理の前後で呼び出すことができます。こうすることで開発者は自分の作業に集中することができます。

技術的には@Aspectと@Componentのアノテーションをクラスに加えることでSpringコンテナにBeanとして外出しします。これも前回やった依存性の注入(DI)のひとつといえるでしょう。

その他、
JoinPoint : 横断的な処理を挿入するメソッド側の情報を取得できます 。getTarget().getClass().toString()で実行しているメソッドのクラス名、
getSignature().getName()で実行しているメソッドのメソッド名を取得できます。

Advice : @Beforeや@Afterといったアノテーションを共通処理を行うメソッドに記述することで処理実行前後に共通処理を行うことができます(今回使用している@AfterReturningは、正常終了したときのみ戻りを返します)。

PointCut : execution(* com.example.demo..*(..))で、com.example.demoパッケージ内の処理実行時に共通処理を行うことができます。
これ以外にもexecution(* com.xyz.service.AccountService.*(..))など特定のクラスやメソッド名を指定することができます。


AOPは、知らなくても実装を進めるすることはできますが、知らないと、他人の書いたロジックを追えなくなることがありますので、注意してください。

/demo/src/main/java/com/example/demo/aspect/AspectLog.java
package com.example.demo.aspect;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
*
* Spring Boot 勉強用 AOP ログ
*
* @author    otoku-se
* @version   1.0.0
*/

@Aspect
@Component
public class AspectLog {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
    *
    * com.example.demo内でメソッドが実行される前に
    * クラスとメソッド名をログに出力する。
    *
    * @param   jp  JoinPoint -> 横断的な処理を埋め込むメソッドの情報
    */

    @Before(value = "execution(* com.example.demo..*(..))")
    public void beforeLog(JoinPoint jp) {
        logger.info("[before aop] start " +
                     jp.getTarget().getClass().toString() + "#" +
                     jp.getSignature().getName()
                    );
    }

    /**
    *
    * com.example.demo内でメソッドが実行された後で
    * 例外が発生しなければ
    * クラスとメソッド名とをログに出力する。
    *
    * @param   jp  JoinPoint -> 横断的な処理を埋め込むメソッドの情報
    * @param   rtn JoinPoint処理後の戻り値
    */
    @AfterReturning(value = "execution(* com.example.demo..*(..))", returning = "rtn")
    public void afterLog(JoinPoint jp, String rtn) {
        logger.info("[afterReturning aop] end " +
                     jp.getTarget().getClass().toString() + "#" +
                     jp.getSignature().getName() +
                     " return : " + rtn);
    }

}


ここまでできたらプロジェクトを右クリックして、実行 -> Spring Boot アプリケーションを選択。
ブラウザのurlにhttp://localhost:8080/usersを入力して確認します。

下記のように、ログファイル(C:\log\app.log)とコンソールに、before aop、afterReturning aop、実行SQL、usersテーブルの情報が確認できればOKです。






今回はここまでです。
気をつけてはいますが、間違いがあったらごめんなさい。またね。

参考
Spring Bootでapplication.propertiesを環境ごとに切り替える方法
Spring Boot解説第10回(開発環境編:ログの設定について~logback)
第4章 アペンダー
第6章 レイアウト
3. プロファイル
4. ロギング
5. Spring によるアスペクト指向プログラミング
Spring Boot入門② ~AOP~

お借りした素材
NYN姉貴.png
NYN姉貴.long

JAVA おすすめの本
[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]
スッキリわかるJava入門第2版 [ 中山清喬 ]
価格:2860円(税込、送料無料) (2020/4/12時点)






カテゴリ

このブログを検索

自己紹介

自分の写真
作らなきゃ(使命感)

QooQ