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