You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by nb...@apache.org on 2017/11/14 20:08:58 UTC

svn commit: r1815258 - in /felix/trunk/osgi-r7/http/jetty: ./ src/main/java/org/apache/felix/http/jetty/internal/ src/test/java/org/apache/felix/http/jetty/internal/

Author: nbartlett
Date: Tue Nov 14 20:08:58 2017
New Revision: 1815258

URL: http://svn.apache.org/viewvc?rev=1815258&view=rev
Log:
FELIX-5744 Felix HTTP Jetty does not support request logging

Applied to R7 branch from rev 1815249

Added:
    felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java
    felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java
    felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java
    felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java
Modified:
    felix/trunk/osgi-r7/http/jetty/pom.xml
    felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
    felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java

Modified: felix/trunk/osgi-r7/http/jetty/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/pom.xml?rev=1815258&r1=1815257&r2=1815258&view=diff
==============================================================================
--- felix/trunk/osgi-r7/http/jetty/pom.xml (original)
+++ felix/trunk/osgi-r7/http/jetty/pom.xml Tue Nov 14 20:08:58 2017
@@ -38,6 +38,8 @@
     </scm>
     
     <properties>
+        <!-- Skip because of problems with Java 8 -->
+        <animal.sniffer.skip>true</animal.sniffer.skip>
         <felix.java.version>8</felix.java.version>
         <jetty.version>9.3.22.v20171030</jetty.version>
     </properties>

Added: felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java?rev=1815258&view=auto
==============================================================================
--- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java (added)
+++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java Tue Nov 14 20:08:58 2017
@@ -0,0 +1,97 @@
+/*
+ * 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.felix.http.jetty.internal;
+
+import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.eclipse.jetty.server.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+class FileRequestLog {
+
+    public static final String SVC_PROP_NAME = "name";
+    public static final String DEFAULT_NAME = "file";
+    public static final String SVC_PROP_FILEPATH = "filepath";
+
+    private final NCSARequestLog delegate;
+    private final String logFilePath;
+    private final String serviceName;
+    private ServiceRegistration<RequestLog> registration = null;
+
+    FileRequestLog(JettyConfig config) {
+        logFilePath = config.getRequestLogFilePath();
+        serviceName = config.getRequestLogFileServiceName() != null ? config.getRequestLogFileServiceName() : DEFAULT_NAME;
+        if (config.isRequestLogFileAsync()) {
+            delegate = new AsyncNCSARequestLog(logFilePath);
+        } else {
+            delegate = new NCSARequestLog(logFilePath);
+        }
+
+        delegate.setAppend(config.isRequestLogFileAppend());
+        delegate.setRetainDays(config.getRequestLogFileRetainDays());
+        delegate.setFilenameDateFormat(config.getRequestLogFilenameDateFormat());
+        delegate.setExtended(config.isRequestLogFileExtended());
+        delegate.setIgnorePaths(config.getRequestLogFileIgnorePaths());
+        delegate.setLogCookies(config.isRequestLogFileLogCookies());
+        delegate.setLogServer(config.isRequestLogFileLogServer());
+        delegate.setLogLatency(config.isRequestLogFileLogLatency());
+    }
+
+    synchronized void start(BundleContext context) throws IOException, IllegalStateException {
+        File logFile = new File(logFilePath).getAbsoluteFile();
+        File logFileDir = logFile.getParentFile();
+        if (logFileDir != null && !logFileDir.isDirectory()) {
+            SystemLogger.info("Creating directory " + logFileDir.getAbsolutePath());
+            Files.createDirectories(logFileDir.toPath(), PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")));
+        }
+
+        if (registration != null) {
+            throw new IllegalStateException(getClass().getSimpleName() + " is already started");
+        }
+        try {
+            delegate.start();
+            Dictionary<String, Object> svcProps = new Hashtable<>();
+            svcProps.put(SVC_PROP_NAME, serviceName);
+            svcProps.put(SVC_PROP_FILEPATH, logFilePath);
+            registration = context.registerService(RequestLog.class, delegate, svcProps);
+        } catch (Exception e) {
+            SystemLogger.error("Error starting File Request Log", e);
+        }
+    }
+
+    synchronized void stop() {
+        try {
+            if (registration != null) {
+                registration.unregister();
+            }
+            delegate.stop();;
+        } catch (Exception e) {
+            SystemLogger.error("Error shutting down File Request Log", e);
+        } finally {
+            registration = null;
+        }
+    }
+
+}

Modified: felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java?rev=1815258&r1=1815257&r2=1815258&view=diff
==============================================================================
--- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java (original)
+++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java Tue Nov 14 20:08:58 2017
@@ -165,6 +165,51 @@ public final class JettyConfig
     /** Felix specific property to set HTTP instance name. */
     public static final String FELIX_HTTP_SERVICE_NAME = "org.apache.felix.http.name";
 
