SLING-11515 : Provide interface to get the recent requests
diff --git a/src/main/java/org/apache/sling/engine/RequestInfo.java b/src/main/java/org/apache/sling/engine/RequestInfo.java
new file mode 100644
index 0000000..428209e
--- /dev/null
+++ b/src/main/java/org/apache/sling/engine/RequestInfo.java
@@ -0,0 +1,60 @@
+/*
+ * 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.engine;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Information about a single request.
+ * @see RequestInfoProvider
+ * @since 2.5
+ */
+public interface RequestInfo {
+    
+    /**
+     * Get the unique id for the request
+     * @return The id
+     */
+    @NotNull String getId();
+
+    /**
+     * Get the request method
+     * @return The request method
+     */
+    @NotNull String getMethod();
+
+    /**
+     * Get the requested path
+     * @return The path
+     */
+    @NotNull String getPath();
+
+    /**
+     * Get the user id for the request
+     * @return the user id or {@code null}
+     */
+    @Nullable String getUserId();
+
+    /**
+     * Get the log for the request
+     * @return The request log, multi-line output
+     */
+    @NotNull String getLog();
+}
diff --git a/src/main/java/org/apache/sling/engine/RequestInfoProvider.java b/src/main/java/org/apache/sling/engine/RequestInfoProvider.java
new file mode 100644
index 0000000..27441e3
--- /dev/null
+++ b/src/main/java/org/apache/sling/engine/RequestInfoProvider.java
@@ -0,0 +1,52 @@
+/*
+ * 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.engine;
+
+/**
+  * This service can be used to gather information about requests processed by the
+  * engine.
+  *
+  * @since 2.5
+  */
+ public interface RequestInfoProvider {
+
+    /**
+     * Get the request info for the id
+     * @param id The id
+     * @return The request info or {@code null}
+     */
+    RequestInfo getRequestInfo(String id);
+
+    /**
+     * Get the request infos
+     * @return An iterator for the request infos
+     */
+    Iterable<RequestInfo> getRequestInfos();
+
+    /**
+     * Get the maximum number of provided infos
+     * @return The maximum number, {@code 0} if no infos are recorded
+     */
+    int getMayNumberOfInfos();
+
+    /**
+     * Clear all request infos
+     */
+    void clear();
+ }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java b/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java
index d1e3add..eabc7eb 100644
--- a/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java
+++ b/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java
@@ -42,6 +42,7 @@
 import org.apache.sling.commons.mime.MimeTypeService;
 import org.apache.sling.engine.SlingRequestProcessor;
 import org.apache.sling.engine.impl.console.RequestHistoryConsolePlugin;
+import org.apache.sling.engine.impl.debug.RequestInfoProviderImpl;
 import org.apache.sling.engine.impl.filter.ServletFilterManager;
 import org.apache.sling.engine.impl.helper.ClientAbortException;
 import org.apache.sling.engine.impl.helper.RequestListenerManager;
@@ -116,7 +117,7 @@
                      "internally recorded for display on the \"Recent Requests\" Web Console page. If " +
                      "this value is less than or equal to zero, no requests are internally kept. The " +
                      "default value is 20. ")
