Fixed FELIX-3315 Log plugin does not show the bundle that has logged the event
https://issues.apache.org/jira/browse/FELIX-3315

Fixed FELIX-3316 Log plugin should provide more detailed exception column
https://issues.apache.org/jira/browse/FELIX-3316


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1235728 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java
index db2c660..d0fb2b5 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/LogServlet.java
@@ -17,7 +17,9 @@
 package org.apache.felix.webconsole.internal.compendium;
 
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.util.Enumeration;
 
@@ -30,6 +32,8 @@
 import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.json.JSONException;
 import org.json.JSONWriter;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.log.LogEntry;
 import org.osgi.service.log.LogReaderService;
@@ -41,9 +45,9 @@
  */
 public class LogServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin
 {
-    private static final String LABEL = "logs";
-    private static final String TITLE = "%logs.pluginTitle";
-    private static final String CSS[] = { "/res/ui/logs.css" };
+    private static final String LABEL = "logs"; //$NON-NLS-1$
+    private static final String TITLE = "%logs.pluginTitle"; //$NON-NLS-1$
+    private static final String CSS[] = { "/res/ui/logs.css" }; //$NON-NLS-1$
 
     private final static int MAX_LOGS = 200; //maximum number of log entries
 
@@ -56,7 +60,7 @@
         super(LABEL, TITLE, CSS);
 
         // load templates
-        TEMPLATE = readTemplateFile( "/templates/logs.html" );
+        TEMPLATE = readTemplateFile( "/templates/logs.html" ); //$NON-NLS-1$
     }
 
 
@@ -65,16 +69,21 @@
      */
     protected void doPost( HttpServletRequest req, HttpServletResponse resp ) throws IOException
     {
-        final int minLevel = WebConsoleUtil.getParameterInt( req, "minLevel", LogService.LOG_DEBUG );
+        final int minLevel = WebConsoleUtil.getParameterInt( req, "minLevel", LogService.LOG_DEBUG ); //$NON-NLS-1$
 
-        resp.setContentType( "application/json" );
-        resp.setCharacterEncoding( "utf-8" );
-
-        renderJSON( resp.getWriter(), minLevel );
+        resp.setContentType( "application/json" ); //$NON-NLS-1$
+        resp.setCharacterEncoding( "utf-8" ); //$NON-NLS-1$
+        
+        renderJSON( resp.getWriter(), minLevel, trasesEnabled(req) );
+    }
+    
+    private static boolean trasesEnabled( final HttpServletRequest req )
+    {
+        String traces = req.getParameter("traces"); //$NON-NLS-1$
+        return null == traces ? false : Boolean.parseBoolean(traces);
     }
 
