Log4J:创建Logger实例的策略


70

我决定将Log4J日志记录框架用于新的Java项目。我想知道应该使用什么策略来创建/管理Logger实例,为什么?

  • 每个类一个Logger实例,例如

    class Foo {
        private static final Logger log = Logger.getLogger(Foo.class);
    }
    
  • 每个线程一个Logger实例
  • 每个应用程序一个Logger实例
  • 水平切片:应用程序每一层(例如,视图层,控制器层和持久层)中Logger的一个实例
  • 垂直切片:应用程序功能分区内Logger的一个实例

注意:这些文章已在某种程度上考虑了此问题:

创建Log4j记录器的开销是多少


注意:“最终”方式最有可能调查调用堆栈以获取调用方。以我的经验,如果您显式地编写代码,而不是依靠神奇地获取代码,Java程序会更强大。
托尔比约恩Ravn的安徒生

JRE中的错误将导致日志记录不正确。
托尔比约恩Ravn的安徒生

Answers:


46

通常,您需要为每个类设置记录器,因为这是一个不错的逻辑组件。线程已经是日志消息的一部分(如果您的过滤器显示了它们),因此以这种方式对记录器进行切片可能是多余的。

对于基于应用程序或基于层的记录器,问题在于您必须找到放置该Logger对象的位置。没什么大不了的。更大的问题是,某些类可能在多个应用程序的多个级别上使用……可能很难使记录器正确。或至少是棘手的。

...而您想要的最后一件事是日志记录设置中的错误假设。

如果您关心应用程序和层并具有简单的分离点,则可以使用NDC。有时代码可能有点多余,但是我不知道我被精确的上下文堆栈保存了多少次,这表明我从Y层的应用程序X调用了Foo.bar()。


如果我有一个具有记录器静态实例的singelton类,该怎么办?并让所有班级都访问singelton班级的这个成员?
杰克

33

最常用的策略是为每个类创建一个记录器。如果创建新线程,请为它们提供一个有用的名称,因此可以轻松区分它们的日志记录。

为每个类创建记录器的好处是可以在您的类的包结构中打开/关闭日志记录:

log4j.logger.org.apache = INFO
log4j.logger.com.example = DEBUG
log4j.logger.com.example.verbose = ERROR

上面的代码将所有apache库代码设置为INFOlevel,将日志记录从您自己的代码切换为DEBUGlevel,除了详细包。


1
如果您想通过程序包结构控制日志记录级别,我会看到好处。有比复制private static Logger logger = Logger.getLogger(MyClass.class);每个班级更好的方法吗?
马库斯·莱昂,

鉴于您需要某种方式来提供记录程序实例的包名和类名,因此我没有看到一种为每个类创建记录器实例的替代方法。如果要为每个包创建记录器,则仍需要检索对记录器实例的引用才能使用它。
rsp

Logger您使用哪个构造函数Logger为包创建?是getLogger(String name)name只是一个指定程序包的字符串(例如:“ abc”)吗?如果这样做,是否可以像提到la那样配置日志记录级别log4j.logger.a.b.c = INFO
马库斯·莱昂,

2
是的,我认为您可以使用String变体,如private static Logger logger = Logger.getLogger(MyClass.class.getPackage());创建名称为“ ab”,“ abc”等的记录器一样,记录器层次结构也可以使用。请参见logging.apache.org/log4j/1.2/manual.html中
rsp

14

我敢肯定这不是最佳做法,但是在保存代码行之前,我已经花了一些启动时间。具体来说,粘贴时:

Logger logger = Logger.getLogger(MyClass.class);

...开发人员经常忘记将“ MyClass”更改为当前的类名称,并且一些记录器总是提示错误的位置。这不好。

我偶尔写过:

static Logger logger = LogUtil.getInstance(); 

和:

class LogUtil {
   public Logger getInstance() {
      String callingClassName = 
         Thread.currentThread().getStackTrace()[2].getClass().getCanonicalName();
      return Logger.getLogger(callingClassName);
   }
}

该代码中的“ 2”可能是错误的,但要旨在那里;(在类加载时,作为静态变量)对性能产生负面影响,以找到类名称,以便开发人员实际上没有办法输错此类型或引入任何错误。

为了防止开发人员在运行时出错,我通常不会为降低性能而感到兴奋,但是如果它是单例发生,一次吗?对我来说,听起来通常是一笔不错的交易。


我更新了您的答案-它没有编译,并且我使用了getClass()。getCanonicalName()而不只是getClassName()。哦,2的确是魔数。
ripper234 2011年

由于您所说的原因,我写了一个类似的课。因此,所需要做的就是:静态最终ClassLogger日志= new ClassLogger(); 我已经合并了slf4j以允许许多日志实现。有关详细信息,请参见位于www.sormula.org上的org.sormula.log.ClassLogger。
杰夫·米勒

1
使用getStackTrace()[2] .getClass()。getCanonicalName(),您将始终获得“ java.lang.StackTraceElement”。我使用'callingClassName = Thread.currentThread()。getStackTrace()[2] .getClassName()'得到了想要的结果。我对答案投了赞成票,但没有编辑。谢谢你的提示。
bigleftie

我已经在我的Spring启动项目中尝试了LogUtil。它正在打印“ [pool-2-thread-6] jlStackTraceElement”而不是类名
valijon

10

正如其他人所说,我将为每个类创建一个Logger:

private final static Logger LOGGER = Logger.getLogger(Foo.class);

要么

private final Logger logger = Logger.getLogger(this.getClass());

