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();
}
}