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