+    /** Felix specific property to configure a filter for RequestLog services */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILTER = "org.apache.felix.http.requestlog.filter";
+
+    /** Felix specific property to enable request logging to the OSGi Log Service */
+    public static final String FELIX_HTTP_REQUEST_LOG_OSGI_ENABLE = "org.apache.felix.http.requestlog.osgi.enable";
+
+    /** Felix specific property to specify the published "name" property of the OSGi Log Service-base Request Log service. Allows server configs to filter on specific log services. */
+    public static final String FELIX_HTTP_REQUEST_LOG_OSGI_SERVICE_NAME = "org.apache.felix.http.requestlog.osgi.name";
+
+    /** Felix specific property to control the level of the log messages generated by the OSGi Log Service-based request log. Values must correspond to the constants defined in the LogService interface, default is 3 "INFO". */
+    public static final String FELIX_HTTP_REQUEST_LOG_OSGI_LEVEL = "org.apache.felix.http.requestlog.osgi.level";
+
+    /** Felix specific property to enable request logging to a file and provide the path to that file. Default is null meaning that the file log is disabled. */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_PATH = "org.apache.felix.http.requestlog.file.path";
+
+    /** Felix specific property to specify the published "name" property of the file-based RequestLog service. Allows server configs to filter on specific log services. */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_SERVICE_NAME = "org.apache.felix.http.requestlog.file.name";
+
+    /** Felix specific property to enable file request logging to be asynchronous */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_ASYNC = "org.apache.felix.http.requestlog.file.async";
+
+    /** Felix specific property to enable request logging to append to the log file rather than overwriting */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_APPEND = "org.apache.felix.http.requestlog.file.append";
+
+    /** Felix specific property to specify the number of days the request log file is retained */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_RETAIN_DAYS = "org.apache.felix.http.requestlog.file.retaindays";
+
+    /** Felix specific property to specify the date format in request log file names */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_FILENAME_DATE_FORMAT = "org.apache.felix.http.requestlog.file.dateformat";
+
+    /** Felix specific property to enable extended request logging to a named file */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_EXTENDED = "org.apache.felix.http.requestlog.file.extended";
+
+    /** Felix specific property to ignore matching paths in the request log file */
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_IGNORE_PATHS = "org.apache.felix.http.requestlog.file.ignorepaths";
+
+    /** Felix specific property to enable request logging cookies in the request log file*/
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_LOG_COOKIES = "org.apache.felix.http.requestlog.file.logcookies";
+
+    /** Felix specific property to enable request logging the host name in the request log file*/
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_LOG_SERVER = "org.apache.felix.http.requestlog.file.logserver";
+
+    /** Felix specific property to enable request logging request processing time in the request log file*/
+    public static final String FELIX_HTTP_REQUEST_LOG_FILE_LOG_LATENCY = "org.apache.felix.http.requestlog.file.loglatency";
+
     /** Felix specific property to define custom properties for the http runtime service. */
     public static final String FELIX_CUSTOM_HTTP_RUNTIME_PROPERTY_PREFIX = "org.apache.felix.http.runtime.init.";
 
@@ -425,6 +470,66 @@ public final class JettyConfig
     	return (String) getProperty(FELIX_HTTP_SERVICE_NAME);
     }
 
