| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.sling.commons.log.logback.internal; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.net.URLEncoder; |
| import java.util.Collection; |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.apache.sling.commons.log.logback.internal.AppenderTracker.AppenderInfo; |
| import org.apache.sling.commons.log.logback.internal.ConfigSourceTracker.ConfigSourceInfo; |
| import org.apache.sling.commons.log.logback.internal.LogbackManager.LoggerStateContext; |
| import org.apache.sling.commons.log.logback.internal.config.ConfigurationException; |
| import org.apache.sling.commons.log.logback.internal.util.SlingRollingFileAppender; |
| import org.apache.sling.commons.log.logback.internal.util.Util; |
| import org.apache.sling.commons.log.logback.internal.util.XmlUtil; |
| import org.apache.sling.commons.log.logback.webconsole.LogPanel; |
| import org.apache.sling.commons.log.logback.webconsole.LoggerConfig; |
| import org.apache.sling.commons.log.logback.webconsole.TailerOptions; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.service.cm.Configuration; |
| import org.osgi.service.cm.ConfigurationAdmin; |
| import org.slf4j.LoggerFactory; |
| import org.xml.sax.InputSource; |
| |
| import ch.qos.logback.classic.Level; |
| import ch.qos.logback.classic.Logger; |
| import ch.qos.logback.classic.spi.ILoggingEvent; |
| import ch.qos.logback.classic.turbo.TurboFilter; |
| import ch.qos.logback.core.Appender; |
| import ch.qos.logback.core.FileAppender; |
| import ch.qos.logback.core.helpers.Transform; |
| import ch.qos.logback.core.status.Status; |
| import ch.qos.logback.core.util.CachingDateFormatter; |
| |
| /** |
| * The <code>SlingLogPanel</code> is a Felix Web Console plugin to display the |
| * current active log bundle configuration. |
| * |
| * <p>In future revisions of this plugin, the configuration may probably even be |
| * modified through this panel. |
| */ |
| public class SlingLogPanel implements LogPanel { |
| |
| private final CachingDateFormatter SDF = new CachingDateFormatter("yyyy-MM-dd HH:mm:ss"); |
| |
| private static final String[] LEVEL_NAMES = { |
| Level.ERROR.levelStr, |
| Level.WARN.levelStr, |
| Level.INFO.levelStr, |
| Level.DEBUG.levelStr, |
| Level.TRACE.levelStr, |
| Level.OFF.levelStr, |
| LogConfigManager.LOG_LEVEL_RESET_TO_DEFAULT |
| }; |
| |
| private static final String PACKAGE_SEPARATOR = "."; |
| |
| private final LogbackManager logbackManager; |
| private final BundleContext bundleContext; |
| |
| private static final org.slf4j.Logger log = LoggerFactory.getLogger(SlingLogPanel.class); |
| |
| public SlingLogPanel(final LogbackManager logbackManager, final BundleContext bundleContext) { |
| this.logbackManager = logbackManager; |
| this.bundleContext = bundleContext; |
| } |
| |
| @Override |
| public void tail(PrintWriter pw, String appenderName, TailerOptions options) throws IOException { |
| final LoggerStateContext ctx = logbackManager.determineLoggerState(); |
| renderAppenderContent(ctx, pw, appenderName, options); |
| } |
| |
| @Override |
| public void render(PrintWriter pw, String consoleAppRoot) throws IOException { |
| final LoggerStateContext ctx = logbackManager.determineLoggerState(); |
| appendLoggerStatus(pw, ctx); |
| appendOsgiConfiguredLoggerData(pw, consoleAppRoot); |
| appendOtherLoggerData(pw, ctx); |
| addAppenderData(pw, consoleAppRoot, ctx); |
| appendTurboFilterData(pw, consoleAppRoot, ctx); |
| appendLogbackMainConfig(pw); |
| appendLogbackFragments(pw, consoleAppRoot); |
| appendLogbackStatus(pw, ctx); |
| addScriptBlock(pw, ctx); |
| } |
| |
| @Override |
| public void deleteLoggerConfig(String pid) { |
| try { |
| removeLogger(pid); |
| } catch (ConfigurationException e) { |
| internalFailure("", e); |
| } |
| } |
| |
| @Override |
| public void createLoggerConfig(LoggerConfig config) throws IOException { |
| try { |
| configureLogger(config.getPid(), config.getLogLevel(), config.getLoggers(), |
| config.getLogFile(), config.isAdditive()); |
| } catch (ConfigurationException e) { |
| internalFailure("", e); |
| } |
| } |
| |
| private void addScriptBlock(final PrintWriter pw, final LoggerStateContext ctx) { |
| pw.println("<script type=\"text/javascript\" src=\"" + RES_LOC + "/slinglog.js\"></script>"); |
| pw.println("<script type=\"text/javascript\" src=\"" + RES_LOC + "/jquery.autocomplete.min.js\"></script>"); |
| pw.println("<script type=\"text/javascript\" src=\"" + RES_LOC + "/prettify.js\"></script>"); |
| |
| pw.println("<script type=\"text/javascript\">$(document).ready(function() { initializeSlingLogPanel(); });</script>"); |
| pw.println("<script>"); |
| // write all present loggers as script variable so the autocomplete script can search over them |
| pw.println("var loggers=["); |
| Set<String> loggers = new TreeSet<String>(); |
| |
| for (Logger logger : ctx.loggerContext.getLoggerList()) { |
| loggers.add(logger.getName()); |
| } |
| |
| Set<String> packageList = new TreeSet<String>(); |
| for (String logger : loggers) { |
| int pos = logger.lastIndexOf(PACKAGE_SEPARATOR); |
| if (pos != -1) { |
| String pack = logger.substring(0, pos); |
| packageList.add(pack); |
| } |
| } |
| loggers.addAll(packageList); |
| for (Iterator<String> loggerIt = loggers.iterator(); loggerIt.hasNext(); ) { |
| String logger = loggerIt.next(); |
| pw.print("'"); |
| pw.print(XmlUtil.escapeXml(logger)); |
| pw.print("'"); |
| if (loggerIt.hasNext()) { |
| pw.print(","); |
| } |
| } |
| pw.println("];"); |
| pw.println("</script>"); |
| pw.println("<script>$(document).ready(prettyPrint);</script>"); |
| } |
| |
| private void appendLoggerStatus(PrintWriter pw, LoggerStateContext ctx) { |
| pw.printf( |
| "<p class='statline'>Log Service Stats: %d categories, %d appender, %d Dynamic appenders, %d Packages</p>%n", |
| ctx.getNumberOfLoggers(), ctx.getNumOfAppenders(), ctx.getNumOfDynamicAppenders(), ctx.packageInfoCollector.size()); |
| } |
| |
| private void appendOsgiConfiguredLoggerData(final PrintWriter pw, final String consoleAppRoot) { |
| pw.println("<div class='table'>"); |
| |
| pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Logger (Configured via OSGi Config)</div>"); |
| |
| pw.println("<form method='POST'><table id=\"loggerConfig\" class='tablesorter nicetable ui-widget'>"); |
| |
| pw.println("<thead class='ui-widget-header'>"); |
| pw.println("<tr>"); |
| pw.println("<th>Log Level</th>"); |
| pw.println("<th>Additive</th>"); |
| pw.println("<th>Log File</th>"); |
| pw.println("<th>Logger</th>"); |
| pw.print("<th width=\"20%\">"); |
| pw.print(getConfigColTitle(consoleAppRoot)); // no need to escape |
| pw.println("</th>"); |
| pw.println("</tr>"); |
| pw.println("</thead>"); |
| pw.println("<tbody class='ui-widget-content'>"); |
| |
| final LogConfigManager configManager = logbackManager.getLogConfigManager(); |
| final String rootPath = logbackManager.getRootDir(); |
| final boolean shortenPaths = areAllLogfilesInSameFolder(configManager.getLogConfigs(), rootPath); |
| for (final LogConfig logConfig : configManager.getLogConfigs()) { |
| pw.print("<tr id=\""); |
| pw.print( XmlUtil.escapeXml(logConfig.getConfigPid()) ); |
| pw.println("\">"); |
| pw.print("<td><span class=\"logLevels\" data-currentloglevel=\""); |
| pw.print(getLevelStr(logConfig)); |
| pw.print("\">"); |
| pw.print(getLevelStr(logConfig)); |
| pw.println("</span></td>"); |
| pw.print("<td><span class=\"logAdditive\" data-currentAdditivity=\""); |
| pw.print(Boolean.toString(logConfig.isAdditive())); |
| pw.print("\">"); |
| pw.print(Boolean.toString(logConfig.isAdditive())); |
| pw.println("</span></td>"); |
| pw.print("<td><span class=\"logFile\">"); |
| pw.print( XmlUtil.escapeXml(getPath(logConfig.getLogWriterName(), rootPath, shortenPaths))); |
| pw.println("</span></td>"); |
| |
| pw.println("<td><span class=\"loggers\">"); |
| String sep = ""; |
| for (final String cat : logConfig.getCategories()) { |
| pw.print(sep); |
| pw.print("<span class=\"logger\">"); |
| pw.print( XmlUtil.escapeXml(cat)); |
| pw.println("</span>"); |
| sep = "<br />"; |
| } |
| pw.println("</td>"); |
| |
| final String pid = logConfig.getConfigPid(); |
| String url = createUrl(consoleAppRoot, "configMgr", pid, true); |
| if (logConfig.getCategories().contains(Logger.ROOT_LOGGER_NAME)) { |
| url = createUrl(consoleAppRoot, "configMgr", pid, false); |
| } |
| pw.print("<td>"); |
| pw.print(url); |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| } |
| |
| pw.println("</tbody><tfoot>"); |
| pw.println("<tr id=\"newlogger\">"); |
| pw.println("<td><span id=\"allLogLevels\" class=\"logLevels\" data-loglevels=\""); |
| String sep = ""; |
| for (final String levelName : LEVEL_NAMES) { |
| pw.print(sep); |
| pw.print(XmlUtil.escapeXml(levelName)); |
| sep = ","; |
| } |
| |
| pw.println("\"></span></td>"); |
| pw.print("<td><span class=\"logAdditive\" data-currentAdditivity=\"false\"></span></td>"); |
| pw.print("<td><span id=\"defaultLogfile\" data-defaultlogfile=\""); |
| pw.print( XmlUtil.escapeXml(getPath(configManager.getDefaultWriter().getFileName(), rootPath, shortenPaths))); |
| pw.println("\" class=\"logFile\"></span></td>"); |
| pw.println("<td><span class=\"loggers\"></span></td>"); |
| pw.println("<td><input type='submit' class=\"configureLink\" value='Add new Logger' /></td></tr></tfoot>"); |
| |
| pw.println("</table></form>"); |
| pw.println("</div>"); |
| } |
| |
| private void appendOtherLoggerData(final PrintWriter pw, final LoggerStateContext ctx) throws UnsupportedEncodingException { |
| if (ctx.nonOSgiConfiguredLoggers.isEmpty()) { |
| return; |
| } |
| |
| pw.println("<div class='table'>"); |
| |
| pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Logger (Configured via other means)</div>"); |
| |
| pw.println("<table class='nicetable ui-widget'>"); |
| |
| pw.println("<thead class='ui-widget-header'>"); |
| pw.println("<tr>"); |
| pw.println("<th>Log Level</th>"); |
| pw.println("<th>Additivity</th>"); |
| pw.println("<th>Name</th>"); |
| pw.println("<th>Appender</th>"); |
| pw.println("</tr>"); |
| pw.println("</thead>"); |
| pw.println("<tbody class='ui-widget-content'>"); |
| |
| for (final Logger logger : ctx.nonOSgiConfiguredLoggers) { |
| pw.println("<tr>"); |
| pw.print("<td>"); |
| pw.print(logger.getLevel()); |
| pw.println("</td>"); |
| pw.print("<td>"); |
| pw.print(Boolean.toString(logger.isAdditive())); |
| pw.println("</td>"); |
| pw.print("<td>"); |
| pw.print(XmlUtil.escapeXml(logger.getName())); |
| pw.println("</td>"); |
| |
| pw.println("<td>"); |
| pw.println("<ul>"); |
| final Iterator<Appender<ILoggingEvent>> itr = logger.iteratorForAppenders(); |
| while (itr.hasNext()) { |
| final Appender<ILoggingEvent> a = itr.next(); |
| pw.print("<li>"); |
| pw.print(XmlUtil.escapeXml(getName(a))); |
| pw.print("</li>"); |
| } |
| pw.println("</ul>"); |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| } |
| |
| pw.println("</tbody>"); |
| pw.println("</table>"); |
| pw.println("</div>"); |
| } |
| |
| private void addAppenderData(final PrintWriter pw, final String consoleAppRoot, final LoggerStateContext ctx) throws UnsupportedEncodingException { |
| pw.println("<div class='table'>"); |
| |
| pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Appender</div>"); |
| |
| pw.println("<table class='nicetable ui-widget'>"); |
| |
| pw.println("<thead class='ui-widget-header'>"); |
| pw.println("<tr>"); |
| pw.println("<th>Appender</th>"); |
| pw.print("<th>"); |
| pw.print(getConfigColTitle(consoleAppRoot)); // no need to escape |
| pw.println("</th>"); |
| pw.println("</tr>"); |
| pw.println("</thead>"); |
| pw.println("<tbody class='ui-widget-content'>"); |
| |
| for (final Appender<ILoggingEvent> appender : ctx.appenders.values()) { |
| pw.println("<tr>"); |
| pw.print("<td>"); |
| if (appender instanceof FileAppender) { |
| pw.print(getLinkedName((FileAppender<ILoggingEvent>) appender)); |
| } else { |
| pw.print(XmlUtil.escapeXml(getName(appender))); |
| } |
| pw.println("</td>"); |
| pw.print("<td>"); |
| pw.print(formatPid(consoleAppRoot, appender, ctx)); |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| } |
| |
| pw.println("</tbody>"); |
| pw.println("</table>"); |
| pw.println("</div>"); |
| } |
| |
| private void appendTurboFilterData(final PrintWriter pw, final String consoleAppRoot, final LoggerStateContext ctx) { |
| if (ctx.loggerContext.getTurboFilterList().isEmpty()) { |
| return; |
| } |
| |
| pw.println("<div class='table'>"); |
| |
| pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Turbo Filters</div>"); |
| |
| pw.println("<table class='nicetable ui-widget'>"); |
| |
| pw.println("<thead class='ui-widget-header'>"); |
| pw.println("<tr>"); |
| pw.println("<th>Turbo Filter</th>"); |
| pw.print("<th>"); |
| pw.print(getConfigColTitle(consoleAppRoot)); // no need to escape |
| pw.println("</th>"); |
| pw.println("</tr>"); |
| pw.println("</thead>"); |
| pw.println("<tbody class='ui-widget-content'>"); |
| |
| |
| for (final TurboFilter tf : ctx.loggerContext.getTurboFilterList()) { |
| pw.println("<tr>"); |
| pw.println("<td>"); |
| pw.print(XmlUtil.escapeXml(getName(tf))); |
| pw.println("</td>"); |
| pw.print("<td>"); |
| pw.print(formatPid(consoleAppRoot, tf, ctx)); |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| |
| } |
| |
| pw.println("</tbody>"); |
| pw.println("</table>"); |
| pw.println("</div>"); |
| } |
| |
| |
| private void appendLogbackStatus(final PrintWriter pw, final LoggerStateContext ctx) { |
| pw.println("<div class='table'>"); |
| |
| pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Logback Status</div>"); |
| pw.println("<div style='overflow-y:scroll; height:400px'>"); |
| pw.println("<table class='nicetable ui-widget'>"); |
| |
| pw.println("<thead class='ui-widget-header'>"); |
| pw.println("<tr>"); |
| pw.println("<th>Date</th>"); |
| pw.println("<th>Level</th>"); |
| pw.println("<th>Origin</th>"); |
| pw.println("<th>Message</th>"); |
| pw.println("</tr>"); |
| pw.println("</thead>"); |
| |
| pw.println("<tbody class='ui-widget-content' >"); |
| |
| final List<Status> statusList = ctx.loggerContext.getStatusManager().getCopyOfStatusList(); |
| for (final Status s : statusList) { |
| pw.println("<tr>"); |
| pw.print("<td class=\"date\">"); |
| pw.print(SDF.format(s.getDate())); |
| pw.println("</td>"); |
| pw.print("<td class=\"level\">"); |
| pw.print(statusLevelAsString(s)); |
| pw.println("</td>"); |
| pw.print("<td>"); |
| pw.print(XmlUtil.escapeXml(SlingConfigurationPrinter.abbreviatedOrigin(s))); |
| pw.println("</td>"); |
| pw.print("<td>"); |
| pw.print(XmlUtil.escapeXml(s.getMessage())); |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| |
| // noinspection ThrowableResultOfMethodCallIgnored |
| if (s.getThrowable() != null) { |
| printThrowable(pw, s.getThrowable()); |
| } |
| } |
| |
| pw.println("</tbody>"); |
| |
| pw.println("</table>"); |
| pw.print("</div>"); |
| pw.println("</div>"); |
| pw.println("<br />"); |
| } |
| |
| private void appendLogbackMainConfig(final PrintWriter pw) { |
| pw.println("<div class='table'>"); |
| pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Logback Config</div>"); |
| pw.println("<table class='nicetable ui-widget'>"); |
| pw.println("<tbody class='ui-widget-content'>"); |
| |
| File configFile = null; |
| URL url = null; |
| InputSource source = null; |
| try { |
| String msg; |
| configFile = logbackManager.getLogConfigManager().getLogbackConfigFile(); |
| if (configFile != null) { |
| source = new InputSource(new BufferedInputStream(new FileInputStream(configFile))); |
| msg = "Source " + configFile.getAbsolutePath(); |
| } else { |
| url = logbackManager.getDefaultConfig(); |
| URLConnection uc = url.openConnection(); |
| uc.setDefaultUseCaches(false); |
| source = new InputSource(new BufferedInputStream(uc.getInputStream())); |
| msg = "Source : Default"; |
| } |
| |
| pw.println("<tr>"); |
| pw.print("<td>"); |
| pw.print(XmlUtil.escapeXml(msg)); |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| |
| pw.println("<tr><td>"); |
| final String textContent = XmlUtil.escapeXml(XmlUtil.prettyPrint(source)); |
| pw.print("<pre class=\"prettyprint lang-xml\" style=\"border: 0px\">"); |
| pw.print(textContent); |
| pw.print("</pre>"); |
| pw.println("</td></tr>"); |
| } catch (IOException e) { |
| String msg = "Error occurred while opening file [" + configFile + "]"; |
| if (url != null) { |
| msg = "Error occurred while opening url [" + url + "]"; |
| } |
| log.warn(msg, e); |
| } finally { |
| Util.close(source); |
| } |
| pw.println("</tbody>"); |
| pw.println("</table>"); |
| pw.println("</div>"); |
| } |
| |
| private void appendLogbackFragments(final PrintWriter pw, final String consoleAppRoot) { |
| final Collection<ConfigSourceInfo> configSources = logbackManager.getConfigSourceTracker().getSources(); |
| |
| if (configSources.isEmpty()) { |
| return; |
| } |
| |
| pw.println("<div class='table'>"); |
| pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Logback Config Fragments</div>"); |
| pw.println("<table class='nicetable ui-widget'>"); |
| pw.println("<tbody class='ui-widget-content'>"); |
| |
| for (final ConfigSourceInfo ci : configSources) { |
| final String pid = ci.getReference().getProperty(Constants.SERVICE_ID).toString(); |
| final String url = createUrl(consoleAppRoot, "services", pid); |
| pw.println("<tr>"); |
| pw.print("<td>"); |
| pw.print(url); |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| |
| pw.println("<tr>"); |
| pw.println("<td>"); |
| // prettify.js adds a border. We eed to remove that |
| pw.print("<pre class=\"prettyprint lang-xml\" style=\"border: 0px\">"); |
| pw.print(ci.getSourceAsEscapedString()); |
| pw.print("</pre>"); |
| |
| pw.println("</td>"); |
| pw.println("</tr>"); |
| } |
| |
| pw.println("</tbody>"); |
| pw.println("</table>"); |
| pw.println("</div>"); |
| } |
| |
| /** |
| * Checks if all log files are in the same folder, then the path can displayed shortened in the panel. |
| * |
| * @param logConfigs list of log configs |
| * @param rootPath root path |
| * @return true if all logfiles are in the same folder |
| */ |
| private boolean areAllLogfilesInSameFolder(final Iterable<LogConfig> logConfigs, final String rootPath) { |
| String lastPath = null; |
| for (final LogConfig config : logConfigs) { |
| String path = getPath(config.getLogWriter().getFileName(), null, false); |
| if (!path.startsWith(rootPath)) { |
| return false; |
| } |
| path = path.substring(0, rootPath.length()); |
| if (lastPath == null) { |
| lastPath = path; |
| } else if (!path.equals(lastPath)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void renderAppenderContent(LoggerStateContext ctx, PrintWriter pw, String appenderName, TailerOptions opts) |
| throws IOException { |
| for (final Appender<ILoggingEvent> appender : ctx.appenders.values()) { |
| if (appender instanceof FileAppender && appenderName.equals(appender.getName())) { |
| final File file = new File(((FileAppender) appender).getFile()); |
| if (file.exists()) { |
| if (opts.tailAll()) { |
| SlingConfigurationPrinter.includeWholeFile(pw, file); |
| } else { |
| int numOfLines = opts.getNumOfLines(); |
| if (numOfLines == 0){ |
| numOfLines = logbackManager.getLogConfigManager().getNumOfLines(); |
| } |
| new Tailer(new FilteringListener(pw, opts.getRegex()), numOfLines).tail(file); |
| } |
| } |
| return; |
| } |
| } |
| pw.printf("No appender with name [%s] found", XmlUtil.escapeXml(appenderName)); |
| } |
| |
| private String getLinkedName(FileAppender<ILoggingEvent> appender) throws UnsupportedEncodingException { |
| String fileName = appender.getFile(); |
| String name = appender.getName(); |
| return String.format("File : [<a href=\"%s/%s?%s=%d&%s=%s&%s=%s\">%s</a>] %s", |
| APP_ROOT, |
| PATH_TAILER, |
| PARAM_TAIL_NUM_OF_LINES, |
| logbackManager.getLogConfigManager().getNumOfLines(), |
| PARAM_TAIL_GREP, |
| FilteringListener.MATCH_ALL, |
| PARAM_APPENDER_NAME, |
| URLEncoder.encode(name, "UTF-8"), |
| XmlUtil.escapeXml(name), |
| XmlUtil.escapeXml(fileName)); |
| |
| } |
| |
| /** |
| * Configures the logger with the given pid. If the pid is empty a new logger configuration is created. |
| * |
| * @param pid configuration pid of the logger |
| * @param logLevel the log level to set |
| * @param loggers list of logger categories to set |
| * @param logFile log file (relative path is ok) |
| * @param additive logger additivity |
| * @throws IOException when an existing configuration couldn't be updated or a configuration couldn't be created. |
| * @throws ConfigurationException when mandatory parameters where not specified |
| */ |
| private void configureLogger(final String pid, final String logLevel, final String[] loggers, final String |
| logFile, boolean additive) |
| throws IOException, ConfigurationException { |
| // try to get the configadmin service reference |
| ServiceReference sr = this.bundleContext |
| .getServiceReference(ConfigurationAdmin.class.getName()); |
| if (sr != null) { |
| try { |
| if (logLevel == null) { |
| throw new ConfigurationException(LogConfigManager.LOG_LEVEL, |
| "Log level has to be specified."); |
| } |
| if (loggers == null) { |
| throw new ConfigurationException(LogConfigManager.LOG_LOGGERS, |
| "Logger categories have to be specified."); |
| } |
| if (logFile == null) { |
| throw new ConfigurationException(LogConfigManager.LOG_FILE, |
| "LogFile name has to be specified."); |
| } |
| // try to get the configadmin |
| final ConfigurationAdmin configAdmin = (ConfigurationAdmin) this.bundleContext |
| .getService(sr); |
| if (configAdmin != null) { |
| Configuration config; |
| if (pid == null || pid.length() == 0) { |
| config = configAdmin.createFactoryConfiguration(LogConfigManager.FACTORY_PID_CONFIGS); |
| } else { |
| config = configAdmin.getConfiguration(pid); |
| } |
| if (config != null) { |
| Dictionary<String, Object> dict = new Hashtable<String, Object>(); |
| dict.put(LogConfigManager.LOG_LEVEL, logLevel.toLowerCase()); |
| dict.put(LogConfigManager.LOG_LOGGERS, loggers); |
| dict.put(LogConfigManager.LOG_FILE, logFile); |
| |
| if (additive){ |
| dict.put(LogConfigManager.LOG_ADDITIV, "true"); |
| } else { |
| dict.put(LogConfigManager.LOG_ADDITIV, "false"); |
| } |
| config.update(dict); |
| } |
| } |
| } finally { |
| // release the configadmin reference |
| this.bundleContext.ungetService(sr); |
| } |
| } |
| } |
| |
| |
| /** |
| * Removes the logger configuration with the given pid in the configadmin. |
| * |
| * @param pid pid of the configuration to delete |
| * @throws ConfigurationException when there is no configuration for this pid |
| */ |
| private void removeLogger(final String pid) |
| throws ConfigurationException { |
| // try to get the configadmin service reference |
| ServiceReference sr = this.bundleContext |
| .getServiceReference(ConfigurationAdmin.class.getName()); |
| if (sr != null) { |
| try { |
| if (pid == null) { |
| throw new ConfigurationException(LogConfigManager.PID, |
| "PID has to be specified."); |
| } |
| // try to get the configadmin |
| final ConfigurationAdmin configAdmin = (ConfigurationAdmin) this.bundleContext |
| .getService(sr); |
| if (configAdmin != null) { |
| try { |
| Configuration config = configAdmin.getConfiguration(pid); |
| if (config != null) { |
| config.delete(); |
| } else { |
| throw new ConfigurationException(LogConfigManager.PID, |
| "No configuration for this PID:" + pid); |
| } |
| } catch (IOException ioe) { |
| internalFailure( |
| "Cannot delete configuration for pid " + pid, |
| ioe); |
| } |
| } |
| } finally { |
| // release the configadmin reference |
| this.bundleContext.ungetService(sr); |
| } |
| } |
| } |
| |
| private void internalFailure(String msg, Exception e) { |
| logbackManager.getLogConfigManager().internalFailure(msg, e); |
| } |
| |
| private String getLevelStr(LogConfig logConfig) { |
| if (logConfig.isResetToDefault()){ |
| return LogConfigManager.LOG_LEVEL_RESET_TO_DEFAULT; |
| } |
| return logConfig.getLogLevel().levelStr; |
| } |
| |
| private static String getName(TurboFilter tf) { |
| if (tf.getName() != null) { |
| return String.format("%s (%s)", tf.getName(), tf.getClass().getName()); |
| } else { |
| return tf.getClass().getName(); |
| } |
| } |
| |
| private static String formatPid(final String consoleAppRoot, final TurboFilter tf, |
| final LoggerStateContext ctx) { |
| ServiceReference sr = ctx.getTurboFilterRef(tf); |
| if (sr != null) { |
| final String pid = sr.getProperty(Constants.SERVICE_ID).toString(); |
| return createUrl(consoleAppRoot, "services", pid); |
| } else { |
| return "[config]"; |
| } |
| } |
| |
| private static String getName(Appender<ILoggingEvent> appender) throws UnsupportedEncodingException { |
| // For normal file appender we also display the name of appender |
| if (appender instanceof FileAppender) { |
| return String.format("File : [%s] %s", appender.getName(), ((FileAppender) appender).getFile()); |
| } |
| |
| final String appenderName = appender.getName(); |
| if(appenderName == null){ |
| return appender.getClass().getName(); |
| } else { |
| return String.format("%s (%s)", appender.getName(), appender.getClass().getName()); |
| } |
| } |
| |
| private static String formatPid(final String consoleAppRoot, final Appender<ILoggingEvent> appender, |
| final LoggerStateContext ctx) { |
| if (appender instanceof SlingRollingFileAppender) { |
| final LogWriter lw = ((SlingRollingFileAppender) appender).getLogWriter(); |
| String pid = lw.getConfigurationPID(); |
| if (lw.isImplicit()) { |
| pid = lw.getImplicitConfigPID(); |
| } |
| return createUrl(consoleAppRoot, "configMgr", pid); |
| } else if (ctx.isDynamicAppender(appender)) { |
| final AppenderInfo ai = ctx.dynamicAppenders.get(appender); |
| |
| final String pid = ai.pid; |
| return createUrl(consoleAppRoot, "services", pid); |
| } else { |
| return "[others]"; |
| } |
| } |
| |
| private static String getConfigColTitle(String consoleAppRoot) { |
| return (consoleAppRoot == null) ? "PID" : "Configuration"; |
| } |
| |
| private static String createUrl(final String consoleAppRoot, final String subContext, final String pid) { |
| return createUrl(consoleAppRoot, subContext, pid, false); |
| } |
| |
| private static String createUrl(final String consoleAppRoot, final String subContext, final String pid, final boolean inlineEditable) { |
| // no recent web console, so just render the pid as the link |
| if (consoleAppRoot == null) { |
| return "<a href=\"" + subContext + "/" + XmlUtil.escapeXml(pid) + "\">" + XmlUtil.escapeXml(pid) + "</a>"; |
| } |
| |
| // recent web console has app root and hence we can use an image |
| String classAttr = "class=\"configureLink\""; |
| if (!inlineEditable) { |
| classAttr = ""; |
| } |
| |
| return "<a " + classAttr + " href=\"" + subContext + "/" + XmlUtil.escapeXml(pid) + "\"><img src=\"" + consoleAppRoot |
| + "/res/imgs/component_configure.png\" border=\"0\" /></a>"; |
| } |
| |
| private static String getPath(String path, final String rootPath, final boolean shortenPaths) { |
| if (shortenPaths && path != null && path.startsWith(rootPath)) { |
| // if the shortenPath parameter is set (all log files are in the same folder) |
| // remove the root path (root log file folder) from the paths |
| path = path.substring(rootPath.length() + 1); |
| } |
| return (path != null) ? path : "[stdout]"; |
| } |
| |
| // ~------------------------------------------------Status Manager |
| // Based on ch.qos.logback.core.status.ViewStatusMessagesServletBase |
| |
| private static String statusLevelAsString(Status s) { |
| switch (s.getEffectiveLevel()) { |
| case Status.INFO: |
| return "INFO"; |
| case Status.WARN: |
| return "<span class=\"warn\">WARN</span>"; |
| case Status.ERROR: |
| return "<span class=\"error\">ERROR</span>"; |
| } |
| return null; |
| } |
| |
| private static void printThrowable(PrintWriter pw, Throwable t) { |
| pw.println(" <tr>"); |
| pw.println(" <td colspan=\"4\" class=\"exception\"><pre>"); |
| StringWriter sw = new StringWriter(); |
| PrintWriter expPw = new PrintWriter(sw); |
| t.printStackTrace(expPw); |
| pw.println(Transform.escapeTags(sw.getBuffer())); |
| pw.println(" </pre></td>"); |
| pw.println(" </tr>"); |
| } |
| } |