java日志之JUL

定义:

JUL全称Java util logging,是java原生的日志框架,使用时不需要另外引入第三方类库,相对于其他框架使用方便,学习简单,能够在小型的应用中灵活使用。

img

架构:

Application:我们的程序应用。
LogManager:管理Logger,是个单例Bean。
Logger:日志记录器,我们的应用程序通过获取日志记录器Logger对象,调用其API来发布日志信息,Logger通常是应用程序访问日志系统的入口。
Handler:日志处理器,每个Logger会关联持有多个Handler,Logger会把日志交给Handler进行处理,由Handler负责日志记录。Handler在这里是一个抽象,其具体实现决定了日志输出的位置,比如控制台,文件等。
Formattter:日志格式转化器,负责对日志的格式进行处理。决定了输出的日志的最终形式。
Level:每条日志信息都有一个关联的日志级别,该级别处理指导了日志信息的重要性。我们可以将Level作用于Logger和Handler上。以便于我们过滤消息。
Filter:过滤器,根据规则过滤掉某些日志信息。
总体流程:

初始化LogManager,加载logging.properties配置文件,添加Logger到LogManager中。
从单例Bean LogManager获取Logger。
设置日志级别Level。
Handler处理日志输出。
Formatter处理日志格式。
日志的级别:
JUL日志级别由类java.util.logging.Level记录,总共有七个日志级别,由高到低分别是:

SEVERE //错误信息,一般是记录导致系统终止的信息。
WARNING //警告信息,一般记录程序的问题,该问题不会导致系统终止,但是却值得我们关注。
INFO // 一般信息,一般记录一些连接信息,访问信息等。(这是JUL的默认级别)
CONFIG // 一般记录加载配置文件等日志信息。
FINE // Debug日志信息,一般记录程序一般运行的状态。执行的流程参数的传递等信息。
FINER //与FINE 类似,只是记录的颗粒度要高一些。
FINEST //与上面两个类似,只是记录的颗粒度要高一些。

还有两个特殊的级别:
OFF: 可用来关闭日志信息,不输出任何级别的日志。
ALL: 记录所有级别的日志信息

源码

Logger.getLogger

LogManager manager = LogManager.getLogManager();
return manager.demandLogger(name, resourceBundleName, caller);

LogManager的创建

忽略一些权限校验
LogManager就是java.util.logging.manager

static {
    
    
        manager = AccessController.doPrivileged(new PrivilegedAction<LogManager>() {
    
    
            @Override
            public LogManager run() {
    
    
                LogManager mgr = null;
                String cname = null;
                try {
    
    
                    cname = System.getProperty("java.util.logging.manager");
                    if (cname != null) {
    
    
                        try {
    
    
                            @SuppressWarnings("deprecation")
                            Object tmp = ClassLoader.getSystemClassLoader()
                                .loadClass(cname).newInstance();
                            mgr = (LogManager) tmp;
                        } catch (ClassNotFoundException ex) {
    
    
                            @SuppressWarnings("deprecation")
                            Object tmp = Thread.currentThread()
                                .getContextClassLoader().loadClass(cname).newInstance();
                            mgr = (LogManager) tmp;
                        }
                    }
                } catch (Exception ex) {
    
    
                    System.err.println("Could not load Logmanager \"" + cname + "\"");
                    ex.printStackTrace();
                }
                if (mgr == null) {
    
    
                    mgr = new LogManager();
                }
                return mgr;

            }
        });
    }

LogManager的初始化

