You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ch...@apache.org on 2016/02/02 11:50:06 UTC

svn commit: r1728077 - in /sling/trunk/contrib/extensions/tracer: ./ src/main/java/org/apache/sling/tracer/internal/ src/test/java/org/apache/sling/tracer/internal/

Author: chetanm
Date: Tue Feb  2 10:50:05 2016
New Revision: 1728077

URL: http://svn.apache.org/viewvc?rev=1728077&view=rev
Log:
SLING-5459 - Recording of tracer logs

Added:
    sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java   (with props)
    sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/Recording.java   (with props)
    sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TraceLogRecorder.java   (with props)
    sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerLogServlet.java   (with props)
    sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java   (with props)
    sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TestUtil.java   (with props)
    sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TracerLogServletTest.java   (with props)
Modified:
    sling/trunk/contrib/extensions/tracer/pom.xml
    sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/LogTracer.java
    sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerContext.java
    sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerModelTest.java
    sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java

Modified: sling/trunk/contrib/extensions/tracer/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/pom.xml?rev=1728077&r1=1728076&r2=1728077&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tracer/pom.xml (original)
+++ sling/trunk/contrib/extensions/tracer/pom.xml Tue Feb  2 10:50:05 2016
@@ -88,7 +88,7 @@
     <dependency>
       <groupId>org.apache.sling</groupId>
       <artifactId>org.apache.sling.api</artifactId>
-      <version>2.1.0</version>
+      <version>2.2.0</version>
     </dependency>
     <dependency>
       <groupId>org.osgi</groupId>
@@ -102,6 +102,28 @@
       <version>4.3.1</version>
       <scope>provided</scope>
     </dependency>
+    <!-- TODO Inline just the cache related classes -->
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>15.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.webconsole</artifactId>
+      <version>4.2.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.commons.json</artifactId>
+      <version>2.0.8</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <version>3.0.0</version>
+      <scope>provided</scope>
+    </dependency>
 
     <dependency>
       <groupId>junit</groupId>
@@ -115,9 +137,16 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+      <version>1.3.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
       <version>1.10.19</version>
+      <scope>test</scope>
     </dependency>
 
   </dependencies>

