Author: nbartlett Date: Tue Nov 14 20:08:58 2017 New Revision: 1815258 URL: http://svn.apache.org/viewvc?rev=1815258&view=rev Log: FELIX-5744 Felix HTTP Jetty does not support request logging
Applied to R7 branch from rev 1815249 Added: felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java Modified: felix/trunk/osgi-r7/http/jetty/pom.xml felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java Modified: felix/trunk/osgi-r7/http/jetty/pom.xml URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/pom.xml?rev=1815258&r1=1815257&r2=1815258&view=diff ============================================================================== --- felix/trunk/osgi-r7/http/jetty/pom.xml (original) +++ felix/trunk/osgi-r7/http/jetty/pom.xml Tue Nov 14 20:08:58 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/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java?rev=1815258&view=auto ============================================================================== --- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java (added) +++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/FileRequestLog.java Tue Nov 14 20:08:58 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/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java?rev=1815258&r1=1815257&r2=1815258&view=diff ============================================================================== --- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java (original) +++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java Tue Nov 14 20:08:58 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/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java?rev=1815258&r1=1815257&r2=1815258&view=diff ============================================================================== --- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java (original) +++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java Tue Nov 14 20:08:58 2017 @@ -66,6 +66,7 @@ import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.service.event.Event; @@ -101,11 +102,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 +297,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 +434,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/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java?rev=1815258&view=auto ============================================================================== --- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java (added) +++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/LogServiceRequestLog.java Tue Nov 14 20:08:58 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/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java?rev=1815258&view=auto ============================================================================== --- felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java (added) +++ felix/trunk/osgi-r7/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/RequestLogTracker.java Tue Nov 14 20:08:58 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/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java URL: http://svn.apache.org/viewvc/felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java?rev=1815258&view=auto ============================================================================== --- felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java (added) +++ felix/trunk/osgi-r7/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/RequestLogTrackerTest.java Tue Nov 14 20:08:58 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()); + } +}