ホームページ  >  記事  >  Java  >  Log4j でログを定期的に出力し、モジュール名構成を追加する Java コード例

Log4j でログを定期的に出力し、モジュール名構成を追加する Java コード例

高洛峰
高洛峰オリジナル
2017-01-18 12:54:211285ブラウズ

間隔を設定してログを定期的に印刷する
log4j を通じてログを定期的に印刷するというリクエストを受け取りました。その需要は次のように説明されています。ログは定期的に印刷する必要があり、時間間隔は設定可能です。タイミングと言えば、最初に思い浮かぶのは DailyRollingFileAppender クラスです。 datePattern によれば、これは SimpleDateFormat クラスを参照できます。

'.'yyyy-MM: 毎月。

'.'yyyy-ww : 毎週

'.'yyyy-MM-dd: 毎日

'.'yyyy-MM-dd-a: 1 日 2 回

'.'yyyy-MM-dd-HH : 毎時

'. 'yyyy-MM-dd-HH-mm: 毎分

n 分の同様の日付形式がないことが観察されたため、DailyRollingFileAppender クラスに基づいてカスタム クラスが作成されました。プロセスは次のとおりです:

1) DailyRollingFileAppender クラスのソース コードをコピーし、名前を MinuteRollingAppender に変更します。これを log4j.xml に設定するには、設定項目 intervalTime を追加し、set メソッドと get メソッドを追加します。 DailyRollingFileAppender クラスは次の間隔時間を計算するために RollingCalendar クラスを使用するため、intervalTime パラメーターを渡す必要があります。そのため、RollingCalendar クラスは、次のロールオーバー アクションの時間を計算するメソッドであるため、内部クラスとして変更されます。 datePattern、現時点では他の時間パターンは必要ありません。変更方法は次のとおりです:

private int intervalTime = 10;

3) 分に従って構成できる場合は、時間モードを無効にし、静的な最終モードに変更する必要があります。同様に、get、set メソッド、および MinuteRollingAppender コンストラクターの datePattern パラメーターを削除します

public Date getNextCheckDate(Date now)
{
 this.setTime(now);
 this.set(Calendar.SECOND, 0);
 this.set(Calendar.MILLISECOND, 0);
 this.add(Calendar.MINUTE, intervalTime);
 return getTime();
}

同様に、さまざまな datePattern メソッド computeCheckPeriod() も削除できます この時点で、変換は完了し、完成した製品クラスは次のようになります。 :

private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";

テスト設定ファイルは次のとおりです:

package net.csdn.blog;
  
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
  
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
  
/**
 * 按分钟可配置定时appender
 *
 * @author coder_xia
 *
 */
public class MinuteRollingAppender extends FileAppender
{
 /**
  * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd"
  * meaning daily rollover.
  */
 private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";
 /**
  * 间隔时间,单位:分钟
  */
 private int intervalTime = 10;
  
 /**
  * The log file will be renamed to the value of the scheduledFilename
  * variable when the next interval is entered. For example, if the rollover
  * period is one hour, the log file will be renamed to the value of
  * "scheduledFilename" at the beginning of the next hour.
  *
  * The precise time when a rollover occurs depends on logging activity.
  */
 private String scheduledFilename;
  
 /**
  * The next time we estimate a rollover should occur.
  */
 private long nextCheck = System.currentTimeMillis() - 1;
  
 Date now = new Date();
  
 SimpleDateFormat sdf;
  
 RollingCalendar rc = new RollingCalendar();
  
 /**
  * The default constructor does nothing.
  */
 public MinuteRollingAppender()
 {
 }
  
 /**
  * Instantiate a MinuteRollingAppender and open the file
  * designated by filename. The opened filename will become the
  * ouput destination for this appender.
  */
 public MinuteRollingAppender(Layout layout, String filename)
   throws IOException
 {
  super(layout, filename, true);
  activateOptions();
 }
  