+    public String getRequestLogFilter() {
+        return (String) getProperty(FELIX_HTTP_REQUEST_LOG_FILTER, null);
+    }
+
+    public boolean isRequestLogOSGiEnabled() {
+        return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_OSGI_ENABLE, false);
+    }
+
+    public String getRequestLogOSGiServiceName() {
+        return (String) getProperty(FELIX_HTTP_REQUEST_LOG_OSGI_SERVICE_NAME);
+    }
+
+    public int getRequestLogOSGiLevel() {
+        return getIntProperty(FELIX_HTTP_REQUEST_LOG_OSGI_LEVEL, 3); // 3 == LogService.LOG_INFO
+    }
+
+    public String getRequestLogFilePath() {
+        return (String) getProperty(FELIX_HTTP_REQUEST_LOG_FILE_PATH, null);
+    }
+
+    public String getRequestLogFileServiceName() {
+        return (String) getProperty(FELIX_HTTP_REQUEST_LOG_FILE_SERVICE_NAME, "file");
+    }
+
+    public boolean isRequestLogFileAsync() {
+        return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_ASYNC, false);
+    }
+
+    public boolean isRequestLogFileAppend() {
+        return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_APPEND, true);
+    }
+
+    public int getRequestLogFileRetainDays() {
+        return getIntProperty(FELIX_HTTP_REQUEST_LOG_FILE_RETAIN_DAYS, 31);
+    }
+
+    public String getRequestLogFilenameDateFormat() {
+        return (String) getProperty(FELIX_HTTP_REQUEST_LOG_FILE_FILENAME_DATE_FORMAT, null);
+    }
+
+    public boolean isRequestLogFileExtended() {
+        return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_EXTENDED, false);
+    }
+
+    public String[] getRequestLogFileIgnorePaths() {
+        return getStringArrayProperty(FELIX_HTTP_REQUEST_LOG_FILE_IGNORE_PATHS, new String[0]);
+    }
+
+    public boolean isRequestLogFileLogCookies() {
+        return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_LOG_COOKIES, false);
+    }
+
+    public boolean isRequestLogFileLogServer() {
+        return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_LOG_SERVER, false);
+    }
+
+    public boolean isRequestLogFileLogLatency() {
+        return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_LOG_LATENCY, false);
+    }
+
     public void reset()
     {
         update(null);
@@ -668,4 +773,5 @@ public final class JettyConfig
             return dflt;
         }
     }
+
 }

Modified: felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java?rev=1815258&r1=1815257&r2=1815258&view=diff
==============================================================================
--- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java (original)
+++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java Tue Nov 14 20:08:58 2017
@@ -66,6 +66,7 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
 import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.event.Event;
@@ -101,11 +102,15 @@ public final class JettyService extends
     private volatile BundleTracker<Deployment> bundleTracker;
     private volatile ServiceTracker<EventAdmin, EventAdmin> eventAdmintTracker;
     private volatile ConnectorFactoryTracker connectorTracker;
+    private volatile RequestLogTracker requestLogTracker;
+    private volatile LogServiceRequestLog osgiRequestLog;
+    private volatile FileRequestLog fileRequestLog;
     private volatile LoadBalancerCustomizerFactoryTracker loadBalancerCustomizerTracker;
     private volatile CustomizerWrapper customizerWrapper;
     private volatile EventAdmin eventAdmin;
     private boolean registerManagedService = true;
 