-
-    private final void renderJSON( final PrintWriter pw, int minLogLevel ) throws IOException
+    private final void renderJSON( final PrintWriter pw, int minLogLevel, boolean traces ) throws IOException
     {
         // create status line
         final LogReaderService logReaderService = ( LogReaderService ) this.getService( LogReaderService.class
@@ -85,10 +94,10 @@
         {
             jw.object();
 
-            jw.key( "status" );
+            jw.key( "status" ); //$NON-NLS-1$
             jw.value( logReaderService == null ? Boolean.FALSE : Boolean.TRUE );
 
-            jw.key( "data" );
+            jw.key( "data" ); //$NON-NLS-1$
             jw.array();
 
             if ( logReaderService != null )
@@ -100,7 +109,7 @@
                     LogEntry nextLog = ( LogEntry ) logEntries.nextElement();
                     if ( nextLog.getLevel() <= minLogLevel )
                     {
-                        logJson( jw, nextLog, index++ );
+                        logJson( jw, nextLog, index++, traces );
                     }
                 }
             }
@@ -124,15 +133,15 @@
     protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
         IOException
     {
-        final int minLevel = WebConsoleUtil.getParameterInt( request, "minLevel", LogService.LOG_DEBUG );
+        final int minLevel = WebConsoleUtil.getParameterInt( request, "minLevel", LogService.LOG_DEBUG ); //$NON-NLS-1$
         final String info = request.getPathInfo();
-        if ( info.endsWith( ".json" ) )
+        if ( info.endsWith( ".json" ) ) //$NON-NLS-1$
         {
-            response.setContentType( "application/json" );
-            response.setCharacterEncoding( "UTF-8" );
+            response.setContentType( "application/json" ); //$NON-NLS-1$
+            response.setCharacterEncoding( "UTF-8" ); //$NON-NLS-1$
 
             PrintWriter pw = response.getWriter();
-            this.renderJSON( pw, minLevel );
+            this.renderJSON( pw, minLevel, trasesEnabled(request) );
             return;
         }
         super.doGet( request, response );
@@ -148,23 +157,40 @@
     }
 
 
-    private static final void logJson( JSONWriter jw, LogEntry info, int index ) throws JSONException
+    private static final void logJson( JSONWriter jw, LogEntry info, int index, boolean traces ) throws JSONException
     {
         jw.object();
-        jw.key( "id" );
+        jw.key( "id" ); //$NON-NLS-1$
         jw.value( String.valueOf( index ) );
-        jw.key( "received" );
+        jw.key( "received" ); //$NON-NLS-1$
         jw.value( info.getTime() );
-        jw.key( "level" );
+        jw.key( "level" ); //$NON-NLS-1$
         jw.value( logLevel( info.getLevel() ) );
-        jw.key( "raw_level" );
+        jw.key( "raw_level" ); //$NON-NLS-1$
         jw.value( info.getLevel() );
-        jw.key( "message" );
+        jw.key( "message" ); //$NON-NLS-1$
         jw.value( info.getMessage() );
-        jw.key( "service" );
+        jw.key( "service" ); //$NON-NLS-1$
         jw.value( serviceDescription( info.getServiceReference() ) );
-        jw.key( "exception" );
-        jw.value( exceptionMessage( info.getException() ) );
+        jw.key( "exception" ); //$NON-NLS-1$
+        jw.value( exceptionMessage( info.getException(), traces ) );
+        Bundle bundle = info.getBundle();
+        if (null != bundle)
+        {
+            jw.key("bundleId"); //$NON-NLS-1$
+            jw.value(bundle.getBundleId());
+            String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+            if (null == name)
+            {
+                name = bundle.getSymbolicName();
+            }
+            if (null == name)
+            {
+                name = bundle.getLocation();
+            }
+            jw.key("bundleName"); //$NON-NLS-1$
+            jw.value(name);
+        }
         jw.endObject();
     }
 
@@ -173,7 +199,7 @@
     {
         if ( serviceReference == null )
         {
-            return "";
+            return ""; //$NON-NLS-1$
         }
         return serviceReference.toString();
     }
@@ -184,25 +210,40 @@
         switch ( level )
         {
             case LogService.LOG_INFO:
-                return "INFO";
+                return "INFO"; //$NON-NLS-1$
             case LogService.LOG_WARNING:
-                return "WARNING";
+                return "WARNING"; //$NON-NLS-1$
             case LogService.LOG_ERROR:
-                return "ERROR";
+                return "ERROR"; //$NON-NLS-1$
             case LogService.LOG_DEBUG:
             default:
-                return "DEBUG";
+                return "DEBUG"; //$NON-NLS-1$
         }
     }
 
+    private static final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    private static final PrintStream printStream = new PrintStream(baos);
 
-    private static final String exceptionMessage( Throwable e )
+    private static final String exceptionMessage( Throwable e, boolean traces )
     {
         if ( e == null )
         {
-            return "";
+            return ""; //$NON-NLS-1$
         }
-        return e.getClass().getName() + ": " + e.getMessage();
+        if (traces) {
+            String ret = null;
+            synchronized (printStream)
+            {
+                try {
+                    e.printStackTrace(printStream);
+                    ret = baos.toString();
+                } finally {
+                    baos.reset();
+                }
+            }
+            return ret;
+        }
+        return e.getClass().getName() + ": " + e.getMessage(); //$NON-NLS-1$
     }
 
 }
diff --git a/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties b/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
index 240412a..bbf41d7 100644
--- a/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
+++ b/webconsole/src/main/native2ascii/OSGI-INF/l10n/bundle_bg.properties
@@ -93,6 +93,9 @@
 log.level.warn=ПРЕДУПР
 log.level.info=ИНФО
 log.level.debug=ДЕТАЙЛИ
+log.traces=Изглед изключения:
+log.traces.full=Пълен
+log.traces.min=Само съобщение
 
 # Bundles plugin
 bundles.pluginTitle=Бъндъли
diff --git a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
index 8928d08..4d4f592 100644
--- a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -93,6 +93,10 @@
 log.level.warn=WARN
 log.level.info=INFO
 log.level.debug=DEBUG
+log.traces=Exception details:
+log.traces.full=Full Trace
+log.traces.min=Message Only
+
 
 # Bundles plugin
 bundles.pluginTitle=Bundles
diff --git a/webconsole/src/main/resources/res/ui/logs.css b/webconsole/src/main/resources/res/ui/logs.css
index 16f46df..21f6d95 100644
--- a/webconsole/src/main/resources/res/ui/logs.css
+++ b/webconsole/src/main/resources/res/ui/logs.css
@@ -22,3 +22,8 @@
 th.col_Level  { width: 6em }
 th.col_Received,
 th.col_Exception { min-width: 10em }
