Log4j2 と sbt-assembly

2020/06/26   #sbt 
このエントリーをはてなブックマークに追加

AWS Lambda を Scala で書く際、ロガーとして Log4j2 を使った。
AWS Lambda の公式ドキュメントにも載っていたので
が、実行してみようと sbt-assembly で jar をつくろうとしたら以下のエラー。

$ sbt assembly
:
[error] 1 error was encountered during merge
[error] stack trace is suppressed; run last assembly for the full output
[error] (assembly) deduplicate: different file contents found in the following:
[error] /Users/hogehoge/.coursier/cache/v1/https/repo1.maven.org/maven2/com/amazonaws/aws-lambda-java-log4j2/1.2.0/aws-lambda-java-log4j2-1.2.0.jar:META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
[error] /Users/hogehoge/.coursier/cache/v1/https/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.13.3/log4j-core-2.13.3.jar:META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat

Log4j2Plugins.dat というファイルが重複。
このファイルが何なのかよくわからなかったので、まず安直に discard してみた。

  assemblyMergeStrategy in assembly := {
    case x if x.endsWith("/Log4j2Plugins.dat") => MergeStrategy.discard
    :

これで jar はつくれたのだが、実行すると Log4j2 でログを出している部分で、適切なロガーが見つからないとエラーになった。

ERROR Error processing element Lambda ([Appenders: null]): CLASS_NOT_FOUND
ERROR Unable to locate appender "Lambda" for logger config "root"

ので、discard じゃない方法で解決する必要がありそうなのだが、
そもそも何が起きてるの? Log4j2Plugins.dat ってなんなの?
ってあたりがここに載っていた。
Fix appender not found / class_not_found error for Log4j2 / SpringBoot
どうやら Log4j2 が適切に動くためには Log4j2Plugins.dat は必要そうだ。

さらにいろいろ調べると、 org.apache.logging.log4j.core.config.plugins.processor.PluginCache に関係してそうで、
そこらへんをうまいことやっている以下に辿り着いた。

mobile-n10n/MergeLog4j2PluginCachesStrategy.scala at master · guardian/mobile-n10n

project 直下に以下の Strategy をつくる。

import java.io.ByteArrayOutputStream
import java.nio.file.Files

import org.apache.logging.log4j.core.config.plugins.processor.PluginCache
import sbt.File
import sbtassembly.MergeStrategy

import scala.collection.JavaConverters.asJavaEnumerationConverter

class MergeLog4j2PluginCachesStrategy extends MergeStrategy {
  override def name: String = "MergeLog4j2PluginCachesStrategy"

  override def apply(tempDir: File, path: String, files: Seq[File]): Either[String, Seq[(File, String)]] = {
    val pluginCache = new PluginCache
    pluginCache.loadCacheFiles(files.map(_.toURI.toURL).toIterator.asJavaEnumeration)
    val baos = new ByteArrayOutputStream // avoid worrying about closeable resources
    pluginCache.writeCache(baos)
    val mergeFile = MergeStrategy.createMergeTarget(tempDir, path)
    Files.write(mergeFile.toPath, baos.toByteArray)
    Right(Seq(mergeFile -> path))
  }
}

で、
https://github.com/guardian/mobile-n10n/blob/f9c6989e360b4bba47763ba84663dadc5f7cf613/build.sbt#L246
にあるように、
build.sbtassemblyMergeStrategyMETA-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat に対して適用する。

assemblyMergeStrategy in assembly := {
  :
  case "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat" => new MergeLog4j2PluginCachesStrategy
  :

これでバッチリ動いた。