com.evolveum.midpoint.common.LoggingConfigurationManager.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.common.LoggingConfigurationManager.java

Source

/*
 * Copyright (c) 2010-2013 Evolveum
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.evolveum.midpoint.common;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

import org.apache.commons.lang.StringUtils;
import org.slf4j.bridge.SLF4JBridgeHandler;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;

import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AppenderConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuditingConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ClassLoggerConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FileAppenderConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LoggingConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SubSystemLoggerConfigurationType;

public class LoggingConfigurationManager {

    public static final String AUDIT_LOGGER_NAME = "com.evolveum.midpoint.audit.log";

    final static Trace LOGGER = TraceManager.getTrace(LoggingConfigurationManager.class);

    private static final String REQUEST_FILTER_LOGGER_CLASS_NAME = "com.evolveum.midpoint.web.util.MidPointProfilingServletFilter";
    private static final String PROFILING_ASPECT_LOGGER = "com.evolveum.midpoint.util.aspect.ProfilingDataManager";
    private static final String IDM_PROFILE_APPENDER = "IDM_LOG";

    public static final String SYSTEM_CONFIGURATION_SKIP_REPOSITORY_LOGGING_SETTINGS = "skipRepositoryLoggingSettings";

    private static String currentlyUsedVersion = null;

    public static void configure(LoggingConfigurationType config, String version, OperationResult result) {

        OperationResult res = result.createSubresult(LoggingConfigurationManager.class.getName() + ".configure");

        if (InternalsConfig.avoidLoggingChange) {
            LOGGER.info(
                    "IGNORING change of logging configuration (current config version: {}, new version {}) because avoidLoggingChange=true",
                    currentlyUsedVersion, version);
            res.recordNotApplicableIfUnknown();
            return;
        }

        if (currentlyUsedVersion != null) {
            LOGGER.info("Applying logging configuration (currently applied version: {}, new version: {})",
                    currentlyUsedVersion, version);
        } else {
            LOGGER.info("Applying logging configuration (version {})", version);
        }
        currentlyUsedVersion = version;

        // JUL Bridge initialization was here. (SLF4JBridgeHandler)
        // But it was moved to a later phase as suggested by http://jira.qos.ch/browse/LOGBACK-740

        // Initialize JUL bridge
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        //Get current log configuration
        LoggerContext lc = (LoggerContext) TraceManager.getILoggerFactory();

        //Prepare configurator in current context
        JoranConfigurator configurator = new JoranConfigurator();
        configurator.setContext(lc);

        //Generate configuration file as string
        String configXml = prepareConfiguration(config);

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("New logging configuration:");
            LOGGER.trace(configXml);
        }

        InputStream cis = new ByteArrayInputStream(configXml.getBytes());
        LOGGER.info("Resetting current logging configuration");
        lc.getStatusManager().clear();
        //Set all loggers to error
        for (Logger l : lc.getLoggerList()) {
            LOGGER.trace("Disable logger: {}", l);
            l.setLevel(Level.ERROR);
        }
        // Reset configuration
        lc.reset();
        //Switch to new logging configuration
        lc.setName("MidPoint");
        try {
            configurator.doConfigure(cis);
            LOGGER.info("New logging configuration applied");
        } catch (JoranException e) {
            System.out.println("Error during applying logging configuration: " + e.getMessage());
            LOGGER.error("Error during applying logging configuration: " + e.getMessage(), e);
            result.createSubresult("Applying logging configuration.").recordFatalError(e.getMessage(), e);
        } catch (NumberFormatException e) {
            System.out.println("Error during applying logging configuration: " + e.getMessage());
            LOGGER.error("Error during applying logging configuration: " + e.getMessage(), e);
            result.createSubresult("Applying logging configuration.").recordFatalError(e.getMessage(), e);
        }

        //Get messages if error occurred;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        StatusPrinter.setPrintStream(new PrintStream(baos));
        StatusPrinter.print(lc);

        String internalLog = null;
        try {
            internalLog = baos.toString("UTF8");
        } catch (UnsupportedEncodingException e) {
            // should never happen
            LOGGER.error("Woops?", e);
        }

        if (!StringUtils.isEmpty(internalLog)) {
            //Parse internal log
            res.recordSuccess();
            String internalLogLines[] = internalLog.split("\n");
            for (int i = 0; i < internalLogLines.length; i++) {
                if (internalLogLines[i].contains("|-ERROR"))
                    res.recordPartialError(internalLogLines[i]);
                res.appendDetail(internalLogLines[i]);
            }
            LOGGER.trace("LogBack internal log:\n{}", internalLog);
        } else {
            res.recordSuccess();
        }

        // Initialize JUL bridge
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        return;
    }

    private static String prepareConfiguration(LoggingConfigurationType config) {

        if (null == config) {
            throw new IllegalArgumentException("Configuration can't be null");
        }

        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        sb.append("<configuration scan=\"false\" debug=\"true\">\n");

        //find and configure ALL logger and bring it to top of turbo stack
        for (SubSystemLoggerConfigurationType ss : config.getSubSystemLogger()) {
            if ("ALL".contentEquals(ss.getComponent().name())) {
                defineTurbo(sb, ss);
            }
        }

        //Generate subsystem logging quickstep
        for (SubSystemLoggerConfigurationType ss : config.getSubSystemLogger()) {
            if (null == ss.getComponent() || null == ss.getLevel()) {
                LOGGER.error("Subsystem ({}) or level ({})is null", ss.getComponent(), ss.getLevel());
                continue;
            }

            //skip disabled subsystem logger
            if ("OFF".equals(ss.getLevel().name())) {
                continue;
            }

            //All ready defined above
            if ("ALL".contentEquals(ss.getComponent().name())) {
                continue;
            }
            defineTurbo(sb, ss);
        }

        //Generate appenders configuration
        for (AppenderConfigurationType appender : config.getAppender()) {
            if (appender instanceof FileAppenderConfigurationType) {
                FileAppenderConfigurationType a = (FileAppenderConfigurationType) appender;
                String fileName = a.getFileName();
                String filePattern = a.getFilePattern();

                boolean isRolling = false;
                String appenderClass = "ch.qos.logback.core.FileAppender";
                if (filePattern != null || a.getMaxHistory() > 0 || !StringUtils.isEmpty(a.getMaxFileSize())) {
                    isRolling = true;
                    appenderClass = "ch.qos.logback.core.rolling.RollingFileAppender";
                }

                sb.append("\t<appender name=\"");
                sb.append(a.getName());
                sb.append("\" class=\"" + appenderClass + "\">\n");

                //Apply profiling appender filter if necessary
                if (IDM_PROFILE_APPENDER.equals(appender.getName())) {
                    for (ClassLoggerConfigurationType cs : config.getClassLogger()) {
                        if (REQUEST_FILTER_LOGGER_CLASS_NAME.equals(cs.getPackage())
                                || PROFILING_ASPECT_LOGGER.endsWith(cs.getPackage())) {
                            LOGGER.info("Defining ProfilingLogbackFilter to IDM_LOG Appender.");
                            sb.append(defineProfilingLogbackFilter());
                        }
                    }
                }

                sb.append("\t\t<file>");
                sb.append(fileName);
                sb.append("</file>\n");
                sb.append("\t\t<append>");
                sb.append(a.isAppend());
                sb.append("</append>\n");
                if (isRolling) {
                    //rolling policy
                    sb.append("\t\t<rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n");
                    sb.append("\t\t\t<fileNamePattern>");
                    sb.append(filePattern);
                    sb.append("</fileNamePattern>\n");
                    if (a.getMaxHistory() > 0) {
                        sb.append("\t\t\t<maxHistory>");
                        sb.append(a.getMaxHistory());
                        sb.append("</maxHistory>\n");
                    }
                    sb.append("\t\t\t<cleanHistoryOnStart>true</cleanHistoryOnStart>");

                    // file triggering
                    // if max size is defined
                    if (!StringUtils.isEmpty(a.getMaxFileSize())) {
                        sb.append(
                                "\t\t\t<timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n");
                        sb.append("\t\t\t\t<maxFileSize>");
                        sb.append(a.getMaxFileSize());
                        sb.append("</maxFileSize>\n");
                        sb.append("\t\t\t</timeBasedFileNamingAndTriggeringPolicy>\n");
                    }
                    sb.append("\t\t</rollingPolicy>\n");
                }
                sb.append("\t\t<encoder>\n");
                sb.append("\t\t\t<pattern>");
                sb.append(a.getPattern());
                sb.append("</pattern>\n");
                sb.append("\t\t</encoder>\n");
                sb.append("\t</appender>\n");
            }

            //define root appender if defined
            if (!StringUtils.isEmpty(config.getRootLoggerAppender())) {
                sb.append("\t<root level=\"");
                sb.append(config.getRootLoggerLevel());
                sb.append("\">\n");
                sb.append("\t\t<appender-ref ref=\"");
                sb.append(config.getRootLoggerAppender());
                sb.append("\" />\n");
                sb.append("\t</root>\n");
            }
        }

        //Generate class based loggers
        for (ClassLoggerConfigurationType logger : config.getClassLogger()) {
            sb.append("\t<logger name=\"");
            sb.append(logger.getPackage());
            sb.append("\" level=\"");
            sb.append(logger.getLevel());
            sb.append("\"");
            //if logger specific appender is defined
            if (null != logger.getAppender() && !logger.getAppender().isEmpty()) {
                sb.append(" additivity=\"false\">\n");
                for (String appenderName : logger.getAppender()) {
                    sb.append("\t\t<appender-ref ref=\"");
                    sb.append(appenderName);
                    sb.append("\"/>");
                }
                sb.append("\t</logger>\n");
            } else {
                sb.append("/>\n");
            }
        }

        generateAuditingLogConfig(config.getAuditing(), sb);

        if (null != config.getAdvanced()) {
            for (Object item : config.getAdvanced().getContent()) {
                sb.append(item.toString());
                sb.append("\n");
            }
        }

        // LevelChangePropagator to propagate log level changes to JUL
        // this keeps us from performance impact of disable JUL logging statements
        // WARNING: if deployed in Tomcat then this propagates only to the JUL loggers in current classloader.
        // It means that ICF connector loggers are not affected by this
        // MAGIC: moved to the end of the "file" as suggested in http://jira.qos.ch/browse/LOGBACK-740
        sb.append("\t<contextListener class=\"ch.qos.logback.classic.jul.LevelChangePropagator\">\n");
        sb.append("\t\t<resetJUL>true</resetJUL>\n");
        sb.append("\t</contextListener>\n");

        sb.append("</configuration>");
        return sb.toString();
    }

    private static void generateAuditingLogConfig(AuditingConfigurationType auditing, StringBuilder sb) {
        sb.append("\t<logger name=\"");
        sb.append(AUDIT_LOGGER_NAME);
        sb.append("\" level=\"");
        if (auditing != null && (auditing.isEnabled() == null || auditing.isEnabled())) {
            if (auditing.isDetails() != null && auditing.isDetails()) {
                sb.append("DEBUG");
            } else {
                sb.append("INFO");
            }
        } else {
            sb.append("OFF");
        }
        sb.append("\"");
        //if logger specific appender is defined
        if (auditing != null && auditing.getAppender() != null && !auditing.getAppender().isEmpty()) {
            sb.append(" additivity=\"false\">\n");
            for (String appenderName : auditing.getAppender()) {
                sb.append("\t\t<appender-ref ref=\"");
                sb.append(appenderName);
                sb.append("\"/>");
            }
            sb.append("\t</logger>\n");
        } else {
            sb.append("/>\n");
        }
    }

    private static void defineTurbo(StringBuilder sb, SubSystemLoggerConfigurationType ss) {
        sb.append("\t<turboFilter class=\"com.evolveum.midpoint.util.logging.MDCLevelTurboFilter\">\n");
        sb.append("\t\t<MDCKey>subsystem</MDCKey>\n");
        sb.append("\t\t<MDCValue>");
        sb.append(ss.getComponent().name());
        sb.append("</MDCValue>\n");
        sb.append("\t\t<level>");
        sb.append(ss.getLevel().name());
        sb.append("</level>\n");
        sb.append("\t\t<OnMatch>ACCEPT</OnMatch>\n");
        sb.append("\t</turboFilter>\n");
    }

    private static String defineProfilingLogbackFilter() {
        return ("\t<filter class=\"com.evolveum.midpoint.util.logging.ProfilingLogbackFilter\" />\n");
    }

    public static String getCurrentlyUsedVersion() {
        return currentlyUsedVersion;
    }

    public static void resetCurrentlyUsedVersion() {
        currentlyUsedVersion = null;
    }

    public static void setCurrentlyUsedVersion(String version) {
        currentlyUsedVersion = version;
    }

}