> On 14 Nov 2017, at 19:32, Carsten Ziegeler <[email protected]> wrote:
> 
> Hi,
> 
> do we have a Jira issue covering this?

No this didn’t come from a JIRA.

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

Sure, will do.

Neil

> 
> 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