package org.apereo.cas.web.report; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.FileAppender; import org.apache.logging.log4j.core.appender.MemoryMappedFileAppender; import org.apache.logging.log4j.core.appender.RandomAccessFileAppender; import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.slf4j.Log4jLoggerFactory; import org.apereo.cas.audit.spi.DelegatingAuditTrailManager; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.web.report.util.ControllerUtils; import org.apereo.inspektr.audit.AuditActionContext; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Controller to handle the logging dashboard requests. * * @author Misagh Moayyed * @since 4.2 */ public class LoggingConfigController extends BaseCasMvcEndpoint { private static final String VIEW_CONFIG = "monitoring/viewLoggingConfig"; private static final String LOGGER_NAME_ROOT = "root"; private static final String FILE_PARAM = "file"; private static final String FILE_PATTERN_PARAM = "filePattern"; private LoggerContext loggerContext; private final DelegatingAuditTrailManager auditTrailManager; @Autowired private Environment environment; @Autowired private ResourceLoader resourceLoader; private Resource logConfigurationFile; public LoggingConfigController(final DelegatingAuditTrailManager auditTrailManager, final CasConfigurationProperties casProperties) { super("casloggingconfig", "/logging", casProperties.getMonitor().getEndpoints().getLoggingConfig(), casProperties); this.auditTrailManager = auditTrailManager; } /** * Init. Attempts to locate the logging configuration to insert listeners. * The log configuration location is pulled directly from the environment * given there is not an explicit property mapping for it provided by Boot, etc. */ @PostConstruct public void initialize() { final Pair<Resource, LoggerContext> pair = ControllerUtils.buildLoggerContext(environment, resourceLoader); if (pair != null) { this.logConfigurationFile = pair.getKey(); this.loggerContext = pair.getValue(); } } /** * Gets default view. * * @param request the request * @param response the response * @return the default view * @throws Exception the exception */ @GetMapping public ModelAndView getDefaultView(final HttpServletRequest request, final HttpServletResponse response) throws Exception { ensureEndpointAccessIsAuthorized(request, response); final Map<String, Object> model = new HashMap<>(); model.put("logConfigurationFile", logConfigurationFile.getURI().toString()); return new ModelAndView(VIEW_CONFIG, model); } /** * Gets active loggers. * * @param request the request * @param response the response * @return the active loggers * @throws Exception the exception */ @GetMapping(value = "/getActiveLoggers") @ResponseBody public Map<String, Object> getActiveLoggers(final HttpServletRequest request, final HttpServletResponse response) throws Exception { ensureEndpointAccessIsAuthorized(request, response); Assert.notNull(this.loggerContext); final Map<String, Object> responseMap = new HashMap<>(); final Map<String, Logger> loggers = getActiveLoggersInFactory(); responseMap.put("activeLoggers", loggers.values()); return responseMap; } /** * Gets configuration as JSON. * Depends on the log4j core API. * * @param request the request * @param response the response * @return the configuration * @throws Exception the exception */ @GetMapping(value = "/getConfiguration") @ResponseBody public Map<String, Object> getConfiguration(final HttpServletRequest request, final HttpServletResponse response) throws Exception { ensureEndpointAccessIsAuthorized(request, response); Assert.notNull(this.loggerContext); final Collection<Map<String, Object>> configuredLoggers = new HashSet<>(); getLoggerConfigurations().forEach(config -> { final Map<String, Object> loggerMap = new HashMap<>(); loggerMap.put("name", StringUtils.defaultIfBlank(config.getName(), LOGGER_NAME_ROOT)); loggerMap.put("state", config.getState()); if (config.getProperties() != null) { loggerMap.put("properties", config.getProperties()); } loggerMap.put("additive", config.isAdditive()); loggerMap.put("level", config.getLevel().name()); final Collection<String> appenders = new HashSet<>(); config.getAppenders().keySet().stream().map(key -> config.getAppenders().get(key)).forEach(appender -> { final ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.JSON_STYLE); builder.append("name", appender.getName()); builder.append("state", appender.getState()); builder.append("layoutFormat", appender.getLayout().getContentFormat()); builder.append("layoutContentType", appender.getLayout().getContentType()); if (appender instanceof FileAppender) { builder.append(FILE_PARAM, ((FileAppender) appender).getFileName()); builder.append(FILE_PATTERN_PARAM, "(none)"); } if (appender instanceof RandomAccessFileAppender) { builder.append(FILE_PARAM, ((RandomAccessFileAppender) appender).getFileName()); builder.append(FILE_PATTERN_PARAM, "(none)"); } if (appender instanceof RollingFileAppender) { builder.append(FILE_PARAM, ((RollingFileAppender) appender).getFileName()); builder.append(FILE_PATTERN_PARAM, ((RollingFileAppender) appender).getFilePattern()); } if (appender instanceof MemoryMappedFileAppender) { builder.append(FILE_PARAM, ((MemoryMappedFileAppender) appender).getFileName()); builder.append(FILE_PATTERN_PARAM, "(none)"); } if (appender instanceof RollingRandomAccessFileAppender) { builder.append(FILE_PARAM, ((RollingRandomAccessFileAppender) appender).getFileName()); builder.append(FILE_PATTERN_PARAM, ((RollingRandomAccessFileAppender) appender).getFilePattern()); } appenders.add(builder.build()); }); loggerMap.put("appenders", appenders); configuredLoggers.add(loggerMap); }); final Map<String, Object> responseMap = new HashMap<>(); responseMap.put("loggers", configuredLoggers); return responseMap; } private Map<String, Logger> getActiveLoggersInFactory() { final Log4jLoggerFactory factory = (Log4jLoggerFactory) getCasLoggerFactoryInstance(); if (factory != null) { return factory.getLoggersInContext(this.loggerContext); } return new HashMap<>(); } private static ILoggerFactory getCasLoggerFactoryInstance() { return LoggerFactory.getILoggerFactory(); } /** * Gets logger configurations. * * @return the logger configurations * @throws IOException the iO exception */ private Set<LoggerConfig> getLoggerConfigurations() throws IOException { final Configuration configuration = this.loggerContext.getConfiguration(); return new HashSet<>(configuration.getLoggers().values()); } /** * Looks up the logger in the logger factory, * and attempts to find the real logger instance * based on the underlying logging framework * and retrieve the logger object. Then, updates the level. * This functionality at this point is heavily dependant * on the log4j API. * * @param loggerName the logger name * @param loggerLevel the logger level * @param additive the additive nature of the logger * @param request the request * @param response the response * @throws Exception the exception */ @PostMapping(value = "/updateLoggerLevel") @ResponseBody public void updateLoggerLevel(@RequestParam final String loggerName, @RequestParam final String loggerLevel, @RequestParam(defaultValue = "false") final boolean additive, final HttpServletRequest request, final HttpServletResponse response) throws Exception { ensureEndpointAccessIsAuthorized(request, response); Assert.notNull(this.loggerContext); final Collection<LoggerConfig> loggerConfigs = getLoggerConfigurations(); loggerConfigs.stream(). filter(cfg -> cfg.getName().equals(loggerName)) .forEachOrdered(cfg -> { cfg.setLevel(Level.getLevel(loggerLevel)); cfg.setAdditive(additive); }); this.loggerContext.updateLoggers(); } /** * Gets audit log. * * @param request the request * @param response the response * @return the audit log * @throws Exception the exception */ @GetMapping(value = "/getAuditLog") @ResponseBody public Set<AuditActionContext> getAuditLog(final HttpServletRequest request, final HttpServletResponse response) throws Exception { ensureEndpointAccessIsAuthorized(request, response); Assert.notNull(this.loggerContext); return this.auditTrailManager.get(); } }