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]