+
     public JettyService(final BundleContext context,
             final HttpServiceController controller)
     {
@@ -292,6 +297,22 @@ public final class JettyService extends
             this.controller.getEventDispatcher().setActive(false);
             this.controller.unregister();
 
+            if (this.fileRequestLog != null)
+            {
+                this.fileRequestLog.stop();
+                this.fileRequestLog = null;
+            }
+            if (this.osgiRequestLog != null)
+            {
+                this.osgiRequestLog.unregister();
+                this.osgiRequestLog = null;
+            }
+            if (this.requestLogTracker != null)
+            {
+                this.requestLogTracker.close();
+                this.requestLogTracker = null;
+            }
+
             if (this.connectorTracker != null)
             {
                 this.connectorTracker.close();
@@ -413,6 +434,26 @@ public final class JettyService extends
                 this.stopJetty();
                 SystemLogger.error("Jetty stopped (no connectors available)", null);
             }
+
+            try {
+                this.requestLogTracker = new RequestLogTracker(this.context, this.config.getRequestLogFilter());
+                this.requestLogTracker.open();
+                this.server.setRequestLog(requestLogTracker);
+            } catch (InvalidSyntaxException e) {
+                SystemLogger.error("Invalid filter syntax in request log tracker", e);
+            }
+
+            if (this.config.isRequestLogOSGiEnabled()) {
+                this.osgiRequestLog = new LogServiceRequestLog(this.config);
+                this.osgiRequestLog.register(this.context);
+                SystemLogger.info("Directing Jetty request logs to the OSGi Log Service");
+            }
+
+            if (this.config.getRequestLogFilePath() != null && !this.config.getRequestLogFilePath().isEmpty()) {
+                this.fileRequestLog = new FileRequestLog(config);
+                this.fileRequestLog.start(this.context);
+                SystemLogger.info("Directing Jetty request logs to " + this.config.getRequestLogFilePath());
+            }
         }
         else
         {

Added: felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java?rev=1815258&view=auto
==============================================================================
--- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java (added)
+++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java Tue Nov 14 20:08:58 2017
@@ -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.felix.http.jetty.internal;
+
+import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.eclipse.jetty.server.AbstractNCSARequestLog;
+import org.eclipse.jetty.server.RequestLog;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A RequestLog that logs to the OSGi LogService when present. Not registered by default.
+ */
+class LogServiceRequestLog extends AbstractNCSARequestLog {
+
+    public static final String SVC_PROP_NAME = "name";
+    public static final String DEFAULT_NAME = "osgi";
+    public static final String PREFIX = "REQUEST: ";
+
+    private static final int DEFAULT_LOG_LEVEL = 3; // LogService.LOG_INFO
+
+    private final int logLevel;
+    private final String serviceName;
+
+    private ServiceRegistration<RequestLog> registration;
+
+    LogServiceRequestLog(JettyConfig config) {
+        this.serviceName = config.getRequestLogOSGiServiceName();
+        this.logLevel = config.getRequestLogOSGiLevel();
+    }
+
+    public synchronized void register(BundleContext context) throws IllegalStateException {
+        if (registration != null) {
+            throw new IllegalStateException(getClass().getSimpleName() + " already registered");
+        }
+        Dictionary<String, Object> svcProps = new Hashtable<>();
+        svcProps.put(SVC_PROP_NAME, serviceName);
+        this.registration = context.registerService(RequestLog.class, this, svcProps);
+    }
+
+    public synchronized void unregister() {
+        try {
+            if (registration != null) {
+                registration.unregister();;
+            }
+        } finally {
+            registration = null;
+        }
+    }
+
+    @Override
+    public void write(String s) throws IOException {
+        SystemLogger.info(PREFIX + s);
+    }
+
+    @Override
+    protected boolean isEnabled() {
+        return true;
+    }
+
+}

Added: felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java?rev=1815258&view=auto
==============================================================================
--- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java (added)
+++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java Tue Nov 14 20:08:58 2017
@@ -0,0 +1,106 @@
+/*
+ * 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.felix.http.jetty.internal;
+
+import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.osgi.framework.*;
+import org.osgi.util.tracker.ServiceTracker;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * An instance of Jetty's RequestLog that dispatches to registered RequestLog services in the service registry. A filter
+ * can be provided so that it only dispatches to selected services.
+ * <p>
+ * Unchecked exceptions from the RequestLog services are caught and logged to the OSGi LogService. to avoid flooding the
+ * LogService, we will remove a RequestLog service if it breaches a maximum number of errors (see {@link
+ * RequestLogTracker#MAX_ERROR_COUNT}). Once this happens we will stop dispatching to that service entirely until it is
+ * unregistered.
+ */
+class RequestLogTracker extends ServiceTracker<RequestLog, RequestLog>  implements RequestLog {
+
+    private static final int MAX_ERROR_COUNT = 100;
+
+    private final ConcurrentMap<ServiceReference<?>, RequestLog> logSvcs = new ConcurrentHashMap<>();
+    private final ConcurrentMap<ServiceReference<?>, Integer> naughtyStep = new ConcurrentHashMap<>();
+
+    RequestLogTracker(BundleContext context, String filter) throws InvalidSyntaxException {
+        super(context, buildFilter(filter), null);
+    }
+
+    private static Filter buildFilter(String inputFilter) throws InvalidSyntaxException {
+        String objectClassFilter = String.format("(%s=%s)", Constants.OBJECTCLASS, RequestLog.class.getName());
+        String compositeFilter;
+        if (inputFilter != null) {
+            // Parse the input filter just for validation before we insert into ours.
+            FrameworkUtil.createFilter(inputFilter);
+            compositeFilter = "(&" + objectClassFilter + inputFilter + ")";
+        } else {
+            compositeFilter = objectClassFilter;
+        }
+        return FrameworkUtil.createFilter(compositeFilter);
+    }
+
+    @Override
+    public RequestLog addingService(ServiceReference<RequestLog> reference) {
+        RequestLog logSvc = context.getService(reference);
+        logSvcs.put(reference, logSvc);
+        return logSvc;
+    }
+
+    @Override
+    public void removedService(ServiceReference<RequestLog> reference, RequestLog logSvc) {
+        logSvcs.remove(reference);
+        naughtyStep.remove(reference);
+        context.ungetService(reference);
+    }
+
+    @Override
+    public void log(Request request, Response response) {
+        for (Map.Entry<ServiceReference<?>, RequestLog> entry : logSvcs.entrySet()) {
+            try {
+                entry.getValue().log(request, response);
+            } catch (Exception e) {
+                processError(entry.getKey(), e);
+            }
+        }
+    }
+
+    /**
+     * Process an exception from a RequestLog service instance, and remove the service if it has reached the maximum
+     * error limit.
+     */
+    private void processError(ServiceReference<?> reference, Exception e) {
+        SystemLogger.error(reference, String.format("Error dispatching to request log service ID %d from bundle %s:%s",
+                reference.getProperty(Constants.SERVICE_ID), reference.getBundle().getSymbolicName(), reference.getBundle().getVersion()), e);
+
+        int naughty = naughtyStep.merge(reference, 1, Integer::sum);
+        if (naughty >= MAX_ERROR_COUNT) {
+            // We have reached (but not exceeded) the maximum, and the last error has been logged. Remove from the maps
+            // so we will not invoke the service again.
+            logSvcs.remove(reference);
+            naughtyStep.remove(reference);
+            SystemLogger.error(reference, String.format("RequestLog service ID %d from bundle %s:%s threw too many errors, it will no longer be invoked.",
+                    reference.getProperty(Constants.SERVICE_ID), reference.getBundle().getSymbolicName(), reference.getBundle().getVersion()), null);
+        }
+    }
+}

Added: felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java?rev=1815258&view=auto
==============================================================================
--- felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java (added)
+++ felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java Tue Nov 14 20:08:58 2017
@@ -0,0 +1,97 @@
+/*
+ * 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.felix.http.jetty.internal;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.framework.*;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RequestLogTrackerTest {
+    
+    @Mock
+    BundleContext context;
+    
+    @Test
+    public void testInvokeRequestLog() throws Exception {
+        RequestLogTracker tracker = new RequestLogTracker(context, null);
+
+        RequestLog mockRequestLog = mock(RequestLog.class);
+
+        ServiceReference<RequestLog> mockSvcRef = mock(ServiceReference.class);
+        when(context.getService(mockSvcRef)).thenReturn(mockRequestLog);
+
+        // These invocations not passed through to the mock because it is not registered yet
+        for (int i = 0; i < 10; i++)
+            tracker.log(null, null);
+
+        tracker.addingService(mockSvcRef);
+
+        // These will pass through
+        for (int i = 0; i < 15; i++)
+            tracker.log(null, null);
+
+        tracker.removedService(mockSvcRef, mockRequestLog);
+
+        // And these will not.
+        for (int i = 0; i < 50; i++)
+            tracker.log(null, null);
+
+        verify(mockRequestLog, times(15)).log(isNull(Request.class), isNull(Response.class));
+    }
+
+    @Test
+    public void testNaughtyService() throws Exception {
+        RequestLogTracker tracker = new RequestLogTracker(context, null);
+
+        AtomicInteger counter = new AtomicInteger(0);
+        RequestLog mockRequestLog = new RequestLog() {
+            @Override
+            public void log(Request request, Response response) {
+                counter.addAndGet(1);
+                throw new RuntimeException("This service always explodes");
+            }
+        };
+        ServiceReference<RequestLog> mockSvcRef = mock(ServiceReference.class);
+        Bundle mockBundle = mock(Bundle.class);
+        when(mockSvcRef.getBundle()).thenReturn(mockBundle);
+        when(mockBundle.getSymbolicName()).thenReturn("org.example");
+        when(mockBundle.getVersion()).thenReturn(new Version("1.0.0"));
+        when(context.getService(mockSvcRef)).thenReturn(mockRequestLog);
+
+        tracker.addingService(mockSvcRef);
+
+        // Invoke 200 times
+        for (int i = 0; i < 200; i++)
+            tracker.log(null, null);
+
+        tracker.removedService(mockSvcRef, mockRequestLog);
+
+        // Invoked 100 times and then removed
+        assertEquals(100, counter.get());
+    }
+}