Done, and done: https://issues.apache.org/jira/browse/FELIX-5744 <https://issues.apache.org/jira/browse/FELIX-5744>
Thanks guys. > On 14 Nov 2017, at 19:52, Karl Pauls <[email protected]> wrote: > > On Tue, Nov 14, 2017 at 8:51 PM, Neil Bartlett (Paremus) > <[email protected] <mailto:[email protected]>> wrote: >> >>> On 14 Nov 2017, at 19:32, Carsten Ziegeler <[email protected] >>> <mailto:[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] <mailto:[email protected]>
