ラベル slf4j の投稿を表示しています。 すべての投稿を表示
ラベル slf4j の投稿を表示しています。 すべての投稿を表示

木曜日, 4月 04, 2013

非同期ロガー

非同期ロギング

Javaはlog4jから始まってslf4j, logbackとロギングAPIの歴史の立役者が揃っている.
そのようなロギングAPIはサイズまたは日付にによる自動ローリング,仔細なフォーマッティングの指定等といった必要不可欠でかつ極めて有用な機能をすべて提供してくれている.

そういった機能の中で私が注目したいのが非同期ロギングである.マルチスレッディングのアプリケーションにおいてロギングは避けることの出来ないクリティカルセクションである.しかしながら,非同期ロガーを使えばロギングはもはやクリティカルセクションではなくなるlog.

ここでは実装はlogback,インターフェースはslf4j-apiの組み合わせによる非同期ロガーの例を紹介してみる.

logback Async Logger

使い方は単純.logback.xml (ユニットテストにはlogback-test.xml )にAsyncAppenderの定義をして,そのappenderをrootの入れ子要素として宣言すればよい.
以下は実際のlogback.xmlの一例.
ここではASYNCアペンダーにFILEアペンダーを宣言して,ファイルへのロギングを非同期化している.


<?xml version="1.0" encoding="UTF-8"?>

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
    </encoder>
  </appender>
  
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <!-- <file>${LOGBACK_LOGDIR}/sample.log</file> -->
    <file>${LOGBACK_HOMEDIR}/samplelog.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${LOGBACK_HOMEDIR}/samplelog-%d{yyyy-MM-dd}.log</fileNamePattern>
      <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
        <maxFileSize>20MB</maxFileSize>
        <maxHistory>2</maxHistory>
      </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>    
    </encoder>
  </appender>
  
  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
      <appender-ref ref="FILE"/>
      <!-- discardingThreashold == 0 means it won't drop TRACE, DEBUG and INFO level log even when the remaining capacity of blocking queue is less than 20% -->
      <discardingThreshold>0</discardingThreshold>
  </appender>

  <!-- Strictly speaking, the level attribute is not necessary since -->
  <!-- the level of the root level is set to DEBUG by default.       -->
  <root level="INFO">          
    <appender-ref ref="STDOUT" />
    <appender-ref ref="ASYNC" />
  </root>  
  
</configuration>

考慮すべきこと

ASYNCロガーは色々便利そうではあるが,非同期の常,扱いは同期のものよりやや手数がかかる.一番気をつけなければいけないのはやはりブロッキングキューのサイズだろう.
もし大量のスレッドが(大量ではないにせよ)沢山のログを非同期ロガーに送り込んだ場合,言い換えるならば沢山のログをロガーのブロッキングキューに追加した場合,最悪の場合ブロッキングキューがフルになってしまう.そのような場合,非同期ロガーはブロッキングキューに空きが出るまでアプリケーションスレッドをブロックさせる.この振る舞いをlogbackはpseudo synchronous (疑似同期)と呼んでいる.logbackでは以下のポイントがpseudo synchronousを引き起こしやすいとしている.

 logback 公式ページより引用
  • Large numbers of application threads (沢山のスレッド)
  • Large numbers of logging events per application call (沢山のロギング)
  • Large amounts of data per logging event (各ロギングイベントでの沢山のデータ)
  • High latency of child appenders (レイテイシの大きい子アペンダー)
上記いずれかに当てはまると思ったならば,是非改善したいところである.

金曜日, 3月 29, 2013

Java ロギングのこと .. log4j-over-slf4j.jar slf4j-log4j12.jar

ロガー移行

Javaには優れたロガーが多い.slf4j, logback 等々.
現時点ではおそらくlogbackならびにslf4jの評価が高く,実際私のところでもアプリケーションデベロッパ達は徐々にlog4jからそれらに移行しつつある.

問題発生!

そんな中私が早晩おこるであろうと思っていたことが案の定(笑)発生した.
log4j-over-slf4jとslf4j-log4j12のクラスパス上での衝突である.

上記2ライブラリはブリッジデザインでのslf4j-api具現パッケージであり,双方をクラスパスに載せると相互参照永久ループが発生しアプリケーションはクラッシュする.

もし以下の点がたくさん思い当たるならば,より問題がおこる確率は高くなる.

  1. プロジェクトやmulti-module mavenプロジェクトがある程度以上の規模である
  2. 相互独立した開発チームが同一のmulti-module mavenプロジェクトを開発している
  3. 未熟にデザインされたライブラリの依存性を持っている
  4. slf4jフレームワークに習熟した人が少ない
  5. 各プロジェクトはロガーを好きに選ぶことができる
  6. library dependenciesの管理が徹底されていない

手前のケースだとまさに上に述べたとおりで,あるプロジェクトがlog4jからslf4jへの移行過渡期にlog4j-over-slf4jへのdependencyを,また別のプロジェクトがlog4j-slf4jを採用した結果あるアプリケーションがクラッシュする羽目に陥った.

解決策。。


この問題を解決するのは決して難しくはない.slf4j-log4j12またはlog4j-over-slf4jのどちらかをdependencyから外せばよい.そして残したほうのスコープをruntimeにする.どちらを外すか?パフォーマンスの理由からlog4j-over-slf4jを外すのがいいだろう.slf4jの公式ホームページからパフォーマンス分析結果を見ることができる.

以下引用
NOTE ON PERFORMANCE Contrary to other bridging modules, namely jcl-over-slf4j and log4j-over-slf4j, which reimplement JCL and respectively log4j, the jul-to-slf4j module does not reimplement the java.util.logging because packages under the java.* namespace cannot be replaced. Instead, jul-to-slf4j translates LogRecord objects into their SLF4J equivalent. Please note this translation process incurs the cost of constructing a LogRecordinstance regardless of whether the SLF4J logger is disabled for the given level or nor. Consequently, j.u.l. to SLF4J translation can seriously increase the cost of disabled logging statements (60 fold or 6000%) and measurably impact the performance of enabled log statements (20% overall increase). As of logback version 0.9.25, it is possible to completely eliminate the 60 fold translation overhead for disabled log statements with the help of LevelChangePropagator.

これはいけない!さっさとけしちゃいましょう  エイヤッ 

以上問題解決.あとはロガー使用を中央管理する体制を強化するなりデベロッパをトレーニングするなりして適宜問題の芽をつむいでおきましょう.