 /**
  * @return the intervalTime
  */
 public int getIntervalTime()
 {
  return intervalTime;
 }
  
 /**
  * @param intervalTime
  *   the intervalTime to set
  */
 public void setIntervalTime(int intervalTime)
 {
  this.intervalTime = intervalTime;
 }
  
 @Override
 public void activateOptions()
 {
  super.activateOptions();
  if (fileName != null)
  {
   now.setTime(System.currentTimeMillis());
   sdf = new SimpleDateFormat(DATEPATTERN);
   File file = new File(fileName);
   scheduledFilename = fileName
     + sdf.format(new Date(file.lastModified()));
  
  }
  else
  {
   LogLog
     .error("Either File or DatePattern options are not set for appender ["
       + name + "].");
  }
 }
  
 /**
  * Rollover the current file to a new file.
  */
 void rollOver() throws IOException
 {
  String datedFilename = fileName + sdf.format(now);
  // It is too early to roll over because we are still within the
  // bounds of the current interval. Rollover will occur once the
  // next interval is reached.
  if (scheduledFilename.equals(datedFilename))
  {
   return;
  }
  
  // close current file, and rename it to datedFilename
  this.closeFile();
  
  File target = new File(scheduledFilename);
  if (target.exists())
  {
   target.delete();
  }
  
  File file = new File(fileName);
  boolean result = file.renameTo(target);
  if (result)
  {
   LogLog.debug(fileName + " -> " + scheduledFilename);
  }
  else
  {
   LogLog.error("Failed to rename [" + fileName + "] to ["
     + scheduledFilename + "].");
  }
  
  try
  {
   // This will also close the file. This is OK since multiple
   // close operations are safe.
   this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
  }
  catch (IOException e)
  {
   errorHandler.error("setFile(" + fileName + ", true) call failed.");
  }
  scheduledFilename = datedFilename;
 }
  
 /**
  * This method differentiates MinuteRollingAppender from its super class.
  *
  * 

* Before actually logging, this method will check whether it is time to do * a rollover. If it is, it will schedule the next rollover time and then * rollover. * */ @Override protected void subAppend(LoggingEvent event) { long n = System.currentTimeMillis(); if (n >= nextCheck) { now.setTime(n); nextCheck = rc.getNextCheckMillis(now); try { rollOver(); } catch (IOException ioe) { if (ioe instanceof InterruptedIOException) { Thread.currentThread().interrupt(); } LogLog.error("rollOver() failed.", ioe); } } super.subAppend(event); } /** * RollingCalendar is a helper class to MinuteRollingAppender. Given a * periodicity type and the current time, it computes the start of the next * interval. * */ class RollingCalendar extends GregorianCalendar { private static final long serialVersionUID = -3560331770601814177L; RollingCalendar() { super(); } public long getNextCheckMillis(Date now) { return getNextCheckDate(now).getTime(); } public Date getNextCheckDate(Date now) { this.setTime(now); this.set(Calendar.SECOND, 0); this.set(Calendar.MILLISECOND, 0); this.add(Calendar.MINUTE, intervalTime); return getTime(); } } }

タイミングの実装については、Java が提供する Timer を使用することもできます。実装により、ログを記録するときに毎回時間を計算して比較する必要がなくなります。違いは、実際に自分でスレッドを開始し、rollOver メソッドを呼び出すことです。実装は次のとおりです:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  
 <appender name="myFile" class="net.csdn.blog.MinuteRollingAppender"> 
  <param name="File" value="log4jTest.log" />
  <param name="Append" value="true" />
  <param name="intervalTime" value="2"/>
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value="%p %d (%c:%L)- %m%n" />
  </layout>
 </appender>
  
 <root>
  <priority value="debug"/>
  <appender-ref ref="myFile"/> 
 </root>
  
</log4j:configuration>

ただし、上記の実装には 2 つの問題があります:

1 ) 同時実行性

