On Tue, Nov 14, 2017 at 8:51 PM, Neil Bartlett (Paremus)
<[email protected]> wrote:
>
>> 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 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]

Reply via email to