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