+td.ex {
+	font-size: smaller;
+	font-family: monospace;
+	white-space: pre
+}
diff --git a/webconsole/src/main/resources/res/ui/logs.js b/webconsole/src/main/resources/res/ui/logs.js
index acfdcec..00f2221 100644
--- a/webconsole/src/main/resources/res/ui/logs.js
+++ b/webconsole/src/main/resources/res/ui/logs.js
@@ -47,6 +47,7 @@
     var message = dataEntry.message;
     var level = dataEntry.level;
     var exception = dataEntry.exception;
+	var exceptionClass = $(".enableTraces").val() == 'true' ? 'ex' : null;
     var service = dataEntry.service;
 	switch (dataEntry.raw_level) { // i18n
 		case 1: level = i18n.error; break;
@@ -54,11 +55,16 @@
 		case 3: level = i18n.info; break;
 		case 4: level = i18n.debug; break;
 	}
+	var bundle = text('');
+	if (dataEntry.bundleId) {
+		bundle = createElement( 'a', null, {href : appRoot + '/bundles/' + dataEntry.bundleId}, [ text (dataEntry.bundleName) ] )
+	}
     parent.appendChild( td( null, null, [ text( printDate(dataEntry.received) ) ] ) );
     parent.appendChild( td( null, { lvl:dataEntry.raw_level }, [ text( level ) ] ) );    
     parent.appendChild( td( null, null, [ text( wordWrap(message) ) ] ) );
     parent.appendChild( td( null, null, [ text( wordWrap(service) ) ] ) );
-    parent.appendChild( td( null, null, [ text( exception ) ] ) );
+    parent.appendChild( td( null, null, [ bundle ] ) );
+    parent.appendChild( td( exceptionClass, null, [ text( exception ) ] ) );
 }
 
 /* displays a date in the user's local timezone */
@@ -68,28 +74,34 @@
 }
 
 function loadData() {
-	$.get(pluginRoot + "/data.json", { "minLevel":$(".minLevel").val()}, renderData, "json");
+	$.post(pluginRoot, {
+		"minLevel" : $(".minLevel").val(),
+		"traces"   : $(".enableTraces").val()
+	}, renderData, "json");
 	return false; // for button
 }
 
 $(document).ready(function() {
 	// install user interaction handlers
     $(".reloadButton").click(loadData);
+	$(".enableTraces").change(function() {
+		var val = $(this).val();
+		$(".enableTraces").val(val); // same values for both select boxes
+		loadData();
+	})
     $(".minLevel").change(function() {
 		var value = $(this).val();
 		$(".minLevel").val(value); // same values for both select boxes
-    	$.post(pluginRoot, {"minLevel":value}, function(data) {
-    	    renderData(data);
-    	}, "json");
+		loadData();
     });
-		// init tablesorte
+	// init tablesorte
 	$('#plugin_table').tablesorter({
 		textExtraction: function(node) {
 			var _ = $(node);
 			return _.attr('lvl') ? _.attr('lvl') : _.text();
 		}
 	});
-	
+
 	logsElem  = $("#logs");
     logs2Elem = $("#logs2");
 	tableElem = $("#plugin_table");
diff --git a/webconsole/src/main/resources/templates/logs.html b/webconsole/src/main/resources/templates/logs.html
index 2086dcb..48a852f 100644
--- a/webconsole/src/main/resources/templates/logs.html
+++ b/webconsole/src/main/resources/templates/logs.html
@@ -20,6 +20,11 @@
 <!-- buttons top -->
 <form method="post" enctype="multipart/form-data" action="">
 	<div class="ui-widget-header ui-corner-top buttonGroup">
+		<label>${log.traces}</label>
+		<select class="enableTraces">
+			<option value="false">${log.traces.min}</option>
+			<option value="true">${log.traces.full}</option>
+		</select>
 		<label>${log.severity.label}</label>
 		<select class="minLevel">
 			<option value="1">${log.level.error}</option>
@@ -39,17 +44,23 @@
 			<th class="col_Level">${log.level}</th>
 			<th class="col_Message">${log.message}</th>
 			<th class="col_Service">${log.service}</th>
+			<th class="col_Bundle">${bundle}</th>
 			<th class="col_Exception">${log.exception}</th>
 		</tr>
 	</thead>
 	<tbody>
-		<tr><td colspan="5">&nbsp;</td></tr>
+		<tr><td colspan="6">&nbsp;</td></tr>
 	</tbody>
 </table>
 
 <!-- buttons bottom -->
 <form method="post" enctype="multipart/form-data" action="">
 	<div class="ui-widget-header ui-corner-bottom buttonGroup">
+		<label>${log.traces}</label>
+		<select class="enableTraces">
+			<option value="false">${log.traces.min}</option>
+			<option value="true">${log.traces.full}</option>
+		</select>
 		<label>${log.severity.label}</label>
 		<select class="minLevel">
 			<option value="1">${log.level.error}</option>