目次
1.背景
Log4jにログを記録するには、同期ログと非同期ログの2つの方法があります。非同期ログは、AsyncAppenderの使用とAsyncLoggerの使用の2つの方法に分けることができます。
2.Log4j2の同期ログ
いわゆる同期ログ、つまりログを出力するときは、ログ出力ステートメントが実行されるのを待ってから、次のビジネスロジックステートメントを実行する必要があります。
例を使用してLog4j2の同期ログを理解し、これを使用してログ出力プロセス全体を調べてみましょう。
log4j2.xmlの構成は次のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" name="MyApp" packages="">
<!--全局Filter-->
<ThresholdFilter level="ALL"/>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd HH}.log">
<!--Appender的Filter-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.meituan.Main" level="trace" additivity="false">
<!--Logger的Filter-->
<ThresholdFilter level="debug"/>
<appender-ref ref="RollingFile"/>
</Logger>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Javaコードは次のとおりです。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
public static void main(String args[]) {
Logger logger = LogManager.getLogger(Main.class);
Person person = new Person("Li", "lei");
logger.info("hello, {}", person);
}
private static class Person {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String toString() {
return "Person[" + firstName + "," + lastName + "]";
}
}
}
上記の構成を使用して、プログラムを実行すると、次のログがlogs /app.logに追加されます。
2017-09-13 19:41:00,889 INFO c.m.Main [main] hello, Person[Li,lei]
logger.infoの実行中に何が起こりましたか?ログ情報はapp.logにどのように出力されますか?
Log4j2でのログ出力の詳細なプロセスは次のとおりです。
1.最初に、グローバルフィルターを使用してログイベントをフィルター処理します。
Log4j2のログレベルは8つのレベルに分割され、高から低への優先順位はOFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALLです。
グローバルフィルターのレベルはALLです。これは、すべてのレベルのログを出力できることを意味します。logger.info()は、INFOレベルのログを出力するように要求します。
2.ロガーのレベルを使用してログイベントをフィルタリングします。
ロガーのレベルはTRACEです。これは、TRACEレベル以上のログを出力できることを意味します。logger.info()は、INFOレベルのログを出力するように要求します。
3.ログ出力メッセージを生成します。
プレースホルダーを使用してログを出力します。出力ステートメントはlogger.info( "increase {} from {} to {}"、arg1、arg2、arg3)の形式です。プレースホルダー{}のコンテンツは最終出力arg1、arg2、およびarg3の文字列パディング。
log4j2はObject []を使用してパラメータ情報を保存します。この段階で、Object []はString []に変換され、出力パターン文字列 "increase {} from {} to {}"とパラメータ配列String []を含むメッセージが生成されます。 、これは後続のログ形式の出力の準備です。
4.LogEventを生成します。
LogEventには、loggerName(ログ出力)、level(ログレベル)、timeMillis(ログ出力時間)、message(ログ出力コンテンツ)、threadName(スレッド名)、およびその他の情報が含まれます。
上記のプログラムでは、生成されたLogEventの属性値はloggerName = com.meituan.Main、Level = INFO、timeMillis = 1505659461759であり、messageはステップ3で作成されたMessage、threadNama = mainです。
5.ロガーによって構成されたフィルターを使用して、ログイベントをフィルター処理します。
ロガーによって構成されたフィルターのレベルはDEBUGです。これは、DEBUG以上のログを出力できることを意味します。logger.info()は、INFOレベルのログを出力するように要求します。
6.ロガーに対応するアペンダーによって構成されたフィルターを使用して、ログイベントをフィルター処理します。
Appender構成のFilter構成のINFOレベルlogonMatch = ACCEPTは、INFOレベルログの出力が許可されていることを示します。logger.info()は、INFOレベルのログを出力するように要求します。
7.ロールオーバーをトリガーする必要があるかどうかを判断します。
このステップは、ログ出力に必要なステップではありません。構成されたアペンダーがロールオーバーを必要としないアペンダーである場合、そのようなステップはありません。
RollingFileAppenderが使用され、ファイルサイズに基づくロールオーバートリガー戦略が構成されているため、この段階で、ロールオーバーをトリガーする必要があるかどうかが判断されます。判断方法は、現在のファイルサイズが指定されたサイズに達しているかどうかであり、達するとロールオーバー操作がトリガーされます。Log4j2でのRollingFileAppenderのロールオーバーについては、Log4j2でのRollingFileのファイルローリング更新メカニズムを参照してください。
8.PatternLayoutは、LogEventをフォーマットして、出力可能な文字列を生成します。
上記のlog4j2.xmlファイルで構成されたパターンと各パラメーターの意味は次のとおりです。
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
パラメータ |
意義 |
---|---|
|
日付形式。デフォルトの形式は2012-11-0214:34:02,781です。 |
|
ログレベル |
|
%cはロガー名を表し、{1。}は精度を表します。ロガー名がorg.apache.commons.Fooの場合、oacFooが出力されます。 |
|
LogEventを処理するスレッドの名前 |
|
ログの内容 |
|
行区切り文字。「\ n」または「\ r \ n」。 |
このステップでは、PatternLayoutはさまざまなコンバーターを使用して、Patternのパターンに従ってLogEventの関連情報を変換し、最後にそれを出力可能なログ文字列に連結します。
たとえば、DatePatternConverterはLogEventのログ出力時間をフォーマットして変換し、LevelPatternConverterはLogEventのログレベル情報をフォーマットして変換し、LoggerPatternConverterはLogEventのロガー名をフォーマットして変換しました。MessagePatternConverterはLogEventのログ出力コンテンツをフォーマットして変換しました。
さまざまなコンバーターによる変換後、LogEventの情報は指定された形式の文字列にフォーマットされます。
9. OutputStreamを使用して、ログをファイルに出力します。
ログ文字列をバイト配列にシリアル化し、バイトストリームOutoutStreamを使用してログをファイルに出力します。ImmediateFlushがtrueに設定されている場合、app.logを開くことで出力ログを確認できます。
3.Log4j2の非同期ログ
ログ出力にはlog4j2の同期ログを使用します。プログラムのログ出力ステートメントとビジネスロジックステートメントは、上記の例のように同じスレッドで実行されます。印刷されたログに表示されるスレッド名はメインであり、同じスレッドにあります。ビジネスロジックステートメントとして(この文は間違っています。LogEventのスレッド名はLogEventを出力するスレッドではなく、LogEventを生成するスレッドです。LogEventを出力するスレッドとLogEventを生成するスレッドは同じスレッドではない可能性があります!)。
非同期ログを出力に使用する場合、ログ出力ステートメントとビジネスロジックステートメントは同じスレッドで実行されませんが、ログ出力操作には専用スレッドが使用されます。ビジネスロジックを処理するメインスレッドは、次のビジネスを実行できます。待機中。ロジック。
Log4j2に非同期ログを実装するには、AsyncAppenderとAsyncLoggerの2つの方法があります。
その中で、AsyncAppenderはArrayBlockingQueueを使用して、非同期で出力する必要のあるログイベントを保存します。AsyncLoggerは、Disruptorフレームワークを使用して高スループットを実現します。
3.1 AsyncAppender
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<RollingFile name="MyFile" fileName="logs/app.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<SizeBasedTriggeringPolicy size="500MB"/>
</RollingFile>
<Async name="Async">
<AppenderRef ref="MyFile"/>
</Async>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>
上記はAsyncAppenderを使用した一般的な構成です。AsyncAppenderを構成した後、ログイベントをファイルに書き込む操作は別のスレッドで実行されます。
AsyncAppenderの一般的なパラメーター
パラメータ名 |
の種類 |
説明 |
---|---|---|
名前 | ストリング | 非同期アペンダーの名前。 |
AppenderRef | ストリング | 非同期呼び出し用に複数のアペンダー名を構成できます。 |
ブロッキング | ブール値 | デフォルトはtrueです。trueの場合、アペンダーはキューが解放されるまで待機します。falseの場合、キューがいっぱいになると、ログイベントは破棄されます。(エラーアペンダーが構成されている場合、破棄されるログイベントはエラーアペンダーによって処理されます) |
バッファサイズ | 整数 | キューに保存できるログイベントの最大数。デフォルトは128です。(ソースコードで128、Log4j2の公式ウェブサイトで1024、公式ウェブサイトの情報が正しくありません) |
AsyncAppenderの他のパラメーターについては、Log4j2のAsyncAppenderの詳細な概要を参照してください。
各非同期アペンダーは、ArrayBlockingQueueを内部的に維持し、ログイベントを出力するスレッドを作成します。複数のAppenderRefが構成されている場合、対応するアペンダーがログ出力に使用されます。
3.2 AsyncLogger
Disruptorフレームワークは、Log4j2のAsyncLoggerで内部的に使用されます。
ディスラプターの紹介
Disruptorは、英国の外国為替取引会社LMAXによって開発された高性能キューです。Disruptorに基づくシステムは、1つのスレッドで1秒あたり600万件の注文をサポートできます。
現在、Apache StromやLog4j2を含む多くの有名なプロジェクトが、高性能を得るためにDisruptorを適用しています。
Disruptorフレームワーク内のコアデータ構造は、ロックフリーのリングキューであるRingBufferです。
1つのスレッドで1秒あたり600万件の注文を処理できますが、Disruptorが非常に高速なのはなぜですか?
a.lock-free-CASはスレッドセーフを実現するために使用されます
ArrayBlockingQueueは、ロックを使用して同時実行制御を実現します。getまたはputすると、現在アクセスしているスレッドがロックされます。複数のプロデューサーと複数のコンシューマーとの同時実行が多数ある場合、ロックの競合、スレッドの切り替え、等
Disruptorは、CASを介して複数のプロデューサーとコンシューマーによるRingBufferへの同時アクセスを実現します。CASは楽観的ロックと同等であり、そのパフォーマンスはロックよりも優れています。
b。キャッシュラインパディングを使用して、偽共有の問題を解決します
コンピュータアーキテクチャでは、メモリアクセス速度はCPU動作速度よりもはるかに低速です。メモリとCPUの間にキャッシュが追加されます。CPUは最初にキャッシュ内のデータにアクセスし、CaCheはデータにアクセスする前にデータを見逃します。メモリ内。
疑似共有:キャッシュはキャッシュラインの単位で格納されます。複数のスレッドが独立変数を変更する場合、これらの変数が同じキャッシュラインを共有すると、意図せずに互いのパフォーマンスに影響を与えます。
偽共有の詳細な分析については、「並行プログラミングのパフォーマンスキラーである偽共有」の記事を参照してください。
AsyncLogger
Log4j2非同期ログの出力をログに記録する方法については、Log4j2の非同期ログを調べる例から始めます。
log4j2.xmlの構成は次のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" name="MyApp" packages="">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd HH}.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
<RollingFile name="RollingFile2" fileName="logs/app2.log"
filePattern="logs/app2-%d{yyyy-MM-dd HH}.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<AsyncLogger name="com.meituan.Main" level="trace" additivity="false">
<appender-ref ref="RollingFile"/>
</AsyncLogger>
<AsyncLogger name="RollingFile2" level="trace" additivity="false">
<appender-ref ref="RollingFile2"/>
</AsyncLogger>
<Root level="debug">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
Javaコードは次のとおりです。
public class Main {
public static void main(String args[]) {
Logger logger = LogManager.getLogger(Main.class);
Logger logger2 = LogManager.getLogger("RollingFile2");
Person person = new Person("Li", "lei");
logger.info("hello, {}", person);
logger2.info("good bye, {}", person);
}
上記のlog4j2.xmlには2つのAsyncLoggerが構成されており、名前はcom.meituan.MainとRollingFile2です。
さらに、2つのログを出力するためにmainメソッドで2つのロガーが使用されます。
log4j2.xmlをロードする起動フェーズで、AsyncRootまたはAsyncLoggerが構成されていることが検出されると、ディスラプターインスタンスが開始されます。
上記のプログラムでは、メインスレッドはプロデューサーであり、EventProcessorスレッドはコンシューマーです。
プロデューサーがニュースを制作
logger.infoやlogger.debugと同様のステートメントを出力するために実行する場合は、生成されたLogEventをRingBufferに配置します。
消費者消費ニュース
処理する必要のあるLogEventがRingBufferにある場合、EventProcessorスレッドはRingBufferからLogEventを取得し、Loggerに関連付けられたAppenderを呼び出してLogEventを出力します(特定の出力プロセスは同期プロセスと同じであり、フィルタフィルタリング、PatternLayoutフォーマットおよびその他の手順が必要です)。
処理するRingBufferにLogEventがない場合、EventProcessorスレッドは待機中のブロッキング状態になります(デフォルトの戦略)。
log4j2.xmlには複数のAsyncLoggerが構成されていますが、各AsyncLoggerが処理スレッドに対応しているわけではなく、非同期ログ処理用のEventProcessorスレッドが1つしかないことに注意してください。
4.まとめ
|
ログ出力方式 |
---|---|
同期 | ログを同期的に印刷します。ログ出力とビジネスロジックは同じスレッドにあります。ログ出力が完了した後、後続のビジネスロジック操作を実行できます。 |
非同期アペンダー | 内部でArrayBlockingQueueを使用してログを非同期に出力し、ログ出力を処理するために各AsyncAppenderのスレッドを作成します。 |
非同期ロガー | 高性能の同時実行フレームワークDisruptorを使用してログを非同期に出力し、ログ出力を処理するスレッドを作成します。 |