同時実行性の問題が発生する可能性がある場所の 1 つは、次のとおりです。 run() で closeFile(); を呼び出した後、subAppend() メソッドがログを書き込むため、この時点でファイルが閉じられ、次のエラーが報告されます。 run() メソッド全体を同期し、synchronized キーワードを追加します。ただし、作成者は、実際に書き込む必要があり、書き込み速度が十分に速い場合にログが失われる問題を現在解決していません。 2) パフォーマンス

Timer を使用して実装するのは比較的簡単ですが、Timer 内のタスクの実行時間が長すぎると、Timer オブジェクトが占有され、後続のタスクが実行できなくなります。解決策も、スレッド プール バージョンを使用することで比較的簡単です。 timer クラス ScheduledExecutorService であり、実装は次のとおりです。

package net.csdn.blog;
  
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
  
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
  
public class TimerTaskRollingAppender extends FileAppender
{
 /**
  * The date pattern. By default, the pattern is set to "&#39;.&#39;yyyy-MM-dd"
  * meaning daily rollover.
  */
 private static final String DATEPATTERN = "&#39;.&#39;yyyy-MM-dd-HH-mm&#39;.log&#39;";
  
 /**
  * 间隔时间,单位:分钟
  */
 private int intervalTime = 10;
  
 SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN);
  
 /**
  * The default constructor does nothing.
  */
 public TimerTaskRollingAppender()
 {
 }
  
 /**
  * Instantiate a <code>TimerTaskRollingAppender</code> and open the file
  * designated by <code>filename</code>. The opened filename will become the
  * ouput destination for this appender.
  */
 public TimerTaskRollingAppender(Layout layout, String filename)
   throws IOException
 {
  super(layout, filename, true);
  activateOptions();
 }
  
 /**
  * @return the intervalTime
  */
 public int getIntervalTime()
 {
  return intervalTime;
 }
  
 /**
  * @param intervalTime
  *   the intervalTime to set
  */
 public void setIntervalTime(int intervalTime)
 {
  this.intervalTime = intervalTime;
 }
  
 @Override
 public void activateOptions()
 {
  super.activateOptions();
  Timer timer = new Timer();
  timer.schedule(new LogTimerTask(), 1000, intervalTime * 60000);
 }
  
 class LogTimerTask extends TimerTask
 {
  @Override
  public void run()
  {
   String datedFilename = fileName + sdf.format(new Date());
   closeFile();
   File target = new File(datedFilename);
   if (target.exists())
    target.delete();
   File file = new File(fileName);
   boolean result = file.renameTo(target);
   if (result)
    LogLog.debug(fileName + " -> " + datedFilename);
   else
    LogLog.error("Failed to rename [" + fileName + "] to ["
      + datedFilename + "].");
   try
   {
    setFile(fileName, true, bufferedIO, bufferSize);
   }
   catch (IOException e)
   {
    errorHandler.error("setFile(" + fileName
      + ", true) call failed.");
   }
  }
 }
}


タイミングの実装に関しては、デフォルトでは 10 分ごとに新しいログ ファイルを生成することになっています。設定中に自分で設定できます。ただし、設定の場合、時間間隔が分であることを認識せず、600 を割り当ててデバッグをオンにすると、間違いなく GB のログ ファイルが生成されます。次の変換は、RollingFileAppender の最大サイズとバックアップ ファイルの最大数を組み合わせることであり、次回も変換プロセスを説明します。

モジュール名の設定を追加します

以前、log4j スケジュールされた印刷のカスタム クラスの実装について説明しましたが、バックアップ ファイルのサイズと数の指定については説明しませんでした。RollingFileAppender クラスからコードをコピーして、前のコードに追加するだけです。解決する必要がある唯一のことは、同時実行性です。つまり、ファイルが閉じられ、名前変更ファイルも閉じられ、ログ イベントが発生すると、出力ストリームのクローズ エラーが報告されます。


現在、このようなアプリケーション シナリオがあり、それがよく起こります。

