Java の基礎となる JDK Logging モジュールを処理する方法
サンプルから始める
JDK ロギングの使用は非常に簡単です。次のコードに示すように、まず Logger クラスの静的メソッド getLogger を使用してロガーを取得し、その後、次のコードを使用できます。取得したロガーを任意の場所に配置し、ログを入力します。たとえば、logger.info("Main running.") のような呼び出しです。
package com.bes.logging; import java.util.logging.Level; import java.util.logging.Logger; public class LoggerTest { private static Loggerlogger = Logger.getLogger("com.bes.logging"); public static void main(String argv[]) { // Log a FINEtracing message logger.info("Main running."); logger.fine("doingstuff"); try { Thread.currentThread().sleep(1000);// do some work } catch(Exception ex) { logger.log(Level.WARNING,"trouble sneezing", ex); } logger.fine("done"); } }
コードの変更や JDK 構成の変更を行わずに上記の例を実行すると、ログ [Main running.] のみがコンソールに表示されることがわかります。次のような疑問が頭に浮かぶはずです...
1. [Main running.] 以外のログが出力されないのはなぜですか?それらを出現させるにはどうすればよいでしょうか?
2. ログ出力に表示される時間、クラス名、メソッド名などはどこにありますか?
3. ログがコンソールに表示されるのはなぜですか?
4. 大規模なシステムには多数のサブモジュール (単に多数のパッケージ名があると理解できます) が存在する場合がありますが、これらのサブモジュールのログ レベルを個別に制御するにはどうすればよいでしょうか?
5, 拡張: Apache の人気のある log4j プロジェクトは JDK ログに関連していますか? 独自の LoggerManager を実装するにはどうすればよいですか?
これらの質問により、JDK のロギング メカニズムについて学習することに興味があるかもしれません。この章では、この単純なモジュールのメカニズムを分析します。
用語の答え
詳細な分析を行う前に、次の用語をマスターする必要があります
logger
: ロガーについては、次のことを理解しておく必要があります。側面
1, Logger は、コードがログを入力する必要がある場合はどこでも使用されます。これはほぼ JDK ログ モジュールのスポークスマンです。取得するには Logger.getLogger("com.aaa.bbb"); をよく使用します。ロガーを選択し、ロガーを使用してログ出力を行います。
2、ロガーは実際には単なる論理管理ユニットであり、その操作のほとんどは他の「ロール」を渡すためのリレーとして機能するだけです。例: Logger.getLogger("xxx") への呼び出しは Relying LogManager クラスでは、ロガーを使用してログ情報を入力するときに、ロガー内のすべてのハンドラーがログを入力するために呼び出されます。
3. ロガーには階層関係があり、一般的にはパッケージ名の親子継承関係として理解できます。各ロガーには通常、Java パッケージ名に基づいて名前が付けられます。子ロガーは通常、ロガー レベル、ハンドラー、ResourceBundle 名 (国際化情報に関連する) などを親ロガーから継承します。
4、JVM 全体に空の名前を持つルート ロガーが存在し、すべての匿名ロガーはルート ロガーを親とみなします
LogManager
: すべてJVM 全体内のロガー管理、ロガーの生成、取得、その他の操作 (構成ファイルの読み取りを含む) はすべて、これに依存します。 LogManager には、現在のすべてのロガーを保存するためのハッシュテーブル [プライベート Hashtable
Handler
: ログ出力の制御に使用されます。たとえば、JDK に付属の ConsoleHanlder は、出力ストリームを System.err 出力にリダイレクトします。ハンドラーは、Logger メソッドが実行されるたびに呼び出されます。は出力のために呼び出されます。publish メソッドでは、各ロガーには複数のハンドラーがあります。ハンドラーを使用して、ログをさまざまな場所 (ファイル システムやリモート ソケット接続など) に入力できます。
Formatter
: ログは、実際に出力される前にフォーマットする必要があります。たとえば、Do時間を出力したいですか?時間形式?スレッド名を入力しますか?国際化情報等を使用するかどうかはFormatterに依存します。
ログ レベル
: 言うまでもなく、これは理解しやすいものであり、ログ出力がさまざまな段階でのログ出力の粒度に対するさまざまなニーズに適応するのに役立つ理由でもあります。開発からデバッグ、導入、オンラインまで。 JDK ログ レベルは高から低まで、OFF(231-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—> ; FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-231). 各レベルは番号に対応します. ログ出力時のレベルの比較は、ログのサイズによって異なります数値を比較してください。ただし、ロガーだけでなくハンドラーにもレベルがあることに注意してください。つまり、ロガー レベルが FINE で、顧客が FINE レベルのログを入力したい場合、ロガーに対応するハンドラー レベルが INFO であれば、 FINEレベルでもログ出力できません。
対応関係の概要
LogManager とロガーは 1 対多の関係にあり、JVM 全体が実行されている場合、LogManager は 1 つだけ存在し、すべてのロガーは LogManager 内にあります。
#ロガーとハンドラーは多対多の関係にあり、ログ出力時にロガーはログ処理のためにすべてのハンドラーを呼び出します。ハンドラーとフォーマッタは 1 対 1 の関係にあり、ハンドラーには、ログをフォーマットするためのフォーマッタがあります。明らかに、ロガーとレベルの間には 1 対 1 の関係があり、ハンドラーとレベルの間にも 1 対 1 の関係があります。ロギング構成: JDK のデフォルトのロギング構成ファイルは $JAVA_HOME/jre /lib/logging.properties です。システム プロパティ java.util.logging.config.file を使用して、対応する構成を指定できます。ファイルを使用してデフォルトの構成ファイルを上書きします。構成ファイルには通常、次の定義が含まれています:
1, handlers:用逗号分隔每个Handler,这些handler将会被加到root logger中。也就是说即使我们不给其他logger配置handler属性,在输出日志的时候logger会一直找到root logger,从而找到handler进行日志的输入。
2, .level是root logger的日志级别
3,
4, logger的配置,所有以[.level]结尾的属性皆被认为是对某个logger的级别的定义,如com.bes.server.level=FINE是给名为[com.bes.server]的logger定义级别为FINE。顺便说下,前边提到过logger的继承关系,如果还有com.bes.server.webcontainer这个logger,且在配置文件中没有定义该logger的任何属性,那么其将会从[com.bes.server]这个logger进行属性继承。除了级别之外,还可以为logger定义handler和useParentHandlers(默认是为true)属性,如com.bes.server.handler=com.bes.test.ServerFileHandler(需要是一个extends java.util.logging.Handler的类),com.bes.server.useParentHandlers=false(意味着com.bes.server这个logger进行日志输出时,日志仅仅被处理一次,用自己的handler输出,不会传递到父logger的handler)。以下是JDK配置文件示例
handlers= java.util.logging.FileHandler,java.util.logging.ConsoleHandler .level= INFO java.util.logging.FileHandler.pattern = %h/java%u.log java.util.logging.FileHandler.limit = 50000 java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.formatter =java.util.logging.XMLFormatter java.util.logging.ConsoleHandler.level = INFO java.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormatter com.xyz.foo.level = SEVERE sun.rmi.transport.tcp.logLevel = FINE
Logging执行原理
Logger的获取
A,首先是调用Logger的如下方法获得一个logger
public static synchronized Logger getLogger(String name) { LogManager manager =LogManager.getLogManager(); returnmanager.demandLogger(name); }
B,上面的调用会触发java.util.logging.LoggerManager的类初始化工作,LoggerManager有一个静态化初始化块(这是会先于LoggerManager的构造函数调用的~_~):
static { AccessController.doPrivileged(newPrivilegedAction<Object>() { public Object run() { String cname =null; try { cname =System.getProperty("java.util.logging.manager"); if (cname !=null) { try { Class clz =ClassLoader.getSystemClassLoader().loadClass(cname); manager= (LogManager) clz.newInstance(); } catch(ClassNotFoundException ex) { Class clz =Thread.currentThread().getContextClassLoader().loadClass(cname); manager= (LogManager) clz.newInstance(); } } } catch (Exceptionex) { System.err.println("Could not load Logmanager \"" + cname+ "\""); ex.printStackTrace(); } if (manager ==null) { manager = newLogManager(); } manager.rootLogger= manager.new RootLogger(); manager.addLogger(manager.rootLogger); Logger.global.setLogManager(manager); manager.addLogger(Logger.global); return null; } }); }
从静态初始化块中可以看出LoggerManager是可以使用系统属性java.util.logging.manager指定一个继承自java.util.logging.LoggerManager的类进行替换的,比如Tomcat启动脚本中就使用该机制以使用自己的LoggerManager。
不管是JDK默认的java.util.logging.LoggerManager还是自定义的LoggerManager,初始化工作中均会给LoggerManager添加两个logger,一个是名称为””的root logger,且logger级别设置为默认的INFO;另一个是名称为global的全局logger,级别仍然为INFO。
LogManager”类”初始化完成之后就会读取配置文件(默认为$JAVA_HOME/jre/lib/logging.properties),把配置文件的属性名<->属性值这样的键值对保存在内存中,方便之后初始化logger的时候使用。
C,A步骤中Logger类发起的getLogger操作将会调用java.util.logging.LoggerManager的如下方法:
Logger demandLogger(String name) { Logger result =getLogger(name); if (result == null) { result = newLogger(name, null); addLogger(result); result =getLogger(name); } return result; }
可以看出,LoggerManager首先从现有的logger列表中查找,如果找不到的话,会新建一个looger并加入到列表中。当然很重要的是新建looger之后需要对logger进行初始化,这个初始化详见java.util.logging.LoggerManager#addLogger()方法中,改方法会根据配置文件设置logger的级别以及给logger添加handler等操作。
到此为止logger已经获取到了,你同时也需要知道此时你的logger中已经有级别、handler等重要信息,下面将分析输出日志时的逻辑。
日志的输出
首先我们通常会调用Logger类下面的方法,传入日志级别以及日志内容。
public void log(Levellevel, String msg) { if (level.intValue() < levelValue ||levelValue == offValue) { return; } LogRecord lr = new LogRecord(level, msg); doLog(lr); }
该方法可以看出,Logger类首先是进行级别的校验,如果级别校验通过,则会新建一个LogRecord对象,LogRecord中除了日志级别,日志内容之外还会包含调用线程信息,日志时刻等;之后调用doLog(LogRecord lr)方法
private void doLog(LogRecord lr) { lr.setLoggerName(name); String ebname =getEffectiveResourceBundleName(); if (ebname != null) { lr.setResourceBundleName(ebname); lr.setResourceBundle(findResourceBundle(ebname)); } log(lr); }
doLog(LogRecord lr)方法中设置了ResourceBundle信息(这个与国际化有关)之后便直接调用log(LogRecord record) 方法
public void log(LogRecord record) { if (record.getLevel().intValue() <levelValue || levelValue == offValue) { return; } synchronized (this) { if (filter != null &&!filter.isLoggable(record)) { return; } } Logger logger = this; while (logger != null) { Handler targets[] = logger.getHandlers(); if(targets != null) { for (int i = 0; i < targets.length; i++){ targets[i].publish(record); } } if(!logger.getUseParentHandlers()) { break; } logger= logger.getParent(); } }
很清晰,while循环是重中之重,首先从logger中获取handler,然后分别调用handler的publish(LogRecordrecord)方法。while循环证明了前面提到的会一直把日志委托给父logger处理的说法,当然也证明了可以使用logger的useParentHandlers属性控制日志不进行往上层logger传递的说法。到此为止logger对日志的控制差不多算是完成,接下来的工作就是看handler的了,这里我们以java.util.logging.ConsoleHandler为例说明日志的输出。
public class ConsoleHandler extends StreamHandler { public ConsoleHandler() { sealed = false; configure(); setOutputStream(System.err); sealed = true; }
ConsoleHandler构造函数中除了需要调用自身的configure()方法进行级别、filter、formatter等的设置之外,最重要的我们最关心的是setOutputStream(System.err)这一句,把系统错误流作为其输出。而ConsoleHandler的publish(LogRecordrecord)是继承自java.util.logging.StreamHandler的,如下所示:
public synchronized void publish(LogRecord record) { if(!isLoggable(record)) { return; } String msg; try { msg =getFormatter().format(record); } catch (Exception ex){ // We don't want tothrow an exception here, but we // report theexception to any registered ErrorManager. reportError(null,ex, ErrorManager.FORMAT_FAILURE); return; } try { if (!doneHeader) { writer.write(getFormatter().getHead(this)); doneHeader =true; } writer.write(msg); } catch (Exception ex){ // We don't want tothrow an exception here, but we // report theexception to any registered ErrorManager. reportError(null,ex, ErrorManager.WRITE_FAILURE); } }
方法逻辑也很清晰,首先是调用Formatter对消息进行格式化,说明一下:格式化其实是进行国际化处理的重要契机。然后直接把消息输出到对应的输出流中。需要注意的是handler也会用自己的level和LogRecord中的level进行比较,看是否真正输出日志。
总结
至此,整个日志输出过程已经分析完成。细心的读者应该可以解答如下四个问题了。
1,【Main running.】以外的日志为什么没有输出?怎么让它们也能够出现?
这就是JDK默认的logging.properties文件中配置的handler级别和跟级别均为info导致的,如果希望看到FINE级别日志,需要修改logging.properties文件,同时进行如下两个修改
java.util.logging.ConsoleHandler.level= FINE//修改 com.bes.logging.level=FINE//添加
2,日志中出现的时间、类名、方法名等是从哪里输出的?
请参照[java.util.logging.ConsoleHandler.formatter= java.util.logging.SimpleFormatter]配置中指定的java.util.logging.SimpleFormatter类,其publicsynchronized String format(LogRecord record) 方法说明了一切。
public synchronized String format(LogRecord record) { StringBuffer sb = new StringBuffer(); // Minimize memory allocations here. dat.setTime(record.getMillis()); args[0] = dat; StringBuffer text = new StringBuffer(); if (formatter == null) { formatter = new MessageFormat(format); } formatter.format(args, text, null); sb.append(text); sb.append(" "); if (record.getSourceClassName() != null) { sb.append(record.getSourceClassName()); } else { sb.append(record.getLoggerName()); } if (record.getSourceMethodName() != null) { sb.append(" "); sb.append(record.getSourceMethodName()); } sb.append(lineSeparator); String message = formatMessage(record); sb.append(record.getLevel().getLocalizedName()); sb.append(": "); sb.append(message); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = newStringWriter(); PrintWriter pw = newPrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString();}public synchronized String format(LogRecord record) { StringBuffer sb = new StringBuffer(); // Minimize memory allocations here. dat.setTime(record.getMillis()); args[0] = dat; StringBuffer text = new StringBuffer(); if (formatter == null) { formatter = new MessageFormat(format); } formatter.format(args, text, null); sb.append(text); sb.append(" "); if (record.getSourceClassName() != null) { sb.append(record.getSourceClassName()); } else { sb.append(record.getLoggerName()); } if (record.getSourceMethodName() != null) { sb.append(" "); sb.append(record.getSourceMethodName()); } sb.append(lineSeparator); String message = formatMessage(record); sb.append(record.getLevel().getLocalizedName()); sb.append(": "); sb.append(message); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = newStringWriter(); PrintWriter pw = newPrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString(); }
3,为什么日志就会出现在控制台?
看到java.util.logging.ConsoleHandler 类构造方法中的[setOutputStream(System.err)]语句,相信你已经明白。
4,大型的系统可能有很多子模块(可简单理解为有很多包名),如何对这些子模块进行单独的日志级别控制?
在logging.properties文件中分别对各个logger的级别进行定义,且最好使用java.util.logging.config.file属性指定自己的配置文件。
以上がJava の基礎となる JDK Logging モジュールを処理する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

Java は、初心者と経験豊富な開発者の両方が学習できる人気のあるプログラミング言語です。このチュートリアルは基本的な概念から始まり、高度なトピックに進みます。 Java Development Kit をインストールしたら、簡単な「Hello, World!」プログラムを作成してプログラミングを練習できます。コードを理解したら、コマンド プロンプトを使用してプログラムをコンパイルして実行すると、コンソールに「Hello, World!」と出力されます。 Java の学習はプログラミングの旅の始まりであり、習熟が深まるにつれて、より複雑なアプリケーションを作成できるようになります。
