这是我生命中的第一次,我发现自己处于一个可以开源的Java API的位置.希望被包括在许多其他项目中.
对于日志记录我(以及与我一起工作的人)总是使用JUL(java.util.logging)并且从未遇到任何问题.但是现在我需要更详细地了解我应该为我的API开发做些什么.我已经对此做了一些研究,并且我得到的信息让我感到更加困惑.因此这篇文章.
由于我来自JUL,我对此持偏见态度.我对其余的知识并不是那么大.
从我所做的研究中我得出了人们不喜欢JUL的原因:
"在Sun发布JUL之前,我开始使用Java进行开发,而且我更容易继续使用logging-framework-X而不是学习新东西".嗯.我不是在开玩笑,这实际上就是人们所说的.有了这个论点,我们都可以做COBOL.(但我当然可以说这是一个懒惰的家伙)
"我不喜欢JUL中日志记录级别的名称".好吧,说真的,这还不足以成为引入新依赖的理由.
"我不喜欢JUL输出的标准格式".嗯.这只是配置.你甚至不需要做任何代码方面的事情.(确实,过去你可能不得不创建自己的Formatter类来实现它).
"我使用其他也使用logging-framework-X的库,所以我觉得使用那个更容易".这是一个循环论证,不是吗?为什么'每个人'都使用logging-framework-X而不是JUL?
"其他人都在使用logging-framework-X".这对我来说只是上面的一个特例.多数并不总是正确的.
所以真正的大问题是为什么不是JUL?.我错过了什么?伐木立面的存在理由(SLF4J,JCL)是历史上存在多种伐木实施,其原因可以追溯到JUL之前的时代,正如我所看到的那样.如果JUL是完美的那么伐木外墙将不存在,或者什么?我们不应该首先质疑为什么它们是必要的而不是拥抱它们?(看看这些原因是否仍然存在)
好吧,到目前为止我的研究已经导致我可以看到的一些事情可能是JUL的真正问题:
表现.有人说SLF4J的表现优于其他表现.在我看来,这是一个过早优化的案例.如果你需要每秒记录数百兆字节,那么无论如何我都不确定你是否在正确的路径上.JUL也在不断发展,你在Java 1.4上做的测试可能不再适用.你可以在这里阅读它,这个修复已经成为Java 7.许多人还谈到了日志记录方法中字符串连接的开销.但是,基于模板的日志记录可以避免这种成本,并且它也存在于JUL中.我个人从来没有真正编写基于模板的日志记录 太懒了.例如,如果我使用JUL执行此操作:
log.finest("Lookup request from username=" + username
+ ", valueX=" + valueX
+ ", valueY=" + valueY));
Run Code Online (Sandbox Code Playgroud)
我的IDE会警告我并请求允许它将其更改为:
log.log(Level.FINEST, "Lookup request from username={0}, valueX={1}, valueY={2}",
new Object[]{username, valueX, valueY});
Run Code Online (Sandbox Code Playgroud)
..我当然会接受.许可授予 !谢谢您的帮助.
所以我自己并没有自己编写这样的语句,这是由IDE完成的.
关于性能问题的结论我没有发现任何迹象表明JUL的表现与竞争对手相比并不好.
从类路径配置.开箱即用的JUL无法从类路径加载配置文件.要做到这一点,需要几行代码.我可以看出为什么这可能很烦人,但解决方案简短而简单.
输出处理程序的可用性.JUL带有5个开箱即用的输出处理程序:控制台,文件流,套接字和内存.这些可以扩展或可以编写新的.例如,这可能是写入UNIX/Linux Syslog和Windows事件日志.我个人从来没有这个要求,也没有看过它,但我当然可以说明为什么它可能是一个有用的功能.例如,Logback附带了Syslog的附加程序.我仍然会争辩
我真的很担心我忽略了一些东西.除了JUL之外,使用伐木外墙和伐木实施是如此普遍,我必须得出结论,我只是不明白.那恐怕不是第一次.:-)
那我该怎么办?我希望它成功.我当然可以"顺其自然"并实施SLF4J(这些日子似乎最受欢迎)但是为了我自己的缘故,我仍然需要明白今天的JUL究竟出了什么问题才能保证所有的模糊?我会为我的图书馆选择JUL来破坏自己吗?
(nolan600于2012年7月7日添加的部分)
下面有一篇来自Ceki的参考文献,其中提到SLF4J的参数化比JUL快10倍或更快.所以我开始做一些简单的测试.乍一看,这种说法肯定是正确的.以下是初步结果(但请继续阅读!):
上面的数字是msecs,所以越少越好.因此,10倍的性能差异实际上非常接近.我最初的反应:这是很多!
这是测试的核心.可以看出,整数和字符串是在循环中构造的,然后在log语句中使用:
for …Run Code Online (Sandbox Code Playgroud) 我在我当前的项目中使用了logback.
它提供六个级别的日志记录:TRACE DEBUG INFO WARN ERROR OFF
我正在寻找一个经验法则来确定常见活动的日志级别.例如,如果线程被锁定,则应将日志消息设置为调试级别或信息级别.或者,如果正在使用套接字,则应在调试级别还是跟踪级别记录其特定标识.
我将欣赏每个日志记录级别的更多示例的答案.
我们在自制包装器后面使用log4j.我们现在计划使用它的更多功能.
我们应该更新到logback吗?
(我的意思是框架不是像SLF4J那样的外观)
如何配置Logback以将记录器的不同级别记录到不同的目标?
例如,给定以下Logback配置,Logback会将INFO消息记录到STDOUT和ERROR消息STDERR吗?
(请注意,此示例是第3章:Logback配置中logback-examples/src/main/java/chapters/configuration/sample4.xml显示的示例的变体).
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="STDERR"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
<target>System.err</target>
</appender>
<!-- What is the effective level of "chapters.configuration"? -->
<logger name="chapters.configuration" level="INFO" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
<logger name="chapters.configuration" level="ERROR" additivity="false">
<appender-ref ref="STDERR" />
</logger>
<!-- turn OFF all logging (children can override) --> …Run Code Online (Sandbox Code Playgroud) 我正在使用logback/slf4j进行日志记录.我想解析我的日志文件来分析一些数据,所以我想要有两个记录器实例,每个记录器实例都记录到一个单独的文件中,而不是解析一个很棒的大文件(主要由调试语句组成); 一个用于分析,一个用于所有目的的日志记录.有没有人知道这是否可以使用Logback或任何其他记录器?
我有以下logback.xml文件:
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Run Code Online (Sandbox Code Playgroud)
现在,在发生特定事件时,我想以编程方式将根记录器的级别从调试更改为错误.我不能使用变量替换,我必须在代码中执行此操作.
怎么做到呢 ?谢谢.
这似乎是一个疏忽错误,但我似乎无法找到原因.使用logback/slf4j进行日志记录(最新版本为slf4j-api-1.6.1,logback core/classic 0.9.24).最简单的测试日志配置是:
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<!-- DONT USE THIS FORMATTER FOR LIVE LOGGING THE %L LINE NUMBER OUTPUTTER IS SLOW -->
<pattern>%le %-1r [%c{1}:%L] %m%n</pattern>
</layout>
</appender>
<root level="DEBUG">
<appender-ref ref="stdout" />
</root>
</configuration>
Run Code Online (Sandbox Code Playgroud)
每个日志设置都以logback的内部状态行开头:
11:21:27,825 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
11:21:27,826 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback-test.xml] at [file:.../logback-test.xml]
11:21:28,116 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
11:21:28,124 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender] …Run Code Online (Sandbox Code Playgroud) 在我们的软件中,我们广泛使用MDC来跟踪会话ID和Web请求的用户名等内容.这在原始线程中运行时工作正常.但是,有很多事情需要在后台处理.为此,我们使用java.concurrent.ThreadPoolExecutor和java.util.Timer类以及一些自动异步执行服务.所有这些服务都管理自己的线程池.
这就是Logback的手册在这样的环境中使用MDC所要说的:
映射的诊断上下文的副本不能始终由来自启动线程的工作线程继承.当java.util.concurrent.Executors用于线程管理时就是这种情况.例如,newCachedThreadPool方法创建一个ThreadPoolExecutor,就像其他线程池代码一样,它具有复杂的线程创建逻辑.
在这种情况下,建议在将任务提交给执行程序之前,在原始(主)线程上调用MDC.getCopyOfContextMap().当任务运行时,作为其第一个操作,它应调用MDC.setContextMapValues()以将原始MDC值的存储副本与新的Executor托管线程相关联.
这样会很好,但是很容易忘记添加这些调用,并且没有简单的方法来识别问题,直到为时已晚.Log4j的唯一标志是您在日志中丢失了MDC信息,而使用Logback,您会收到过时的MDC信息(因为胎面池中的线程从其上运行的第一个任务继承其MDC).两者都是生产系统中的严重问题.
我不认为我们的情况有任何特殊之处,但我在网上找不到这个问题.显然,这不是很多人碰到的东西,所以必须有办法避免它.我们在这做错了什么?
我们在我们的项目中使用SLF4J + Logback组合已经有一段时间了,并且对它非常满意,但是我们的日志记录策略相当简单,使用简单的基于类的记录器并且没有像MDC或Markers这样的花哨的东西.
我想知道的是,社区中是否有人实际使用这些功能以及它们如何用于改进日志记录/过滤.
我特别感兴趣的是在哪里,为什么以及如何使用[1]标记进行记录.他们认为这是一个非常简洁的功能,可以在记录中添加语义上下文 - 例如,当一个类可能正在处理多个问题时,可以使用任务/关注特定标记来区分日志语句.
在日志记录中创建和使用标记的最佳实践,约定或策略可能是什么.
更新:我想,我真正追求的不是为什么使用标记,而是如何使用标记- 是否有一些命名标记的好方法(例如使用带空格的明文或短划线/下划线/标点符号分隔的关键字样式名称),是否应该有某种"标准名称"池,根据业务功能命名.我可以为自己解决的问题,但如果我想系统地使用这些功能并将它们介绍给开发人员团队,那么就可以制定一些可正式的指导方针......
[1] - 通过询问如何使用标记我并不是真的问如何使用API(它确实非常直接) - 我更倾向于指出如何设置使用标记一致地记录的更一般级别
为什么人们会使用以下其中一个而不是另一个?