incubator-sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From chet...@apache.org
Subject svn commit: r1547417 - in /sling/trunk/bundles/commons/log/src: main/appended-resources/META-INF/ main/java/org/apache/sling/commons/log/logback/internal/ main/resources/res/ui/ test/java/org/apache/sling/commons/log/logback/integration/ test/java/org/...
Date Tue, 03 Dec 2013 15:01:23 GMT
Author: chetanm
Date: Tue Dec  3 15:01:23 2013
New Revision: 1547417

URL: http://svn.apache.org/r1547417
Log:
SLING-2897 - [LOG] Enhance web console plugin with edit feature

Applying a modified patch from Bjoern Weide. Thanks!

Added:
    sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.css   (with props)
    sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.min.js   (with props)
    sling/trunk/bundles/commons/log/src/main/resources/res/ui/slinglog.js   (with props)
Modified:
    sling/trunk/bundles/commons/log/src/main/appended-resources/META-INF/NOTICE
    sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogConfigManager.java
    sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogbackManager.java
    sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java
    sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/ITWebConsoleRemote.java
    sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/remote/WebConsoleTestActivator.java

Modified: sling/trunk/bundles/commons/log/src/main/appended-resources/META-INF/NOTICE
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/main/appended-resources/META-INF/NOTICE?rev=1547417&r1=1547416&r2=1547417&view=diff
==============================================================================
--- sling/trunk/bundles/commons/log/src/main/appended-resources/META-INF/NOTICE (original)
+++ sling/trunk/bundles/commons/log/src/main/appended-resources/META-INF/NOTICE Tue Dec  3 15:01:23 2013
@@ -7,3 +7,6 @@ Licensed under the Apache License 2.0.
 
 This product includes software from http://www.slf4j.org/
 Licensed under the MIT License
+
+This product includes software from https://github.com/dyve/jquery-autocomplete
+Licensed under the Apache License 2.0.

Modified: sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogConfigManager.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogConfigManager.java?rev=1547417&r1=1547416&r2=1547417&view=diff
==============================================================================
--- sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogConfigManager.java (original)
+++ sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogConfigManager.java Tue Dec  3 15:01:23 2013
@@ -207,6 +207,14 @@ public class LogConfigManager implements
         return logbackConfigFile;
     }
 
+    public Iterable<LogConfig> getLogConfigs() {
+        return configByPid.values();
+    }
+
+    public Iterable<LogWriter> getLogWriters(){
+        return writerByFileName.values();
+    }
+
     public Appender<ILoggingEvent> getDefaultAppender() {
         OutputStreamAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
         appender.setName(DEFAULT_CONSOLE_APPENDER_NAME);
@@ -620,7 +628,7 @@ public class LogConfigManager implements
         return new LogWriter(getAppnderName(logWriterName),logWriterName, defaultWriter.getLogNumber(), defaultWriter.getLogRotation());
     }
 
-    private LogWriter getDefaultWriter() {
+    public LogWriter getDefaultWriter() {
         return writerByPid.get(LogConfigManager.PID);
     }
 
@@ -632,9 +640,7 @@ public class LogConfigManager implements
         return getDefaultConfig().createLayout();
     }
 
-    Iterable<LogConfig> getLogConfigs() {
-        return configByPid.values();
-    }
+
 
     /**
      * Returns the <code>logFileName</code> argument converted into an absolute

Modified: sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogbackManager.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogbackManager.java?rev=1547417&r1=1547416&r2=1547417&view=diff
==============================================================================
--- sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogbackManager.java (original)
+++ sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/LogbackManager.java Tue Dec  3 15:01:23 2013
@@ -36,6 +36,7 @@ import ch.qos.logback.core.status.Status
 import ch.qos.logback.core.status.StatusUtil;
 import ch.qos.logback.core.util.StatusPrinter;
 import org.apache.sling.commons.log.logback.internal.AppenderTracker.AppenderInfo;
+import org.apache.sling.commons.log.logback.internal.util.SlingRollingFileAppender;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -47,16 +48,31 @@ import org.osgi.util.tracker.ServiceTrac
 import org.slf4j.LoggerFactory;
 
 public class LogbackManager extends LoggerContextAwareBase {
+    //These properties should have been defined in SlingLogPanel
+    //But we need them while registering ServiceFactory and hence
+    //would not want to load SlingLogPanel class for registration
+    //purpose as we need to run in cases where Servlet classes
+    //are not available
+    static final String APP_ROOT = "slinglog";
+
+    static final String RES_LOC = APP_ROOT + "/res/ui";
+
+    public static final String[] CSS_REFS = {
+            RES_LOC + "/jquery.autocomplete.css",
+            RES_LOC + "/prettify.css",
+            RES_LOC + "/log.css",
+    };
+
     private static final String PREFIX = "org.apache.sling.commons.log";
 
     private static final String DEBUG = PREFIX + "." + "debug";
 
-    private static final String PLUGIN_URL = "slinglog";
-
     private static final String PRINTER_URL = "slinglogs";
 
     private static final String RESET_EVENT_TOPIC = "org/apache/sling/commons/log/RESET";
 
+    private final BundleContext bundleContext;
+
     private final String rootDir;
 
     private final String contextName = "sling";
@@ -89,6 +105,7 @@ public class LogbackManager extends Logg
     private final TurboFilterTracker turboFilterTracker;
 
     private final List<ServiceRegistration> registrations = new ArrayList<ServiceRegistration>();
+
     private final List<ServiceTracker> serviceTrackers = new ArrayList<ServiceTracker>();
 
     /**
@@ -99,16 +116,19 @@ public class LogbackManager extends Logg
 
     public LogbackManager(BundleContext bundleContext) throws InvalidSyntaxException {
         final long startTime = System.currentTimeMillis();
+        this.bundleContext = bundleContext;
+
         setLoggerContext((LoggerContext) LoggerFactory.getILoggerFactory());
+
         this.log = LoggerFactory.getLogger(getClass());
         this.rootDir = getRootDir(bundleContext);
-
         this.debug = Boolean.parseBoolean(bundleContext.getProperty(DEBUG));
 
         this.appenderTracker = new AppenderTracker(bundleContext, getLoggerContext());
         this.configSourceTracker = new ConfigSourceTracker(bundleContext, this);
         this.filterTracker = new FilterTracker(bundleContext,this);
         this.turboFilterTracker = new TurboFilterTracker(bundleContext,getLoggerContext());
+
         // TODO Make it configurable
         // TODO: what should it be ?
         getLoggerContext().setName(contextName);
@@ -129,8 +149,8 @@ public class LogbackManager extends Logg
         getLoggerContext().addListener(osgiIntegrationListener);
 
         configure();
-        registerWebConsoleSupport(bundleContext);
-        registerEventHandler(bundleContext);
+        registerWebConsoleSupport();
+        registerEventHandler();
         StatusPrinter.printInCaseOfErrorsOrWarnings(getLoggerContext(), startTime);
         started = true;
     }
@@ -190,6 +210,10 @@ public class LogbackManager extends Logg
         return getClass().getClassLoader().getResource("logback-empty.xml");
     }
 
+    public String getRootDir() {
+        return rootDir;
+    }
+
     private void configure() {
         ConfiguratorCallback cb = new DefaultCallback();
 
@@ -434,25 +458,45 @@ public class LogbackManager extends Logg
     public LoggerStateContext determineLoggerState() {
         final List<Logger> loggers = getLoggerContext().getLoggerList();
         final LoggerStateContext ctx = new LoggerStateContext(loggers);
-        for (Logger logger : loggers) {
-            if (logger.iteratorForAppenders().hasNext() || logger.getLevel() != null) {
-                ctx.loggerInfos.add(logger);
+
+        //Distinguish between Logger configured via
+        //1. OSGi Config - The ones configured via ConfigAdmin
+        //2. Other means - Configured via Logback config or any other means
+        for (LogConfig lc : logConfigManager.getLogConfigs()) {
+            for (String category : lc.getCategories()) {
+                ctx.osgiConfiguredLoggers.put(category, lc);
             }
+        }
 
+        for (Logger logger : loggers) {
+            boolean hasOnlySlingRollingAppenders = true;
             Iterator<Appender<ILoggingEvent>> itr = logger.iteratorForAppenders();
             while (itr.hasNext()) {
                 Appender<ILoggingEvent> a = itr.next();
                 if (a.getName() != null && !ctx.appenders.containsKey(a.getName())) {
                     ctx.appenders.put(a.getName(), a);
                 }
+
+                if(!(a instanceof SlingRollingFileAppender)){
+                    hasOnlySlingRollingAppenders = false;
+                }
             }
-        }
 
-        for (LogConfig lc : logConfigManager.getLogConfigs()) {
-            for (String category : lc.getCategories()) {
-                ctx.loggerNameToConfigMapping.put(category, lc);
+            if(logger.getLevel() == null){
+                continue;
+            }
+
+            boolean configuredViaOSGiConfig =
+                    ctx.osgiConfiguredLoggers.containsKey(logger.getName());
+            if (!configuredViaOSGiConfig
+                    || (configuredViaOSGiConfig && !hasOnlySlingRollingAppenders))
+                    {
+                ctx.nonOSgiConfiguredLoggers.add(logger);
             }
+
         }
+
+
         return ctx;
     }
 
@@ -464,9 +508,9 @@ public class LogbackManager extends Logg
         /**
          * List of logger which have explicitly defined level or appenders set
          */