但是,我发现过去在记录器中包含其他信息很有用。例如,如果您有一个网站,则可以在每条日志消息中包括用户ID。这样,您就可以跟踪用户所做的一切(对于调试问题等非常有用)。

最简单的方法是使用MDC,但是您可以使用为类的每个实例创建的Logger,其名称包括用户ID。

使用MDC的另一个优点是,如果使用SL4J,则可以根据MDC中的值更改设置。因此,如果您希望以DEBUG级别记录特定用户的所有活动,而将所有其他用户都保留为ERROR,则可以。您还可以根据您的MDC将不同的输出重定向到不同的位置。

一些有用的链接:

http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html

http://www.slf4j.org/api/index.html?org/slf4j/MDC.html


我喜欢这个this.getClass()部分。这样,就不会有关于记录器名称的任何复制/粘贴错误。
winklerrr 2015年

4
  • 每个课程创建一个记录器。
  • 如果您有需要依赖共享日志记录(很可能)使用SLF4J的桥梁为共享日志记录。使用Commons Logging界面实例化您的记录器(每个类):private static final Log log = LogFactory.getLog(MyClass.class);
  • 使用快捷方式在IDE中显示此模式。为此,我使用IDEA的实时模板
  • 使用NDC(字符串的本地线程堆栈)或MDC(String的本地线程→?)为线程提供上下文信息。

模板示例:

private static final Log log = LogFactory.getLog($class$.class); // live template 'log'

if (log.isDebugEnabled())
    log.debug(String.format("$string$", $vars$)); // live template 'ld', 'lw', 'le' ...

3

另一个选择:您可以尝试在日志记录中使用AspectJ横切。在这里检查简化日志记录。(如果您不想使用AOP,可以查看slf4j

//Without AOP

    Class A{
       methodx(){
        logger.info("INFO");
       }
    }

    Class B{
       methody(){
        logger.info("INFO");
       }
    }

//With AOP

    Class A{
       methodx(){
         ......
       }
    }

    Class B{
       methody(){
         ......
       }
    }

    Class LoggingInterceptor{

       //Catched defined method before process
       public void before(...xyz){
         logger.info("INFO" + ...xyz);
       }

       //Catched defined method after processed          
       public void after(...xyz){
         logger.info("INFO" + ...xyz);
       }
       .....

    }

PS: AOP会更好,这是DRY(不要重复自己)的方式。


2

没有链接到任何类名的创建自定义记录器的最佳和最简单的方法是:

// create logger
Logger customLogger = Logger.getLogger("myCustomLogName");

// create log file, where messages will be sent, 
// you can also use console appender
FileAppender fileAppender = new FileAppender(new PatternLayout(), 
                                             "/home/user/some.log");

// sometimes you can call this if you reuse this logger 
// to avoid useless traces
customLogger.removeAllAppenders();

// tell to logger where to write
customLogger.addAppender(fileAppender);

 // send message (of type :: info, you can use also error, warn, etc)
customLogger.info("Hello! message from custom logger");

现在,如果您需要同一类中的另一个记录器,没问题:)只需创建一个新的记录器

// create logger
Logger otherCustomLogger = Logger.getLogger("myOtherCustomLogName");

现在看到上面的代码并创建新的fileappender,这样您的输出将发送到其他文件中

这对于(至少)2种情况很有用

  • 当您想要将错误与信息分开并发出警告时

  • 当您管理多个流程并且需要每个流程的输出时

ps。有问题吗?随便问!:)


1
如果您没有通用代码,如果您有2个不同进程的2个不同日志文件,并且两个进程都有一些通用代码,则此方法很好。
Spark Spark先生2012年

0

通用约定是“记录器pr类,并使用该类的名称作为其名称”。这是个好建议。

我的个人经验是,不应将此记录程序变量声明为静态,而应为每个新变量检索一个实例变量。这允许日志记录框架根据两个呼叫来自何处来区别对待它们。对于该类的所有实例(在该类加载器中),静态变量都是相同的。

另外,您还应该通过选择的日志记录后端学习所有可能性。您可能有您没有想到的可能性。


请注意,至少对于log4j,如果您使用类名作为查找对象,则每次都将是同一记录器实例。没有理由为每个类实例一次又一次地抓住它。您最好还是静态地保留它。
PSpeed

1
拥有静态记录器有利有弊。它们在托管容器中可能会出现问题,根本不应该在打算在OSGi内部运行的代码中使用它们。请参阅SLF4J的优缺点:slf4j.org/faq.html#declared_static
SteveD

0

部署多个EAR / WAR时,最好将log4j.jar打包到类装入器层次结构中的更高位置。
也就是说,不是在WAR或EAR中,而是在容器的System-classloader中,否则,多个Log4J实例将同时写入同一文件,从而导致异常行为。


-1

如果您的应用程序遵循SOA原则,那么对于每个服务A,您将具有以下组件:

  1. 控制器
  2. 服务实施
  3. 执行者
  4. 持久性

因此,拥有aController.log aService.log aExecutor.log和aPersistance.log使生活变得更轻松

这是基于层的分离,因此所有您的Remoting / REST / SOAP类都将写入aController.log

您所有的调度机制,后端服务等都将写入aService.log

并且所有任务执行都写入aExecutor.log,依此类推。

如果您有多线程执行程序,则可能必须使用日志累加器或其他技术才能正确对齐多个线程的日志消息。

这样,您将始终拥有4个日志文件,这些文件不是很多,也不会太少。我从经验中告诉您,这使生活变得更加轻松。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.