final void ensureLogManagerInitialized() {
    
    
        final LogManager owner = this;
        //已经初始化就直接退出
        if (initializationDone || owner != manager) {
    
    
            return;
        }
		//锁
        configurationLock.lock();
        try {
    
    

            final boolean isRecursiveInitialization = (initializedCalled == true);
			
			//如果被调用或者已经执行完毕,直接报错
            assert initializedCalled || !initializationDone
                    : "Initialization can't be done if initialized has not been called!";
                    
			//执行完了直接返回
            if (isRecursiveInitialization || initializationDone) {
    
    
                return;
            }
			//忽略权限的代码,就是设置RootLogger,名字是空串,是系统log
            initializedCalled = true;
            try {
    
    
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
    
    
                    @Override
                    public Object run() {
    
    
                        assert rootLogger == null;
                        assert initializedCalled && !initializationDone;

                        // create root logger before reading primordial
                        // configuration - to ensure that it will be added
                        // before the global logger, and not after.
                        final Logger root = owner.rootLogger = owner.new RootLogger();

                        // Read configuration.
                        owner.readPrimordialConfiguration();

                        // Create and retain Logger for the root of the namespace.
                        owner.addLogger(root);

                        // Initialize level if not yet initialized
                        if (!root.isLevelInitialized()) {
    
    
                            root.setLevel(defaultLevel);
                        }

                        // Adding the global Logger.
                        // Do not call Logger.getGlobal() here as this might trigger
                        // subtle inter-dependency issues.
                        @SuppressWarnings("deprecation")
                        final Logger global = Logger.global;

                        // Make sure the global logger will be registered in the
                        // global manager
                        owner.addLogger(global);
                        return null;
                    }
                });
            } finally {
    
    
            //initializationDone设置为true
                initializationDone = true;
            }
        } finally {
    
    
            configurationLock.unlock();
        }
    }

getLogger

Logger demandLogger(String name, String resourceBundleName, Module module) {
    
    
    //1.从userContext里面获取log,userContext里面有一个map,WeakHashMap<Object, LoggerContext> contextsMap
        Logger result = getLogger(name);
        if (result == null) {
    
    
            // 没有直接new一个
            Logger newLogger = new Logger(name, resourceBundleName,
                                          module, this, false);
            do {
    
    
                //加入userContext
                if (addLogger(newLogger)) {
    
    
                    return newLogger;
                }
                result = getLogger(name);
            } while (result == null);
        }
        return result;
    }

log.info

public void log(Level level, String msg) {
    
    
    	//检查级别
        if (!isLoggable(level)) {
    
    
            return;
        }
    	//LogRecord
        LogRecord lr = new LogRecord(level, msg);
        doLog(lr);
    }

//一些国际化设置
private void doLog(LogRecord lr) {
    
    
        lr.setLoggerName(name);
        final LoggerBundle lb = getEffectiveLoggerBundle();
        final ResourceBundle  bundle = lb.userBundle;
        final String ebname = lb.resourceBundleName;
        if (ebname != null && bundle != null) {
    
    
            lr.setResourceBundleName(ebname);
            lr.setResourceBundle(bundle);
        }
        log(lr);
    }


doLog

public void log(LogRecord record) {
    
    
    //级别不够返回
        if (!isLoggable(record.getLevel())) {
    
    
            return;
        }
    //调用filter.isLoggable方法
        Filter theFilter = config.filter;
        if (theFilter != null && !theFilter.isLoggable(record)) {
    
    
            return;
        }


		//
        Logger logger = this;
        while (logger != null) {
    
    
            final Handler[] loggerHandlers = isSystemLogger
                ? logger.accessCheckedHandlers()
                : logger.getHandlers();

            for (Handler handler : loggerHandlers) {
    
    
                //执行loggerHandlers的publish方法
                handler.publish(record);
            }

            final boolean useParentHdls = isSystemLogger
                ? logger.config.useParentHandlers
                : logger.getUseParentHandlers();
			//不使用ParentHandler直接返回
            if (!useParentHdls) {
    
    
                break;
            }
			//使用ParentHandler,logger设置为parent,继续打印日志,这里能解释为什么有的日志打印好几份
            logger = isSystemLogger ? logger.parent : logger.getParent();
        }
    }

handler.publish(record);

handler.publish(record);
简单看一下ConsoleHandler extends StreamHandler

    
    @Override
    public synchronized void publish(LogRecord record) {
    
    
        String msg;
        try {
    
    
            //格式化组件
            msg = getFormatter().format(record);
        } catch (Exception ex) {
    
    
            reportError(null, ex, ErrorManager.FORMAT_FAILURE);
            return;
        }
		//这里面的write就是System.err,所以默认的都是红色日志
        try {
    
    
            if (!doneHeader) {
    
    
                writer.write(getFormatter().getHead(this));
                doneHeader = true;
            }
            writer.write(msg);
        } catch (Exception ex) {
    
    
            // We don't want to throw an exception here, but we
            // report the exception to any registered ErrorManager.
            reportError(null, ex, ErrorManager.WRITE_FAILURE);
        }
    }
