001package com.identityworksllc.iiq.common.logging; 002 003import com.identityworksllc.iiq.common.AccountUtilities; 004import com.identityworksllc.iiq.common.IdentityLinkUtil; 005import org.apache.logging.log4j.Level; 006import org.apache.logging.log4j.LogManager; 007import org.apache.logging.log4j.core.Appender; 008import org.apache.logging.log4j.core.Filter; 009import org.apache.logging.log4j.core.Layout; 010import org.apache.logging.log4j.core.LogEvent; 011import org.apache.logging.log4j.core.LoggerContext; 012import org.apache.logging.log4j.core.appender.AbstractAppender; 013import org.apache.logging.log4j.core.config.AppenderRef; 014import org.apache.logging.log4j.core.config.Configuration; 015import org.apache.logging.log4j.core.config.LoggerConfig; 016import org.apache.logging.log4j.core.config.Property; 017import org.apache.logging.log4j.core.layout.PatternLayout; 018import sailpoint.rest.plugin.BasePluginResource; 019import sailpoint.task.AbstractTaskExecutor; 020 021import java.io.Serializable; 022import java.nio.charset.StandardCharsets; 023import java.util.Date; 024import java.util.Map; 025 026/** 027 * Capture logs in IIQ 8, which uses Log4J 2.x. That version has entirely 028 * different Logger semantics, requiring separate code. 029 */ 030public class CaptureLogs8 implements CaptureLogs { 031 /** 032 * The name of the appender inserted by this class 033 */ 034 public static final String APPENDER_NAME_IDW_WRITER = "idw-writer-appender"; 035 /** 036 * The layout used for captured messages 037 */ 038 private static final PatternLayout formatter = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN).build(); 039 040 /** 041 * The appender that will log messages to either the messages queue 042 * or the listener. 043 */ 044 private static class CapturingAppender extends AbstractAppender { 045 046 /** 047 * @see AbstractAppender#AbstractAppender(String, Filter, Layout, boolean, Property[]) 048 */ 049 protected CapturingAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions, Property[] properties) { 050 super(name, filter, layout, ignoreExceptions, properties); 051 } 052 053 /** 054 * @see AbstractAppender#append(LogEvent) 055 */ 056 @Override 057 public void append(LogEvent event) { 058 if (LogCapture.messages.get() != null) { 059 StringBuilder builder = new StringBuilder(); 060 formatter.serialize(event, builder); 061 LogCapture.messages.get().add(builder.toString()); 062 } else if (LogCapture.listener.get() != null) { 063 LogListener listener = LogCapture.listener.get(); 064 if (listener != null) { 065 LogListener.LogMessage message = new LogListener.LogMessage(new Date(event.getTimeMillis()), event.getLevel().toString(), event.getLoggerName(), String.valueOf(event.getMessage()), null); 066 listener.logMessageReceived(message); 067 } 068 } 069 070 } 071 } 072 073 /** 074 * Captures the majority of the most important loggers in IIQ 8+, as well as a few 075 * from IIQCommon itself. 076 */ 077 @Override 078 public void captureMost() { 079 captureLogger("org.hibernate.SQL"); 080 captureLogger("hibernate.hql.ast.AST"); 081 captureLogger("sailpoint.api.AbstractEntitlizer"); 082 captureLogger("sailpoint.api.Aggregator"); 083 captureLogger("sailpoint.api.Identitizer"); 084 captureLogger("sailpoint.api.IdentityService"); 085 captureLogger("sailpoint.api.PasswordPolice"); 086 captureLogger("sailpoint.api.RoleEntitlizer"); 087 captureLogger("sailpoint.api.Workflower"); 088 captureLogger("sailpoint.provisioning.AssignmentExpander"); 089 captureLogger("sailpoint.provisioning.ApplicationPolicyExpander"); 090 captureLogger("sailpoint.provisioning.IIQEvaluator"); 091 captureLogger("sailpoint.provisioning.Provisioner"); 092 captureLogger("sailpoint.provisioning.PlanApplier"); 093 captureLogger("sailpoint.provisioning.PlanCompiler"); 094 captureLogger("sailpoint.provisioning.PlanEvaluator"); 095 captureLogger("sailpoint.provisioning.PlanSimplifier"); 096 captureLogger("sailpoint.provisioning.TemplateCompiler"); 097 captureLogger("sailpoint.persistence.hql"); 098 captureLogger("sailpoint.connector.AbstractConnector"); 099 captureLogger("sailpoint.connector.ConnectorProxy"); 100 captureLogger("sailpoint.connector.ADLDAPConnector"); 101 captureLogger("sailpoint.connector.LDAPConnector"); 102 captureLogger("sailpoint.connector.AbstractIQServiceConnector"); 103 captureLogger("sailpoint.connector.DelimitedFileConnector"); 104 captureLogger("sailpoint.connector.AbstractFileBasedConnector"); 105 captureLogger("sailpoint.connector.JDBCConnector"); 106 captureLogger("sailpoint.connector.webservices.WebServicesConnector"); 107 captureLogger("sailpoint.connector.webservices.AbstractHTTPRequestBuilder"); 108 captureLogger("sailpoint.connector.webservices.JSONRequestBuilder"); 109 captureLogger("sailpoint.connector.webservices.JSONResponseParser"); 110 captureLogger("sailpoint.connector.webservices.WebServicesClient"); 111 captureLogger("sailpoint.connector.webservices.paging.WebServicesPaginator"); 112 captureLogger("sailpoint.connector.webservices.paging.WebServicesPaginator"); 113 captureLogger("openconnector.connector.okta.OktaConnector"); 114 captureLogger("openconnector.connector.WorkDay"); 115 captureLogger("openconnector.AbstractConnector"); 116 117 captureLogger(AbstractTaskExecutor.class.getName()); 118 captureLogger(AccountUtilities.class.getName()); 119 captureLogger(IdentityLinkUtil.class.getName()); 120 121 final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 122 final Configuration config = ctx.getConfiguration(); 123 124 config.getLoggerConfig("sailpoint.web").setLevel(Level.ERROR); 125 config.getLoggerConfig("sailpoint.server.Servicer").setLevel(Level.ERROR); 126 config.getLoggerConfig("org.apache.commons.dbcp2").setLevel(Level.ERROR); 127 128 ctx.updateLoggers(config); 129 } 130 131 /** 132 * Adds the given loggers to the capture list 133 * @param names The names of the loggers to capture 134 */ 135 @Override 136 public void capture(String... names) { 137 for(String name : names) { 138 captureLogger(name); 139 } 140 } 141 142 /** 143 * Captures the logger with the given name by replacing its appender with our 144 * capturing appender. If the appender or logger does not exist, they will be 145 * created here. 146 * 147 * @param loggerName The logger 148 */ 149 public static void captureLogger(String loggerName) { 150 final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 151 final Configuration config = ctx.getConfiguration(); 152 PatternLayout layout = 153 PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN).withConfiguration(config).withCharset(StandardCharsets.UTF_8).build(); 154 155 Appender appender = config.getAppender(APPENDER_NAME_IDW_WRITER); 156 157 if (appender == null) { 158 synchronized (CaptureLogs8.class) { 159 appender = config.getAppender(APPENDER_NAME_IDW_WRITER); 160 if (appender == null) { 161 appender = new CapturingAppender(APPENDER_NAME_IDW_WRITER, null, layout, false, null); 162 appender.start(); 163 config.addAppender(appender); 164 } 165 } 166 } 167 168 AppenderRef ref = AppenderRef.createAppenderRef(APPENDER_NAME_IDW_WRITER, Level.DEBUG, null); 169 AppenderRef[] refs = new AppenderRef[] {ref}; 170 LoggerConfig loggerConfig = config.getLoggerConfig(loggerName); 171 if (loggerConfig == null) { 172 loggerConfig = LoggerConfig.createLogger(false, Level.DEBUG, loggerName, "true", refs, null, config, null); 173 loggerConfig.start(); 174 config.addLogger(loggerName, loggerConfig); 175 } 176 177 Level originalLevel = loggerConfig.getLevel(); 178 179 if (!loggerConfig.getName().equals(loggerName)) { 180 LoggerConfig specificConfig = new LoggerConfig(loggerName, Level.DEBUG, false); 181 specificConfig.setParent(loggerConfig); 182 config.addLogger(loggerName, specificConfig); 183 loggerConfig = specificConfig; 184 } 185 186 wrapAppenders(loggerConfig, loggerConfig, originalLevel); 187 188 loggerConfig.setAdditive(false); 189 190 if (!loggerConfig.getAppenders().containsKey(APPENDER_NAME_IDW_WRITER)) { 191 loggerConfig.addAppender(appender, Level.DEBUG, null); 192 } 193 194 ctx.updateLoggers(config); 195 } 196 197 /** 198 * Wraps the appenders for the logger and any of its parents, preventing 199 * the default appenders from logging at a strange level. 200 * 201 * @param toModify The LoggerConfig to modify (the one we're hooking) 202 * @param current The LoggerConfig to consider (may be a parent) 203 * @param originalLevel The original level to set on the appenders 204 */ 205 private static void wrapAppenders(LoggerConfig toModify, LoggerConfig current, Level originalLevel) { 206 Map<String, Appender> appenders = current.getAppenders(); 207 for(String name : appenders.keySet()) { 208 if (!name.equals(APPENDER_NAME_IDW_WRITER)) { 209 Appender existing = appenders.get(name); 210 toModify.removeAppender(name); 211 toModify.addAppender(existing, originalLevel, null); 212 } 213 } 214 if (current.getParent() != null) { 215 wrapAppenders(toModify, current.getParent(), originalLevel); 216 } 217 } 218}