SpringCloud+Logback配置实例

boot+logback+nacos方案:https://blog.csdn.net/z69183787/article/details/108948325

正常情况下使用 springboot + logback + nacos 进行日志级别动态变更是没有问题的,但当和springcloud一起使用时,会发生 logger级别重置的情况,即设置完某个logger的独立级别后,整个logger上下文会重新刷新,并且重置logger 为 Root设置的 level 级别。经过一番源码的探查,目前还未找到问题的原因和解决方案;但与此同时,却发现了另一种实现这个效果的方案:

使用 logging.level 属性进行配置,在nacos变更后,应用会自动生效

nacos配置:

logging:
  level:
   com.xxx.xxxx.demo.controller: ERROR
   com.zzz.zzzz.demo.controller: INFO

关键源码:

Nacos接收到配置变化后进入 NacosContextRefresher 类,核心方法如下:

可以看到 配置修改后,触发了listener,在listener中发布了一个 RefreshEvent

private void registerNacosListener(final String groupKey, final String dataKey) {
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		Listener listener = listenerMap.computeIfAbsent(key,
				lst -> new AbstractSharedListener() {
					@Override
					public void innerReceive(String dataId, String group,
							String configInfo) {
						refreshCountIncrement();
						nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
						// todo feature: support single refresh for listening
						applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));
						if (log.isDebugEnabled()) {
							log.debug(String.format(
									"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
									group, dataId, configInfo));
						}
					}
				});
		try {
			configService.addListener(dataKey, groupKey, listener);
		}
		catch (NacosException e) {
			log.warn(String.format(
					"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
					groupKey), e);
		}
	}

RefreshEvent

public RefreshEvent(Object source, Object event, String eventDesc) {
		super(source);
		this.event = event;
		this.eventDesc = eventDesc;
	}

进入 AbstractApplicationContext中的 event广播 multicastEvent

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

进入SimpleApplicationEventMulticaster 的 invokeListener方法,进而触发所有满足 eventType 的 listener进行 onApplicationEvent 回调

	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}



	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}

满足 RefreshEvent 的 Listener 是  RefreshEventListener

@Override
	public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return ApplicationReadyEvent.class.isAssignableFrom(eventType)
				|| RefreshEvent.class.isAssignableFrom(eventType);
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationReadyEvent) {
			handle((ApplicationReadyEvent) event);
		}
		else if (event instanceof RefreshEvent) {
			handle((RefreshEvent) event);
		}
	}

	public void handle(ApplicationReadyEvent event) {
		this.ready.compareAndSet(false, true);
	}

	public void handle(RefreshEvent event) {
		if (this.ready.get()) { // don't handle events before app is ready
			log.debug("Event received " + event.getEventDesc());
			Set<String> keys = this.refresh.refresh();
			log.info("Refresh keys changed: " + keys);
		}
	}

Set<String> keys = this.refresh.refresh(); 方法中 调用了 ContextRefresher 中的方法,可以看到发布了一个 EnvironmentChangeEvent 事件

	public synchronized Set<String> refresh() {
		Set<String> keys = refreshEnvironment();
		this.scope.refreshAll();
		return keys;
	}

	public synchronized Set<String> refreshEnvironment() {
		Map<String, Object> before = extract(
				this.context.getEnvironment().getPropertySources());
		addConfigFilesToEnvironment();
		Set<String> keys = changes(before,
				extract(this.context.getEnvironment().getPropertySources())).keySet();
		this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
		return keys;
	}

监听 EnvironmentChangeEvent 事件的监听器是 LoggingRebinder,可以明显的看到

1、获取 yaml配置文件中的 logging.level 中的配置,即上文中 nacos 的配置项(Map<String,String> 结构)

2、然后分别进行设置生效

@Override
	public void onApplicationEvent(EnvironmentChangeEvent event) {
		if (this.environment == null) {
			return;
		}
		LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader());
		setLogLevels(system, this.environment);
	}

	protected void setLogLevels(LoggingSystem system, Environment environment) {
		Map<String, String> levels = Binder.get(environment)
				.bind("logging.level", STRING_STRING_MAP)
				.orElseGet(Collections::emptyMap);
		for (Entry<String, String> entry : levels.entrySet()) {
			setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
		}
	}

	private void setLogLevel(LoggingSystem system, Environment environment, String name,
			String level) {
		try {
			if (name.equalsIgnoreCase("root")) {
				name = null;
			}
			level = environment.resolvePlaceholders(level);
			system.setLogLevel(name, resolveLogLevel(level));
		}
		catch (RuntimeException ex) {
			this.logger.error("Cannot set level: " + level + " for '" + name + "'");
		}
	}

最终经过调试,可以完美满足 动态变更 日志级别的需求,但是为何方案一会 重置 日志级别还是没有解决,未完待续。。。

 

附上工具类,输出所有日志及package的 日志级别

 /**
  * 查询所有配置的日志级别
 */
 @GetMapping("/loggerList")
 public List<LogInfoVO> queryLoggerList(){
       LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
       return loggerContext.getLoggerList().stream()
               .map(log -> new LogInfoVO(log.getName(), log.getLevel()!=null?log.getLevel().levelStr:"null",log.getEffectiveLevel().levelStr))
               .collect(Collectors.toList());
 }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,非常感谢您的提问。以下是一个使用SpringBoot集成MongoDB和Logback实现日志存储的示例: 1. 添加依赖 在pom.xml中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> ``` 2. 配置MongoDB 在application.yml中添加以下内容: ```yaml spring: data: mongodb: uri: mongodb://localhost:27017/mydb ``` 其中,uri是MongoDB的连接URI,mydb是数据库的名称。 3. 配置Logbacklogback.xml中配置MongoDB的Appender: ```xml <appender name="MONGO" class="de.flapdoodle.embed.log.LogCollectorAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <encoder class="net.logstash.logback.encoder.LogstashEncoder" /> </appender> ``` 其中,LogstashEncoder用于将日志信息转换为JSON格式,方便存储。 4. 配置Logger 在logback.xml中添加Logger: ```xml <logger name="com.example" additivity="false" level="INFO"> <!-- ConsoleAppender --> <appender-ref ref="STDOUT" /> <!-- MongoDBAppender --> <appender-ref ref="MONGO" /> </logger> ``` 其中,name为包名,level为日志记录的最低级别,可以根据实际情况设置。 5. 测试 使用Logging接口记录日志: ```java @Autowired private Logger logger; @Test public void testLogger() { logger.info("This is a test message."); } ``` 记录的日志信息将同时输出到控制台和MongoDB中。 希望以上内容能够对您有所帮助。如果您有其他问题,可以随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值