Formatter
简单看一下SimpleFormatter
    核心就是调用String.format
@Override
    public String format(LogRecord record) {
    
    
        ZonedDateTime zdt = ZonedDateTime.ofInstant(
                record.getInstant(), ZoneId.systemDefault());
        String source;
        if (record.getSourceClassName() != null) {
    
    
            source = record.getSourceClassName();
            if (record.getSourceMethodName() != null) {
    
    
               source += " " + record.getSourceMethodName();
            }
        } else {
    
    
            source = record.getLoggerName();
        }
        String message = formatMessage(record);
        String throwable = "";
        if (record.getThrown() != null) {
    
    
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            pw.println();
            record.getThrown().printStackTrace(pw);
            pw.close();
            throwable = sw.toString();
        }
        //系统默认的格式化format  %1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n
        return String.format(format,
                             zdt,
                             source,
                             record.getLoggerName(),
                             record.getLevel().getLocalizedLevelName(),
                             message,
                             throwable);
    }

扩展点

1.指定配置文件或者修改默认的

2.手工加载配置文件

LogManager logManager = LogManager.getLogManager();
logManager.readConfiguration(自己的配置文件inputStreaqm);

3.手动实现以下format和handler

public class MyConsoleHandler extends Handler {
    
    

    public MyConsoleHandler() {
    
    
        setFormatter(new MyFormatter());
    }

    @Override
    public void publish(LogRecord record) {
    
    
        if(record==null) return;
        String msg = getFormatter().format(record);
        if(record.getLevel().intValue()> Level.WARNING.intValue()){
    
    
            System.err.print(msg);
        }else{
    
    
            System.out.print(msg);
        }
    }
    
    @Override
    public void flush() {
    
    

    }

    @Override
    public void close() throws SecurityException {
    
    

    }
}
public class MyFormatter extends Formatter {
    
    

    public static final String format = "%1$tF %1$tT.%1$tL %2$s%n%4$s: %5$s%6$s%n";
    @Override
    public String format(LogRecord record) {
    
    
        ZonedDateTime zdt = ZonedDateTime.ofInstant(
                record.getInstant(), ZoneId.systemDefault());
        String source;
        if (record.getSourceClassName() != null) {
    
    
            source = record.getSourceClassName();
            if (record.getSourceMethodName() != null) {
    
    
                source += " " + record.getSourceMethodName();
            }
        } else {
    
    
            source = record.getLoggerName();
        }
        String message = formatMessage(record);
        String throwable = "";
        if (record.getThrown() != null) {
    
    
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            pw.println();
            record.getThrown().printStackTrace(pw);
            pw.close();
            throwable = sw.toString();
        }

        return String.format(format,
                zdt,
                source,
                record.getLoggerName(),
                record.getLevel().getLocalizedName(),
                message,
                throwable);
    }
}

使用记录

        Logger logger = Logger.getLogger("TestLog.class");
        //logger.addHandler(new MyConsoleHandler());
        //用了自定义的可以考虑不适用父级的
        //logger.setUseParentHandlers(false);

        //2.打印日志
        logger.info("hello jul");
        logger.log(Level.INFO,"jul level.info");
        logger.severe("姓名:李四,年龄:"+33);
        String name = "张三";
        int age = 18;
        logger.log(Level.INFO,"姓名:{0},年龄:{1}",new Object[]{
    
    name,age});

在这里插入图片描述

使用自定义的之后

在这里插入图片描述