-        int sling_max_record_requests() default RequestHistoryConsolePlugin.STORED_REQUESTS_COUNT;
+        int sling_max_record_requests() default RequestInfoProviderImpl.STORED_REQUESTS_COUNT;
 
         @AttributeDefinition(name = "Recorded Request Path Patterns",
                 description = "One or more regular expressions which " +
diff --git a/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java b/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java
index 63b1458..8337d5a 100644
--- a/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java
+++ b/src/main/java/org/apache/sling/engine/impl/SlingRequestProcessorImpl.java
@@ -48,6 +48,7 @@
 import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;
 import org.apache.sling.engine.SlingRequestProcessor;
 import org.apache.sling.engine.impl.console.RequestHistoryConsolePlugin;
+import org.apache.sling.engine.impl.debug.RequestInfoProviderImpl;
 import org.apache.sling.engine.impl.filter.AbstractSlingFilterChain;
 import org.apache.sling.engine.impl.filter.FilterHandle;
 import org.apache.sling.engine.impl.filter.RequestSlingFilterChain;
@@ -123,9 +124,6 @@
         final SlingHttpServletRequest request = requestData.getSlingRequest();
         final SlingHttpServletResponse response = requestData.getSlingResponse();
 
-        // record the request for the web console display
-        RequestHistoryConsolePlugin.recordRequest(request);
-
         try {
             final ServletResolver sr = this.servletResolver;
 
@@ -226,6 +224,9 @@
             handleError(t, request, response);
 
         } finally {
+            // record the request for the web console and info provider
+            RequestInfoProviderImpl.recordRequest(request);
+
             if (mbean != null) {
                 mbean.addRequestData(requestData);
             }
diff --git a/src/main/java/org/apache/sling/engine/impl/console/RequestHistoryConsolePlugin.java b/src/main/java/org/apache/sling/engine/impl/console/RequestHistoryConsolePlugin.java
index bfc3c71..c6d6fa5 100644
--- a/src/main/java/org/apache/sling/engine/impl/console/RequestHistoryConsolePlugin.java
+++ b/src/main/java/org/apache/sling/engine/impl/console/RequestHistoryConsolePlugin.java
@@ -21,12 +21,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.regex.Pattern;
 
 import javax.servlet.Servlet;
 import javax.servlet.ServletException;
@@ -34,15 +29,13 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.api.request.RequestProgressTracker;
 import org.apache.sling.api.request.ResponseUtil;
 import org.apache.sling.api.resource.ResourceUtil;
-import org.apache.sling.engine.impl.SlingMainServlet;
+import org.apache.sling.engine.RequestInfo;
+import org.apache.sling.engine.RequestInfoProvider;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Deactivate;
-import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.propertytypes.ServiceDescription;
 import org.osgi.service.component.propertytypes.ServiceVendor;
 
@@ -52,7 +45,6 @@
  * useful when testing or explaining things.
  */
 @Component(service = Servlet.class,
-    configurationPid = SlingMainServlet.PID,
     property = {
             "felix.webconsole.label=" + RequestHistoryConsolePlugin.LABEL,
             "felix.webconsole.title=Recent requests",
@@ -70,104 +62,32 @@
 
     public static final String CLEAR = "clear";
 
-    private static volatile RequestHistoryConsolePlugin instance;
-
-    public static final int STORED_REQUESTS_COUNT = 20;
-
-    private volatile RequestInfoMap requests;
-
-    private volatile List<Pattern> storePatterns = Collections.emptyList();
+    private final RequestInfoProvider infoProvider;
 
     @Activate
-    public RequestHistoryConsolePlugin(final SlingMainServlet.Config config) {
-        update(config);
-        instance = this;
+    public RequestHistoryConsolePlugin(final @Reference RequestInfoProvider provider) {
+        this.infoProvider = provider;
     }
 
-
-    @Modified
-    protected void update(final SlingMainServlet.Config config) {
-        this.requests = (config.sling_max_record_requests() > 0)
-                ? new RequestInfoMap(config.sling_max_record_requests())
-                : null;
-        final List<Pattern> compiledPatterns = new ArrayList<>();
-        if (config.sling_store_pattern_requests() != null) {
-            for (String pattern : config.sling_store_pattern_requests()) {
-                if (pattern != null && pattern.trim().length() > 0) {
-                    compiledPatterns.add(Pattern.compile(pattern));
-                }
-            }
-        }
-        this.storePatterns = compiledPatterns;
-
-    }
-
-    @Deactivate
-    protected void deactivate() {
-        instance = null;
-        clear();
-    }
-
-    public static void recordRequest(final SlingHttpServletRequest r) {
-        final RequestHistoryConsolePlugin local = instance;
-        if (local != null) {
-            local.addRequest(r);
-        }
-    }
-
-    private void addRequest(SlingHttpServletRequest r) {
-        final RequestInfoMap local = requests;
-        if (local != null) {
-            String requestPath = r.getPathInfo();
-            boolean accept = true;
-            final List<Pattern> patterns = storePatterns;
-            if (!patterns.isEmpty()) {
-                accept = false;
-                for (Pattern pattern : patterns) {
-                    if (pattern.matcher(requestPath).matches()) {
-                        accept = true;
-                        break;
-                    }
-                }
-            }
-
-            if (accept) {
-                RequestInfo info = new RequestInfo(r);
-                synchronized (local) {
-                    local.put(info.getKey(), info);
-                }
-            }
-        }
-    }
-
-    private void clear() {
-        final RequestInfoMap local = requests;
-        if (local != null) {
-            local.clear();
-        }
-    }
-
-    private void printLinksTable(PrintWriter pw, List<RequestInfo> values, String currentRequestIndex) {
+    private void printLinksTable(final PrintWriter pw, final List<RequestInfo> values, final String currentRequestIndex) {
         final List<String> links = new ArrayList<String>();
-        if (values != null) {
-            for (RequestInfo info : values) {
-                final String key = ResponseUtil.escapeXml(info.getKey());
-                final boolean isCurrent = info.getKey().equals(currentRequestIndex);
-                final StringBuilder sb = new StringBuilder();
-                sb.append("<span style='white-space: pre; text-align:right; font-size:80%'>");
-                sb.append(String.format("%1$8s", key));
-                sb.append("</span> ");
-                sb.append("<a href='" + LABEL + "?index=" + key + "'>");
-                if (isCurrent) {
-                    sb.append("<b>");
-                }
-                sb.append(ResponseUtil.escapeXml(info.getLabel()));
-                if (isCurrent) {
-                    sb.append("</b>");
-                }
-                sb.append("</a> ");
-                links.add(sb.toString());
+        for (final RequestInfo info : values) {
+            final String key = ResponseUtil.escapeXml(info.getId());
+            final boolean isCurrent = info.getId().equals(currentRequestIndex);
+            final StringBuilder sb = new StringBuilder();
+            sb.append("<span style='white-space: pre; text-align:right; font-size:80%'>");
+            sb.append(String.format("%1$8s", key));
+            sb.append("</span> ");
+            sb.append("<a href='" + LABEL + "?index=" + key + "'>");
+            if (isCurrent) {
+                sb.append("<b>");
             }
+            sb.append(ResponseUtil.escapeXml(getLabel(info)));
+            if (isCurrent) {
+                sb.append("</b>");
+            }
+            sb.append("</a> ");
+            links.add(sb.toString());
         }
 
         final int nCols = 5;
@@ -197,30 +117,21 @@
     }
 
     @Override
-    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
             throws ServletException, IOException {
-        final RequestInfoMap local = requests;
-
         // get all requests and select request to display
         final String key = req.getParameter(INDEX);
-        final List<RequestInfo> values;
-        RequestInfo info = null;
-        if (local != null) {
-            synchronized (local) {
-                values = new ArrayList<>(local.values());
-                if (key != null) {
-                    info = local.get(key);
-                }
-            }
-        } else {
-            values = null;
+        final RequestInfo info = key == null ? null : this.infoProvider.getRequestInfo(key);
+        final List<RequestInfo> values = new ArrayList<>();
+        for(final RequestInfo i : this.infoProvider.getRequestInfos()) {
+            values.add(i);
         }
 
         final PrintWriter pw = resp.getWriter();
 
-        if (local != null) {
+        if (this.infoProvider.getMayNumberOfInfos() > 0) {
             pw.println("<p class='statline ui-state-highlight'>Recorded "
-                    + values.size() + " requests (max: " + local.getMaxSize() + ")</p>");
+                    + values.size() + " requests (max: " + this.infoProvider.getMayNumberOfInfos() + ")</p>");
         } else {
             pw.println("<p class='statline ui-state-highlight'>Request Recording disabled</p>");
         }
@@ -243,109 +154,42 @@
             pw.printf(
                 "<th class='ui-widget-header'>Request %s (%s %s) by %s - RequestProgressTracker Info</th>%n",
                 key, ResponseUtil.escapeXml(info.getMethod()),
-                ResponseUtil.escapeXml(info.getPathInfo()), ResponseUtil.escapeXml(info.getUser()));
+                ResponseUtil.escapeXml(info.getPath()), ResponseUtil.escapeXml(info.getUserId()));
             pw.println("</tr>");
             pw.println("</thead>");
 
             pw.println("<tbody>");
 
             // Request Progress Tracker Info
-            pw.println("<tr><td>");
-            final Iterator<String> it = info.getTracker().getMessages();
-            pw.print("<pre>");
-            while (it.hasNext()) {
-                pw.print(ResponseUtil.escapeXml(it.next()));
-            }
+            pw.println("<tr><td><pre>");
+            pw.print(ResponseUtil.escapeXml(info.getLog()));
             pw.println("</pre></td></tr>");
             pw.println("</tbody></table>");
         }
     }
 
     @Override
-    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+    protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
             throws IOException {
         if (req.getParameter(CLEAR) != null) {
-            clear();
+            this.infoProvider.clear();
             resp.sendRedirect(req.getRequestURI());
         }
     }
 
-    private static class RequestInfo {
+    public String getLabel(final RequestInfo info) {
+        final StringBuilder sb = new StringBuilder();
 
-        private static AtomicLong requestCounter = new AtomicLong(0);
+        sb.append(info.getMethod());
+        sb.append(' ');
 
-        private final String key;
-
-        private final String method;
-
-        private final String pathInfo;
-
-        private final String user;
-
-        private final RequestProgressTracker tracker;
-
-        RequestInfo(SlingHttpServletRequest request) {
-            this.key = String.valueOf(requestCounter.incrementAndGet());
-            this.method = request.getMethod();
-            this.pathInfo = request.getPathInfo();
-            this.user = request.getRemoteUser();
-            this.tracker = request.getRequestProgressTracker();
+        final String path = info.getPath();
+        if (path.length() > 0) {
+            sb.append(ResourceUtil.getName(path));
+        } else {
+            sb.append('/');
         }
 
-        public String getKey() {
-            return key;
-        }
-
-        public String getMethod() {
-            return method;
-        }
-
-        public String getPathInfo() {
-            return pathInfo;
-        }
-
-        public String getUser() {
-            return user;
-        }
-
-        public String getLabel() {
-            final StringBuilder sb = new StringBuilder();
-
-            sb.append(getMethod());
-            sb.append(' ');
-
-            final String path = getPathInfo();
-            if (path != null && path.length() > 0) {
-                sb.append(ResourceUtil.getName(getPathInfo()));
-            } else {
-                sb.append('/');
-            }
-
-            return sb.toString();
-        }
-
-        public RequestProgressTracker getTracker() {
-            return tracker;
-        }
-    }
-
-    private static class RequestInfoMap extends LinkedHashMap<String, RequestInfo> {
-
-        private static final long serialVersionUID = 4120391774146501524L;
-
-        private int maxSize;
-
-        RequestInfoMap(int maxSize) {
-            this.maxSize = maxSize;
-        }
-
-        @Override
-        protected boolean removeEldestEntry(java.util.Map.Entry<String, RequestInfo> eldest) {
-            return size() > maxSize;
-        }
-
-        public int getMaxSize() {
-            return maxSize;
-        }
+        return sb.toString();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/engine/impl/debug/RequestInfoProviderImpl.java b/src/main/java/org/apache/sling/engine/impl/debug/RequestInfoProviderImpl.java
new file mode 100644
index 0000000..8afe76f
--- /dev/null
+++ b/src/main/java/org/apache/sling/engine/impl/debug/RequestInfoProviderImpl.java
@@ -0,0 +1,209 @@
+/*
+ * 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.engine.impl.debug;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Pattern;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.engine.RequestInfo;
+import org.apache.sling.engine.RequestInfoProvider;
+import org.apache.sling.engine.impl.SlingMainServlet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+
+@Component(service = {RequestInfoProvider.class}, configurationPid = SlingMainServlet.PID)
+public class RequestInfoProviderImpl implements RequestInfoProvider {
+
+    /** Default for stored requests */
+    public static final int STORED_REQUESTS_COUNT = 20;
+
+    private volatile ConcurrentNavigableMap<String, RequestInfo> requests;
+
+    private volatile List<Pattern> patterns;
+
+    private volatile int maxSize;
+
+    private static volatile RequestInfoProviderImpl INSTANCE;
+
+    @Activate
+    public RequestInfoProviderImpl(final SlingMainServlet.Config config) {
+        update(config);
+        INSTANCE = this;
+    }
+
+    @Modified
+    protected void update(final SlingMainServlet.Config config) {
+        this.maxSize = config.sling_max_record_requests();
+        if ( this.maxSize < 0 ) {
+            this.maxSize = 0;
+        }
+        this.requests = (this.maxSize > 0) ? new ConcurrentSkipListMap<>() : null;
+        final List<Pattern> compiledPatterns = new ArrayList<>();
+        if (config.sling_store_pattern_requests() != null) {
+            for (final String pattern : config.sling_store_pattern_requests()) {
+                if (pattern != null && pattern.trim().length() > 0) {
+                    compiledPatterns.add(Pattern.compile(pattern.trim()));
+                }
+            }
+        }
+        this.patterns = compiledPatterns;
+
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        INSTANCE = null;
+        this.requests = null;
+        this.patterns = Collections.emptyList();
+    }
+
+    public static void recordRequest(final SlingHttpServletRequest r) {
+        final RequestInfoProviderImpl local = INSTANCE;
+        if (local != null) {
+            local.addRequest(r);
+        }
+    }
+
+    private void addRequest(final SlingHttpServletRequest r) {
+        final ConcurrentNavigableMap<String, RequestInfo> local = requests;
+        if (local != null) {
+            final String requestPath = r.getPathInfo();
+            final List<Pattern> patterns = this.patterns;
+            boolean accept = patterns.isEmpty();
+            for (Pattern pattern : patterns) {
+                if (pattern.matcher(requestPath).matches()) {
+                    accept = true;
+                    break;
+                }
+            }
+
+            if (accept) {
+                final RequestInfo info = new RequestInfoImpl(r);
+                synchronized (local) {
+                    if ( local.size() == this.maxSize ) {
+                        local.remove(local.firstKey());
+                    }
+                    local.put(info.getId(), info);
+                }
+            }
+        }
+    }
+
+    @Override
+    public int getMayNumberOfInfos() {
+        return this.maxSize;
+    }
+
+    @Override
+    public void clear() {
+        final ConcurrentNavigableMap<String, RequestInfo> local = requests;
+        if (local != null) {
+            local.clear();
+        }
+    }
+
+    @Override
+    public RequestInfo getRequestInfo(final String id) {
+        final ConcurrentNavigableMap<String, RequestInfo> local = requests;
+        if ( local != null ) {
+            return local.get(id);
+        }
+        return null;
+    }
+
+    @Override
+    public Iterable<RequestInfo> getRequestInfos() {
+        final ConcurrentNavigableMap<String, RequestInfo> local = requests;
+        if ( local != null ) {
+            return local.values();
+        }
+        return Collections.emptyList();
+    }
+
+    private static class RequestInfoImpl implements RequestInfo {
+
+        private static AtomicLong requestCounter = new AtomicLong(0);
+
+        private final String id;
+
+        private final String method;
+
+        private final String path;
+
+        private final String userId;
+
+        private final String log;
+
+        RequestInfoImpl(final SlingHttpServletRequest request) {
+            this.id = String.valueOf(System.currentTimeMillis()).concat("-").concat(String.valueOf(requestCounter.incrementAndGet()));
+            this.method = request.getMethod();
+            this.path = request.getPathInfo() == null ? "" : request.getPathInfo();
+            this.userId = request.getRemoteUser();
+            String text;
+            try ( final StringWriter writer = new StringWriter()) {
+                final PrintWriter pw = new PrintWriter(writer);
+                request.getRequestProgressTracker().dump(pw);
+                pw.flush();
+                text = writer.toString();
+            } catch ( final IOException ioe) {
+                text = "";
+            }
+            this.log = text;
+        }
+
+        @Override
+        public @NotNull String getId() {
+            return this.id;
+        }
+
+        @Override
+        public @NotNull String getMethod() {
+            return this.method;
+        }
+
+        @Override
+        public @NotNull String getPath() {
+            return this.path;
+        }
+
+        @Override
+        public @Nullable String getUserId() {
+            return this.userId;
+        }
+
+        @Override
+        public @NotNull String getLog() {
+            return this.log;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/engine/package-info.java b/src/main/java/org/apache/sling/engine/package-info.java
index c4c2df0..7843654 100644
--- a/src/main/java/org/apache/sling/engine/package-info.java
+++ b/src/main/java/org/apache/sling/engine/package-info.java
@@ -17,6 +17,6 @@
  * under the License.
  */
 
-@org.osgi.annotation.versioning.Version("2.4.0")
+@org.osgi.annotation.versioning.Version("2.5.0")
 package org.apache.sling.engine;
 
diff --git a/src/test/java/org/apache/sling/engine/impl/debug/RequestInfoProviderImplTest.java b/src/test/java/org/apache/sling/engine/impl/debug/RequestInfoProviderImplTest.java
new file mode 100644
index 0000000..989c6d8
--- /dev/null
+++ b/src/test/java/org/apache/sling/engine/impl/debug/RequestInfoProviderImplTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.engine.impl.debug;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.builder.Builders;
+import org.apache.sling.engine.RequestInfo;
+import org.apache.sling.engine.impl.SlingMainServlet;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class RequestInfoProviderImplTest {
+
+    @Test public void testDisabledProvider() {
+        final SlingMainServlet.Config config = Mockito.mock(SlingMainServlet.Config.class);
+        Mockito.when(config.sling_max_record_requests()).thenReturn(0);
+
+        final SlingHttpServletRequest request = Mockito.mock(SlingHttpServletRequest.class);
+        Mockito.when(request.getPathInfo()).thenReturn("/content");
+        Mockito.when(request.getRemoteUser()).thenReturn("admin");
+        Mockito.when(request.getMethod()).thenReturn("GET");
+        Mockito.when(request.getRequestProgressTracker()).thenReturn(Builders.newRequestProgressTracker());
+
+        final RequestInfoProviderImpl provider = new RequestInfoProviderImpl(config);
+        assertEquals(0, provider.getMayNumberOfInfos());
+        RequestInfoProviderImpl.recordRequest(request);
+
+        assertFalse(provider.getRequestInfos().iterator().hasNext());
+    }
+
+    @Test public void testEnabledProvider() {
+        final SlingMainServlet.Config config = Mockito.mock(SlingMainServlet.Config.class);
+        Mockito.when(config.sling_max_record_requests()).thenReturn(5);
+
+        final SlingHttpServletRequest request = Mockito.mock(SlingHttpServletRequest.class);
+        Mockito.when(request.getPathInfo()).thenReturn("/content");
+        Mockito.when(request.getRemoteUser()).thenReturn("admin");
+        Mockito.when(request.getMethod()).thenReturn("GET");
+        Mockito.when(request.getRequestProgressTracker()).thenReturn(Builders.newRequestProgressTracker());
+
+        final RequestInfoProviderImpl provider = new RequestInfoProviderImpl(config);
+        assertEquals(5, provider.getMayNumberOfInfos());
+        RequestInfoProviderImpl.recordRequest(request);
+
+        String id = null;
+        for(final RequestInfo info : provider.getRequestInfos()) {
+            if ( id != null ) {
+                fail("More than one request info");
+            }
+            id = info.getId();
+        }
+        final RequestInfo info = provider.getRequestInfo(id);
+        assertNotNull(info);
+
+        assertEquals("/content", info.getPath());
+        assertEquals("admin", info.getUserId());
+        assertEquals(id, info.getId());
+        assertEquals("GET", info.getMethod());
+        assertFalse(info.getLog().isEmpty());
+    }
+}