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}