Done, and done: https://issues.apache.org/jira/browse/FELIX-5744 
<https://issues.apache.org/jira/browse/FELIX-5744>

Thanks guys.

> On 14 Nov 2017, at 19:52, Karl Pauls <[email protected]> wrote:
> 
> On Tue, Nov 14, 2017 at 8:51 PM, Neil Bartlett (Paremus)
> <[email protected] <mailto:[email protected]>> wrote:
>> 
>>> On 14 Nov 2017, at 19:32, Carsten Ziegeler <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>> Hi,
>>> 
>>> do we have a Jira issue covering this?
>> 
>> No this didn’t come from a JIRA.
> 
> Could you maybe create one so that we don't forget that this happend?
> 
> regards,
> 
> Karl
> 
>>> 
>>> 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]
>> 
> 
> 
> 
> -- 
> Karl Pauls
> [email protected] <mailto:[email protected]>

Reply via email to