Added: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java?rev=1728077&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java (added)
+++ sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java Tue Feb  2 10:50:05 2016
@@ -0,0 +1,80 @@
+/*
+ * 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.tracer.internal;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.io.JSONWriter;
+
+class JSONRecording implements Recording {
+    private final String method;
+    private final List<String> queries = new ArrayList<String>();
+    private RequestProgressTracker tracker;
+
+    public JSONRecording(HttpServletRequest r) {
+        this.method = r.getMethod();
+    }
+
+    public void render(JSONWriter jw) throws JSONException {
+        jw.key("method").value(method);
+
+        if (tracker != null) {
+            jw.key("logs");
+            jw.array();
+            Iterator<String> it = tracker.getMessages();
+            while (it.hasNext()) {
+                jw.value(it.next());
+            }
+            jw.endArray();
+        }
+
+        jw.key("queries");
+        jw.array();
+        for (String q : queries) {
+            jw.value(q);
+        }
+        jw.endArray();
+    }
+
+    //~---------------------------------------< Recording >
+
+    @Override
+    public void log(String logger, String format, Object[] params) {
+        if (TracerContext.QUERY_LOGGER.equals(logger)
+                && params != null && params.length == 2) {
+            queries.add((String) params[1]);
+        }
+    }
+
+    @Override
+    public void registerTracker(RequestProgressTracker tracker) {
+        this.tracker = tracker;
+    }
+
+    RequestProgressTracker getTracker() {
+        return tracker;
+    }
+}

Propchange: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/JSONRecording.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/LogTracer.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/LogTracer.java?rev=1728077&r1=1728076&r2=1728077&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/LogTracer.java (original)
+++ sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/LogTracer.java Tue Feb  2 10:50:05 2016
@@ -29,6 +29,7 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.annotation.Nullable;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -36,6 +37,7 @@ import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.Logger;
@@ -108,6 +110,13 @@ public class LogTracer {
     )
     private static final String PROP_TRACER_ENABLED = "enabled";
 
+    private static final boolean PROP_TRACER_SERVLET_ENABLED_DEFAULT = false;
+    @Property(label = "Servlet Enabled",
+            description = "Enable the Tracer Servlet",
+            boolValue = PROP_TRACER_SERVLET_ENABLED_DEFAULT
+    )
+    private static final String PROP_TRACER_SERVLET_ENABLED = "servletEnabled";
+
     private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogTracer.class);
 
     private final Map<String, TracerSet> tracers = new HashMap<String, TracerSet>();
@@ -125,6 +134,11 @@ public class LogTracer {
 
     private static final ThreadLocal<TracerContext> requestContextHolder = new ThreadLocal<TracerContext>();
 
+    @Nullable
+    private TracerLogServlet logServlet;
+
+    private TraceLogRecorder recorder = TraceLogRecorder.DEFAULT;
+
     @Activate
     private void activate(Map<String, ?> config, BundleContext context) {
         this.bundleContext = context;
@@ -132,12 +146,23 @@ public class LogTracer {
         boolean enabled = PropertiesUtil.toBoolean(config.get(PROP_TRACER_ENABLED), PROP_TRACER_ENABLED_DEFAULT);
         if (enabled) {
             registerFilters(context);
-            LOG.info("Log tracer enabled. Required filters registered");
+            boolean servletEnabled = PropertiesUtil.toBoolean(config.get(PROP_TRACER_SERVLET_ENABLED),
+                    PROP_TRACER_SERVLET_ENABLED_DEFAULT);
+
+            if (servletEnabled) {
+                this.logServlet = new TracerLogServlet(context);
+                recorder = logServlet;
+            }
+            LOG.info("Log tracer enabled. Required filters registered. Tracer servlet enabled {}", servletEnabled);
         }
     }
 
     @Deactivate
     private void deactivate() {
+        if (logServlet != null) {
+            logServlet.unregister();
+        }
+
         if (slingFilterRegistration != null) {
             slingFilterRegistration.unregister();
             slingFilterRegistration = null;
@@ -156,7 +181,7 @@ public class LogTracer {
         requestContextHolder.remove();
     }
 
-    TracerContext getTracerContext(String tracerSetNames, String tracerConfig) {
+    TracerContext getTracerContext(String tracerSetNames, String tracerConfig, Recording recording) {
         //No config or tracer set name provided. So tracing not required
         if (tracerSetNames == null && tracerConfig == null) {
             return null;
@@ -185,7 +210,7 @@ public class LogTracer {
             configs.addAll(ts.getConfigs());
         }
 
-        return new TracerContext(configs.toArray(new TracerConfig[configs.size()]));
+        return new TracerContext(configs.toArray(new TracerConfig[configs.size()]), recording);
     }
 
     private void initializeTracerSet(Map<String, ?> config) {
@@ -277,8 +302,13 @@ public class LogTracer {
             //parameter map
 
             HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
+
+            //Invoke at start so that header can be set. If done at end there is a chance
+            //that response is committed
+            Recording recording = recorder.startRecording(httpRequest, (HttpServletResponse) servletResponse);
+
             TracerContext tracerContext = getTracerContext(httpRequest.getHeader(HEADER_TRACER),
-                    httpRequest.getHeader(HEADER_TRACER_CONFIG));
+                    httpRequest.getHeader(HEADER_TRACER_CONFIG), recording);
             try {
                 if (tracerContext != null) {
                     enableCollector(tracerContext);
@@ -304,14 +334,15 @@ public class LogTracer {
                              FilterChain filterChain) throws IOException, ServletException {
             SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
             TracerContext tracerContext = requestContextHolder.get();
-
+            Recording recording = recorder.getRecordingForRequest(slingRequest);
+            recording.registerTracker(slingRequest.getRequestProgressTracker());
             boolean createdContext = false;
 
             //Check if the global filter created context based on HTTP headers. If not
             //then check from request params
             if (tracerContext == null) {
                 tracerContext = getTracerContext(slingRequest.getParameter(PARAM_TRACER),
-                        slingRequest.getParameter(PARAM_TRACER_CONFIG));
+                        slingRequest.getParameter(PARAM_TRACER_CONFIG), recording);
                 if (tracerContext != null) {
                     createdContext = true;
                 }

Added: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/Recording.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/Recording.java?rev=1728077&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/Recording.java (added)
+++ sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/Recording.java Tue Feb  2 10:50:05 2016
@@ -0,0 +1,45 @@
+/*
+ * 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.tracer.internal;
+
+import org.apache.sling.api.request.RequestProgressTracker;
+
+interface Recording {
+    Recording NOOP = new Recording() {
+        @Override
+        public void log(String logger, String format, Object[] params) {
+
+        }
+
+        @Override
+        public void registerTracker(RequestProgressTracker tracker) {
+
+        }
+    };
+
+    void log(String logger, String format, Object[] params);
+
+    /**
+     * Register the {@link RequestProgressTracker} associated with
+     * current request
+     * @param tracker from current request
+     */
+    void registerTracker(RequestProgressTracker tracker);
+}

