/* * Copyright (C) NetStruxr, Inc. All rights reserved. * * This software is published under the terms of the NetStruxr * Public Software License version 0.5, a copy of which has been * included with this distribution in the LICENSE.NPL file. */ package er.extensions.logging; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.log4j.Appender; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.Priority; import org.apache.log4j.spi.LoggerRepository; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOResponse; import com.webobjects.eocontrol.EOSortOrdering; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSMutableArray; import er.extensions.ERXExtensions; import er.extensions.foundation.ERXArrayUtilities; /** * Configures and manages the log4j logging system. Will also configure the system for rapid turn around, i.e. when * WOCaching is disabled when the conf file changes it will get reloaded. */ public class ERXLog4JConfiguration extends WOComponent { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; /** * A representation of the various Logger levels. */ public enum LoggerLevel { ALL(Level.ALL, "All"), TRACE(Level.TRACE, "Trace"), DEBUG(Level.DEBUG, "Debug"), INFO(Level.INFO, "Info"), WARN(Level.WARN, "Warn"), ERROR(Level.ERROR, "Error"), FATAL(Level.FATAL, "Fatal"), OFF(Level.OFF, "Off"), UNSET(null, "Unset"); // Unset is a "fake" level that doesn't correspond to an actual Log4J Level. private Level level; private String displayName; private static Map<Level, LoggerLevel> levelsByLog4JLevel; static { levelsByLog4JLevel = new HashMap<>(8); for (LoggerLevel level : LoggerLevel.values()) { levelsByLog4JLevel.put(level.level(), level); } } LoggerLevel(Level level, String displayName) { this.level = level; this.displayName = displayName; } public Level level() { return level; } public String displayName() { return displayName; } public static LoggerLevel loggerLevelForLog4JLevel(Level level) { return levelsByLog4JLevel.get(level); } } /** * A representation of the available page sections/views. */ public enum PageSection { LOGGERS("Loggers", "Loggers"), REPOSITORY("Repository", "Repository"), APPENDERS("Appenders", "Appenders"), OTHER("Other", "Other Settings"); private String displayName; private String id; private static Map<String, PageSection> sectionsById; static { sectionsById = new HashMap<>(4); for (PageSection section : PageSection.values()) { sectionsById.put(section.id(), section); } } PageSection(String id, String displayName) { this.displayName = displayName; this.id = id; } public String displayName() { return displayName; } public String id() { return id; } public static PageSection sectionWithId(String name) { return sectionsById.get(name); } } private Logger _logger; private String _filterString; private String _ruleKey; private String _loggerName; public LoggerLevel filterLevel; public LoggerLevel newLoggerLevel = null; private transient NSArray _appenders; public AppenderSkeleton anAppender; public Level aLevel; public LoggerLevel aLoggerLevel; public boolean isNewLoggerARuleLogger = false; public boolean showAll = false; public int rowIndex = 0; private static final NSArray _pageSections = new NSArray(PageSection.values()); public PageSection aPageSection; private PageSection _activeSection = PageSection.LOGGERS; public final static EOSortOrdering NAME_SORT_ORDERING = new EOSortOrdering("name", EOSortOrdering.CompareAscending); public final static NSMutableArray SORT_BY_NAME = new NSMutableArray(NAME_SORT_ORDERING); public ERXLog4JConfiguration(WOContext aContext) { super(aContext); } public Logger logger() { return _logger; } public void setLogger(Logger newValue) { _logger = newValue; } public String filterString() { return _filterString; } public void setFilterString(String newValue) { _filterString = newValue; } public String loggerName() { return _loggerName; } public void setLoggerName(String newValue) { _loggerName = newValue; } public String ruleKey() { return _ruleKey; } public void setRuleKey(String newValue) { _ruleKey = newValue; } public NSArray pageSections() { return _pageSections; } public String activeSection() { return _activeSection.displayName(); } public void setActiveSection(String name) { _activeSection = PageSection.sectionWithId(name); if (null == _activeSection) { _activeSection = PageSection.LOGGERS; } } /** * Gets all of the configured {@link Logger loggers} that pass the filters for logger name and level. * @return the loggers */ public NSArray loggers() { NSMutableArray result = new NSMutableArray(); for (Enumeration e = allLoggers().objectEnumerator(); e.hasMoreElements();) { Logger log = (Logger)e.nextElement(); while (log != null) { addLogger(log, result); log = (Logger)log.getParent(); } } return result; } private NSArray allLoggers() { NSMutableArray result = new NSMutableArray(); Logger rootLogger = LogManager.getRootLogger(); // Float the root logger to the top. addLogger(rootLogger, result); NSMutableArray otherLoggers = new NSMutableArray(); for (Enumeration e = LogManager.getCurrentLoggers(); e.hasMoreElements();) { Logger log = (Logger)e.nextElement(); while (log != null) { if (log != rootLogger) { otherLoggers.addObject(log); } log = (Logger)log.getParent(); } } EOSortOrdering.sortArrayUsingKeyOrderArray(otherLoggers, SORT_BY_NAME); result.addObjectsFromArray(otherLoggers); return result; } /** * Adds a logger instance to the provided array, filtering those that don't fit the filter string / filter level. * @param log to add * @param result array to which the logger will be added if it passes the filter constraint */ public void addLogger(Logger log, NSMutableArray result) { if (!result.containsObject(log)) { boolean passesFilterString = false; boolean passesFilterLevel = false; if ((filterString() == null || filterString().length() == 0 || log.getName().toLowerCase().indexOf(filterString().toLowerCase()) != -1) && (showAll || log.getLevel() != null)) { passesFilterString = true; } if (null == filterLevel || LoggerLevel.loggerLevelForLog4JLevel(log.getLevel()) == filterLevel) { passesFilterLevel = true; } if (passesFilterString && passesFilterLevel) { result.addObject(log); } } } public LoggerLevel currentLoggerLevel() { return _logger != null ? LoggerLevel.loggerLevelForLog4JLevel(_logger.getLevel()) : LoggerLevel.UNSET; } public void setCurrentLoggerLevel(LoggerLevel loggerLevel) { _logger.setLevel(loggerLevel.level()); } public String classNameForLoggerLevelName() { NSMutableArray classes = new NSMutableArray(); if (aLoggerLevel == LoggerLevel.UNSET) { classes.addObject("unset"); } if (currentLoggerLevel() == aLoggerLevel) { classes.addObject("selected"); } return classes.componentsJoinedByString(" "); } public String classForLoggerRow() { NSMutableArray array = new NSMutableArray(); Level level = logger().getLevel(); if (level != null) { array.addObject(level.toString().toLowerCase()); } if (rowIndex % 2 == 0) { array.addObject("alt"); } return array.componentsJoinedByString(" "); } public boolean omitLoggerLevelSettingDecoration() { return classNameForLoggerLevelName().length() == 0; } public NSArray loggerLevels() { return new NSArray(LoggerLevel.values()); } public NSArray loggerLevelsWithoutUnset() { return ERXArrayUtilities.arrayMinusObject(new NSArray(LoggerLevel.values()), LoggerLevel.UNSET); } public LoggerRepository loggerRepository() { return LogManager.getLoggerRepository(); } public String classNameForLoggerRepositoryThresholdName() { return loggerRepository().getThreshold() == aLevel ? "selected" : null; } public boolean omitLoggerRepositoryThresholdSettingDecoration() { return null == classNameForLoggerRepositoryThresholdName(); } /** * Gets the attached to the loggers. This class currently only knows how to work with appenders that subclass * {@link AppenderSkeleton}. * @return the array of appenders */ public NSArray appenders() { if (null == _appenders) { Set<AppenderSkeleton> appenders = new TreeSet<>(new Comparator<AppenderSkeleton>() { public int compare(AppenderSkeleton o1, AppenderSkeleton o2) { int result = 0; if (o1 == o2) { result = 0; } else { String name1 = o1.getName(); String name2 = o2.getName(); if (name1.equals(name2)) { // Two appenders share the same name. Probably a misconfiguration... name1 = o1.getName() + "_" + o1.hashCode(); name2 = o2.getName() + "_" + o2.hashCode(); } result = name1.compareTo(name2); } return result; } }); // Gather appenders attached to the root logger. Logger rootLogger = LogManager.getRootLogger(); Enumeration rootAppendersEnum = rootLogger.getAllAppenders(); while (rootAppendersEnum.hasMoreElements()) { Appender appender = (Appender)rootAppendersEnum.nextElement(); if (appender instanceof AppenderSkeleton) { appenders.add((AppenderSkeleton)appender); } } // Gather appenders attached to other loggers. Enumeration loggersEnum = LogManager.getCurrentLoggers(); while (loggersEnum.hasMoreElements()) { Logger logger = (Logger)loggersEnum.nextElement(); Enumeration appendersEnum = logger.getAllAppenders(); while (appendersEnum.hasMoreElements()) { Appender appender = (Appender)appendersEnum.nextElement(); if (appender instanceof AppenderSkeleton) { appenders.add((AppenderSkeleton)appender); } } } _appenders = new NSArray(appenders.toArray()); } return _appenders; } public NSArray levelsWithoutUnset() { NSArray applicableLevels = ERXArrayUtilities.arrayMinusObject(new NSArray(LoggerLevel.values()), LoggerLevel.UNSET); return (NSArray)applicableLevels.valueForKey("level"); } public LoggerLevel currentAppenderLevel() { Priority threshold = anAppender.getThreshold(); return LoggerLevel.loggerLevelForLog4JLevel(threshold != null ? Level.toLevel(threshold.toInt()) : null); } public void setCurrentAppenderLevel(LoggerLevel loggerLevel) { anAppender.setThreshold(loggerLevel.level()); } public String classForAppenderRow() { NSMutableArray array = new NSMutableArray(); Level level = currentAppenderLevel().level(); if (level != null) { array.addObject(level); } if (rowIndex % 2 == 0) { array.addObject("alt"); } return array.componentsJoinedByString(" "); } public String classNameForAppenderThresholdName() { NSMutableArray classes = new NSMutableArray(); if (aLoggerLevel == LoggerLevel.UNSET) { classes.addObject("unset"); } if (currentAppenderLevel() == aLoggerLevel) { classes.addObject("selected"); } return classes.componentsJoinedByString(" "); } public boolean omitAppenderThresholdSettingDecoration() { return null == classNameForAppenderThresholdName(); } public WOComponent updateAppenderSettings() { return null; } public WOComponent updateRepositorySettings() { return null; } public WOComponent filter() { return null; } public WOComponent resetFilter() { _filterString = null; filterLevel = null; return null; } public WOComponent update() { ERXExtensions.configureAdaptorContext(); return null; } public String showAllLoggersSelection() { return showAll ? "all" : "explicit"; } public void setShowAllLoggersSelection(String value) { showAll = "all".equals(value); } public WOComponent addLogger() { if (isNewLoggerARuleLogger) { _addRuleKeyLogger(); } else { _addLogger(); } return null; } private void _addLogger() { final Logger log = Logger.getLogger(loggerName()); if (newLoggerLevel != null) { filterLevel = newLoggerLevel; log.setLevel(newLoggerLevel.level()); } else { showAll = true; filterLevel = null; } setFilterString(loggerName()); } // This functionality depends on ERDirectToWeb's presence.. private void _addRuleKeyLogger() { final String prefix = "er.directtoweb.rules." + loggerName(); final Logger ruleFireLog = Logger.getLogger(prefix + ".fire"); final Logger ruleCacheHitLog = Logger.getLogger(prefix + ".cache"); final Logger ruleCandidatesLog = Logger.getLogger(prefix + ".candidates"); if (newLoggerLevel != null) { filterLevel = newLoggerLevel; Level level = newLoggerLevel.level(); ruleFireLog.setLevel(level); ruleCacheHitLog.setLevel(level); ruleCandidatesLog.setLevel(level); } else { showAll = true; filterLevel = null; } setFilterString(prefix); } public String loggerPropertiesString() { String result = ""; for (Enumeration e = allLoggers().objectEnumerator(); e.hasMoreElements();) { Logger log = (Logger)e.nextElement(); String name = log.getName(); Level level = log.getLevel(); if (level != null && !"root".equals(name)) { result += "log4j.logger." + log.getName() + "=" + log.getLevel() + "\n"; } } return result; } @Override public void appendToResponse(WOResponse response, WOContext context) { if (session().objectForKey("ERXLog4JConfiguration.enabled") != null) { super.appendToResponse(response, context); } else { response.appendContentString("please use the ERXDirectAction log4jAction to login first!"); } } public int instanceNumber() { return context().request().applicationNumber(); } public boolean knowsAppNumber() { return context().request().applicationNumber() != -1; } //* this assumes you use ERXPatternLayout public String conversionPattern() { return ERXPatternLayout.instance().getConversionPattern(); } public void setConversionPattern(String newPattern) { ERXPatternLayout.instance().setConversionPattern(newPattern); } public WOComponent updateConversionPattern() { return null; } public String classForNavItem() { return aPageSection == _activeSection ? "active" : null; } public String classForLoggersDiv() { return PageSection.LOGGERS == _activeSection ? "active" : null; } public String classForRepositoryDiv() { return PageSection.REPOSITORY == _activeSection ? "active" : null; } public String classForAppendersDiv() { return PageSection.APPENDERS == _activeSection ? "active" : null; } public String classForOtherSettingsDiv() { return PageSection.OTHER == _activeSection ? "active" : null; } public String classForLoggerConfigurationControlBar() { return PageSection.LOGGERS == _activeSection ? "active" : null; } @Override public void awake() { super.awake(); _appenders = null; } }