かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その6 )( Groovy スクリプトからログをコンソールやファイルに出力する )

概要

記事一覧はこちらです。

Grooy スクリプトをそのまま渡して実行する Spring Boot+Picocli ベースのコマンドラインアプリを作成する ( その5 )( CSV ファイルのデータをテーブルに登録する Groovy スクリプトを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • Groovy スクリプトからログをコンソール、あるいはファイルに出力できるようにします。

参照したサイト・書籍

  1. Runtime and compile-time metaprogramming
    https://groovy-lang.org/metaprogramming.html

  2. Unicode Support
    https://conemu.github.io/en/UnicodeSupport.html

  3. Chapter 3: Logback configuration - Conditional processing of configuration files
    http://logback.qos.ch/manual/configuration.html#conditional

  4. バッチファイルでファイルパスからファイル名や拡張子を自由に取り出す方法
    https://orangeclover.hatenablog.com/entry/20101004/1286120668

  5. Get Log Output in JSON
    https://www.baeldung.com/java-log-json-output

  6. Structured logging with SLF4J and Logback
    https://gquintana.github.io/2017/12/01/Structured-logging-with-SL-FJ-and-Logback.html

  7. logfellow / logstash-logback-encoder
    https://github.com/logfellow/logstash-logback-encoder

  8. Java ログ収集
    https://docs.datadoghq.com/ja/logs/log_collection/java/?tab=logback

目次

  1. Groovy スクリプトからログを出力する
    1. CsvFileToBookTable.groovy にログ出力処理を追加する
    2. コンソールにログを出力する
    3. ファイルにログを出力する
    4. ログを JSON フォーマットで出力する

手順

Groovy スクリプトからログを出力する

CsvFileToBookTable.groovy にログ出力処理を追加する

groovy-script-executor/src/main/groovy/sample/CsvFileToBookTable.groovy にログ出力処理を追加します。

package sample

import com.univocity.parsers.annotations.Parsed
import com.univocity.parsers.common.processor.BeanListProcessor
import com.univocity.parsers.csv.CsvParserSettings
import com.univocity.parsers.csv.CsvRoutines
import groovy.sql.Sql
import groovy.util.logging.Slf4j

@Slf4j
class CsvFileToBookTable {

    static class CsvRecord {
        @Parsed(index = 0, field = "isbm")
        String isbm
        @Parsed(index = 1, field = "title_author")
        String title_author
    }

    static void main(args) {
        def sql = Sql.newInstance("jdbc:mysql://localhost:3306/testdb?sslMode=DISABLED&characterEncoding=utf8",
                "testdb_user",
                "xxxxxxxx",
                "org.mariadb.jdbc.Driver")
        sql.connection.autoCommit = false

        CsvParserSettings settings = new CsvParserSettings()
        settings.format.lineSeparator = "\r\n"
        settings.headerExtractionEnabled = true
        BeanListProcessor<CsvRecord> rowProcessor = new BeanListProcessor<>(CsvRecord)
        settings.processor = rowProcessor

        sql.execute("truncate table book")
        log.info("bookテーブルをtruncateしました。")

        new File("publications.csv").withReader { reader ->
            CsvRoutines csvRoutines = new CsvRoutines(settings)
            for (CsvRecord csvRecord : csvRoutines.iterate(CsvRecord, reader)) {
                String[] titleAndAuthor = csvRecord.title_author.split(" / ")
                def title = titleAndAuthor[0]
                def author = null
                if (titleAndAuthor.size() == 1) {
                    log.warn("title_authorカラムにはauthorが記載されていません。")
                } else {
                    author = titleAndAuthor[1]
                }

                sql.execute("""
                                insert into book (isbm, title, author)
                                values (:isbm, :title, :author)
                            """,
                        isbm: csvRecord.isbm,
                        title: title,
                        author: author)
                log.info("bookテーブルに登録しました (isbm = {}, title = {}, author = {})",
                        csvRecord.isbm, title, author)
            }
        }

        sql.commit()
        sql.close()
    }

}

コンソールにログを出力する

D:\tmp の下に application.properties を新規作成し、以下の内容を記述します。Groovy スクリプトと同じディレクトリにある application.properties の設定で groovy-script-executor.jar の groovy-script-executor/src/main/resources/application.properties の設定が上書きされます。

logging.level.root=INFO

#sample パッケージのログだけ出力したい場合には、root → sample に変更する
#logging.level.sample=INFO

gse CsvFileToBookTable.groovy で Groovy スクリプトを実行するとログが出力されますが、gse.bat 内で -Dfile.encoding=UTF-8 を指定しているのでログの文字コードUTF-8 になります。そのままでは Windowsコマンドプロンプトでは文字化けします。

f:id:ksby:20211112235312p:plain

chcp 65001 & cmd コマンドを実行してから gse CsvFileToBookTable.groovy を実行すれば文字化けしなくなります(Unicode Support 参照)。

f:id:ksby:20211112235826p:plain

ファイルにログを出力する

logback-spring.xml の記述で janino を使いたいので build.gradle に追加します。

dependencies {
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されるもの
    // Dependency Versions ( https://docs.spring.io/spring-boot/docs/current/reference/html/dependency-versions.html#dependency-versions ) 参照
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.apache.commons:commons-lang3")
    implementation("org.codehaus.janino:janino")
    testImplementation("org.springframework.boot:spring-boot-starter-test")

    ..........
  • dependencies block に implementation("org.codehaus.janino:janino") を追加します。

groovy-script-executor/src/main/resources の下に logback-spring.xml を新規作成し、以下の内容を記述します。logging.file.name が設定されていればログファイルへ、設定されていなければコンソールへログを出力します。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    <property name="LOGGING_APPENDER" value="${logging.appender:-FILE}"/>

    <if condition='isDefined("LOG_FILE")'>
        <then>
            <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <encoder>
                    <pattern>${FILE_LOG_PATTERN}</pattern>
                </encoder>
                <file>${LOG_FILE}</file>
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}</fileNamePattern>
                    <maxHistory>30</maxHistory>
                </rollingPolicy>
            </appender>
        </then>
    </if>

    <if condition='isDefined("LOG_FILE")'>
        <then>
            <root>
                <appender-ref ref="${LOGGING_APPENDER}"/>
            </root>
        </then>
        <else>
            <root>
                <appender-ref ref="CONSOLE"/>
            </root>
        </else>
    </if>
</configuration>

build タスクを実行し、生成した groovy-script-executor.jar を D:\tmp にコピーします。

Groovy スクリプトと同じディレクトリにログを出力するよう gse.bat を以下のように変更します。

@echo off

java -Dfile.encoding=UTF-8 ^
     -XX:TieredStopAtLevel=1 ^
     -Dspring.main.lazy-initialization=true ^
     -Dlogging.file.name=%~n1.log ^
     -jar groovy-script-executor-1.0.0-RELEASE.jar ^
     %*
  • -Dlogging.file.name=%~n1.log ^ を追加します。

gse CsvFileToBookTable.groovy で Groovy スクリプトを実行するとコンソールには何も出力されず、

f:id:ksby:20211115062824p:plain

CsvFileToBookTable.groovy と同じディレクトリに CsvFileToBookTable.log が作成されて、その中にログが出力されます。

f:id:ksby:20211115062958p:plain f:id:ksby:20211115063111p:plain

gse.bat 内の -Dlogging.file.name=%~n1.log ^ を削除すれば、コンソールにログが出力されます。

f:id:ksby:20211115063443p:plain

ログを JSON フォーマットで出力する

logstash-logback-encoder を導入してログファイルを JSON フォーマットで出力してみます。

build.gradle を以下のように変更します。

dependencies {
    ..........

    // dependency-management-plugin によりバージョン番号が自動で設定されないもの、あるいは最新バージョンを指定したいもの
    runtimeOnly("${postgresqlJdbcDriver}")
    runtimeOnly("${mariadbJdbcDriver}")
    implementation("com.univocity:univocity-parsers:2.9.1")
    implementation("net.logstash.logback:logstash-logback-encoder:6.6")
    testImplementation("org.assertj:assertj-core:3.21.0")
  • dependencies block に implementation("net.logstash.logback:logstash-logback-encoder:6.6") を追加します。

logback-spring.xml を以下のように変更します。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    <property name="LOGGING_APPENDER" value="${logging.appender:-FILE}"/>

    <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator"/>
            <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
                <maxDepthPerThrowable>30</maxDepthPerThrowable>
                <maxLength>2048</maxLength>
                <shortenedClassNameLength>20</shortenedClassNameLength>
                <rootCauseFirst>true</rootCauseFirst>
                <inlineHash>true</inlineHash>
            </throwableConverter>
        </encoder>
    </appender>

    <if condition='isDefined("LOG_FILE")'>
        <then>
            <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <encoder>
                    <pattern>${FILE_LOG_PATTERN}</pattern>
                </encoder>
                <file>${LOG_FILE}</file>
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}</fileNamePattern>
                    <maxHistory>30</maxHistory>
                </rollingPolicy>
            </appender>
        </then>
    </if>

    <if condition='isDefined("LOG_FILE")'>
        <then>
            <root>
                <appender-ref ref="${LOGGING_APPENDER}"/>
            </root>
        </then>
        <else>
            <root>
                <!--<appender-ref ref="CONSOLE"/>-->
                <appender-ref ref="JSON"/>
            </root>
        </else>
    </if>
</configuration>
  • <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">...</appender> を追加します。
  • <appender-ref ref="CONSOLE"/>コメントアウトして <appender-ref ref="JSON"/> を追加します。

今のままでは stack trace が JSON フォーマットで出力されないので、groovy-script-executor/src/main/java/ksby/cmdapp/groovyscriptexecutor/command/GroovyScriptExecutorCommand.java を以下のように変更します。

public class GroovyScriptExecutorCommand
        implements Callable<Integer>, IExitCodeExceptionMapper, IVersionProvider {

    ..........

    @Override
    public Integer call() throws IOException {
        try {
            Binding binding = new Binding();
            GroovyShell shell = new GroovyShell(binding);
            shell.run(groovyScript, args);
        } catch (Exception e) {
            log.error("Groovyスクリプトでエラーが発生しました。", e);
        }

        return ExitCode.OK;
    }

    ..........

}
  • call メソッド内の処理を try { ... } catch (Exception e) { log.error("Groovyスクリプトでエラーが発生しました。", e); } で囲みます。

build タスクを実行し、生成した groovy-script-executor.jar を D:\tmp にコピーします。

gse.bat から -Dlogging.file.name=%~n1.log を削除してログがコンソールに出力されるようにしてから gse CsvFileToBookTable.groovy を実行すると、コンソールにログが JSON フォーマットで出力されます。

f:id:ksby:20211116230032p:plain

また CsvFileToBookTable.groovy 内でエラーが発生するよう変更してから実行すると stack trace も JSON フォーマットで出力されます。

f:id:ksby:20211116230314p:plain

履歴

2021/11/16
初版発行。