如何使用java8slambdas改进日志记录机制
如何通过不增加字符串连接的开销来改进日志记录机制 考虑以下示例:如何使用java8slambdas改进日志记录机制,java,logging,lambda,java-8,Java,Logging,Lambda,Java 8,如何通过不增加字符串连接的开销来改进日志记录机制 考虑以下示例: import java.util.logging.Level; import java.util.logging.Logger; public class LoggerTest { public static void main(String[] args) { // get logger Logger log = Logger.getLogger(LoggerTest.class.get
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get logger
Logger log = Logger.getLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, but will evaluate the getValue method
log.fine("Trace value: " + getValue());
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
import java.util.logging.Level;
导入java.util.logging.Logger;
公共类LoggerTest{
公共静态void main(字符串[]args){
//获取记录器
Logger log=Logger.getLogger(LoggerTest.class.getName());
//将日志级别设置为INFO(因此不会记录罚款)
log.setLevel(Level.INFO);
//此行不会记录任何内容,但会计算getValue方法
log.fine(“跟踪值:+getValue());
}
//获取具有大量字符串串联的值的示例方法
私有静态字符串getValue(){
字符串val=”“;
对于(int i=0;i<1000;i++){
val+=“foo”;
}
返回val;
}
}
日志方法log.fine(…)
不会记录任何内容,因为日志级别设置为INFO
。问题是,方法getValue
无论如何都会被评估
在有大量调试语句的大型应用程序中,这是一个很大的性能问题
那么,如何解决这个问题呢?自从Java8以来,就有可能在这个场景中使用新引入的 以下是日志记录的修改示例: LoggerTest.class
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get own lambda logger
LambdaLogger log = new LambdaLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, and will also not evaluate the getValue method!
log.fine(()-> "Trace value: " + getValue()); // changed to lambda expression
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LambdaLogger extends Logger {
public LambdaLogger(String name) {
super(name, null);
}
public void fine(Callable<String> message) {
// log only, if it's loggable
if (isLoggable(Level.FINE)) {
try {
// evaluate here the callable method
super.fine(message.call());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
import java.util.logging.Level;
导入java.util.logging.Logger;
公共类LoggerTest{
公共静态void main(字符串[]args){
//拥有自己的lambda记录器
LambdaLogger log=新的LambdaLogger(LoggerTest.class.getName());
//将日志级别设置为INFO(因此不会记录罚款)
log.setLevel(Level.INFO);
//此行不会记录任何内容,也不会计算getValue方法!
log.fine(()->“跟踪值:+getValue());//更改为lambda表达式
}
//获取具有大量字符串串联的值的示例方法
私有静态字符串getValue(){
字符串val=”“;
对于(int i=0;i<1000;i++){
val+=“foo”;
}
返回val;
}
}
LambdaLogger.class
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get own lambda logger
LambdaLogger log = new LambdaLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, and will also not evaluate the getValue method!
log.fine(()-> "Trace value: " + getValue()); // changed to lambda expression
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LambdaLogger extends Logger {
public LambdaLogger(String name) {
super(name, null);
}
public void fine(Callable<String> message) {
// log only, if it's loggable
if (isLoggable(Level.FINE)) {
try {
// evaluate here the callable method
super.fine(message.call());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.Callable;
导入java.util.logging.Level;
导入java.util.logging.Logger;
公共类LambdaLogger扩展记录器{
公共LambdaLogger(字符串名称){
super(名称,空);
}
公共无效罚款(可调用消息){
//仅记录,如果可记录
if(IsLogable(标高精细)){
试一试{
//在这里计算可调用的方法
super.fine(message.call());
}捕获(例外e){
e、 printStackTrace();
}
}
}
}
通过这种修改,如果您有许多只用于调试目的的日志语句,那么您可以大大提高应用程序的性能
当然,您可以使用任何您想要的记录器。这只是bobbel解释过的
java.util.Logger
的一个例子
我想补充一点,虽然这代表了对原始代码的性能改进,但处理此问题的经典方法仍然更快:
if (log.isLoggable(Level.FINE)) {
log.fine("Trace value: " + getValue());
}
只是稍微多了点冗长/罗嗦
它之所以更快,是因为lambda版本在创建可调用实例时有额外的运行时开销(捕获成本),以及额外级别的方法调用
最后,还有创建
LambdaLogger
实例的问题@bobbel的代码显示这是使用构造函数完成的,但实际上,java.util.logging.Logger
对象需要通过工厂方法创建,以避免对象的扩散。这意味着需要一系列额外的基础结构(和代码更改)才能使其与记录器的自定义子类一起工作使用格式字符串,以及供应商的数组。这样,除非日志记录实际上是可发布的,否则不会调用toString
方法。这样,您就不必担心有关登录应用程序代码的丑陋的if
语句 只需为当前记录器创建包装器方法,如下所示:
public static void info(Logger logger, Supplier<String> message) {
if (logger.isLoggable(Level.INFO))
logger.info(message.get());
}
参考资料:JAVA SE 8,第48-49页。显然,Log4j 2.4包含了对lambda表达式的支持,这些表达式对您的案例非常有用(其他答案已手动复制):
从
大概如果需要将数据从调用方法传递到Callable.call
,则可能会产生捕获成本。这可能比字符串连接更好,但仍可能比将fine
调用包装在检查级别是否启用的if语句中更昂贵。讨论该视频中的捕获成本(跳过所有精彩的背景信息)的一个良好起点是[36:25][1]。[1] :无需扩展Logger类并实现自定义方法,因为Java 8已经有了具有类似签名的Logger方法:public void fine(Supplier msgSupplier)
。当然,这也是一个选项(可能更快)!但是,假设您必须为每个日志语句编写这个额外的if
。因此,如果您没有太多的日志语句,应该首选此选项@波贝尔-对不起。不,我不喜欢。我不认为有必要“保护”大多数日志调用,或者通常您的代码中首先应该有那么多“精细”的日志调用。另一点是保持if
guard和实际log语句的级别同步。@René-True。但是这两段代码通常在连续的行上,两个日志级别符号在所有大写字母中。程序员必须非常粗心,才能使它们不同步。使用?谢谢你提供的信息!但是,如果我们说“代码> log .罚款”(“跟踪值:{}”,GETV)