关于Spring Actuator获取日志初始级别及日志级别变更监控的技术咨询
Hi there! Let's walk through your problem and explore practical solutions that fit Spring Boot's ecosystem.
1. Does Spring Store Initial Log Levels for Direct Use?
Unfortunately, Spring Boot doesn't have a built-in, publicly accessible store for initial log levels. When your app starts up, it loads log configurations from log4j2.xml/application.properties etc., but these initial values aren't retained in a ready-to-use structure for later access.
That said, you can collect initial levels proactively during app startup instead of waiting for a user's first modification. Here's how:
- Create a component that listens for the
ContextRefreshedEvent(triggered once the app context is fully initialized). - Use Log4j2's
LogManager.getLoggers()to iterate through all configured loggers, then store their initial levels in a thread-safe map likeConcurrentHashMap<String, Level>.
Example snippet:
@Component public class InitialLogLevelCollector { private final ConcurrentHashMap<String, Level> initialLogLevels = new ConcurrentHashMap<>(); @EventListener(ContextRefreshedEvent.class) public void collectInitialLogLevels() { for (Logger logger : LogManager.getLoggers()) { initialLogLevels.put(logger.getName(), logger.getLevel()); } } // Add getter for initialLogLevels public ConcurrentHashMap<String, Level> getInitialLogLevels() { return initialLogLevels; } }
This way, you have a complete record of all initial levels right from app start, not just for loggers that get modified later.
2. Customizing the Loggers Endpoint (Alternative to Overriding Log4J2LoggingSystem)
Instead of overriding Log4J2LoggingSystem.setLogLevel(), a more Spring-idiomatic approach is to leverage event-driven monitoring:
Spring Boot 2.1+ publishes a LoggerConfigurationChangedEvent whenever a log level is modified via the Actuator /actuator/loggers endpoint. You can listen for this event to capture:
- The logger name
- Old log level
- New log level
- (If you have Spring Security enabled) The user who made the change via
SecurityContextHolder.getContext().getAuthentication()
Example listener:
@Component public class LogLevelChangeMonitor { private final InitialLogLevelCollector initialLogLevelCollector; public LogLevelChangeMonitor(InitialLogLevelCollector initialLogLevelCollector) { this.initialLogLevelCollector = initialLogLevelCollector; } @EventListener(LoggerConfigurationChangedEvent.class) public void onLogLevelChange(LoggerConfigurationChangedEvent event) { String loggerName = event.getLoggerName(); Level oldLevel = event.getOldConfiguration().getEffectiveLevel(); Level newLevel = event.getNewConfiguration().getEffectiveLevel(); Level initialLevel = initialLogLevelCollector.getInitialLogLevels().get(loggerName); // Get current user (if using Spring Security) String user = SecurityContextHolder.getContext().getAuthentication().getName(); // Log or store the change: e.g., "User [user] modified logger [loggerName] from initial level [initialLevel] to old [oldLevel] → new [newLevel]" System.out.printf("User %s modified logger %s: initial=%s, old=%s, new=%s%n", user, loggerName, initialLevel, oldLevel, newLevel); } }
If you still want to customize the Loggers Endpoint directly:
- Spring Boot allows you to create a custom
@Endpoint(id = "loggers")component that replaces the default one. You can copy the core logic from the originalLoggersEndpoint(check Spring Boot's source code) and inject your own level-tracking logic into theconfigureLogLevelmethod. - No external docs are needed beyond Spring's official Actuator customization guides—just remember that custom endpoints need to be registered properly, and you'll want to maintain compatibility with existing endpoint behavior unless you need to change it.
3. Evaluating Your Original Approach
Overriding Log4J2LoggingSystem.setLogLevel() is technically feasible, but it has a few downsides:
- It couples your code directly to Log4j2's implementation, making it harder to switch logging frameworks later.
- You'll need to handle thread safety for your static map (use
ConcurrentHashMapinstead of a regularHashMap). - You still need to populate the map with initial levels at startup, since the method only triggers when a level is modified.
Final Recommendations
- Prefer the event-driven approach: It's decoupled, aligns with Spring's design principles, and gives you all the context you need (initial level, old level, new level, user).
- Always collect initial levels at startup: This ensures you have a baseline even if some loggers are never modified by users.
备注:内容来源于stack exchange,提问作者user3387870

