001package com.identityworksllc.iiq.common.logging;
002
003import java.util.ArrayList;
004import java.util.Date;
005import java.util.List;
006import java.util.Objects;
007
008import sailpoint.VersionConstants;
009import sailpoint.tools.GeneralException;
010
011/**
012 * A class to start and stop log capture as needed. Start interception at any point
013 * by calling {@link LogCapture#startInterception(String...)}, passing one or more logger
014 * names. If no logger names are passed, a predefined set of common ones will be used.
015 *
016 * All messages down to DEBUG level will be captured, even if the logger is set to a
017 * higher level. Existing appenders will be copied and adjusted so that they continue
018 * to log at the previous level. In other words, you'll get DEBUG messages intercepted
019 * but they won't end up in your actual log file.
020 *
021 * When you're done, make sure to call {@link LogCapture#stopInterception()} or you will
022 * have a definite memory leak. You will get back your log messages as a list of strings.
023 *
024 * For a streaming experience, you can register a LogListener with the other version
025 * of {@link #startInterception(LogListener, String...)}. This listener will receive
026 * all log messages as instances of LogListener.LogMessage.
027 */
028public class LogCapture {
029        
030        /**
031         * The log message listener for this thread; if a listener is defined, no messages will be stored
032         */
033        public static ThreadLocal<LogListener> listener = new InheritableThreadLocal<>();
034        
035        /**
036         * The log messages captured for this thread
037         */
038        public static ThreadLocal<List<String>> messages = new InheritableThreadLocal<>();
039        
040        /**
041         * Adds logger interceptors for the given loggers if they don't already have any
042         * @param loggerNames The logger names to intercept
043         * @throws GeneralException If a reflection exception occurs
044         */
045        public static void addLoggers(String... loggerNames) throws GeneralException {
046                if (VersionConstants.VERSION.startsWith("7")) {
047                        try {
048                                CaptureLogs capturer = (CaptureLogs)Class.forName("com.identityworksllc.iiq.common.logging.CaptureLogs7", true, LogCapture.class.getClassLoader()).newInstance();
049                                capturer.capture(loggerNames);
050                        } catch(Exception e) {
051                                throw new GeneralException(e);
052                        }
053                } else if (VersionConstants.VERSION.startsWith("8")) {
054                        try {
055                                CaptureLogs capturer = (CaptureLogs)Class.forName("com.identityworksllc.iiq.common.logging.CaptureLogs8", true, LogCapture.class.getClassLoader()).newInstance();
056                                capturer.capture(loggerNames);
057                        } catch(Exception e) {
058                                throw new GeneralException(e);
059                        }
060                }
061        }
062
063        /**
064         * Sets the log listener for the current thread to the given listener object. The existing message queue
065         * will be retrieved, dumped to the listener, then disabled.
066         * @param _listener The listener to assign
067         */
068        public static void setListener(LogListener _listener) {
069                listener.set(_listener);
070                if (messages.get() != null) {
071                        List<String> existingMessages = messages.get();
072                        messages.set(null);
073                        for(String message: existingMessages) {
074                                LogListener.LogMessage messageObject = new LogListener.LogMessage(new Date(), null, null, message, null);
075                                _listener.logMessageReceived(messageObject);
076                        }
077                }
078        }
079        
080        /**
081         * Starts capture of the given loggers
082         * @param loggers Specific loggers to capture, if desired
083         * @throws GeneralException if any failures occur
084         */
085        private static void startCapture(String... loggers) throws GeneralException {
086                CaptureLogs capturer = null;
087                try {
088                        if (VersionConstants.VERSION.startsWith("7")) {
089                                capturer = (CaptureLogs) Class.forName("com.identityworksllc.iiq.common.logging.CaptureLogs7", true, LogCapture.class.getClassLoader()).newInstance();
090                        } else if (VersionConstants.VERSION.startsWith("8")) {
091                                capturer = (CaptureLogs) Class.forName("com.identityworksllc.iiq.common.logging.CaptureLogs8", true, LogCapture.class.getClassLoader()).newInstance();
092                        }
093                } catch(Exception e) {
094                        throw new GeneralException(e);
095                }
096                if (capturer != null) {
097                        if (loggers == null || loggers.length == 0) {
098                                capturer.captureMost();
099                        } else {
100                                capturer.capture(loggers);
101                        }
102                }
103        }
104
105        /**
106         * Starts log interception if it hasn't already been started
107         * @throws GeneralException on reflection failure setting up the logging interceptor
108         */
109        public static void startInterception(LogListener _listener, String... loggers) throws GeneralException {
110                if (listener.get() != null) {
111                        return;
112                }
113                Objects.requireNonNull(_listener);
114                startCapture(loggers);
115                messages.set(null);
116                listener.set(_listener);
117        }
118
119        /**
120         * Starts log interception if it hasn't already been started
121         * @throws GeneralException on reflection failure setting up the logging interceptor
122         */
123        public static void startInterception(String... loggers) throws GeneralException {
124                if (messages.get() != null) {
125                        return;
126                }
127                startCapture(loggers);
128                messages.set(new ArrayList<>());
129                listener.set(null);
130        }
131
132        /**
133         * Stops interception for this thread, clears the message queue, and returns the list of messages
134         * @return The list of log messages received
135         */
136        public static List<String> stopInterception() {
137                List<String> messageStrings =  messages.get();
138                messages.set(null);
139                listener.set(null);
140                return messageStrings;
141        }
142
143        /**
144         * Private utility constructor
145         */
146        private LogCapture() {
147
148        }
149}