-        final List<Logger> loggerInfos = new ArrayList<Logger>();
+        final List<Logger> nonOSgiConfiguredLoggers = new ArrayList<Logger>();
 
-        final Map<String,LogConfig> loggerNameToConfigMapping = new HashMap<String, LogConfig>();
+        final Map<String,LogConfig> osgiConfiguredLoggers = new HashMap<String, LogConfig>();
 
         final Map<String, Appender<ILoggingEvent>> appenders = new HashMap<String, Appender<ILoggingEvent>>();
 
@@ -517,24 +561,22 @@ public class LogbackManager extends Logg
         }
 
         LogConfig getConfig(String loggerName) {
-            return loggerNameToConfigMapping.get(loggerName);
+            return osgiConfiguredLoggers.get(loggerName);
         }
     }
 
-    private void registerWebConsoleSupport(BundleContext context) {
-        final ServiceFactory serviceFactory = new PluginServiceFactory(PLUGIN_URL);
+    private void registerWebConsoleSupport() {
+        final ServiceFactory serviceFactory = new PluginServiceFactory();
 
         Properties pluginProps = new Properties();
         pluginProps.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
         pluginProps.put(Constants.SERVICE_DESCRIPTION, "Sling Log Support");
-        pluginProps.put("felix.webconsole.label", PLUGIN_URL);
+        pluginProps.put("felix.webconsole.label", APP_ROOT);
         pluginProps.put("felix.webconsole.title", "Log Support");
         pluginProps.put("felix.webconsole.category", "Sling");
-        pluginProps.put("felix.webconsole.css", new String[] {
-            "/" + PLUGIN_URL + "/res/ui/prettify.css", "/" + PLUGIN_URL + "/res/ui/log.css"
-        });
+        pluginProps.put("felix.webconsole.css", CSS_REFS);
 
-        registrations.add(context.registerService("javax.servlet.Servlet", serviceFactory, pluginProps));
+        registrations.add(bundleContext.registerService("javax.servlet.Servlet", serviceFactory, pluginProps));
 
         Properties printerProps = new Properties();
         printerProps.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
@@ -544,23 +586,17 @@ public class LogbackManager extends Logg
         printerProps.put("felix.webconsole.configprinter.modes", "always");
 
         // TODO need to see to add support for Inventory Feature
-        registrations.add(context.registerService(SlingConfigurationPrinter.class.getName(),
+        registrations.add(bundleContext.registerService(SlingConfigurationPrinter.class.getName(),
             new SlingConfigurationPrinter(this), printerProps));
     }
 
     private class PluginServiceFactory implements ServiceFactory {
         private Object instance;
 
-        private final String label;
-
-        private PluginServiceFactory(String label) {
-            this.label = label;
-        }
-
         public Object getService(Bundle bundle, ServiceRegistration registration) {
             synchronized (this) {
                 if (this.instance == null) {
-                    this.instance = new SlingLogPanel(LogbackManager.this, label);
+                    this.instance = new SlingLogPanel(LogbackManager.this,bundleContext);
                 }
                 return instance;
             }
@@ -570,7 +606,7 @@ public class LogbackManager extends Logg
         }
     }
 
-    private void registerEventHandler(BundleContext bundleContext) {
+    private void registerEventHandler() {
         Properties props = new Properties();
         props.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
         props.put(Constants.SERVICE_DESCRIPTION, "Sling Log Reset Event Handler");

Modified: sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java?rev=1547417&r1=1547416&r2=1547417&view=diff
==============================================================================
--- sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java (original)
+++ sling/trunk/bundles/commons/log/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java Tue Dec  3 15:01:23 2013
@@ -27,13 +27,19 @@ import java.io.StringWriter;
 import java.net.URL;
 import java.net.URLConnection;
 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 javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+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;
@@ -45,20 +51,26 @@ import ch.qos.logback.core.status.Status
 import ch.qos.logback.core.util.CachingDateFormatter;
 import org.apache.sling.commons.log.logback.internal.AppenderTracker.AppenderInfo;
 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.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 static org.apache.sling.commons.log.logback.internal.ConfigSourceTracker.ConfigSourceInfo;
+import static org.apache.sling.commons.log.logback.internal.LogbackManager.APP_ROOT;
+import static org.apache.sling.commons.log.logback.internal.LogbackManager.RES_LOC;
 
 /**
  * The <code>SlingLogPanel</code> is a Felix Web Console plugin to display the
  * current active log bundle configuration.
- * <p>
+ * <p/>
  * In future revisions of this plugin, the configuration may probably even be
  * modified through this panel.
  */
@@ -68,7 +80,18 @@ public class SlingLogPanel extends HttpS
 
     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,
+    };
+
+    private static final String PACKAGE_SEPARATOR = ".";
+
     private final LogbackManager logbackManager;
+    private final BundleContext bundleContext;
 
     private final String labelRes;
 
@@ -76,9 +99,10 @@ public class SlingLogPanel extends HttpS
 
     private static final org.slf4j.Logger log = LoggerFactory.getLogger(SlingLogPanel.class);
 
-    public SlingLogPanel(final LogbackManager logbackManager, String label) {
+    public SlingLogPanel(final LogbackManager logbackManager, BundleContext bundleContext) {
         this.logbackManager = logbackManager;
-        this.labelRes = '/' + label + '/';
+        this.bundleContext = bundleContext;
+        this.labelRes = '/' + APP_ROOT + '/';
         this.labelResLen = labelRes.length() - 1;
     }
 
@@ -92,32 +116,161 @@ public class SlingLogPanel extends HttpS
 
         final LoggerStateContext ctx = logbackManager.determineLoggerState();
         appendLoggerStatus(pw, ctx);
-        appendLoggerData(pw, consoleAppRoot, ctx);
+        appendOsgiConfiguredLoggerData(pw, consoleAppRoot);
+        appendOtherLoggerData(pw, ctx);
         addAppenderData(pw, consoleAppRoot, ctx);
-        appendTurboFilterData(pw, consoleAppRoot,ctx);
+        appendTurboFilterData(pw, consoleAppRoot, ctx);
         appendLogbackMainConfig(pw);
         appendLogbackFragments(pw, consoleAppRoot);
         appendLogbackStatus(pw, ctx);
-        enablePrettifier(pw, pluginRoot);
+        addScriptBlock(pw, ctx);
     }
 
-
-    private void enablePrettifier(PrintWriter pw, String pluginRoot) {
-        pw.printf("<script type=\"text/javascript\" src=\"%s/res/ui/prettify.js\"></script>", pluginRoot);
+    @Override
+    protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
+            throws ServletException, IOException {
+        // check if a configuration should be deleted
+        boolean isDelete = req.getParameter("delete") != null;
+        // get the configuration pid
+        String pid = req.getParameter("pid");
+        try {
+            if (isDelete) {
+                // in delete mode remove the logger with the given pid
+                removeLogger(pid);
+            } else {
+                // get the logger parameters and configure the logger
+                // if the given pid is empty a new logger with be created
+                String logger = req.getParameter("logger");
+                String logLevel = req.getParameter("loglevel");
+                String logFile = req.getParameter("logfile");
+                String[] loggers = req.getParameterValues("logger");
+                if (null != logger) {
+                    configureLogger(pid, logLevel, loggers, logFile);
+                }
+            }
+        } catch (ConfigurationException e) {
+            internalFailure("", e);
+        }
+        // send the redirect back to the logpanel
+        final String consoleAppRoot = (String) req
+                .getAttribute("felix.webconsole.appRoot");
+        resp.sendRedirect(consoleAppRoot + "/" + APP_ROOT);
+    }
+
+    private void addScriptBlock(PrintWriter pw, 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("'" + logger + "'");
+            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</p>%n",
-            ctx.getNumberOfLoggers(), ctx.getNumOfAppenders(), ctx.getNumOfDynamicAppenders());
+                "<p class='statline'>Log Service Stats: %d categories, %d appender, %d Dynamic appenders</p>%n",
+                ctx.getNumberOfLoggers(), ctx.getNumOfAppenders(), ctx.getNumOfDynamicAppenders());
     }
 
-    private void appendLoggerData(PrintWriter pw, String consoleAppRoot, LoggerStateContext ctx) {
+    private void appendOsgiConfiguredLoggerData(PrintWriter pw, String consoleAppRoot) {
         pw.println("<div class='table'>");
 
-        pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>Logger</div>");
+        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>Log File</th>");
+        pw.println("<th>Logger</th>");
+        pw.println("<th width=\"20%\">" + getConfigColTitle(consoleAppRoot) + "</th>");
+        pw.println("</tr>");
+        pw.println("</thead>");
+        pw.println("<tbody class='ui-widget-content'>");
+
+        final LogConfigManager configManager = logbackManager.getLogConfigManager();
+        String rootPath = logbackManager.getRootDir();
+        boolean shortenPaths = areAllLogfilesInSameFolder(configManager.getLogWriters(), rootPath);
+        for (LogConfig logConfig : configManager.getLogConfigs()) {
+            pw.println("<tr id=\"" + logConfig.getConfigPid() + "\">");
+            pw.println("<td><span class=\"logLevels\" data-currentloglevel=\""
+                    + logConfig.getLogLevel().levelStr + "\">" + logConfig.getLogLevel().levelStr
+                    + "</span></td>");
+            pw.println("<td><span class=\"logFile\">" + getPath(logConfig.getLogWriterName(), rootPath, shortenPaths) + "</span></td>");
+
+            pw.println("<td><span class=\"loggers\">");
+            String sep = "";
+            for (String cat : logConfig.getCategories()) {
+                pw.println(sep + "<span class=\"logger\">" + cat + "</span>");
+                sep = "<br />";
+            }
+            pw.println("</td>");
+
+            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.println("<td>" + url + "</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 (String levelName : LEVEL_NAMES) {
+            pw.print(sep + levelName);
+            sep = ",";
+        }
+
+        pw.println("\"></span></td>");
+        pw.println("<td><span id=\"defaultLogfile\" data-defaultlogfile=\""
+                + getPath(configManager.getDefaultWriter().getFileName(), rootPath, shortenPaths)
+                + "\" 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(PrintWriter pw, LoggerStateContext ctx) {
+        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'>");
 
@@ -127,12 +280,11 @@ public class SlingLogPanel extends HttpS
         pw.println("<th>Additivity</th>");
         pw.println("<th>Name</th>");
         pw.println("<th>Appender</th>");
-        pw.println("<th>" + getConfigColTitle(consoleAppRoot) + "</th>");
         pw.println("</tr>");
         pw.println("</thead>");
         pw.println("<tbody class='ui-widget-content'>");
 
-        for (Logger logger : ctx.loggerInfos) {
+        for (Logger logger : ctx.nonOSgiConfiguredLoggers) {
             pw.println("<tr>");
             pw.println("<td>" + logger.getLevel() + "</td>");
             pw.println("<td>" + Boolean.toString(logger.isAdditive()) + "</td>");
@@ -149,21 +301,6 @@ public class SlingLogPanel extends HttpS
             }
             pw.println("</ul>");
             pw.println("</td>");
-
-            pw.println("<td>");
-            pw.println("<ul>");
-            Iterator<Appender<ILoggingEvent>> itr2 = logger.iteratorForAppenders();
-            while (itr2.hasNext()) {
-                Appender<ILoggingEvent> a = itr2.next();
-                pw.print("<li>");
-                if(a instanceof SlingRollingFileAppender){
-                    pw.print(formatPidForLogger(consoleAppRoot, ctx, logger.getName()));
-                }
-                pw.print("</li>");
-            }
-            pw.println("</ul>");
-            pw.println("</td>");
-
             pw.println("</tr>");
         }
 
@@ -199,8 +336,8 @@ public class SlingLogPanel extends HttpS
         pw.println("</div>");
     }
 
-    private void appendTurboFilterData(PrintWriter pw, String consoleAppRoot,LoggerStateContext ctx) {
-        if(ctx.loggerContext.getTurboFilterList().isEmpty()){
+    private void appendTurboFilterData(PrintWriter pw, String consoleAppRoot, LoggerStateContext ctx) {
+        if (ctx.loggerContext.getTurboFilterList().isEmpty()) {
             return;
         }
 
@@ -219,7 +356,7 @@ public class SlingLogPanel extends HttpS
         pw.println("<tbody class='ui-widget-content'>");
 
 
-        for(TurboFilter tf : ctx.loggerContext.getTurboFilterList()){
+        for (TurboFilter tf : ctx.loggerContext.getTurboFilterList()) {
             pw.println("<tr>");
             pw.println("<td>" + getName(tf) + "</td>");
             pw.println("<td>" + formatPid(consoleAppRoot, tf, ctx) + "</td>");
@@ -362,7 +499,7 @@ public class SlingLogPanel extends HttpS
      * resources are accessed like <code>/system/console/abc/res/logo.gif</code>
      * , the code here will try load resource <code>/res/logo.gif</code> from
      * the bundle, providing the plugin.
-     * 
+     *
      * @param path the path to read.
      * @return the URL of the resource or <code>null</code> if not found.
      */
@@ -373,10 +510,135 @@ public class SlingLogPanel extends HttpS
                 : null;
     }
 
+    /**
+     * Checks if all log files are in the same folder, then the path can displayed shortened in the panel.
+     *
+     * @param logWriters list of log writers
+     * @param rootPath   root path
+     * @return true if all logfiles are in the same folder
+     */
+    private boolean areAllLogfilesInSameFolder(final Iterable<LogWriter> logWriters, final String rootPath) {
+        String lastPath = null;
+        for (final LogWriter writer : logWriters) {
+            String path = getPath(writer.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;
+    }
+
+    /**
+     * 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)
+     * @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)
+            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);
+                        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 static String getName(TurboFilter tf) {
-        if(tf.getName() != null){
+        if (tf.getName() != null) {
             return String.format("%s (%s)", tf.getName(), tf.getClass().getName());
-        } else{
+        } else {
             return tf.getClass().getName();
         }
     }
@@ -401,22 +663,8 @@ public class SlingLogPanel extends HttpS
         return String.format("%s (%s)", appender.getName(), appender.getClass().getName());
     }
 
-    private static String formatPidForLogger(final String consoleAppRoot,
-                                             final LoggerStateContext ctx, final String loggerName) {
-        //Each logger configured via OSGi config would have a
-        //backing LogConfig. Extract PID from that
-        final LogConfig lc = ctx.getConfig(loggerName);
-        if (lc != null) {
-            String pid = lc.getConfigPid();
-            return createUrl(consoleAppRoot, "configMgr", pid);
-        }
-
-        //Should not happen
-        return null;
-    }
-
     private static String formatPid(final String consoleAppRoot, final Appender<ILoggingEvent> appender,
-            final LoggerStateContext ctx) {
+                                    final LoggerStateContext ctx) {
         if (appender instanceof SlingRollingFileAppender) {
             final LogWriter lw = ((SlingRollingFileAppender) appender).getLogWriter();
             String pid = lw.getConfigurationPID();
@@ -439,16 +687,35 @@ public class SlingLogPanel extends HttpS
     }
 
     private static String createUrl(String consoleAppRoot, String subContext, String pid) {
+        return createUrl(consoleAppRoot, subContext, pid, false);
+    }
+
+    private static String createUrl(String consoleAppRoot, String subContext, String pid, boolean inlineEditable) {
         // no recent web console, so just render the pid as the link
         if (consoleAppRoot == null) {
             return "<a href=\"" + subContext + "/" + pid + "\">" + pid + "</a>";
         }
 
         // recent web console has app root and hence we can use an image
-        return "<a href=\"" + subContext + "/" + pid + "\"><img src=\"" + consoleAppRoot
-            + "/res/imgs/component_configure.png\" border=\"0\" /></a>";
+        String classAttr = "class=\"configureLink\"";
+        if (!inlineEditable) {
+            classAttr = "";
+        }
+
+        return "<a " + classAttr + " href=\"" + subContext + "/" + 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) {
+            // 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
 

Added: sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.css
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.css?rev=1547417&view=auto
==============================================================================
--- sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.css (added)
+++ sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.css Tue Dec  3 15:01:23 2013
@@ -0,0 +1,38 @@
+/**
+ * @fileOverview CSS for jquery-autocomplete, the jQuery Autocompleter
+ * @author <a href="mailto:dylan@dyve.net">Dylan Verheul</a>
+ * @license MIT | GPL | Apache 2.0, see LICENSE.txt
+ * @see https://github.com/dyve/jquery-autocomplete
+ */
+.acResults {
+    padding: 0px;
+    border: 1px solid WindowFrame;
+    background-color: Window;
+    overflow: hidden;
+}
+
+.acResults ul {
+    margin: 0px;
+    padding: 0px;
+    list-style-position: outside;
+    list-style: none;
+}
+
+.acResults ul li {
+    margin: 0px;
+    padding: 2px 5px;
+    cursor: pointer;
+    display: block;
+    font: menu;
+    font-size: 12px;
+    overflow: hidden;
+}
+
+.acLoading {
+    background : url('indicator.gif') right center no-repeat;
+}
+
+.acSelect {
+    background-color: Highlight;
+    color: HighlightText;
+}
\ No newline at end of file

Propchange: sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.css
------------------------------------------------------------------------------
    svn:eol-style = native

Added: sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.min.js
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.min.js?rev=1547417&view=auto
==============================================================================
--- sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.min.js (added)
+++ sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.min.js Tue Dec  3 15:01:23 2013
@@ -0,0 +1,9 @@
+/**
+ * @fileOverview jquery-autocomplete, the jQuery Autocompleter
+ * @author <a href="mailto:dylan@dyve.net">Dylan Verheul</a>
+ * @version 2.4.4
+ * @requires jQuery 1.6+
+ * @license MIT | GPL | Apache 2.0, see LICENSE.txt
+ * @see https://github.com/dyve/jquery-autocomplete
+ */
+(function($){"use strict";$.fn.autocomplete=function(options){var url;if(arguments.length>1){url=options;options=arguments[1];options.url=url}else if(typeof options==="string"){url=options;options={url:url}}var opts=$.extend({},$.fn.autocomplete.defaults,options);return this.each(function(){var $this=$(this);$this.data("autocompleter",new $.Autocompleter($this,$.meta?$.extend({},opts,$this.data()):opts))})};$.fn.autocomplete.defaults={inputClass:"acInput",loadingClass:"acLoading",resultsClass:"acResults",selectClass:"acSelect",queryParamName:"q",extraParams:{},remoteDataType:false,lineSeparator:"\n",cellSeparator:"|",minChars:2,maxItemsToShow:10,delay:400,useCache:true,maxCacheLength:10,matchSubset:true,matchCase:false,matchInside:true,mustMatch:false,selectFirst:false,selectOnly:false,showResult:null,preventDefaultReturn:1,preventDefaultTab:0,autoFill:false,filterResults:true,filter:true,sortResults:true,sortFunction:null,onItemSelect:null,onNoMatch:null,onFinish:null,matchStringCo
 nverter:null,beforeUseConverter:null,autoWidth:"min-width",useDelimiter:false,delimiterChar:",",delimiterKeyCode:188,processData:null,onError:null,enabled:true};var sanitizeResult=function(result){var value,data;var type=typeof result;if(type==="string"){value=result;data={}}else if($.isArray(result)){value=result[0];data=result.slice(1)}else if(type==="object"){value=result.value;data=result.data}value=String(value);if(typeof data!=="object"){data={}}return{value:value,data:data}};var sanitizeInteger=function(value,stdValue,options){var num=parseInt(value,10);options=options||{};if(isNaN(num)||options.min&&num<options.min){num=stdValue}return num};var makeUrlParam=function(name,value){return[name,encodeURIComponent(value)].join("=")};var makeUrl=function(url,params){var urlAppend=[];$.each(params,function(index,value){urlAppend.push(makeUrlParam(index,value))});if(urlAppend.length){url+=url.indexOf("?")===-1?"?":"&";url+=urlAppend.join("&")}return url};var sortValueAlpha=function(a
 ,b,matchCase){a=String(a.value);b=String(b.value);if(!matchCase){a=a.toLowerCase();b=b.toLowerCase()}if(a>b){return 1}if(a<b){return-1}return 0};var plainTextParser=function(text,lineSeparator,cellSeparator){var results=[];var i,j,data,line,value,lines;lines=String(text).replace("\r\n","\n").split(lineSeparator);for(i=0;i<lines.length;i++){line=lines[i].split(cellSeparator);data=[];for(j=0;j<line.length;j++){data.push(decodeURIComponent(line[j]))}value=data.shift();results.push({value:value,data:data})}return results};$.Autocompleter=function($elem,options){if(!$elem||!($elem instanceof $)||$elem.length!==1||$elem.get(0).tagName.toUpperCase()!=="INPUT"){throw new Error("Invalid parameter for jquery.Autocompleter, jQuery object with one element with INPUT tag expected.")}var self=this;this.options=options;this.cacheData_={};this.cacheLength_=0;this.selectClass_="jquery-autocomplete-selected-item";this.keyTimeout_=null;this.finishTimeout_=null;this.lastKeyPressed_=null;this.lastProces
 sedValue_=null;this.lastSelectedValue_=null;this.active_=false;this.finishOnBlur_=true;this.options.minChars=sanitizeInteger(this.options.minChars,$.fn.autocomplete.defaults.minChars,{min:0});this.options.maxItemsToShow=sanitizeInteger(this.options.maxItemsToShow,$.fn.autocomplete.defaults.maxItemsToShow,{min:0});this.options.maxCacheLength=sanitizeInteger(this.options.maxCacheLength,$.fn.autocomplete.defaults.maxCacheLength,{min:1});this.options.delay=sanitizeInteger(this.options.delay,$.fn.autocomplete.defaults.delay,{min:0});if(this.options.preventDefaultReturn!=2){this.options.preventDefaultReturn=this.options.preventDefaultReturn?1:0}if(this.options.preventDefaultTab!=2){this.options.preventDefaultTab=this.options.preventDefaultTab?1:0}this.dom={};this.dom.$elem=$elem;this.dom.$elem.attr("autocomplete","off").addClass(this.options.inputClass);this.dom.$results=$("<div></div>").hide().addClass(this.options.resultsClass).css({position:"absolute"});$("body").append(this.dom.$resul
 ts);$elem.keydown(function(e){self.lastKeyPressed_=e.keyCode;switch(self.lastKeyPressed_){case self.options.delimiterKeyCode:if(self.options.useDelimiter&&self.active_){self.selectCurrent()}break;case 35:case 36:case 16:case 17:case 18:case 37:case 39:break;case 38:e.preventDefault();if(self.active_){self.focusPrev()}else{self.activate()}return false;case 40:e.preventDefault();if(self.active_){self.focusNext()}else{self.activate()}return false;case 9:if(self.active_){self.selectCurrent();if(self.options.preventDefaultTab){e.preventDefault();return false}}if(self.options.preventDefaultTab===2){e.preventDefault();return false}break;case 13:if(self.active_){self.selectCurrent();if(self.options.preventDefaultReturn){e.preventDefault();return false}}if(self.options.preventDefaultReturn===2){e.preventDefault();return false}break;case 27:if(self.active_){e.preventDefault();self.deactivate(true);return false}break;default:self.activate()}});$elem.on("paste",function(){self.activate()});var 
 onBlurFunction=function(){self.deactivate(true)};$elem.blur(function(){if(self.finishOnBlur_){self.finishTimeout_=setTimeout(onBlurFunction,200)}});$elem.parents("form").on("submit",onBlurFunction)};$.Autocompleter.prototype.position=function(){var offset=this.dom.$elem.offset();var height=this.dom.$results.outerHeight();var totalHeight=$(window).outerHeight();var inputBottom=offset.top+this.dom.$elem.outerHeight();var bottomIfDown=inputBottom+height;var position={top:inputBottom,left:offset.left};if(bottomIfDown>totalHeight){var topIfUp=offset.top-height;if(topIfUp>=0){position.top=topIfUp}}this.dom.$results.css(position)};$.Autocompleter.prototype.cacheRead=function(filter){var filterLength,searchLength,search,maxPos,pos;if(this.options.useCache){filter=String(filter);filterLength=filter.length;if(this.options.matchSubset){searchLength=1}else{searchLength=filterLength}while(searchLength<=filterLength){if(this.options.matchInside){maxPos=filterLength-searchLength}else{maxPos=0}pos=
 0;while(pos<=maxPos){search=filter.substr(0,searchLength);if(this.cacheData_[search]!==undefined){return this.cacheData_[search]}pos++}searchLength++}}return false};$.Autocompleter.prototype.cacheWrite=function(filter,data){if(this.options.useCache){if(this.cacheLength_>=this.options.maxCacheLength){this.cacheFlush()}filter=String(filter);if(this.cacheData_[filter]!==undefined){this.cacheLength_++}this.cacheData_[filter]=data;return this.cacheData_[filter]}return false};$.Autocompleter.prototype.cacheFlush=function(){this.cacheData_={};this.cacheLength_=0};$.Autocompleter.prototype.callHook=function(hook,data){var f=this.options[hook];if(f&&$.isFunction(f)){return f(data,this)}return false};$.Autocompleter.prototype.activate=function(){if(!this.options.enabled)return;var self=this;if(this.keyTimeout_){clearTimeout(this.keyTimeout_)}this.keyTimeout_=setTimeout(function(){self.activateNow()},this.options.delay)};$.Autocompleter.prototype.activateNow=function(){var value=this.beforeUse
 Converter(this.dom.$elem.val());if(value!==this.lastProcessedValue_&&value!==this.lastSelectedValue_){this.fetchData(value)}};$.Autocompleter.prototype.fetchData=function(value){var self=this;var processResults=function(results,filter){if(self.options.processData){results=self.options.processData(results)}self.showResults(self.filterResults(results,filter),filter)};this.lastProcessedValue_=value;if(value.length<this.options.minChars){processResults([],value)}else if(this.options.data){processResults(this.options.data,value)}else{this.fetchRemoteData(value,function(remoteData){processResults(remoteData,value)})}};$.Autocompleter.prototype.fetchRemoteData=function(filter,callback){var data=this.cacheRead(filter);if(data){callback(data)}else{var self=this;var dataType=self.options.remoteDataType==="json"?"json":"text";var ajaxCallback=function(data){var parsed=false;if(data!==false){parsed=self.parseRemoteData(data);self.cacheWrite(filter,parsed)}self.dom.$elem.removeClass(self.options
 .loadingClass);callback(parsed)};this.dom.$elem.addClass(this.options.loadingClass);$.ajax({url:this.makeUrl(filter),success:ajaxCallback,error:function(jqXHR,textStatus,errorThrown){if($.isFunction(self.options.onError)){self.options.onError(jqXHR,textStatus,errorThrown)}else{ajaxCallback(false)}},dataType:dataType})}};$.Autocompleter.prototype.setExtraParam=function(name,value){var index=$.trim(String(name));if(index){if(!this.options.extraParams){this.options.extraParams={}}if(this.options.extraParams[index]!==value){this.options.extraParams[index]=value;this.cacheFlush()}}};$.Autocompleter.prototype.makeUrl=function(param){var self=this;var url=this.options.url;var params=$.extend({},this.options.extraParams);if(this.options.queryParamName===false){url+=encodeURIComponent(param)}else{params[this.options.queryParamName]=param}return makeUrl(url,params)};$.Autocompleter.prototype.parseRemoteData=function(remoteData){var remoteDataType;var data=remoteData;if(this.options.remoteData
 Type==="json"){remoteDataType=typeof remoteData;switch(remoteDataType){case"object":data=remoteData;break;case"string":data=$.parseJSON(remoteData);break;default:throw new Error("Unexpected remote data type: "+remoteDataType)}return data}return plainTextParser(data,this.options.lineSeparator,this.options.cellSeparator)};$.Autocompleter.prototype.defaultFilter=function(result,filter){if(!result.value){return false}if(this.options.filterResults){var pattern=this.matchStringConverter(filter);var testValue=this.matchStringConverter(result.value);if(!this.options.matchCase){pattern=pattern.toLowerCase();testValue=testValue.toLowerCase()}var patternIndex=testValue.indexOf(pattern);if(this.options.matchInside){return patternIndex>-1}else{return patternIndex===0}}return true};$.Autocompleter.prototype.filterResult=function(result,filter){if(this.options.filter===false){return true}if($.isFunction(this.options.filter)){return this.options.filter(result,filter)}return this.defaultFilter(resul
 t,filter)};$.Autocompleter.prototype.filterResults=function(results,filter){var filtered=[];var i,result;for(i=0;i<results.length;i++){result=sanitizeResult(results[i]);if(this.filterResult(result,filter)){filtered.push(result)}}if(this.options.sortResults){filtered=this.sortResults(filtered,filter)}if(this.options.maxItemsToShow>0&&this.options.maxItemsToShow<filtered.length){filtered.length=this.options.maxItemsToShow}return filtered};$.Autocompleter.prototype.sortResults=function(results,filter){var self=this;var sortFunction=this.options.sortFunction;if(!$.isFunction(sortFunction)){sortFunction=function(a,b,f){return sortValueAlpha(a,b,self.options.matchCase)}}results.sort(function(a,b){return sortFunction(a,b,filter,self.options)});return results};$.Autocompleter.prototype.matchStringConverter=function(s,a,b){var converter=this.options.matchStringConverter;if($.isFunction(converter)){s=converter(s,a,b)}return s};$.Autocompleter.prototype.beforeUseConverter=function(s){s=this.ge
 tValue(s);var converter=this.options.beforeUseConverter;if($.isFunction(converter)){s=converter(s)}return s};$.Autocompleter.prototype.enableFinishOnBlur=function(){this.finishOnBlur_=true};$.Autocompleter.prototype.disableFinishOnBlur=function(){this.finishOnBlur_=false};$.Autocompleter.prototype.createItemFromResult=function(result){var self=this;var $li=$("<li/>");$li.text(this.showResult(result.value,result.data));$li.data({value:result.value,data:result.data}).click(function(){self.selectItem($li)}).mousedown(self.disableFinishOnBlur).mouseup(self.enableFinishOnBlur);return $li};$.Autocompleter.prototype.getItems=function(){return $(">ul>li",this.dom.$results)};$.Autocompleter.prototype.showResults=function(results,filter){var numResults=results.length;var self=this;var $ul=$("<ul></ul>");var i,result,$li,autoWidth,first=false,$first=false;if(numResults){for(i=0;i<numResults;i++){result=results[i];$li=this.createItemFromResult(result);$ul.append($li);if(first===false){first=Str
 ing(result.value);$first=$li;$li.addClass(this.options.firstItemClass)}if(i===numResults-1){$li.addClass(this.options.lastItemClass)}}this.dom.$results.html($ul).show();this.position();if(this.options.autoWidth){autoWidth=this.dom.$elem.outerWidth()-this.dom.$results.outerWidth()+this.dom.$results.width();this.dom.$results.css(this.options.autoWidth,autoWidth)}this.getItems().hover(function(){self.focusItem(this)},function(){});if(this.autoFill(first,filter)||this.options.selectFirst||this.options.selectOnly&&numResults===1){this.focusItem($first)}this.active_=true}else{this.hideResults();this.active_=false}};$.Autocompleter.prototype.showResult=function(value,data){if($.isFunction(this.options.showResult)){return this.options.showResult(value,data)}else{return value}};$.Autocompleter.prototype.autoFill=function(value,filter){var lcValue,lcFilter,valueLength,filterLength;if(this.options.autoFill&&this.lastKeyPressed_!==8){lcValue=String(value).toLowerCase();lcFilter=String(filter).t
 oLowerCase();valueLength=value.length;filterLength=filter.length;if(lcValue.substr(0,filterLength)===lcFilter){var d=this.getDelimiterOffsets();var pad=d.start?" ":"";this.setValue(pad+value);var start=filterLength+d.start+pad.length;var end=valueLength+d.start+pad.length;this.selectRange(start,end);return true}}return false};$.Autocompleter.prototype.focusNext=function(){this.focusMove(+1)};$.Autocompleter.prototype.focusPrev=function(){this.focusMove(-1)};$.Autocompleter.prototype.focusMove=function(modifier){var $items=this.getItems();modifier=sanitizeInteger(modifier,0);if(modifier){for(var i=0;i<$items.length;i++){if($($items[i]).hasClass(this.selectClass_)){this.focusItem(i+modifier);return}}}this.focusItem(0)};$.Autocompleter.prototype.focusItem=function(item){var $item,$items=this.getItems();if($items.length){$items.removeClass(this.selectClass_).removeClass(this.options.selectClass);if(typeof item==="number"){if(item<0){item=0}else if(item>=$items.length){item=$items.length
 -1}$item=$($items[item])}else{$item=$(item)}if($item){$item.addClass(this.selectClass_).addClass(this.options.selectClass)}}};$.Autocompleter.prototype.selectCurrent=function(){var $item=$("li."+this.selectClass_,this.dom.$results);if($item.length===1){this.selectItem($item)}else{this.deactivate(false)}};$.Autocompleter.prototype.selectItem=function($li){var value=$li.data("value");var data=$li.data("data");var displayValue=this.displayValue(value,data);var processedDisplayValue=this.beforeUseConverter(displayValue);this.lastProcessedValue_=processedDisplayValue;this.lastSelectedValue_=processedDisplayValue;var d=this.getDelimiterOffsets();var delimiter=this.options.delimiterChar;var elem=this.dom.$elem;var extraCaretPos=0;if(this.options.useDelimiter){if(elem.val().substring(d.start-1,d.start)==delimiter&&delimiter!=" "){displayValue=" "+displayValue}if(elem.val().substring(d.end,d.end+1)!=delimiter&&this.lastKeyPressed_!=this.options.delimiterKeyCode){displayValue=displayValue+del
 imiter}else{extraCaretPos=1}}this.setValue(displayValue);this.setCaret(d.start+displayValue.length+extraCaretPos);this.callHook("onItemSelect",{value:value,data:data});this.deactivate(true);elem.focus()};$.Autocompleter.prototype.displayValue=function(value,data){if($.isFunction(this.options.displayValue)){return this.options.displayValue(value,data)}return value};$.Autocompleter.prototype.hideResults=function(){this.dom.$results.hide()};$.Autocompleter.prototype.deactivate=function(finish){if(this.finishTimeout_){clearTimeout(this.finishTimeout_)}if(this.keyTimeout_){clearTimeout(this.keyTimeout_)}if(finish){if(this.lastProcessedValue_!==this.lastSelectedValue_){if(this.options.mustMatch){this.setValue("")}this.callHook("onNoMatch")}if(this.active_){this.callHook("onFinish")}this.lastKeyPressed_=null;this.lastProcessedValue_=null;this.lastSelectedValue_=null;this.active_=false}this.hideResults()};$.Autocompleter.prototype.selectRange=function(start,end){var input=this.dom.$elem.get
 (0);if(input.setSelectionRange){input.focus();input.setSelectionRange(start,end)}else if(input.createTextRange){var range=input.createTextRange();range.collapse(true);range.moveEnd("character",end);range.moveStart("character",start);range.select()}};$.Autocompleter.prototype.setCaret=function(pos){this.selectRange(pos,pos)};$.Autocompleter.prototype.getCaret=function(){var $elem=this.dom.$elem;var elem=$elem[0];var val,selection,range,start,end,stored_range;if(elem.createTextRange){selection=document.selection;if(elem.tagName.toLowerCase()!="textarea"){val=$elem.val();range=selection.createRange().duplicate();range.moveEnd("character",val.length);if(range.text===""){start=val.length}else{start=val.lastIndexOf(range.text)}range=selection.createRange().duplicate();range.moveStart("character",-val.length);end=range.text.length}else{range=selection.createRange();stored_range=range.duplicate();stored_range.moveToElementText(elem);stored_range.setEndPoint("EndToEnd",range);start=stored_ra
 nge.text.length-range.text.length;end=start+range.text.length}}else{start=$elem[0].selectionStart;end=$elem[0].selectionEnd}return{start:start,end:end}};$.Autocompleter.prototype.setValue=function(value){if(this.options.useDelimiter){var val=this.dom.$elem.val();var d=this.getDelimiterOffsets();var preVal=val.substring(0,d.start);var postVal=val.substring(d.end);value=preVal+value+postVal}this.dom.$elem.val(value)};$.Autocompleter.prototype.getValue=function(value){if(this.options.useDelimiter){var d=this.getDelimiterOffsets();return value.substring(d.start,d.end).trim()}else{return value}};$.Autocompleter.prototype.getDelimiterOffsets=function(){var val=this.dom.$elem.val();if(this.options.useDelimiter){var preCaretVal=val.substring(0,this.getCaret().start);var start=preCaretVal.lastIndexOf(this.options.delimiterChar)+1;var postCaretVal=val.substring(this.getCaret().start);var end=postCaretVal.indexOf(this.options.delimiterChar);if(end==-1)end=val.length;end+=this.getCaret().start}
 else{start=0;end=val.length}return{start:start,end:end}}})(jQuery);
\ No newline at end of file

Propchange: sling/trunk/bundles/commons/log/src/main/resources/res/ui/jquery.autocomplete.min.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: sling/trunk/bundles/commons/log/src/main/resources/res/ui/slinglog.js
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/main/resources/res/ui/slinglog.js?rev=1547417&view=auto
==============================================================================
--- sling/trunk/bundles/commons/log/src/main/resources/res/ui/slinglog.js (added)
+++ sling/trunk/bundles/commons/log/src/main/resources/res/ui/slinglog.js Tue Dec  3 15:01:23 2013
@@ -0,0 +1,133 @@
+/**
+ * Removes the editor (toggles all displayfields/editables).
+ */
+function removeEditor(row) {
+    $(row).find(".loggers").toggle();
+	$(row).find(".logLevels").toggle();
+	$(row).find(".logFile").toggle();
+	$(row).find(".configureLink").toggle();
+	$(row).find(".editElement").remove();
+	$(row).removeClass("currentEditor");
+}
+
+/**
+ * Turns the loglevel element into an selectfield (current loglevel is selected).
+ */
+function addLogLevelSelect(row) {
+    var logLevelElement = $(row).find(".logLevels");
+    // get the current loglevel
+	var currentLogLevel = logLevelElement.attr("data-currentloglevel");
+	if(!currentLogLevel) {
+	    // convenience default for new loggers
+	    currentLogLevel = "INFO";
+	}
+	// get all available loglevels (present in the "newlogger" element)
+	var allLogLevels = $("#allLogLevels").attr("data-loglevels").split(",");
+	var select = $('<select class="editElement" name="loglevel"></select>');
+	$.each(allLogLevels, function(index, logLevel) {
+		select.append('<option'+(logLevel == currentLogLevel ? ' selected="selected"' : '')+'>'+logLevel+'</option>');
+    });
+	logLevelElement.after(select);
+	logLevelElement.toggle();
+}
+
+/**
+ * Adds a new editable logger for the given loggerelement (with controls for adding/removing).
+ * @param loggersElement logger element
+ * @param loggerName name of the logger
+ */
+function addLogger(loggersElement, loggerName) {
+    var addButton = $('<input type="submit" name="add" class="ui-state-default ui-corner-all" value="+" style="width:5%;" />');
+    addButton.bind("click", function() {
+    	addLogger($(this).parent(), "");
+    	return false;
+    });
+	var removeButton = $('<input type="submit" class="ui-state-default ui-corner-all" name="remove" value="-" style="width:5%;" />');
+	removeButton.bind("click", function() {
+		$(this).parent().remove();
+		return false;
+	});
+	var loggerField = $('<input type="text" name="logger" class="loggerField ui-state-default ui-corner-all inputText" value="'+loggerName+'" autocomplete="off" style="width:89%;" />');
+	// add the autocomplete with the array of all loggers
+	loggerField.autocomplete({
+        data: loggers
+    });
+	var logger = $('<div class="editElement"></div>').append(loggerField, addButton, removeButton);
+	loggersElement.after(logger);
+}
+
+/**
+ * Turns the logger elements into inputfields (with controls).
+ */
+function addLoggers(row) {
+    var loggersElement = $(row).find(".loggers");
+	var loggers = loggersElement.find(".logger");
+	if(loggers.length == 0) {
+	    addLogger(loggersElement, "");	
+	}
+	$.each(loggers, function(index, logger) {
+	      addLogger(loggersElement, $(logger).html());		  
+	});
+	loggersElement.toggle();
+}
+
+/**
+ * Turns the logfile element into an inputfield.
+ */
+function addLogFile(row) {
+    var logFileElement = $(row).find(".logFile");
+    var logFile = "";
+    if(logFileElement.length > 0) {
+        logFile = $(logFileElement).html();
+    }
+    if (logFile.length == 0) {
+    	// no logfile -> new logger -> take default
+    	logFile = $("#defaultLogfile").attr("data-defaultlogfile");
+    }
+    logFileElement.after('<input style="width:100%" class="editElement ui-state-default ui-corner-all inputText" type="text" name="logfile" value="'+logFile+'" />');
+    logFileElement.toggle();
+}
+
+/**
+ * Activates the logger configurator (called by clicking the configure link).
+ * Turns all display fields in the logger row containing the configure link into edit fields.
+ * @param button configure link
+ */
+function configureLogger(button) {
+	var configureLink = $(button.currentTarget);
+	var row = configureLink.parent().parent();
+	var rowId = $(row).attr("id");
+	// remove the current editor, since we have only one form only one editor can be active the same time
+	removeEditor($(".currentEditor"));
+	// add class as marker (id is already used for pid)
+	row.addClass("currentEditor");
+	// add the editables
+    addLogLevelSelect(row);
+	addLoggers(row);
+    addLogFile(row);
+    // add controls
+    var hiddenField = $('<input class="editElement" type="hidden" name="pid" value="'+(rowId != 'newlogger' ? rowId : '')+'" />');
+    var saveButton = $('<input class="editElement" type="submit" name="save" value="Save" />');
+    var cancelButton=$('<input class="editElement" type="submit" value="Cancel" />');
+	cancelButton.bind("click", function() {
+	    var row = $(this).parent().parent();
+	    removeEditor(row);
+	    return false;
+	});
+    var deleteButton = $('<input class="editElement" type="submit" name="delete" value="Remove Logger" />');
+    configureLink.after(saveButton, cancelButton, hiddenField);
+    if (rowId !== "newlogger") {
+    	// add a delete buttons for existing loggers
+    	cancelButton.after(deleteButton);
+    }
+    configureLink.toggle();
+	// prevent click on link
+    return false;
+}
+
+/**
+ * Initializes the log panel.
+ */
+function initializeSlingLogPanel() {
+	$("#loggerConfig").find(".configureLink").bind("click", configureLogger);
+}

Propchange: sling/trunk/bundles/commons/log/src/main/resources/res/ui/slinglog.js
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/ITWebConsoleRemote.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/ITWebConsoleRemote.java?rev=1547417&r1=1547416&r2=1547417&view=diff
==============================================================================
--- sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/ITWebConsoleRemote.java (original)
+++ sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/ITWebConsoleRemote.java Tue Dec  3 15:01:23 2013
@@ -25,7 +25,6 @@ import java.io.IOException;
 import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider;
 import com.gargoylesoftware.htmlunit.WebClient;
 import com.gargoylesoftware.htmlunit.html.HtmlPage;
-
 import org.apache.commons.io.FilenameUtils;
 import org.apache.sling.commons.log.logback.integration.remote.WebConsoleTestActivator;
 import org.junit.AfterClass;
@@ -74,6 +73,7 @@ public class ITWebConsoleRemote extends 
             mavenBundle("commons-io", "commons-io").versionAsInProject(),
             wrappedBundle(mavenBundle("commons-fileupload", "commons-fileupload").versionAsInProject()),
             wrappedBundle(mavenBundle("org.json", "json").versionAsInProject()),
+            configAdmin(),
             frameworkProperty("org.apache.sling.commons.log.configurationFile").value(
                 FilenameUtils.concat(new File(".").getAbsolutePath(), "src/test/resources/test-webconsole-remote.xml")),
             createWebConsoleTestBundle()

Modified: sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/remote/WebConsoleTestActivator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/remote/WebConsoleTestActivator.java?rev=1547417&r1=1547416&r2=1547417&view=diff
==============================================================================
--- sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/remote/WebConsoleTestActivator.java (original)
+++ sling/trunk/bundles/commons/log/src/test/java/org/apache/sling/commons/log/logback/integration/remote/WebConsoleTestActivator.java Tue Dec  3 15:01:23 2013
@@ -18,6 +18,12 @@
  */
 package org.apache.sling.commons.log.logback.integration.remote;
 
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
 import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.Logger;
 import ch.qos.logback.classic.spi.ILoggingEvent;
@@ -33,12 +39,6 @@ import org.osgi.framework.BundleContext;
 import org.slf4j.Marker;
 import org.xml.sax.InputSource;
 
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
 /**
  * Test bundle activator which registers all type of extension point supported by bundle
  * Used by ITWebConsoleRemote to assert output of the WebConsole Plugin
@@ -61,8 +61,8 @@ public class WebConsoleTestActivator imp
         Dictionary<String, Object> props = new Hashtable<String, Object>();
         String prefix = "WebConsoleTest";
         String[] loggers = {
-                prefix + ".foo.bar:DEBUG",
-                prefix + ".foo.baz:INFO",
+                prefix + ".foo.bar",
+                prefix + ".foo.baz",
         };
 
         props.put("loggers", loggers);



Mime
View raw message