1. プロジェクトには複数の異なるプロジェクトが含まれています。

2. 同じプロジェクトに異なるモジュールが含まれています。

最初のケースでは、log4j73d3276cca4423eeeaf44766d19395f2 を設定し、ロガーの生成時に次のようなメソッドを使用できます:

java.io.IOException: Stream closed
 at sun.nio.cs.StreamEncoder.ensureOpen(Unknown Source)
 at sun.nio.cs.StreamEncoder.write(Unknown Source)
 at sun.nio.cs.StreamEncoder.write(Unknown Source)
 at java.io.OutputStreamWriter.write(Unknown Source)
 at java.io.Writer.write(Unknown Source)
..............................

2 番目のケースでは、印刷できることを期待しています。同じログ ファイルに、問題が発生したときに問題を特定できるように、ログ内のモジュール名を出力したいと考えています。そのため、必要に応じて Appender クラスに構成 ModuleName を追加する必要があります。変換を開始しましょう。スケジュールされた印刷とは異なり、RollingFileAppender クラスが基本クラスに変換されます。

まず、構成項目 moduleName を追加し、get メソッドと set メソッドを追加します。

RollingFileAppender から継承されているため、subAppend() の LoggingEvent でデータをフォーマットし、データをフォーマットする formatInfo メソッドを追加するだけです。コードは省略されています。

最終的な製品カテゴリは次のとおりです:

/**
 *
 */
package net.csdn.blog;
  
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
  
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
  
/**
 * @author coder_xia
 *   <p>
 *   采用ScheduledExecutorService实现定时配置打印日志
 *   <p>
 *
 */
public class ScheduledExecutorServiceAppender extends FileAppender
{
 /**
  * The date pattern. By default, the pattern is set to "&#39;.&#39;yyyy-MM-dd"
  * meaning daily rollover.
  */
 private static final String DATEPATTERN = "&#39;.&#39;yyyy-MM-dd-HH-mm&#39;.log&#39;";
  
 /**
  * 间隔时间,单位:分钟
  */
 private int intervalTime = 10;
  
 SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN);
  
 /**
  * The default constructor does nothing.
  */
 public ScheduledExecutorServiceAppender()
 {
 }
  
 /**
  * Instantiate a <code>ScheduledExecutorServiceAppender</code> and open the
  * file designated by <code>filename</code>. The opened filename will become
  * the ouput destination for this appender.
  */
 public ScheduledExecutorServiceAppender(Layout layout, String filename)
   throws IOException
 {
  super(layout, filename, true);
  activateOptions();
 }
  
 /**
  * @return the intervalTime
  */
 public int getIntervalTime()
 {
  return intervalTime;
 }
  
 /**
  * @param intervalTime
  *   the intervalTime to set
  */
 public void setIntervalTime(int intervalTime)
 {
  this.intervalTime = intervalTime;
 }
  
 @Override
 public void activateOptions()
 {
  super.activateOptions();
  Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
    new LogTimerTask(), 1, intervalTime * 60000,
    TimeUnit.MILLISECONDS);
 }
  
 class LogTimerTask implements Runnable
 {
  @Override
  public void run()
  {
   String datedFilename = fileName + sdf.format(new Date());
   closeFile();
   File target = new File(datedFilename);
   if (target.exists())
    target.delete();
   File file = new File(fileName);
   boolean result = file.renameTo(target);
   if (result)
    LogLog.debug(fileName + " -> " + datedFilename);
   else
    LogLog.error("Failed to rename [" + fileName + "] to ["
      + datedFilename + "].");
   try
   {
    setFile(fileName, true, bufferedIO, bufferSize);
   }
   catch (IOException e)
   {
    errorHandler.error("setFile(" + fileName
      + ", true) call failed.");
   }
  }
 }
}

Log4j のスケジュールされたログ出力とモジュール名設定の追加に関する Java コード例については、PHP 中国語 Web サイトに注目してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。