Propchange: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/Recording.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TraceLogRecorder.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TraceLogRecorder.java?rev=1728077&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TraceLogRecorder.java (added)
+++ sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TraceLogRecorder.java Tue Feb  2 10:50:05 2016
@@ -0,0 +1,41 @@
+/*
+ * 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.tracer.internal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+interface TraceLogRecorder {
+    TraceLogRecorder DEFAULT = new TraceLogRecorder() {
+        @Override
+        public Recording startRecording(HttpServletRequest request, HttpServletResponse response) {
+            return Recording.NOOP;
+        }
+
+        @Override
+        public Recording getRecordingForRequest(HttpServletRequest request) {
+            return Recording.NOOP;
+        }
+    };
+
+    Recording startRecording(HttpServletRequest request, HttpServletResponse response);
+
+    Recording getRecordingForRequest(HttpServletRequest request);
+}

Propchange: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TraceLogRecorder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerContext.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerContext.java?rev=1728077&r1=1728076&r2=1728077&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerContext.java (original)
+++ sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerContext.java Tue Feb  2 10:50:05 2016
@@ -27,7 +27,7 @@ import org.apache.sling.api.request.Requ
 import org.slf4j.helpers.MessageFormatter;
 
 class TracerContext {
-    private static final String QUERY_LOGGER = "org.apache.jackrabbit.oak.query.QueryEngineImpl";
+    static final String QUERY_LOGGER = "org.apache.jackrabbit.oak.query.QueryEngineImpl";
 
     /**
      * Following queries are internal to Oak and are fired for login/access control
@@ -54,9 +54,11 @@ class TracerContext {
     private RequestProgressTracker progressTracker;
     private int queryCount;
     private final TracerConfig[] tracers;
+    private final Recording recording;
 
-    public TracerContext(TracerConfig[] tracers) {
+    public TracerContext(TracerConfig[] tracers, Recording recording) {
         this.tracers = tracers;
+        this.recording = recording;
 
         //Say if the list is like com.foo;level=trace,com.foo.bar;level=info.
         // Then first config would result in a match and later config would
@@ -79,6 +81,7 @@ class TracerContext {
     }
 
     public boolean log(String logger, String format, Object[] params) {
+        recording.log(logger, format, params);
         if (QUERY_LOGGER.equals(logger)
                 && params != null && params.length == 2) {
             return logQuery((String) params[1]);

Added: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerLogServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerLogServlet.java?rev=1728077&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerLogServlet.java (added)
+++ sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerLogServlet.java Tue Feb  2 10:50:05 2016
@@ -0,0 +1,196 @@
+/*
+ * 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.tracer.internal;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.io.JSONWriter;
+import org.osgi.framework.BundleContext;
+
+class TracerLogServlet extends SimpleWebConsolePlugin implements TraceLogRecorder {
+    static final String ATTR_REQUEST_ID = TracerLogServlet.class.getName();
+
+    public static final String CLEAR = "clear";
+
+    private static final String LABEL = "tracer";
+
+    public static final String HEADER_TRACER_RECORDING = "Sling-Tracer-Record";
+
+    public static final String HEADER_TRACER_REQUEST_ID = "Sling-Tracer-Request-Id";
+
+    private final Cache<String, JSONRecording> cache;
+
+    public TracerLogServlet(BundleContext context) {
+        super(LABEL, "Sling Tracer", "Sling", null);
+        //TODO Make things configurable
+        this.cache = CacheBuilder.newBuilder()
+                .maximumSize(100)
+                .expireAfterAccess(10, TimeUnit.MINUTES)
+                .recordStats()
+                .build();
+        register(context);
+    }
+
+    //~-----------------------------------------------< WebConsole Plugin >
+
+    @Override
+    protected void renderContent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        final PrintWriter pw = response.getWriter();
+        if (isHtmlRequest(request)){
+            renderStatus(pw);
+            renderRequests(pw);
+        } else {
+            String requestId = getRequestId(request);
+            prepareJSONResponse(response);
+            JSONWriter jw = new JSONWriter(pw);
+            try {
+                jw.setTidy(true);
+                jw.object();
+                if (requestId != null) {
+                    renderRequestData(requestId, jw);
+                }
+                jw.endObject();
+            } catch (JSONException e) {
+                throw new ServletException(e);
+            }
+        }
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+            throws IOException {
+        if (req.getParameter(CLEAR) != null) {
+            resetCache();
+            resp.sendRedirect(req.getRequestURI());
+        }
+    }
+
+    @Override
+    protected boolean isHtmlRequest(HttpServletRequest request) {
+        return request.getRequestURI().endsWith(LABEL);
+    }
+
+    private static void prepareJSONResponse(HttpServletResponse response) {
+        response.setContentType("application/json");
+        response.setCharacterEncoding("UTF-8");
+    }
+
+    private void renderRequestData(String requestId, JSONWriter jw) throws JSONException {
+        JSONRecording recording = cache.getIfPresent(requestId);
+        if (recording == null){
+            jw.key("error").value("Not found");
+            return;
+        }
+        recording.render(jw);
+    }
+
+    private void renderStatus(PrintWriter pw) {
+        pw.printf("<p class='statline'>Log Tracer Recordings: %d recordings</p>%n", cache.size());
+
+        pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
+        pw.println("<span style='float: left; margin-left: 1em'>Tracer Recordings</span>");
+        pw.println("<form method='POST'><input type='hidden' name='clear' value='clear'><input type='submit' value='Clear' class='ui-state-default ui-corner-all'></form>");
+        pw.println("</div>");
+    }
+
+    private void renderRequests(PrintWriter pw) {
+        if (cache.size() > 0){
+            pw.println("<ul>");
+            for (String id : cache.asMap().keySet()){
+                pw.printf("<li><a href='%s/%s.json'>%s</a></li>", LABEL, id, id);
+            }
+            pw.println("</ul>");
+        }
+
+    }
+
+    private static String getRequestId(HttpServletRequest request) {
+        String requestUri = request.getRequestURI();
+        int lastSlash = requestUri.lastIndexOf('/');
+        int lastDot = requestUri.indexOf('.', lastSlash + 1);
+        if (lastDot > 0){
+            return requestUri.substring(lastSlash + 1, lastDot);
+        }
+        return null;
+    }
+
+    //~-----------------------------------------------< TraceLogRecorder >
+
+    @Override
+    public Recording startRecording(HttpServletRequest request, HttpServletResponse response) {
+        if (request.getHeader(HEADER_TRACER_RECORDING) == null){
+            return Recording.NOOP;
+        }
+
+        if (request.getAttribute(ATTR_REQUEST_ID) != null){
+            //Already processed
+            return getRecordingForRequest(request);
+        }
+
+        String requestId = generateRequestId();
+        JSONRecording recording = record(requestId, request);
+
+        request.setAttribute(ATTR_REQUEST_ID, requestId);
+
+        response.setHeader(HEADER_TRACER_REQUEST_ID, requestId);
+        //TODO Show we also sent tracer version to enable client determine
+        //is server is capable of given version
+        return recording;
+    }
+
+    @Override
+    public Recording getRecordingForRequest(HttpServletRequest request) {
+        String requestId = (String) request.getAttribute(ATTR_REQUEST_ID);
+        if (requestId != null){
+            return getRecording(requestId);
+        }
+        return Recording.NOOP;
+    }
+
+    Recording getRecording(String requestId) {
+        Recording recording = cache.getIfPresent(requestId);
+        return recording == null ? Recording.NOOP : recording;
+    }
+
+    private JSONRecording record(String requestId, HttpServletRequest request) {
+        JSONRecording data = new JSONRecording(request);
+        cache.put(requestId, data);
+        return data;
+    }
+
+    private static String generateRequestId() {
+        return UUID.randomUUID().toString();
+    }
+
+    void resetCache(){
+        cache.invalidateAll();
+    }
+}

Propchange: sling/trunk/contrib/extensions/tracer/src/main/java/org/apache/sling/tracer/internal/TracerLogServlet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java?rev=1728077&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java (added)
+++ sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java Tue Feb  2 10:50:05 2016
@@ -0,0 +1,71 @@
+/*
+ * 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.tracer.internal;
+
+import java.io.StringWriter;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.commons.json.io.JSONWriter;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class JSONRecordingTest {
+    private HttpServletRequest request = mock(HttpServletRequest.class);
+
+    @Test
+    public void logQueries() throws Exception{
+        StringWriter sw = new StringWriter();
+
+        when(request.getMethod()).thenReturn("GET");
+        JSONRecording r = new JSONRecording(request);
+
+        r.log(TracerContext.QUERY_LOGGER, "foo bar", new Object[]{"x" , "y"});
+        r.log(TracerContext.QUERY_LOGGER, "foo bar", new Object[]{"x" , "z"});
+
+        JSONWriter jw = new JSONWriter(sw).object();
+        r.render(jw);
+        jw.endObject();
+
+        JSONObject json = new JSONObject(sw.toString());
+        assertEquals("GET", json.get("method"));
+        assertEquals(2, json.getJSONArray("queries").length());
+    }
+
+    @Test
+    public void requestTrackerLogs() throws Exception{
+        StringWriter sw = new StringWriter();
+        JSONRecording r = new JSONRecording(request);
+
+        r.registerTracker(TestUtil.createTracker("x", "y"));
+
+        JSONWriter jw = new JSONWriter(sw).object();
+        r.render(jw);
+        jw.endObject();
+
+        JSONObject json = new JSONObject(sw.toString());
+        assertEquals(2, json.getJSONArray("logs").length());
+    }
+
+}

Propchange: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/JSONRecordingTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerModelTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerModelTest.java?rev=1728077&r1=1728076&r2=1728077&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerModelTest.java (original)
+++ sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerModelTest.java Tue Feb  2 10:50:05 2016
@@ -86,7 +86,7 @@ public class LogTracerModelTest {
     }
 
     private static TracerContext getContext(TracerSet ts) {
-        return new TracerContext(ts.getConfigs().toArray(new TracerConfig[ts.getConfigs().size()]));
+        return new TracerContext(ts.getConfigs().toArray(new TracerConfig[ts.getConfigs().size()]), Recording.NOOP);
     }
 
 }
\ No newline at end of file

Modified: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java?rev=1728077&r1=1728076&r2=1728077&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java (original)
+++ sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/LogTracerTest.java Tue Feb  2 10:50:05 2016
@@ -25,6 +25,7 @@ import java.util.List;
 
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
@@ -39,9 +40,11 @@ import ch.qos.logback.core.read.ListAppe
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestProgressTracker;
 import org.apache.sling.testing.mock.osgi.MockOsgi;
 import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 import org.apache.sling.testing.mock.osgi.junit.OsgiContextCallback;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestWatcher;
@@ -50,12 +53,15 @@ import org.osgi.framework.InvalidSyntaxE
 import org.osgi.framework.ServiceReference;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.sling.tracer.internal.TestUtil.createTracker;
+import static org.apache.sling.tracer.internal.TestUtil.getRequestId;
 import static org.hamcrest.CoreMatchers.hasItem;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -89,12 +95,25 @@ public class LogTracerTest {
         LogTracer tracer = context.registerInjectActivateService(new LogTracer(),
                 ImmutableMap.<String, Object>of("enabled", "true"));
         assertEquals(2, context.getServices(Filter.class, null).length);
+        assertNull(context.getService(Servlet.class));
 
         MockOsgi.deactivate(tracer);
         assertNull(context.getService(Filter.class));
     }
 
     @Test
+    public void enableTracerLogServlet() throws Exception {
+        LogTracer tracer = context.registerInjectActivateService(new LogTracer(),
+                ImmutableMap.<String, Object>of("enabled", "true", "servletEnabled", "true"));
+        assertEquals(2, context.getServices(Filter.class, null).length);
+        assertNotNull(context.getService(Servlet.class));
+
+        MockOsgi.deactivate(tracer);
+        assertNull(context.getService(Filter.class));
+        assertNull(context.getService(Servlet.class));
+    }
+
+    @Test
     public void noTurboFilterRegisteredUnlessTracingRequested() throws Exception {
         HttpServletRequest request = mock(HttpServletRequest.class);
         HttpServletResponse response = mock(HttpServletResponse.class);
@@ -206,12 +225,54 @@ public class LogTracerTest {
         rootLogger().setLevel(oldLevel);
     }
 
+    @Test
+    public void recordingWithoutTracing() throws Exception{
+        activateTracerAndServlet();
+        MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(){
+            @Override
+            public RequestProgressTracker getRequestProgressTracker() {
+                return createTracker("x", "y");
+            }
+        };
+        request.setHeader(TracerLogServlet.HEADER_TRACER_RECORDING, "true");
+
+        HttpServletResponse response = mock(HttpServletResponse.class);
+
+        FilterChain chain = new FilterChain() {
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response)
+                    throws IOException, ServletException {
+                //No TurboFilter should be registered if tracing is not requested
+                assertNull(context.getService(TurboFilter.class));
+            }
+        };
+
+        prepareChain(chain).doFilter(request, response);
+
+        String requestId = getRequestId(response);
+        assertNotNull(requestId);
+        Recording r = ((TracerLogServlet)context.getService(Servlet.class)).getRecording(requestId);
+        assertTrue(r instanceof JSONRecording);
+        assertNotNull(((JSONRecording)r).getTracker());
+    }
+
 
     private void activateTracer() {
         context.registerInjectActivateService(new LogTracer(),
                 ImmutableMap.<String, Object>of("enabled", "true"));
     }
 
+    private void activateTracerAndServlet() {
+        context.registerInjectActivateService(new LogTracer(),
+                ImmutableMap.<String, Object>of("enabled", "true", "servletEnabled", "true"));
+    }
+
+    private FilterChain prepareChain(FilterChain end) throws InvalidSyntaxException {
+        Filter servletFilter = getFilter(false);
+        Filter slingFilter = getFilter(true);
+        return new FilterChainImpl(end, servletFilter, slingFilter);
+    }
+
     private Filter getFilter(boolean slingFilter) throws InvalidSyntaxException {
         Collection<ServiceReference<Filter>> refs =
                 context.bundleContext().getServiceReferences(Filter.class, null);
@@ -268,5 +329,25 @@ public class LogTracerTest {
             }
         }
     }
+
+    private static class FilterChainImpl implements FilterChain {
+        private final Filter[] filters;
+        private final FilterChain delegate;
+        private int pos;
+
+        public FilterChainImpl(FilterChain delegate, Filter ... filter){
+            this.delegate = delegate;
+            this.filters = filter;
+        }
+
+        @Override
+        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
+            if (pos == filters.length){
+                delegate.doFilter(request, response);
+            } else {
+                filters[pos++].doFilter(request, response, this);
+            }
+        }
+    }
 
 }

Added: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TestUtil.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TestUtil.java?rev=1728077&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TestUtil.java (added)
+++ sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TestUtil.java Tue Feb  2 10:50:05 2016
@@ -0,0 +1,46 @@
+/*
+ * 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.tracer.internal;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.mockito.ArgumentCaptor;
+
+import static java.util.Arrays.asList;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class TestUtil {
+
+    static RequestProgressTracker createTracker(String ... logs){
+        RequestProgressTracker tracker = mock(RequestProgressTracker.class);
+        when(tracker.getMessages()).thenReturn(asList(logs).iterator());
+        return tracker;
+    }
+
+    static String getRequestId(HttpServletResponse response){
+        ArgumentCaptor<String> requestIdCaptor = ArgumentCaptor.forClass(String.class);
+        verify(response).setHeader(eq(TracerLogServlet.HEADER_TRACER_REQUEST_ID), requestIdCaptor.capture());
+        return requestIdCaptor.getValue();
+    }
+}

Propchange: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TestUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TracerLogServletTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TracerLogServletTest.java?rev=1728077&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TracerLogServletTest.java (added)
+++ sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TracerLogServletTest.java Tue Feb  2 10:50:05 2016
@@ -0,0 +1,123 @@
+/*
+ * 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.tracer.internal;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static org.apache.sling.tracer.internal.TestUtil.createTracker;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TracerLogServletTest {
+
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private HttpServletRequest request;
+    @Mock
+    private HttpServletResponse response;
+
+    @Test
+    public void noRecordingByDefault() throws Exception{
+        TracerLogServlet logServlet = new TracerLogServlet(context.bundleContext());
+        assertSame(Recording.NOOP, logServlet.startRecording(request, response));
+        assertSame(Recording.NOOP, logServlet.getRecordingForRequest(request));
+    }
+
+    @Test
+    public void recordingWhenRequested() throws Exception{
+        TracerLogServlet logServlet = new TracerLogServlet(context.bundleContext());
+        request = new MockSlingHttpServletRequest();
+
+        Recording recording = logServlet.startRecording(request, response);
+        assertNotNull(recording);
+
+        //Once recording is created then it should be returned
+        Recording recording2 = logServlet.getRecordingForRequest(request);
+        assertSame(recording, recording2);
+
+        //Repeated call should return same recording instance
+        Recording recording3 = logServlet.startRecording(request, response);
+        assertSame(recording, recording3);
+
+        logServlet.resetCache();
+
+        //If recording gets lost then NOOP must be returned
+        Recording recording4 = logServlet.getRecordingForRequest(request);
+        assertSame(Recording.NOOP, recording4);
+    }
+
+    @Test
+    public void jsonRendering() throws Exception{
+        TracerLogServlet logServlet = new TracerLogServlet(context.bundleContext());
+        when(request.getMethod()).thenReturn("GET");
+        when(request.getHeader(TracerLogServlet.HEADER_TRACER_RECORDING)).thenReturn("true");
+
+        Recording recording = logServlet.startRecording(request, response);
+        recording.registerTracker(createTracker("x" ,"y"));
+
+        ArgumentCaptor<String> requestIdCaptor = ArgumentCaptor.forClass(String.class);
+        verify(response).setHeader(eq(TracerLogServlet.HEADER_TRACER_REQUEST_ID), requestIdCaptor.capture());
+
+        StringWriter sw = new StringWriter();
+        when(response.getWriter()).thenReturn(new PrintWriter(sw));
+        when(request.getRequestURI()).thenReturn("/system/console/" + requestIdCaptor.getValue() + ".json" );
+
+        logServlet.renderContent(request, response);
+        JSONObject json = new JSONObject(sw.toString());
+        assertEquals("GET", json.getString("method"));
+        assertEquals(2, json.getJSONArray("logs").length());
+    }
+
+    @Test
+    public void pluginRendering() throws Exception{
+        TracerLogServlet logServlet = new TracerLogServlet(context.bundleContext());
+        when(request.getRequestURI()).thenReturn("/system/console/tracer" );
+
+        StringWriter sw = new StringWriter();
+        when(response.getWriter()).thenReturn(new PrintWriter(sw));
+        logServlet.renderContent(request, response);
+
+        assertThat(sw.toString(), containsString("Log Tracer"));
+    }
+}

Propchange: sling/trunk/contrib/extensions/tracer/src/test/java/org/apache/sling/tracer/internal/TracerLogServletTest.java
------------------------------------------------------------------------------
    svn:eol-style = native