Hi, do we have a Jira issue covering this?
Could you please also apply the patch at the R7 branch so they don't get out of sync? Thanks Carsten Nbartlett wrote > Author: nbartlett > Date: Tue Nov 14 19:09:34 2017 > New Revision: 1815249 > > URL: http://svn.apache.org/viewvc?rev=1815249&view=rev > Log: > Implement HTTP request logging. This closes #127 > > Added: > > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java > > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java > > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java > > felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java > Modified: > felix/trunk/http/jetty/pom.xml > > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java > > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java > > Modified: felix/trunk/http/jetty/pom.xml > URL: > http://svn.apache.org/viewvc/felix/trunk/http/jetty/pom.xml?rev=1815249&r1=1815248&r2=1815249&view=diff > ============================================================================== > --- felix/trunk/http/jetty/pom.xml (original) > +++ felix/trunk/http/jetty/pom.xml Tue Nov 14 19:09:34 2017 > @@ -38,6 +38,8 @@ > </scm> > > <properties> > + <!-- Skip because of problems with Java 8 --> > + <animal.sniffer.skip>true</animal.sniffer.skip> > <felix.java.version>8</felix.java.version> > <jetty.version>9.3.22.v20171030</jetty.version> > </properties> > > Added: > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java > URL: > http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java?rev=1815249&view=auto > ============================================================================== > --- > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java > (added) > +++ > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java > Tue Nov 14 19:09:34 2017 > @@ -0,0 +1,97 @@ > +/* > + * Licensed to the Apache Software Foundation (ASF) under one or more > + * contributor license agreements. See the NOTICE file distributed with > + * this work for additional information regarding copyright ownership. > + * The ASF licenses this file to You under the Apache License, Version 2.0 > + * (the "License"); you may not use this file except in compliance with > + * the License. You may obtain a copy of the License at > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > +package org.apache.felix.http.jetty.internal; > + > +import org.apache.felix.http.base.internal.logger.SystemLogger; > +import org.eclipse.jetty.server.*; > +import org.osgi.framework.BundleContext; > +import org.osgi.framework.ServiceRegistration; > + > +import java.io.File; > +import java.io.IOException; > +import java.nio.file.Files; > +import java.nio.file.attribute.PosixFilePermission; > +import java.nio.file.attribute.PosixFilePermissions; > +import java.util.Dictionary; > +import java.util.Hashtable; > + > +class FileRequestLog { > + > + public static final String SVC_PROP_NAME = "name"; > + public static final String DEFAULT_NAME = "file"; > + public static final String SVC_PROP_FILEPATH = "filepath"; > + > + private final NCSARequestLog delegate; > + private final String logFilePath; > + private final String serviceName; > + private ServiceRegistration<RequestLog> registration = null; > + > + FileRequestLog(JettyConfig config) { > + logFilePath = config.getRequestLogFilePath(); > + serviceName = config.getRequestLogFileServiceName() != null ? > config.getRequestLogFileServiceName() : DEFAULT_NAME; > + if (config.isRequestLogFileAsync()) { > + delegate = new AsyncNCSARequestLog(logFilePath); > + } else { > + delegate = new NCSARequestLog(logFilePath); > + } > + > + delegate.setAppend(config.isRequestLogFileAppend()); > + delegate.setRetainDays(config.getRequestLogFileRetainDays()); > + > delegate.setFilenameDateFormat(config.getRequestLogFilenameDateFormat()); > + delegate.setExtended(config.isRequestLogFileExtended()); > + delegate.setIgnorePaths(config.getRequestLogFileIgnorePaths()); > + delegate.setLogCookies(config.isRequestLogFileLogCookies()); > + delegate.setLogServer(config.isRequestLogFileLogServer()); > + delegate.setLogLatency(config.isRequestLogFileLogLatency()); > + } > + > + synchronized void start(BundleContext context) throws IOException, > IllegalStateException { > + File logFile = new File(logFilePath).getAbsoluteFile(); > + File logFileDir = logFile.getParentFile(); > + if (logFileDir != null && !logFileDir.isDirectory()) { > + SystemLogger.info("Creating directory " + > logFileDir.getAbsolutePath()); > + Files.createDirectories(logFileDir.toPath(), > PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"))); > + } > + > + if (registration != null) { > + throw new IllegalStateException(getClass().getSimpleName() + " > is already started"); > + } > + try { > + delegate.start(); > + Dictionary<String, Object> svcProps = new Hashtable<>(); > + svcProps.put(SVC_PROP_NAME, serviceName); > + svcProps.put(SVC_PROP_FILEPATH, logFilePath); > + registration = context.registerService(RequestLog.class, > delegate, svcProps); > + } catch (Exception e) { > + SystemLogger.error("Error starting File Request Log", e); > + } > + } > + > + synchronized void stop() { > + try { > + if (registration != null) { > + registration.unregister(); > + } > + delegate.stop();; > + } catch (Exception e) { > + SystemLogger.error("Error shutting down File Request Log", e); > + } finally { > + registration = null; > + } > + } > + > +} > > Modified: > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java > URL: > http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java?rev=1815249&r1=1815248&r2=1815249&view=diff > ============================================================================== > --- > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java > (original) > +++ > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java > Tue Nov 14 19:09:34 2017 > @@ -165,6 +165,51 @@ public final class JettyConfig > /** Felix specific property to set HTTP instance name. */ > public static final String FELIX_HTTP_SERVICE_NAME = > "org.apache.felix.http.name"; > > + /** Felix specific property to configure a filter for RequestLog > services */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILTER = > "org.apache.felix.http.requestlog.filter"; > + > + /** Felix specific property to enable request logging to the OSGi Log > Service */ > + public static final String FELIX_HTTP_REQUEST_LOG_OSGI_ENABLE = > "org.apache.felix.http.requestlog.osgi.enable"; > + > + /** Felix specific property to specify the published "name" property of > the OSGi Log Service-base Request Log service. Allows server configs to > filter on specific log services. */ > + public static final String FELIX_HTTP_REQUEST_LOG_OSGI_SERVICE_NAME = > "org.apache.felix.http.requestlog.osgi.name"; > + > + /** Felix specific property to control the level of the log messages > generated by the OSGi Log Service-based request log. Values must correspond > to the constants defined in the LogService interface, default is 3 "INFO". */ > + public static final String FELIX_HTTP_REQUEST_LOG_OSGI_LEVEL = > "org.apache.felix.http.requestlog.osgi.level"; > + > + /** Felix specific property to enable request logging to a file and > provide the path to that file. Default is null meaning that the file log is > disabled. */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_PATH = > "org.apache.felix.http.requestlog.file.path"; > + > + /** Felix specific property to specify the published "name" property of > the file-based RequestLog service. Allows server configs to filter on > specific log services. */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_SERVICE_NAME = > "org.apache.felix.http.requestlog.file.name"; > + > + /** Felix specific property to enable file request logging to be > asynchronous */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_ASYNC = > "org.apache.felix.http.requestlog.file.async"; > + > + /** Felix specific property to enable request logging to append to the > log file rather than overwriting */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_APPEND = > "org.apache.felix.http.requestlog.file.append"; > + > + /** Felix specific property to specify the number of days the request > log file is retained */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_RETAIN_DAYS = > "org.apache.felix.http.requestlog.file.retaindays"; > + > + /** Felix specific property to specify the date format in request log > file names */ > + public static final String > FELIX_HTTP_REQUEST_LOG_FILE_FILENAME_DATE_FORMAT = > "org.apache.felix.http.requestlog.file.dateformat"; > + > + /** Felix specific property to enable extended request logging to a > named file */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_EXTENDED = > "org.apache.felix.http.requestlog.file.extended"; > + > + /** Felix specific property to ignore matching paths in the request log > file */ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_IGNORE_PATHS = > "org.apache.felix.http.requestlog.file.ignorepaths"; > + > + /** Felix specific property to enable request logging cookies in the > request log file*/ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_LOG_COOKIES = > "org.apache.felix.http.requestlog.file.logcookies"; > + > + /** Felix specific property to enable request logging the host name in > the request log file*/ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_LOG_SERVER = > "org.apache.felix.http.requestlog.file.logserver"; > + > + /** Felix specific property to enable request logging request processing > time in the request log file*/ > + public static final String FELIX_HTTP_REQUEST_LOG_FILE_LOG_LATENCY = > "org.apache.felix.http.requestlog.file.loglatency"; > + > /** Felix specific property to define custom properties for the http > runtime service. */ > public static final String FELIX_CUSTOM_HTTP_RUNTIME_PROPERTY_PREFIX = > "org.apache.felix.http.runtime.init."; > > @@ -425,6 +470,66 @@ public final class JettyConfig > return (String) getProperty(FELIX_HTTP_SERVICE_NAME); > } > > + public String getRequestLogFilter() { > + return (String) getProperty(FELIX_HTTP_REQUEST_LOG_FILTER, null); > + } > + > + public boolean isRequestLogOSGiEnabled() { > + return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_OSGI_ENABLE, false); > + } > + > + public String getRequestLogOSGiServiceName() { > + return (String) > getProperty(FELIX_HTTP_REQUEST_LOG_OSGI_SERVICE_NAME); > + } > + > + public int getRequestLogOSGiLevel() { > + return getIntProperty(FELIX_HTTP_REQUEST_LOG_OSGI_LEVEL, 3); // 3 == > LogService.LOG_INFO > + } > + > + public String getRequestLogFilePath() { > + return (String) getProperty(FELIX_HTTP_REQUEST_LOG_FILE_PATH, null); > + } > + > + public String getRequestLogFileServiceName() { > + return (String) > getProperty(FELIX_HTTP_REQUEST_LOG_FILE_SERVICE_NAME, "file"); > + } > + > + public boolean isRequestLogFileAsync() { > + return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_ASYNC, false); > + } > + > + public boolean isRequestLogFileAppend() { > + return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_APPEND, true); > + } > + > + public int getRequestLogFileRetainDays() { > + return getIntProperty(FELIX_HTTP_REQUEST_LOG_FILE_RETAIN_DAYS, 31); > + } > + > + public String getRequestLogFilenameDateFormat() { > + return (String) > getProperty(FELIX_HTTP_REQUEST_LOG_FILE_FILENAME_DATE_FORMAT, null); > + } > + > + public boolean isRequestLogFileExtended() { > + return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_EXTENDED, > false); > + } > + > + public String[] getRequestLogFileIgnorePaths() { > + return > getStringArrayProperty(FELIX_HTTP_REQUEST_LOG_FILE_IGNORE_PATHS, new > String[0]); > + } > + > + public boolean isRequestLogFileLogCookies() { > + return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_LOG_COOKIES, > false); > + } > + > + public boolean isRequestLogFileLogServer() { > + return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_LOG_SERVER, > false); > + } > + > + public boolean isRequestLogFileLogLatency() { > + return getBooleanProperty(FELIX_HTTP_REQUEST_LOG_FILE_LOG_LATENCY, > false); > + } > + > public void reset() > { > update(null); > @@ -668,4 +773,5 @@ public final class JettyConfig > return dflt; > } > } > + > } > > Modified: > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java > URL: > http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java?rev=1815249&r1=1815248&r2=1815249&view=diff > ============================================================================== > --- > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java > (original) > +++ > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java > Tue Nov 14 19:09:34 2017 > @@ -101,11 +101,15 @@ public final class JettyService extends > private volatile BundleTracker<Deployment> bundleTracker; > private volatile ServiceTracker<EventAdmin, EventAdmin> > eventAdmintTracker; > private volatile ConnectorFactoryTracker connectorTracker; > + private volatile RequestLogTracker requestLogTracker; > + private volatile LogServiceRequestLog osgiRequestLog; > + private volatile FileRequestLog fileRequestLog; > private volatile LoadBalancerCustomizerFactoryTracker > loadBalancerCustomizerTracker; > private volatile CustomizerWrapper customizerWrapper; > private volatile EventAdmin eventAdmin; > private boolean registerManagedService = true; > > + > public JettyService(final BundleContext context, > final HttpServiceController controller) > { > @@ -292,6 +296,22 @@ public final class JettyService extends > this.controller.getEventDispatcher().setActive(false); > this.controller.unregister(); > > + if (this.fileRequestLog != null) > + { > + this.fileRequestLog.stop(); > + this.fileRequestLog = null; > + } > + if (this.osgiRequestLog != null) > + { > + this.osgiRequestLog.unregister(); > + this.osgiRequestLog = null; > + } > + if (this.requestLogTracker != null) > + { > + this.requestLogTracker.close(); > + this.requestLogTracker = null; > + } > + > if (this.connectorTracker != null) > { > this.connectorTracker.close(); > @@ -413,6 +433,26 @@ public final class JettyService extends > this.stopJetty(); > SystemLogger.error("Jetty stopped (no connectors > available)", null); > } > + > + try { > + this.requestLogTracker = new RequestLogTracker(this.context, > this.config.getRequestLogFilter()); > + this.requestLogTracker.open(); > + this.server.setRequestLog(requestLogTracker); > + } catch (InvalidSyntaxException e) { > + SystemLogger.error("Invalid filter syntax in request log > tracker", e); > + } > + > + if (this.config.isRequestLogOSGiEnabled()) { > + this.osgiRequestLog = new LogServiceRequestLog(this.config); > + this.osgiRequestLog.register(this.context); > + SystemLogger.info("Directing Jetty request logs to the OSGi > Log Service"); > + } > + > + if (this.config.getRequestLogFilePath() != null && > !this.config.getRequestLogFilePath().isEmpty()) { > + this.fileRequestLog = new FileRequestLog(config); > + this.fileRequestLog.start(this.context); > + SystemLogger.info("Directing Jetty request logs to " + > this.config.getRequestLogFilePath()); > + } > } > else > { > > Added: > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java > URL: > http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java?rev=1815249&view=auto > ============================================================================== > --- > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java > (added) > +++ > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java > Tue Nov 14 19:09:34 2017 > @@ -0,0 +1,80 @@ > +/* > + * Licensed to the Apache Software Foundation (ASF) under one or more > + * contributor license agreements. See the NOTICE file distributed with > + * this work for additional information regarding copyright ownership. > + * The ASF licenses this file to You under the Apache License, Version 2.0 > + * (the "License"); you may not use this file except in compliance with > + * the License. You may obtain a copy of the License at > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > +package org.apache.felix.http.jetty.internal; > + > +import org.apache.felix.http.base.internal.logger.SystemLogger; > +import org.eclipse.jetty.server.AbstractNCSARequestLog; > +import org.eclipse.jetty.server.RequestLog; > +import org.osgi.framework.BundleContext; > +import org.osgi.framework.ServiceRegistration; > + > +import java.io.IOException; > +import java.util.Dictionary; > +import java.util.Hashtable; > +import java.util.concurrent.atomic.AtomicReference; > + > +/** > + * A RequestLog that logs to the OSGi LogService when present. Not > registered by default. > + */ > +class LogServiceRequestLog extends AbstractNCSARequestLog { > + > + public static final String SVC_PROP_NAME = "name"; > + public static final String DEFAULT_NAME = "osgi"; > + public static final String PREFIX = "REQUEST: "; > + > + private static final int DEFAULT_LOG_LEVEL = 3; // LogService.LOG_INFO > + > + private final int logLevel; > + private final String serviceName; > + > + private ServiceRegistration<RequestLog> registration; > + > + LogServiceRequestLog(JettyConfig config) { > + this.serviceName = config.getRequestLogOSGiServiceName(); > + this.logLevel = config.getRequestLogOSGiLevel(); > + } > + > + public synchronized void register(BundleContext context) throws > IllegalStateException { > + if (registration != null) { > + throw new IllegalStateException(getClass().getSimpleName() + " > already registered"); > + } > + Dictionary<String, Object> svcProps = new Hashtable<>(); > + svcProps.put(SVC_PROP_NAME, serviceName); > + this.registration = context.registerService(RequestLog.class, this, > svcProps); > + } > + > + public synchronized void unregister() { > + try { > + if (registration != null) { > + registration.unregister();; > + } > + } finally { > + registration = null; > + } > + } > + > + @Override > + public void write(String s) throws IOException { > + SystemLogger.info(PREFIX + s); > + } > + > + @Override > + protected boolean isEnabled() { > + return true; > + } > + > +} > > Added: > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java > URL: > http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java?rev=1815249&view=auto > ============================================================================== > --- > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java > (added) > +++ > felix/trunk/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java > Tue Nov 14 19:09:34 2017 > @@ -0,0 +1,106 @@ > +/* > + * Licensed to the Apache Software Foundation (ASF) under one or more > + * contributor license agreements. See the NOTICE file distributed with > + * this work for additional information regarding copyright ownership. > + * The ASF licenses this file to You under the Apache License, Version 2.0 > + * (the "License"); you may not use this file except in compliance with > + * the License. You may obtain a copy of the License at > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > +package org.apache.felix.http.jetty.internal; > + > +import org.apache.felix.http.base.internal.logger.SystemLogger; > +import org.eclipse.jetty.server.Request; > +import org.eclipse.jetty.server.RequestLog; > +import org.eclipse.jetty.server.Response; > +import org.osgi.framework.*; > +import org.osgi.util.tracker.ServiceTracker; > + > +import java.util.Map; > +import java.util.concurrent.ConcurrentHashMap; > +import java.util.concurrent.ConcurrentMap; > + > +/** > + * An instance of Jetty's RequestLog that dispatches to registered > RequestLog services in the service registry. A filter > + * can be provided so that it only dispatches to selected services. > + * <p> > + * Unchecked exceptions from the RequestLog services are caught and logged > to the OSGi LogService. to avoid flooding the > + * LogService, we will remove a RequestLog service if it breaches a maximum > number of errors (see {@link > + * RequestLogTracker#MAX_ERROR_COUNT}). Once this happens we will stop > dispatching to that service entirely until it is > + * unregistered. > + */ > +class RequestLogTracker extends ServiceTracker<RequestLog, RequestLog> > implements RequestLog { > + > + private static final int MAX_ERROR_COUNT = 100; > + > + private final ConcurrentMap<ServiceReference<?>, RequestLog> logSvcs = > new ConcurrentHashMap<>(); > + private final ConcurrentMap<ServiceReference<?>, Integer> naughtyStep = > new ConcurrentHashMap<>(); > + > + RequestLogTracker(BundleContext context, String filter) throws > InvalidSyntaxException { > + super(context, buildFilter(filter), null); > + } > + > + private static Filter buildFilter(String inputFilter) throws > InvalidSyntaxException { > + String objectClassFilter = String.format("(%s=%s)", > Constants.OBJECTCLASS, RequestLog.class.getName()); > + String compositeFilter; > + if (inputFilter != null) { > + // Parse the input filter just for validation before we insert > into ours. > + FrameworkUtil.createFilter(inputFilter); > + compositeFilter = "(&" + objectClassFilter + inputFilter + ")"; > + } else { > + compositeFilter = objectClassFilter; > + } > + return FrameworkUtil.createFilter(compositeFilter); > + } > + > + @Override > + public RequestLog addingService(ServiceReference<RequestLog> reference) { > + RequestLog logSvc = context.getService(reference); > + logSvcs.put(reference, logSvc); > + return logSvc; > + } > + > + @Override > + public void removedService(ServiceReference<RequestLog> reference, > RequestLog logSvc) { > + logSvcs.remove(reference); > + naughtyStep.remove(reference); > + context.ungetService(reference); > + } > + > + @Override > + public void log(Request request, Response response) { > + for (Map.Entry<ServiceReference<?>, RequestLog> entry : > logSvcs.entrySet()) { > + try { > + entry.getValue().log(request, response); > + } catch (Exception e) { > + processError(entry.getKey(), e); > + } > + } > + } > + > + /** > + * Process an exception from a RequestLog service instance, and remove > the service if it has reached the maximum > + * error limit. > + */ > + private void processError(ServiceReference<?> reference, Exception e) { > + SystemLogger.error(reference, String.format("Error dispatching to > request log service ID %d from bundle %s:%s", > + reference.getProperty(Constants.SERVICE_ID), > reference.getBundle().getSymbolicName(), reference.getBundle().getVersion()), > e); > + > + int naughty = naughtyStep.merge(reference, 1, Integer::sum); > + if (naughty >= MAX_ERROR_COUNT) { > + // We have reached (but not exceeded) the maximum, and the last > error has been logged. Remove from the maps > + // so we will not invoke the service again. > + logSvcs.remove(reference); > + naughtyStep.remove(reference); > + SystemLogger.error(reference, String.format("RequestLog service > ID %d from bundle %s:%s threw too many errors, it will no longer be invoked.", > + reference.getProperty(Constants.SERVICE_ID), > reference.getBundle().getSymbolicName(), reference.getBundle().getVersion()), > null); > + } > + } > +} > > Added: > felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java > URL: > http://svn.apache.org/viewvc/felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java?rev=1815249&view=auto > ============================================================================== > --- > felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java > (added) > +++ > felix/trunk/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java > Tue Nov 14 19:09:34 2017 > @@ -0,0 +1,97 @@ > +/* > + * Licensed to the Apache Software Foundation (ASF) under one or more > + * contributor license agreements. See the NOTICE file distributed with > + * this work for additional information regarding copyright ownership. > + * The ASF licenses this file to You under the Apache License, Version 2.0 > + * (the "License"); you may not use this file except in compliance with > + * the License. You may obtain a copy of the License at > + * > + * http://www.apache.org/licenses/LICENSE-2.0 > + * > + * Unless required by applicable law or agreed to in writing, software > + * distributed under the License is distributed on an "AS IS" BASIS, > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > + * See the License for the specific language governing permissions and > + * limitations under the License. > + */ > +package org.apache.felix.http.jetty.internal; > + > +import org.eclipse.jetty.server.Request; > +import org.eclipse.jetty.server.RequestLog; > +import org.eclipse.jetty.server.Response; > +import org.junit.Test; > +import org.junit.runner.RunWith; > +import org.mockito.Mock; > +import org.mockito.runners.MockitoJUnitRunner; > +import org.osgi.framework.*; > + > +import java.util.concurrent.atomic.AtomicInteger; > + > +import static org.junit.Assert.assertEquals; > +import static org.mockito.Mockito.*; > + > +@RunWith(MockitoJUnitRunner.class) > +public class RequestLogTrackerTest { > + > + @Mock > + BundleContext context; > + > + @Test > + public void testInvokeRequestLog() throws Exception { > + RequestLogTracker tracker = new RequestLogTracker(context, null); > + > + RequestLog mockRequestLog = mock(RequestLog.class); > + > + ServiceReference<RequestLog> mockSvcRef = > mock(ServiceReference.class); > + when(context.getService(mockSvcRef)).thenReturn(mockRequestLog); > + > + // These invocations not passed through to the mock because it is > not registered yet > + for (int i = 0; i < 10; i++) > + tracker.log(null, null); > + > + tracker.addingService(mockSvcRef); > + > + // These will pass through > + for (int i = 0; i < 15; i++) > + tracker.log(null, null); > + > + tracker.removedService(mockSvcRef, mockRequestLog); > + > + // And these will not. > + for (int i = 0; i < 50; i++) > + tracker.log(null, null); > + > + verify(mockRequestLog, times(15)).log(isNull(Request.class), > isNull(Response.class)); > + } > + > + @Test > + public void testNaughtyService() throws Exception { > + RequestLogTracker tracker = new RequestLogTracker(context, null); > + > + AtomicInteger counter = new AtomicInteger(0); > + RequestLog mockRequestLog = new RequestLog() { > + @Override > + public void log(Request request, Response response) { > + counter.addAndGet(1); > + throw new RuntimeException("This service always explodes"); > + } > + }; > + ServiceReference<RequestLog> mockSvcRef = > mock(ServiceReference.class); > + Bundle mockBundle = mock(Bundle.class); > + when(mockSvcRef.getBundle()).thenReturn(mockBundle); > + when(mockBundle.getSymbolicName()).thenReturn("org.example"); > + when(mockBundle.getVersion()).thenReturn(new Version("1.0.0")); > + when(context.getService(mockSvcRef)).thenReturn(mockRequestLog); > + > + tracker.addingService(mockSvcRef); > + > + // Invoke 200 times > + for (int i = 0; i < 200; i++) > + tracker.log(null, null); > + > + tracker.removedService(mockSvcRef, mockRequestLog); > + > + // Invoked 100 times and then removed > + assertEquals(100, counter.get()); > + } > +} > > -- Carsten Ziegeler Adobe Research Switzerland [email protected]