转换符 说明 示例
%s 字符串类型 “mingrisoft”
%c 字符类型 ‘m’
%b 布尔类型 true
%d 整数类型(十进制) 99
%x 整数类型(十六进制) FF
%o 整数类型(八进制) 77
%f 浮点类型 99.99
%a 十六进制浮点类型 FF.35AE
%e 指数类型 9.38e+5
%g 通用浮点类型(f和e类型中较短的)
%h 散列码,Integer.toHexString(arg.hashCode())
%% 百分比类型
%n 换行符
%tx 日期与时间类型(x代表不同的日期与时间转换符

日期格式化

转换符 说明 示例
c 包括全部日期和时间信息 星期六 十月 27 14:21:20 CST 2007
F “年-月-日”格式 2007-10-27
D “月/日/年”格式 10/27/07
r “HH:MM:SS PM”格式(12时制) 02:25:51 下午
T “HH:MM:SS”格式(24时制) 14:28:16
R “HH:MM”格式(24时制) 14:28

时间格式化

转换符 说明 示例
H 2位数字24时制的小时(不足2位前面补0) 15
I 2位数字12时制的小时(不足2位前面补0) 03
K 2位数字24时制的小时(前面不补0) 15
L 2位数字12时制的小时(前面不补0) 3
M 2位数字的分钟(不足2位前面补0) 03
S 2位数字的秒(不足2位前面补0) 09
L 3位数字的毫秒(不足3位前面补0) 015
N 9位数字的毫秒数(不足9位前面补0) 562000000
p 小写字母的上午或下午标记 中:下午,英:pm
z 相对于GMT的RFC822时区的偏移量 +0800
Z 时区缩写字符串 CST
S 1970-1-1 00:00:00 到现在所经过的秒数 1193468128
Q 1970-1-1 00:00:00 到现在所经过的毫秒数 1193468128984

%1 t b tb %1 tbtd, %1 t Y tY %1 tYtl:%1 t M : tM:%1 tM:tS %1 T p Tp %2 Tps%n%4 s : s: %5 s:s%6$s%n

String format = "%1$tF %1$tT.%1$tL %2$s%n%4$s: %5$s%6$s%n";

2021-11-29 16:34:25.177 com.git.log.TestLog testJUL
信息: hello jul

标Cprintf

转换符 含义
%A 浮点数、十六进制数字和p-记法(C99)
%c 一个字符
%d 有符号十进制整数
%e 浮点数、e-记数法
%E 浮点数、E-记数法
%f 浮点数、十进制记数法
%g 根据数值不同自动选择%f或%e.
%G 根据数值不同自动选择%f或%e.
%i 有符号十进制数(与%d相同)
%o 无符号八进制整数
%p 指针
%s 字符串
%u 无符号十进制整数
%x 使用十六进制数字0f的无符号十六进制整数
%X 使用十六进制数字0f的无符号十六进制整数
%% 打印一个百分号

符号 作用
──────────────────────────
%d 十进制有符号整数
%u 十进制无符号整数
%f 浮点数
%s 字符串
%c 单个字符
%p 指针的值
%e 指数形式的浮点数
%x, %X 无符号以十六进制表示的整数
%0 无符号以八进制表示的整数
%g 自动选择合适的表示法

\n 换行
\f 清屏并换页
\r 回车
\t Tab符
\xhh 表示一个ASCII码用16进表示,
其中hh是1到2个16进制数
━━━━━━━━━━━━━━━━━━━━━━━━━━

# RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler
handlers= java.util.logging.FileHandler
 
# RootLogger 顶级父元素默认的日志级别为:ALL
.level= ALL
 
# 自定义 Logger 使用
com.leon.handlers = java.util.logging.ConsoleHandler
com.leon.level = CONFIG
 
# 关闭默认配置
com.leon.useParentHanlders = false
 
 
# 向日志文件输出的 handler 对象
# 指定日志文件路径 /logs/java0.log
java.util.logging.FileHandler.pattern = /logs/java%u.log
# 指定日志文件内容大小
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1
# 指定 handler 对象日志消息格式对象
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定以追加方式添加日志内容
java.util.logging.FileHandler.append = true
 
 
# 向控制台输出的 handler 对象
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定 handler 对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定 handler 对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8
 
# 指定日志消息格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n
 

参考:

Java日志框架之JUL(java util logging)详解 引用其中一些概念

java字符串格式化

print格式化
jul配置文件参考
logback使用参考

猜你喜欢

转载自blog.csdn.net/weixin_43328357/article/details/121616270