网络异常,图片无法展示
|
Spring Boot 默认使用的是 Logback 的日志框架、Logback 的组件主要通过 Spring Boot ApplicationListener 启动的
// LoggingApplicationListener @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartingEvent) { onApplicationStartingEvent((ApplicationStartingEvent) event); } else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } else if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) { onContextClosedEvent(); } else if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent(); } } 复制代码
onApplicationStartingEvent
创建 LoggingSystem
private void onApplicationStartingEvent(ApplicationStartingEvent event) { this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); this.loggingSystem.beforeInitialize(); } 复制代码
LoggingSystem 是 Spring Boot 抽象的日志系统接口
public static LoggingSystem get(ClassLoader classLoader) { String loggingSystem = System.getProperty(SYSTEM_PROPERTY); if (StringUtils.hasLength(loggingSystem)) { if (NONE.equals(loggingSystem)) { return new NoOpLoggingSystem(); } return get(classLoader, loggingSystem); } return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader)) .map((entry) -> get(classLoader, entry.getValue())).findFirst() .orElseThrow(() -> new IllegalStateException("No suitable logging system located")); } private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) { try { Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader); Constructor<?> constructor = systemClass.getDeclaredConstructor(ClassLoader.class); constructor.setAccessible(true); return (LoggingSystem) constructor.newInstance(classLoader); } catch (Exception ex) { throw new IllegalStateException(ex); } } 复制代码
SYSTEMS
的值如下、默认情况下创建的就是 LogbackLoggingSystem
private static final Map<String, String> SYSTEMS; static { Map<String, String> systems = new LinkedHashMap<>(); systems.put("ch.qos.logback.classic.LoggerContext", "org.springframework.boot.logging.logback.LogbackLoggingSystem"); systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem"); systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem"); SYSTEMS = Collections.unmodifiableMap(systems); } 复制代码
接下来进入到 LogbackLoggingSystem#beforeInitialize 方法
@Override public void beforeInitialize() { LoggerContext loggerContext = getLoggerContext(); if (isAlreadyInitialized(loggerContext)) { return; } super.beforeInitialize(); loggerContext.getTurboFilterList().add(FILTER); } 复制代码
通过 StaticLoggerBinder.getSingleton() 创建 LoggerContext
private LoggerContext getLoggerContext() { ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); Assert.isInstanceOf(LoggerContext.class, factory, String.format( "LoggerFactory is not a Logback LoggerContext but Logback is on " + "the classpath. Either remove Logback or the competing " + "implementation (%s loaded from %s). If you are using " + "WebLogic you will need to add 'org.slf4j' to " + "prefer-application-packages in WEB-INF/weblogic.xml", factory.getClass(), getLocation(factory))); return (LoggerContext) factory; } 复制代码
网络异常,图片无法展示
|
进入到静态代码块的 init 方法
void init() { try { try { new ContextInitializer(defaultLoggerContext).autoConfig(); } catch (JoranException je) { Util.report("Failed to auto configure default logger context", je); } // logback-292 if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); } contextSelectorBinder.init(defaultLoggerContext, KEY); initialized = true; } catch (Exception t) { // see LOGBACK-1159 Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t); } } 复制代码
最终会执行下面的代码、尝试获取默认配置文件、如果不存在则通过 SPI 获取 Configurator 实现类、如果还是没有、则使用默认的配置 BasicConfigurator
public void autoConfig() throws JoranException { StatusListenerConfigHelper.installIfAsked(loggerContext); URL url = findURLOfDefaultConfigurationFile(true); if (url != null) { configureByResource(url); } else { Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class); if (c != null) { try { c.setContext(loggerContext); c.configure(loggerContext); } catch (Exception e) { throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass() .getCanonicalName() : "null"), e); } } else { BasicConfigurator basicConfigurator = new BasicConfigurator(); basicConfigurator.setContext(loggerContext); basicConfigurator.configure(loggerContext); } } } 复制代码
onApplicationEnvironmentPreparedEvent
初始化阶段
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { if (this.loggingSystem == null) { this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader()); } initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader()); } 复制代码
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) { new LoggingSystemProperties(environment).apply(); this.logFile = LogFile.get(environment); if (this.logFile != null) { this.logFile.applyToSystemProperties(); } this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS); initializeEarlyLoggingLevel(environment); initializeSystem(environment, this.loggingSystem, this.logFile); initializeFinalLoggingLevels(environment, this.loggingSystem); registerShutdownHookIfNecessary(environment, this.loggingSystem); } 复制代码
主要就是从 environment 对象中设置相关属性值到 System 中、然后判断是否设置了 log file
最主要的 initializeSystem
private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) { LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment); String logConfig = environment.getProperty(CONFIG_PROPERTY); if (ignoreLogConfig(logConfig)) { system.initialize(initializationContext, null, logFile); } else { try { system.initialize(initializationContext, logConfig, logFile); } catch (Exception ex) { Throwable exceptionToReport = ex; while (exceptionToReport != null && !(exceptionToReport instanceof FileNotFoundException)) { exceptionToReport = exceptionToReport.getCause(); } exceptionToReport = (exceptionToReport != null) ? exceptionToReport : ex; // NOTE: We can't use the logger here to report the problem System.err.println("Logging system failed to initialize using configuration from '" + logConfig + "'"); exceptionToReport.printStackTrace(System.err); throw new IllegalStateException(ex); } } } 复制代码
正常情况下、我们都没有配置 logConfig 进入到下面的方法
@Override public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { LoggerContext loggerContext = getLoggerContext(); if (isAlreadyInitialized(loggerContext)) { return; } super.initialize(initializationContext, configLocation, logFile); loggerContext.getTurboFilterList().remove(FILTER); markAsInitialized(loggerContext); if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) { getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY + "' system property. Please use 'logging.config' instead."); } } 复制代码
进入到父类的 initialize 方法中
@Override public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { if (StringUtils.hasLength(configLocation)) { initializeWithSpecificConfig(initializationContext, configLocation, logFile); return; } initializeWithConventions(initializationContext, logFile); } 复制代码
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) { String config = getSelfInitializationConfig(); if (config != null && logFile == null) { // self initialization has occurred, reinitialize in case of property changes reinitialize(initializationContext); return; } if (config == null) { config = getSpringInitializationConfig(); } if (config != null) { loadConfiguration(initializationContext, config, logFile); return; } loadDefaults(initializationContext, logFile); } 复制代码
正常建议使用的是 xxx-spring.xml 的配置文件、那么我们进入到 loadConfiguration 中
@Override protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { super.loadConfiguration(initializationContext, location, logFile); LoggerContext loggerContext = getLoggerContext(); stopAndReset(loggerContext); try { configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location)); } catch (Exception ex) { throw new IllegalStateException("Could not initialize Logback logging from " + location, ex); } List<Status> statuses = loggerContext.getStatusManager().getCopyOfStatusList(); StringBuilder errors = new StringBuilder(); for (Status status : statuses) { if (status.getLevel() == Status.ERROR) { errors.append((errors.length() > 0) ? String.format("%n") : ""); errors.append(status.toString()); } } if (errors.length() > 0) { throw new IllegalStateException(String.format("Logback configuration error detected: %n%s", errors)); } } 复制代码
private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext, URL url) throws JoranException { if (url.toString().endsWith("xml")) { JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext); configurator.setContext(loggerContext); configurator.doConfigure(url); } else { new ContextInitializer(loggerContext).configureByResource(url); } 复制代码
我们发现了一个 SpringBootJoranConfigurator 这个配置一看就是 Spring Boot 相关的
网络异常,图片无法展示
|
增加了一些 Spring Boot 的规则、非常明显、用于从 environment 中获取属性填充到 logback 配置文件中
网络异常,图片无法展示
|
一起看下 logback-spring.xml 文件就能感受到了
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true"> <springProperty scope="context" name="logName" source="spring.application.name" defaultValue="localhost.log"/> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/${logName}.log</file> <append>true</append> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>logs/${logName}-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>7</maxHistory> <totalSizeCap>3GB</totalSizeCap> </rollingPolicy> <encoder> <pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%logger:%line] --%mdc{client} %msg%n</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>DEBUG</level> </filter> </appender> <!-- Console output --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern> [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n </pattern> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> </appender> <springProfile name="dev,test"> <root level="DEBUG"> <appender-ref ref="FILE"/> <appender-ref ref="STDOUT"/> </root> </springProfile> <springProfile name="prod"> <root level="INFO"> <appender-ref ref="FILE"/> <appender-ref ref="STDOUT"/> </root> </springProfile> <logger name="org.springframework" level="INFO"/> <logger name="com.netflix" level="WARN"/> <logger name="org" level="INFO"/> <logger name="springfox.documentation" level="INFO"/> </configuration>