Hi,

do we have a Jira issue covering this?

Could you please also apply the patch at the R7 branch so they don't get
out of sync?

Thanks

Carsten


Nbartlett wrote
> Author: nbartlett
> Date: Tue Nov 14 19:09:34 2017
> New Revision: 1815249
> 
> URL: http://svn.apache.org/viewvc?rev=1815249&view=rev
> Log:
> Implement HTTP request logging. This closes #127
> 
> Added:
>     
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java
>     
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java
>     
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java
>     
> felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java
> Modified:
>     felix/trunk/http/jetty/pom.xml
>     
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
>     
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
> 
> Modified: felix/trunk/http/jetty/pom.xml
> URL: 
> http://svn.apache.org/viewvc/felix/trunk/http/jetty/pom.xml?rev=1815249&r1=1815248&r2=1815249&view=diff
> ==============================================================================
> --- felix/trunk/http/jetty/pom.xml (original)
> +++ felix/trunk/http/jetty/pom.xml Tue Nov 14 19:09:34 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/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java
> URL: 
> http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java?rev=1815249&view=auto
> ==============================================================================
> --- 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java
>  (added)
> +++ 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java
>  Tue Nov 14 19:09:34 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/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
> URL: 
> http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java?rev=1815249&r1=1815248&r2=1815249&view=diff
> ==============================================================================
> --- 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
>  (original)
> +++ 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
>  Tue Nov 14 19:09:34 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/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
> URL: 
> http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java?rev=1815249&r1=1815248&r2=1815249&view=diff
> ==============================================================================
> --- 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
>  (original)
> +++ 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
>  Tue Nov 14 19:09:34 2017
> @@ -101,11 +101,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 +296,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 +433,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/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java
> URL: 
> http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java?rev=1815249&view=auto
> ==============================================================================
> --- 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java
>  (added)
> +++ 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java
>  Tue Nov 14 19:09:34 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/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java
> URL: 
> http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java?rev=1815249&view=auto
> ==============================================================================
> --- 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java
>  (added)
> +++ 
> felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java
>  Tue Nov 14 19:09:34 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/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java
> URL: 
> http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java?rev=1815249&view=auto
> ==============================================================================
> --- 
> felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java
>  (added)
> +++ 
> felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java
>  Tue Nov 14 19:09:34 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());
> +    }
> +}
> 
> 
-- 
Carsten Ziegeler
Adobe Research Switzerland
[email protected]

Reply via email to