From 834a8ae734c7af26826962d52e1aace2bd7bbef3 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Fri, 03 May 2013 12:33:08 +0000
Subject: [PATCH] OPENDJ-879 (CR-1642) Add HTTP access log
---
opends/resource/schema/02-config.ldif | 22
opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml | 59 ++
opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java | 17
opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java | 101 ++++
opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java | 177 +++++++
opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml | 316 ++++++++++++
opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java | 96 ++-
opends/resource/config/config.ldif | 15
opends/src/server/org/opends/server/core/LoggerConfigManager.java | 26 +
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java | 38
opends/src/messages/messages/config.properties | 9
opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties | 26 +
opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java | 148 +++++
opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java | 409 ++++++++++++++++
opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties | 6
15 files changed, 1,410 insertions(+), 55 deletions(-)
diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index e5221c9..8c659e0 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -727,6 +727,21 @@
ds-cfg-rotation-policy: cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config
ds-cfg-retention-policy: cn=File Count Retention Policy,cn=Log Retention Policies,cn=config
+dn: cn=File-Based HTTP Access Logger,cn=Loggers,cn=config
+objectClass: top
+objectClass: ds-cfg-log-publisher
+objectClass: ds-cfg-http-access-log-publisher
+objectClass: ds-cfg-file-based-http-access-log-publisher
+cn: File-Based HTTP Access Logger
+ds-cfg-java-class: org.opends.server.loggers.TextHTTPAccessLogPublisher
+ds-cfg-enabled: true
+ds-cfg-log-file: logs/http-access
+ds-cfg-log-file-permissions: 640
+ds-cfg-asynchronous: true
+ds-cfg-rotation-policy: cn=24 Hours Time Limit Rotation Policy,cn=Log Rotation Policies,cn=config
+ds-cfg-rotation-policy: cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config
+ds-cfg-retention-policy: cn=File Count Retention Policy,cn=Log Retention Policies,cn=config
+
dn: cn=File-Based Audit Logger,cn=Loggers,cn=config
objectClass: top
objectClass: ds-cfg-log-publisher
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index d620358..e456f2d 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -3937,6 +3937,11 @@
ds-cfg-suppress-internal-operations $
ds-cfg-suppress-synchronization-operations )
X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.16
+ NAME 'ds-cfg-http-access-log-publisher'
+ SUP ds-cfg-log-publisher
+ STRUCTURAL
+ X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.23
NAME 'ds-cfg-error-log-publisher'
SUP ds-cfg-log-publisher
@@ -3974,6 +3979,23 @@
ds-cfg-log-record-time-format $
ds-cfg-log-control-oids )
X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.17
+ NAME 'ds-cfg-file-based-http-access-log-publisher'
+ SUP ds-cfg-http-access-log-publisher
+ STRUCTURAL
+ MUST ( ds-cfg-log-file $
+ ds-cfg-asynchronous $
+ ds-cfg-log-file-permissions )
+ MAY ( ds-cfg-rotation-policy $
+ ds-cfg-rotation-action $
+ ds-cfg-retention-policy $
+ ds-cfg-time-interval $
+ ds-cfg-buffer-size $
+ ds-cfg-auto-flush $
+ ds-cfg-append $
+ ds-cfg-queue-size $
+ ds-cfg-log-record-time-format )
+ X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.26
NAME 'ds-cfg-file-based-debug-log-publisher'
SUP ds-cfg-debug-log-publisher
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml
new file mode 100644
index 0000000..dec8d36
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/FileBasedHTTPAccessLogPublisherConfiguration.xml
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ! CDDL HEADER START
+ !
+ ! The contents of this file are subject to the terms of the
+ ! Common Development and Distribution License, Version 1.0 only
+ ! (the "License"). You may not use this file except in compliance
+ ! with the License.
+ !
+ ! You can obtain a copy of the license at
+ ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ ! See the License for the specific language governing permissions
+ ! and limitations under the License.
+ !
+ ! When distributing Covered Code, include this CDDL HEADER in each
+ ! file and include the License file at
+ ! trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ ! add the following below this CDDL HEADER, with the fields enclosed
+ ! by brackets "[]" replaced with your own identifying information:
+ ! Portions Copyright [yyyy] [name of copyright owner]
+ !
+ ! CDDL HEADER END
+ !
+ !
+ ! Copyright 2013 ForgeRock AS
+ ! -->
+<adm:managed-object name="file-based-http-access-log-publisher"
+ plural-name="file-based-http-access-log-publishers"
+ package="org.opends.server.admin.std" extends="http-access-log-publisher"
+ xmlns:adm="http://www.opends.org/admin"
+ xmlns:ldap="http://www.opends.org/admin-ldap">
+ <adm:synopsis>
+ <adm:user-friendly-plural-name />
+ publish HTTP access messages to the file system.
+ </adm:synopsis>
+ <adm:profile name="ldap">
+ <ldap:object-class>
+ <ldap:name>ds-cfg-file-based-http-access-log-publisher</ldap:name>
+ <ldap:superior>ds-cfg-http-access-log-publisher</ldap:superior>
+ </ldap:object-class>
+ </adm:profile>
+ <adm:property-override name="java-class" advanced="true">
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>
+ org.opends.server.loggers.TextHTTPAccessLogPublisher
+ </adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ </adm:property-override>
+ <adm:property name="asynchronous" mandatory="true" advanced="true">
+ <adm:synopsis>
+ Indicates whether the
+ <adm:user-friendly-name />
+ will publish records asynchronously.
+ </adm:synopsis>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>true</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:boolean />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-asynchronous</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="queue-size" advanced="true">
+ <adm:synopsis>
+ The maximum number of log records that can be stored in the
+ asynchronous queue.
+ </adm:synopsis>
+ <adm:description>
+ Setting the queue size to zero activates parallel log writer
+ implementation which has no queue size limit and as such the
+ parallel log writer should only be used on a very well tuned
+ server configuration to avoid potential out of memory errors.
+ </adm:description>
+ <adm:requires-admin-action>
+ <adm:other>
+ <adm:synopsis>
+ The <adm:user-friendly-name /> must be restarted if this property
+ is changed and the asynchronous property is set to true.
+ </adm:synopsis>
+ </adm:other>
+ </adm:requires-admin-action>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>5000</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:integer lower-limit="0" />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-queue-size</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="log-file" mandatory="true">
+ <adm:synopsis>
+ The file name to use for the log files generated by the
+ <adm:user-friendly-name />.
+ The path to the file is relative to the server root.
+ </adm:synopsis>
+ <adm:requires-admin-action>
+ <adm:component-restart />
+ </adm:requires-admin-action>
+ <adm:syntax>
+ <adm:string>
+ <adm:pattern>
+ <adm:regex>.*</adm:regex>
+ <adm:usage>FILE</adm:usage>
+ <adm:synopsis>
+ A path to an existing file that is readable by the server.
+ </adm:synopsis>
+ </adm:pattern>
+ </adm:string>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-log-file</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="log-file-permissions" mandatory="true">
+ <adm:synopsis>
+ The UNIX permissions of the log files created by this
+ <adm:user-friendly-name />.
+ </adm:synopsis>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>640</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string>
+ <adm:pattern>
+ <adm:regex>^([0-7][0-7][0-7])$</adm:regex>
+ <adm:usage>MODE</adm:usage>
+ <adm:synopsis>
+ A valid UNIX mode string. The mode string must contain
+ three digits between zero and seven.
+ </adm:synopsis>
+ </adm:pattern>
+ </adm:string>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-log-file-permissions</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="time-interval" advanced="true">
+ <adm:synopsis>
+ Specifies the interval at which to check whether the log files
+ need to be rotated.
+ </adm:synopsis>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>5s</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:duration base-unit="ms" lower-limit="1" />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-time-interval</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="buffer-size" advanced="true">
+ <adm:synopsis>Specifies the log file buffer size.</adm:synopsis>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>64kb</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:size lower-limit="1" />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-buffer-size</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="auto-flush" advanced="true">
+ <adm:synopsis>
+ Specifies whether to flush the writer after every log record.
+ </adm:synopsis>
+ <adm:description>
+ If the asynchronous writes option is used, the writer is
+ flushed after all the log records in the queue are written.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>true</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:boolean />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-auto-flush</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="append">
+ <adm:synopsis>
+ Specifies whether to append to existing log files.
+ </adm:synopsis>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>true</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:boolean />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-append</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="rotation-policy" multi-valued="true">
+ <adm:synopsis>
+ The rotation policy to use for the
+ <adm:user-friendly-name />
+ .
+ </adm:synopsis>
+ <adm:description>
+ When multiple policies are used, rotation will occur if any
+ policy's conditions are met.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:alias>
+ <adm:synopsis>
+ No rotation policy is used and log rotation will not occur.
+ </adm:synopsis>
+ </adm:alias>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:aggregation parent-path="/"
+ relation-name="log-rotation-policy" />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-rotation-policy</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="retention-policy" multi-valued="true">
+ <adm:synopsis>
+ The retention policy to use for the
+ <adm:user-friendly-name />
+ .
+ </adm:synopsis>
+ <adm:description>
+ When multiple policies are used, log files are cleaned when
+ any of the policy's conditions are met.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:alias>
+ <adm:synopsis>
+ No retention policy is used and log files are never cleaned.
+ </adm:synopsis>
+ </adm:alias>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:aggregation parent-path="/"
+ relation-name="log-retention-policy" />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-retention-policy</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="log-record-time-format">
+ <adm:synopsis>
+ Specifies the format string that is used to generate log record
+ timestamps.
+ </adm:synopsis>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>dd/MMM/yyyy:HH:mm:ss Z</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string>
+ <adm:pattern>
+ <adm:regex>.*</adm:regex>
+ <adm:usage>STRING</adm:usage>
+ <adm:synopsis>
+ Any valid format string that can be used with the
+ java.text.SimpleDateFormat class.
+ </adm:synopsis>
+ </adm:pattern>
+ </adm:string>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-log-record-time-format</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+</adm:managed-object>
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml
new file mode 100644
index 0000000..c33e8e0
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/HTTPAccessLogPublisherConfiguration.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ! CDDL HEADER START
+ !
+ ! The contents of this file are subject to the terms of the
+ ! Common Development and Distribution License, Version 1.0 only
+ ! (the "License"). You may not use this file except in compliance
+ ! with the License.
+ !
+ ! You can obtain a copy of the license at
+ ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ ! See the License for the specific language governing permissions
+ ! and limitations under the License.
+ !
+ ! When distributing Covered Code, include this CDDL HEADER in each
+ ! file and include the License file at
+ ! trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ ! add the following below this CDDL HEADER, with the fields enclosed
+ ! by brackets "[]" replaced with your own identifying information:
+ ! Portions Copyright [yyyy] [name of copyright owner]
+ !
+ ! CDDL HEADER END
+ !
+ !
+ ! Copyright 2013 ForgeRock AS
+ ! -->
+<adm:managed-object name="http-access-log-publisher"
+ plural-name="http-access-log-publishers"
+ package="org.opends.server.admin.std" extends="log-publisher"
+ xmlns:adm="http://www.opends.org/admin"
+ xmlns:ldap="http://www.opends.org/admin-ldap"
+ xmlns:cli="http://www.opends.org/admin-cli">
+ <adm:synopsis>
+ <adm:user-friendly-plural-name />
+ are responsible for distributing HTTP access log messages from the HTTP
+ access logger to a destination.
+ </adm:synopsis>
+ <adm:description>
+ HTTP access log messages provide information about the types of HTTP
+ requests processed by the server.
+ </adm:description>
+ <adm:profile name="ldap">
+ <ldap:object-class>
+ <ldap:name>ds-cfg-http-access-log-publisher</ldap:name>
+ <ldap:superior>ds-cfg-log-publisher</ldap:superior>
+ </ldap:object-class>
+ </adm:profile>
+ <adm:profile name="cli">
+ <cli:managed-object custom="true" />
+ </adm:profile>
+ <adm:property-override name="java-class">
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>org.opends.server.api.HTTPAccessLogPublisher</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ </adm:property-override>
+</adm:managed-object>
diff --git a/opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties b/opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties
new file mode 100644
index 0000000..e3f7417
--- /dev/null
+++ b/opends/src/admin/messages/FileBasedHTTPAccessLogPublisherCfgDefn.properties
@@ -0,0 +1,26 @@
+user-friendly-name=File Based HTTP Access Log Publisher
+user-friendly-plural-name=File Based HTTP Access Log Publishers
+synopsis=File Based HTTP Access Log Publishers publish HTTP access messages to the file system.
+property.append.synopsis=Specifies whether to append to existing log files.
+property.asynchronous.synopsis=Indicates whether the File Based HTTP Access Log Publisher will publish records asynchronously.
+property.auto-flush.synopsis=Specifies whether to flush the writer after every log record.
+property.auto-flush.description=If the asynchronous writes option is used, the writer is flushed after all the log records in the queue are written.
+property.buffer-size.synopsis=Specifies the log file buffer size.
+property.enabled.synopsis=Indicates whether the File Based HTTP Access Log Publisher is enabled for use.
+property.java-class.synopsis=The fully-qualified name of the Java class that provides the File Based HTTP Access Log Publisher implementation.
+property.log-file.synopsis=The file name to use for the log files generated by the File Based HTTP Access Log Publisher. The path to the file is relative to the server root.
+property.log-file.syntax.string.pattern.synopsis=A path to an existing file that is readable by the server.
+property.log-file-permissions.synopsis=The UNIX permissions of the log files created by this File Based HTTP Access Log Publisher.
+property.log-file-permissions.syntax.string.pattern.synopsis=A valid UNIX mode string. The mode string must contain three digits between zero and seven.
+property.log-record-time-format.synopsis=Specifies the format string that is used to generate log record timestamps.
+property.log-record-time-format.syntax.string.pattern.synopsis=Any valid format string that can be used with the java.text.SimpleDateFormat class.
+property.queue-size.synopsis=The maximum number of log records that can be stored in the asynchronous queue.
+property.queue-size.description=Setting the queue size to zero activates parallel log writer implementation which has no queue size limit and as such the parallel log writer should only be used on a very well tuned server configuration to avoid potential out of memory errors.
+property.queue-size.requires-admin-action.synopsis=The File Based HTTP Access Log Publisher must be restarted if this property is changed and the asynchronous property is set to true.
+property.retention-policy.synopsis=The retention policy to use for the File Based HTTP Access Log Publisher .
+property.retention-policy.description=When multiple policies are used, log files are cleaned when any of the policy's conditions are met.
+property.retention-policy.default-behavior.alias.synopsis=No retention policy is used and log files are never cleaned.
+property.rotation-policy.synopsis=The rotation policy to use for the File Based HTTP Access Log Publisher .
+property.rotation-policy.description=When multiple policies are used, rotation will occur if any policy's conditions are met.
+property.rotation-policy.default-behavior.alias.synopsis=No rotation policy is used and log rotation will not occur.
+property.time-interval.synopsis=Specifies the interval at which to check whether the log files need to be rotated.
diff --git a/opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties b/opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties
new file mode 100644
index 0000000..d74905f
--- /dev/null
+++ b/opends/src/admin/messages/HTTPAccessLogPublisherCfgDefn.properties
@@ -0,0 +1,6 @@
+user-friendly-name=HTTP Access Log Publisher
+user-friendly-plural-name=HTTP Access Log Publishers
+synopsis=HTTP Access Log Publishers are responsible for distributing HTTP access log messages from the HTTP access logger to a destination.
+description=HTTP access log messages provide information about the types of HTTP requests processed by the server.
+property.enabled.synopsis=Indicates whether the HTTP Access Log Publisher is enabled for use.
+property.java-class.synopsis=The fully-qualified name of the Java class that provides the HTTP Access Log Publisher implementation.
diff --git a/opends/src/messages/messages/config.properties b/opends/src/messages/messages/config.properties
index 6b3a1f5..8ad3d2e 100644
--- a/opends/src/messages/messages/config.properties
+++ b/opends/src/messages/messages/config.properties
@@ -165,9 +165,6 @@
SEVERE_WARN_CONFIG_LOGGER_NO_ACTIVE_ERROR_LOGGERS_45=There are no active \
error loggers defined in the Directory Server configuration. No error \
logging will be performed
-MILD_WARN_CONFIG_LOGGER_NO_ACTIVE_DEBUG_LOGGERS_46=There are no active debug \
- loggers defined in the Directory Server configuration. No debug logging will \
- be performed
SEVERE_ERR_CONFIG_LOGGER_ENTRY_UNACCEPTABLE_47=Configuration entry %s does \
not contain a valid logger configuration: %s. It will be ignored
INFO_CONFIG_UNKNOWN_UNACCEPTABLE_REASON_48=Unknown unacceptable reason
@@ -2178,3 +2175,9 @@
SEVERE_ERR_CONFIG_LOGGING_INVALID_TARGET_DN_PATTERN_730=Thee access log filtering \
criteria defined in "%s" could not be parsed because it contains an invalid \
target DN pattern "%s"
+SEVERE_WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS_731=There are no active \
+ HTTP access loggers defined in the Directory Server configuration. No HTTP \
+ access logging will be performed
+SEVERE_ERR_CONFIG_LOGGER_INVALID_HTTP_ACCESS_LOGGER_CLASS_732=Class %s specified \
+ in attribute ds-cfg-java-class of configuration entry %s cannot be \
+ instantiated as a Directory Server HTTP access logger: %s
diff --git a/opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java b/opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java
new file mode 100644
index 0000000..7f3796a
--- /dev/null
+++ b/opends/src/server/org/opends/server/api/HTTPAccessLogPublisher.java
@@ -0,0 +1,101 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2013 ForgeRock AS.
+ */
+package org.opends.server.api;
+
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg;
+import org.opends.server.loggers.HTTPRequestInfo;
+
+/**
+ * This class defines the set of methods and structures that must be implemented
+ * for a Directory Server HTTP access log publisher.
+ *
+ * @param <T>
+ * The type of HTTP access log publisher configuration handled by this
+ * log publisher implementation.
+ */
+@org.opends.server.types.PublicAPI(
+ stability = org.opends.server.types.StabilityLevel.VOLATILE,
+ mayInstantiate = false,
+ mayExtend = true,
+ mayInvoke = false)
+public abstract class HTTPAccessLogPublisher
+ <T extends HTTPAccessLogPublisherCfg> implements LogPublisher<T>
+{
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isConfigurationAcceptable(T configuration,
+ List<Message> unacceptableReasons)
+ {
+ // This default implementation does not perform any special
+ // validation. It should be overridden by HTTP access log publisher
+ // implementations that wish to perform more detailed validation.
+ return true;
+ }
+
+ /**
+ * Logs the request info according to the common logfile format. The common
+ * logfile format is as follows:
+ *
+ * <pre>
+ * remotehost rfc931 authuser [date] "request" status bytes "useragent"
+ * </pre>
+ * <dl>
+ * <dt>remotehost</dt>
+ * <dd>Remote hostname (or IP number if DNS hostname is not available, or if
+ * DNSLookup is Off.</dd>
+ * <dt>rfc931</dt>
+ * <dd>The remote logname of the user.</dd>
+ * <dt>authuser</dt>
+ * <dd>The username as which the user has authenticated himself.</dd>
+ * <dt>[date]</dt>
+ * <dd>Date and time of the request.</dd>
+ * <dt>"request"</dt>
+ * <dd>The request line exactly as it came from the client.</dd>
+ * <dt>status</dt>
+ * <dd>The HTTP status code returned to the client.</dd>
+ * <dt>bytes</dt>
+ * <dd>The content-length of the document transferred.</dd>
+ * <dt>"useragent"</dt>
+ * <dd>The user agent that issued the request.</dd>
+ * </dl>
+ * <p>
+ * <b>NOTE:</b> The bytes field is not currently supported.
+ * </p>
+ *
+ * @param requestInfo
+ * The request info to log
+ */
+ public void logRequestInfo(HTTPRequestInfo requestInfo)
+ {
+ // Do nothing
+ }
+
+}
diff --git a/opends/src/server/org/opends/server/core/LoggerConfigManager.java b/opends/src/server/org/opends/server/core/LoggerConfigManager.java
index 1ef20fd..b8eb7a9 100644
--- a/opends/src/server/org/opends/server/core/LoggerConfigManager.java
+++ b/opends/src/server/org/opends/server/core/LoggerConfigManager.java
@@ -37,11 +37,17 @@
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.server.ServerManagementContext;
-import org.opends.server.admin.std.server.*;
+import org.opends.server.admin.std.server.AccessLogPublisherCfg;
+import org.opends.server.admin.std.server.DebugLogPublisherCfg;
+import org.opends.server.admin.std.server.ErrorLogPublisherCfg;
+import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg;
+import org.opends.server.admin.std.server.LogPublisherCfg;
+import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.loggers.AbstractLogger;
import org.opends.server.loggers.AccessLogger;
import org.opends.server.loggers.ErrorLogger;
+import org.opends.server.loggers.HTTPAccessLogger;
import org.opends.server.loggers.debug.DebugLogger;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.InitializationException;
@@ -87,6 +93,9 @@
List<AccessLogPublisherCfg> accessPublisherCfgs =
new ArrayList<AccessLogPublisherCfg>();
+ List<HTTPAccessLogPublisherCfg> httpAccessPublisherCfgs =
+ new ArrayList<HTTPAccessLogPublisherCfg>();
+
List<ErrorLogPublisherCfg> errorPublisherCfgs =
new ArrayList<ErrorLogPublisherCfg>();
@@ -102,6 +111,10 @@
{
accessPublisherCfgs.add((AccessLogPublisherCfg)config);
}
+ else if (config instanceof HTTPAccessLogPublisherCfg)
+ {
+ httpAccessPublisherCfgs.add((HTTPAccessLogPublisherCfg) config);
+ }
else if(config instanceof ErrorLogPublisherCfg)
{
errorPublisherCfgs.add((ErrorLogPublisherCfg)config);
@@ -116,10 +129,16 @@
// See if there are active loggers in all categories. If not, then log a
// message.
+ // Do not output warn message for debug loggers because it is valid to fully
+ // disable all debug loggers.
if (accessPublisherCfgs.isEmpty())
{
logError(WARN_CONFIG_LOGGER_NO_ACTIVE_ACCESS_LOGGERS.get());
}
+ if (httpAccessPublisherCfgs.isEmpty())
+ {
+ logError(WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS.get());
+ }
if (errorPublisherCfgs.isEmpty())
{
logError(WARN_CONFIG_LOGGER_NO_ACTIVE_ERROR_LOGGERS.get());
@@ -127,6 +146,7 @@
DebugLogger.getInstance().initializeLogger(debugPublisherCfgs);
AccessLogger.getInstance().initializeLogger(accessPublisherCfgs);
+ HTTPAccessLogger.getInstance().initializeLogger(httpAccessPublisherCfgs);
ErrorLogger.getInstance().initializeLogger(errorPublisherCfgs);
}
@@ -154,6 +174,10 @@
{
return AccessLogger.getInstance();
}
+ else if (config instanceof HTTPAccessLogPublisherCfg)
+ {
+ return HTTPAccessLogger.getInstance();
+ }
else if (config instanceof ErrorLogPublisherCfg)
{
return ErrorLogger.getInstance();
diff --git a/opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java b/opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java
new file mode 100644
index 0000000..58f80ec
--- /dev/null
+++ b/opends/src/server/org/opends/server/loggers/HTTPAccessLogger.java
@@ -0,0 +1,148 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2013 ForgeRock AS
+ */
+package org.opends.server.loggers;
+
+import static org.opends.messages.ConfigMessages.*;
+
+import java.util.Collection;
+
+import org.opends.server.admin.ClassPropertyDefinition;
+import org.opends.server.admin.std.meta.HTTPAccessLogPublisherCfgDefn;
+import org.opends.server.admin.std.server.HTTPAccessLogPublisherCfg;
+import org.opends.server.api.HTTPAccessLogPublisher;
+
+/**
+ * This class defines the wrapper that will invoke all registered HTTP access
+ * loggers for each type of request received or response sent.
+ */
+public class HTTPAccessLogger extends AbstractLogger
+<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg>
+{
+
+ private static LoggerStorage
+ <HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg>
+ loggerStorage = new LoggerStorage
+ <HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>, HTTPAccessLogPublisherCfg>
+ ();
+
+ /** The singleton instance of this class for configuration purposes. */
+ private static final HTTPAccessLogger instance = new HTTPAccessLogger();
+
+ /**
+ * The constructor for this class.
+ */
+ public HTTPAccessLogger()
+ {
+ super((Class) HTTPAccessLogPublisher.class,
+ ERR_CONFIG_LOGGER_INVALID_HTTP_ACCESS_LOGGER_CLASS);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected ClassPropertyDefinition getJavaClassPropertyDefinition()
+ {
+ return HTTPAccessLogPublisherCfgDefn.getInstance()
+ .getJavaClassPropertyDefinition();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected LoggerStorage<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>,
+ HTTPAccessLogPublisherCfg> getStorage()
+ {
+ return loggerStorage;
+ }
+
+ /**
+ * Retrieve the singleton instance of this class.
+ *
+ * @return The singleton instance of this logger.
+ */
+ public static HTTPAccessLogger getInstance()
+ {
+ return instance;
+ }
+
+ /**
+ * Add an HTTP access log publisher to the HTTP access logger.
+ *
+ * @param publisher
+ * The HTTP access log publisher to add.
+ */
+ public synchronized static void addHTTPAccessLogPublisher(
+ HTTPAccessLogPublisher publisher)
+ {
+ loggerStorage.addLogPublisher(publisher);
+ }
+
+ /**
+ * Remove an HTTP access log publisher from the HTTP access logger.
+ *
+ * @param publisher
+ * The HTTP access log publisher to remove.
+ * @return The publisher that was removed or null if it was not found.
+ */
+ public synchronized static boolean removeHTTPAccessLogPublisher(
+ HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg> publisher)
+ {
+ return loggerStorage.removeLogPublisher(publisher);
+ }
+
+ /**
+ * Removes all existing HTTP access log publishers from the logger.
+ */
+ public synchronized static void removeAllHTTPAccessLogPublishers()
+ {
+ loggerStorage.removeAllLogPublishers();
+ }
+
+ /**
+ * Returns all the registered HTTP access log publishers.
+ *
+ * @return a Collection of {@link HTTPAccessLogPublisher} objects
+ */
+ private static Collection<HTTPAccessLogPublisher<HTTPAccessLogPublisherCfg>>
+ getHTTPAccessLogPublishers()
+ {
+ return loggerStorage.getLogPublishers();
+ }
+
+ /**
+ * Logs the given HTTPRequestInfo.
+ *
+ * @param requestInfo
+ * the HTTP request info to log
+ */
+ public static void logRequestInfo(HTTPRequestInfo requestInfo)
+ {
+ for (HTTPAccessLogPublisher<?> publisher : loggerStorage.getLogPublishers())
+ {
+ publisher.logRequestInfo(requestInfo);
+ }
+ }
+
+}
diff --git a/opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java b/opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java
new file mode 100644
index 0000000..8accb23
--- /dev/null
+++ b/opends/src/server/org/opends/server/loggers/HTTPRequestInfo.java
@@ -0,0 +1,177 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2013 ForgeRock AS
+ */
+package org.opends.server.loggers;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Contains the information required for logging the HTTP request.
+ */
+public class HTTPRequestInfo
+{
+
+ /** The client's host. */
+ private final String remoteHost;
+ /** The client's address. */
+ private final String remoteAddress;
+ /** The protocol used for this request. */
+ private final String protocol;
+ /** The HTTP method/verb used for this request. */
+ private final String method;
+ /** The query issued by the client. */
+ private final String query;
+ /** The user agent used by the client. */
+ private final String userAgent;
+
+ /** The username that was used to authenticate. */
+ private String authUser;
+ /** The HTTP status code returned to the client. */
+ private volatile Integer statusCode;
+
+ /**
+ * Constructor for this class.
+ *
+ * @param request
+ * The {@link HttpServletRequest} for which to log the information
+ */
+ public HTTPRequestInfo(HttpServletRequest request)
+ {
+ this.remoteHost = request.getRemoteHost();
+ this.remoteAddress = request.getRemoteAddr();
+ this.method = request.getMethod();
+ this.query = request.getRequestURI() + "/" + request.getQueryString();
+ this.protocol = request.getProtocol();
+ this.userAgent = request.getHeader("User-Agent");
+ }
+
+ /**
+ * Returns the client's host.
+ *
+ * @return the remoteHost
+ */
+ public String getRemoteHost()
+ {
+ return remoteHost;
+ }
+
+ /**
+ * Returns the client's address.
+ *
+ * @return the remoteAddress
+ */
+ public String getRemoteAddress()
+ {
+ return remoteAddress;
+ }
+
+ /**
+ * Returns the protocol used for this request.
+ *
+ * @return the protocol
+ */
+ public String getProtocol()
+ {
+ return protocol;
+ }
+
+ /**
+ * Returns the HTTP method/verb used for this request.
+ *
+ * @return the method
+ */
+ public String getMethod()
+ {
+ return method;
+ }
+
+ /**
+ * Returns the query issued by the client.
+ *
+ * @return the query
+ */
+ public String getQuery()
+ {
+ return query;
+ }
+
+ /**
+ * Returns the user agent used by the client.
+ *
+ * @return the userAgent
+ */
+ public String getUserAgent()
+ {
+ return userAgent;
+ }
+
+ /**
+ * Returns the username that was used to authenticate.
+ *
+ * @return the authUser
+ */
+ public String getAuthUser()
+ {
+ return authUser;
+ }
+
+ /**
+ * Sets the username that was used to authenticate.
+ *
+ * @param authUser
+ * the authUser to set
+ */
+ public void setAuthUser(String authUser)
+ {
+ this.authUser = authUser;
+ }
+
+ /**
+ * Returns the HTTP status code returned to the client.
+ *
+ * @return the statusCode
+ */
+ public int getStatusCode()
+ {
+ return statusCode != null ? statusCode : 200;
+ }
+
+ /**
+ * Logs the current request info in the HTTP access log.
+ *
+ * @param statusCode
+ * the HTTP status code that was returned to the client.
+ */
+ public void log(int statusCode)
+ {
+ if (this.statusCode == null)
+ { // this request was not logged before
+ this.statusCode = statusCode;
+ HTTPAccessLogger.logRequestInfo(this);
+ }
+ }
+
+}
diff --git a/opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java b/opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java
new file mode 100644
index 0000000..efc7b3f
--- /dev/null
+++ b/opends/src/server/org/opends/server/loggers/TextHTTPAccessLogPublisher.java
@@ -0,0 +1,409 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2013 ForgeRock AS
+ */
+package org.opends.server.loggers;
+
+import static org.opends.messages.ConfigMessages.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.server.FileBasedHTTPAccessLogPublisherCfg;
+import org.opends.server.api.HTTPAccessLogPublisher;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.FilePermission;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.ResultCode;
+import org.opends.server.util.TimeThread;
+
+/**
+ * This class provides the implementation of the HTTP access logger used by the
+ * directory server.
+ */
+public final class TextHTTPAccessLogPublisher extends
+ HTTPAccessLogPublisher<FileBasedHTTPAccessLogPublisherCfg>
+ implements ConfigurationChangeListener<FileBasedHTTPAccessLogPublisherCfg>
+{
+
+ /**
+ * Returns an instance of the text HTTP access log publisher that will print
+ * all messages to the provided writer. This is used to print the messages to
+ * the console when the server starts up.
+ *
+ * @param writer
+ * The text writer where the message will be written to.
+ * @return The instance of the text error log publisher that will print all
+ * messages to standard out.
+ */
+ public static TextHTTPAccessLogPublisher getStartupTextHTTPAccessPublisher(
+ final TextWriter writer)
+ {
+ final TextHTTPAccessLogPublisher startupPublisher =
+ new TextHTTPAccessLogPublisher();
+ startupPublisher.writer = writer;
+ return startupPublisher;
+ }
+
+
+
+ private TextWriter writer = null;
+ private FileBasedHTTPAccessLogPublisherCfg cfg = null;
+ private String timeStampFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+ private DateFormat dateFormatter = new SimpleDateFormat(timeStampFormat);
+
+
+
+ /** {@inheritDoc} */
+ @Override
+ public ConfigChangeResult applyConfigurationChange(
+ final FileBasedHTTPAccessLogPublisherCfg config)
+ {
+ // Default result code.
+ ResultCode resultCode = ResultCode.SUCCESS;
+ boolean adminActionRequired = false;
+ final ArrayList<Message> messages = new ArrayList<Message>();
+
+ final File logFile = getFileForPath(config.getLogFile());
+ final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
+ try
+ {
+ final FilePermission perm = FilePermission.decodeUNIXMode(config
+ .getLogFilePermissions());
+
+ final boolean writerAutoFlush = config.isAutoFlush()
+ && !config.isAsynchronous();
+
+ TextWriter currentWriter;
+ // Determine the writer we are using. If we were writing
+ // asynchronously, we need to modify the underlying writer.
+ if (writer instanceof AsyncronousTextWriter)
+ {
+ currentWriter = ((AsyncronousTextWriter) writer).getWrappedWriter();
+ }
+ else if (writer instanceof ParallelTextWriter)
+ {
+ currentWriter = ((ParallelTextWriter) writer).getWrappedWriter();
+ }
+ else
+ {
+ currentWriter = writer;
+ }
+
+ if (currentWriter instanceof MultifileTextWriter)
+ {
+ final MultifileTextWriter mfWriter =
+ (MultifileTextWriter) currentWriter;
+
+ mfWriter.setNamingPolicy(fnPolicy);
+ mfWriter.setFilePermissions(perm);
+ mfWriter.setAppend(config.isAppend());
+ mfWriter.setAutoFlush(writerAutoFlush);
+ mfWriter.setBufferSize((int) config.getBufferSize());
+ mfWriter.setInterval(config.getTimeInterval());
+
+ mfWriter.removeAllRetentionPolicies();
+ mfWriter.removeAllRotationPolicies();
+
+ for (final DN dn : config.getRotationPolicyDNs())
+ {
+ mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
+ }
+
+ for (final DN dn : config.getRetentionPolicyDNs())
+ {
+ mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
+ }
+
+ if (writer instanceof AsyncronousTextWriter && !config.isAsynchronous())
+ {
+ // The asynchronous setting is being turned off.
+ final AsyncronousTextWriter asyncWriter =
+ ((AsyncronousTextWriter) writer);
+ writer = mfWriter;
+ asyncWriter.shutdown(false);
+ }
+
+ if (writer instanceof ParallelTextWriter && !config.isAsynchronous())
+ {
+ // The asynchronous setting is being turned off.
+ final ParallelTextWriter asyncWriter = ((ParallelTextWriter) writer);
+ writer = mfWriter;
+ asyncWriter.shutdown(false);
+ }
+
+ if (!(writer instanceof AsyncronousTextWriter)
+ && config.isAsynchronous())
+ {
+ // The asynchronous setting is being turned on.
+ final AsyncronousTextWriter asyncWriter = new AsyncronousTextWriter(
+ "Asyncronous Text Writer for " + config.dn().toNormalizedString(),
+ config.getQueueSize(), config.isAutoFlush(), mfWriter);
+ writer = asyncWriter;
+ }
+
+ if (!(writer instanceof ParallelTextWriter) && config.isAsynchronous())
+ {
+ // The asynchronous setting is being turned on.
+ final ParallelTextWriter asyncWriter = new ParallelTextWriter(
+ "Parallel Text Writer for " + config.dn().toNormalizedString(),
+ config.isAutoFlush(), mfWriter);
+ writer = asyncWriter;
+ }
+
+ if ((cfg.isAsynchronous() && config.isAsynchronous())
+ && (cfg.getQueueSize() != config.getQueueSize()))
+ {
+ adminActionRequired = true;
+ }
+
+ if (!config.getLogRecordTimeFormat().equals(timeStampFormat))
+ {
+ TimeThread.removeUserDefinedFormatter(timeStampFormat);
+ setTimeStampFormat(config.getLogRecordTimeFormat());
+ }
+
+ cfg = config;
+ }
+ }
+ catch (final Exception e)
+ {
+ final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(
+ config.dn().toString(), stackTraceToSingleLineString(e));
+ resultCode = DirectoryServer.getServerErrorResultCode();
+ messages.add(message);
+ }
+
+ return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+ }
+
+
+
+ /** {@inheritDoc} */
+ @Override
+ public void initializeLogPublisher(
+ final FileBasedHTTPAccessLogPublisherCfg cfg)
+ throws ConfigException, InitializationException
+ {
+ final File logFile = getFileForPath(cfg.getLogFile());
+ final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
+
+ try
+ {
+ final FilePermission perm = FilePermission.decodeUNIXMode(cfg
+ .getLogFilePermissions());
+
+ final LogPublisherErrorHandler errorHandler =
+ new LogPublisherErrorHandler(cfg.dn());
+
+ final boolean writerAutoFlush = cfg.isAutoFlush()
+ && !cfg.isAsynchronous();
+
+ final MultifileTextWriter theWriter = new MultifileTextWriter(
+ "Multifile Text Writer for " + cfg.dn().toNormalizedString(),
+ cfg.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8",
+ writerAutoFlush, cfg.isAppend(), (int) cfg.getBufferSize());
+
+ // Validate retention and rotation policies.
+ for (final DN dn : cfg.getRotationPolicyDNs())
+ {
+ theWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
+ }
+
+ for (final DN dn : cfg.getRetentionPolicyDNs())
+ {
+ theWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
+ }
+
+ if (cfg.isAsynchronous())
+ {
+ if (cfg.getQueueSize() > 0)
+ {
+ this.writer = new AsyncronousTextWriter(
+ "Asyncronous Text Writer for " + cfg.dn().toNormalizedString(),
+ cfg.getQueueSize(), cfg.isAutoFlush(), theWriter);
+ }
+ else
+ {
+ this.writer = new ParallelTextWriter("Parallel Text Writer for "
+ + cfg.dn().toNormalizedString(), cfg.isAutoFlush(), theWriter);
+ }
+ }
+ else
+ {
+ this.writer = theWriter;
+ }
+ }
+ catch (final DirectoryException e)
+ {
+ final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg
+ .dn().toString(), String.valueOf(e));
+ throw new InitializationException(message, e);
+ }
+ catch (final IOException e)
+ {
+ final Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(
+ logFile.toString(), cfg.dn().toString(), String.valueOf(e));
+ throw new InitializationException(message, e);
+
+ }
+
+ this.cfg = cfg;
+ setTimeStampFormat(cfg.getLogRecordTimeFormat());
+
+ cfg.addFileBasedHTTPAccessChangeListener(this);
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isConfigurationAcceptable(
+ final FileBasedHTTPAccessLogPublisherCfg configuration,
+ final List<Message> unacceptableReasons)
+ {
+ return isConfigurationChangeAcceptable(configuration, unacceptableReasons);
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isConfigurationChangeAcceptable(
+ final FileBasedHTTPAccessLogPublisherCfg config,
+ final List<Message> unacceptableReasons)
+ {
+ // Validate the time-stamp formatter.
+ final String formatString = config.getLogRecordTimeFormat();
+ try
+ {
+ new SimpleDateFormat(formatString);
+ }
+ catch (final Exception e)
+ {
+ final Message message = ERR_CONFIG_LOGGING_INVALID_TIME_FORMAT.get(String
+ .valueOf(formatString));
+ unacceptableReasons.add(message);
+ return false;
+ }
+
+ // Make sure the permission is valid.
+ try
+ {
+ final FilePermission filePerm = FilePermission.decodeUNIXMode(config
+ .getLogFilePermissions());
+ if (!filePerm.isOwnerWritable())
+ {
+ final Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config
+ .getLogFilePermissions());
+ unacceptableReasons.add(message);
+ return false;
+ }
+ }
+ catch (final DirectoryException e)
+ {
+ final Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get(
+ config.getLogFilePermissions(), String.valueOf(e));
+ unacceptableReasons.add(message);
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public final void close()
+ {
+ writer.shutdown();
+ TimeThread.removeUserDefinedFormatter(timeStampFormat);
+ if (cfg != null)
+ {
+ cfg.removeFileBasedHTTPAccessChangeListener(this);
+ }
+ }
+
+ private void setTimeStampFormat(String timeStampFormat)
+ {
+ this.timeStampFormat = timeStampFormat;
+ this.dateFormatter = new SimpleDateFormat(timeStampFormat);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final DN getDN()
+ {
+ return cfg != null ? cfg.dn() : null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void logRequestInfo(HTTPRequestInfo ri)
+ {
+ final StringBuilder sb = new StringBuilder(100);
+
+ // remotehost
+ if (ri.getRemoteHost() != null)
+ {
+ sb.append(ri.getRemoteHost());
+ }
+ else
+ {
+ sb.append(ri.getRemoteAddress());
+ }
+ // rfc931 - not supported
+ // authuser
+ sb.append(" ");
+ if (ri.getAuthUser() != null)
+ {
+ sb.append(ri.getAuthUser());
+ }
+ // [dateAndTime]
+ sb.append(" [").append(TimeThread.getUserDefinedTime(timeStampFormat))
+ .append("]");
+ // "request"
+ sb.append(" \"").append(ri.getMethod());
+ sb.append(" ").append(ri.getQuery());
+ sb.append(" ").append(ri.getProtocol()).append("\"");
+ // HTTP response status code
+ sb.append(" ").append(ri.getStatusCode());
+ // bytes - not supported
+ // "user agent"
+ sb.append(" \"").append(ri.getUserAgent()).append("\"");
+
+ writer.writeRecord(sb.toString());
+ }
+
+}
diff --git a/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java b/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
index f03cc51..0adde11 100644
--- a/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
+++ b/opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
@@ -44,11 +44,11 @@
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.opendj.ldap.Connection;
@@ -66,6 +66,7 @@
import org.forgerock.opendj.rest2ldap.servlet.Rest2LDAPContextFactory;
import org.opends.messages.Message;
import org.opends.server.admin.std.server.ConnectionHandlerCfg;
+import org.opends.server.loggers.HTTPRequestInfo;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.schema.SchemaConstants;
import org.opends.server.types.AddressMask;
@@ -85,8 +86,8 @@
private final class HTTPRequestContext
{
private AsyncContext asyncContext;
- private ServletRequest request;
- private ServletResponse response;
+ private HttpServletRequest request;
+ private HttpServletResponse response;
private FilterChain chain;
private HTTPClientConnection clientConnection;
@@ -94,8 +95,12 @@
/** Whether to pretty print the resulting JSON. */
private boolean prettyPrint;
- /** Can be used for the bind request. */
+ /** Used for the bind request when credentials are specified. */
+ private String userName;
+ /** Used for the bind request when credentials are specified. */
private String password;
+ /** Request information for logging. */
+ private HTTPRequestInfo requestInfo;
}
/**
@@ -176,6 +181,8 @@
@Override
public void handleResult(BindResult result)
{
+ ctx.requestInfo.setAuthUser(ctx.userName);
+
final AuthenticationInfo authInfo =
new AuthenticationInfo(to(resultEntry), to(resultEntry.getName()),
ByteString.valueOf(ctx.password), false);
@@ -231,23 +238,44 @@
/** {@inheritDoc} */
@Override
- public void doFilter(ServletRequest request, ServletResponse response,
+ public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain)
{
- final boolean prettyPrint =
+ final HttpServletRequest request = (HttpServletRequest) req;
+ final HttpServletResponse response = (HttpServletResponse) resp;
+
+ final HTTPRequestContext ctx = new HTTPRequestContext();
+
+ ctx.request = request;
+ ctx.response = new HttpServletResponseWrapper(response)
+ {
+
+ /** {@inheritDoc} */
+ @Override
+ public void setStatus(int sc)
+ {
+ ctx.requestInfo.log(sc);
+ super.setStatus(sc);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setStatus(int sc, String sm)
+ {
+ ctx.requestInfo.log(sc);
+ super.setStatus(sc, sm);
+ }
+ };
+ ctx.chain = chain;
+ ctx.prettyPrint =
Boolean.parseBoolean(request.getParameter("_prettyPrint"));
+ ctx.requestInfo = new HTTPRequestInfo(ctx.request);
final HTTPClientConnection clientConnection =
new HTTPClientConnection(this.connectionHandler, request);
this.connectionHandler.addClientConnection(clientConnection);
- final HTTPRequestContext ctx = new HTTPRequestContext();
- ctx.request = request;
- ctx.response = response;
- ctx.chain = chain;
-
ctx.clientConnection = clientConnection;
- ctx.prettyPrint = prettyPrint;
try
{
@@ -259,17 +287,18 @@
// checked.
logConnect(clientConnection);
- ctx.connection = new SdkConnectionAdapter(clientConnection);
+ ctx.connection =
+ new SdkConnectionAdapter(clientConnection, ctx.requestInfo);
final String[] userPassword = extractUsernamePassword(request);
if (userPassword != null && userPassword.length == 2)
{
- final String userName = userPassword[0];
+ ctx.userName = userPassword[0];
ctx.password = userPassword[1];
ctx.asyncContext = getAsyncContext(request);
- ctx.connection.searchSingleEntryAsync(buildSearchRequest(userName),
+ ctx.connection.searchSingleEntryAsync(buildSearchRequest(ctx.userName),
new DoBindResultHandler(ctx));
}
else if (this.connectionHandler.acceptUnauthenticatedRequests())
@@ -308,13 +337,13 @@
private void sendAuthenticationFailure(HTTPRequestContext ctx)
{
+ final int statusCode = HttpServletResponse.SC_UNAUTHORIZED;
try
{
// The user could not be authenticated. Send an HTTP Basic authentication
// challenge if HTTP Basic authentication is enabled.
ResourceException unauthorizedException =
- ResourceException.getException(HttpServletResponse.SC_UNAUTHORIZED,
- "Invalid Credentials");
+ ResourceException.getException(statusCode, "Invalid Credentials");
sendErrorReponse(ctx.response, ctx.prettyPrint, unauthorizedException);
ctx.clientConnection.disconnect(DisconnectReason.INVALID_CREDENTIALS,
@@ -322,6 +351,8 @@
}
finally
{
+ ctx.requestInfo.log(statusCode);
+
if (ctx.asyncContext != null)
{
ctx.asyncContext.complete();
@@ -331,6 +362,7 @@
private void onFailure(Exception e, HTTPRequestContext ctx)
{
+ ResourceException ex = Rest2LDAP.asResourceException(e);
try
{
if (debugEnabled())
@@ -338,8 +370,7 @@
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
- sendErrorReponse(ctx.response, ctx.prettyPrint, Rest2LDAP
- .asResourceException(e));
+ sendErrorReponse(ctx.response, ctx.prettyPrint, ex);
Message message =
INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(ctx.clientConnection
@@ -352,6 +383,8 @@
}
finally
{
+ ctx.requestInfo.log(ex.getCode());
+
if (ctx.asyncContext != null)
{
ctx.asyncContext.complete();
@@ -359,7 +392,7 @@
}
}
- private boolean canProcessRequest(ServletRequest request,
+ private boolean canProcessRequest(HttpServletRequest request,
final HTTPClientConnection clientConnection) throws UnknownHostException
{
InetAddress clientAddr = InetAddress.getByName(request.getRemoteAddr());
@@ -415,20 +448,18 @@
* @throws ResourceException
* if any error occur
*/
- String[] extractUsernamePassword(ServletRequest request)
+ String[] extractUsernamePassword(HttpServletRequest request)
throws ResourceException
{
- HttpServletRequest req = (HttpServletRequest) request;
-
// TODO Use session to reduce hits with search + bind?
// Use proxied authorization control for session.
if (authConfig.isCustomHeadersAuthenticationSupported())
{
final String userName =
- req.getHeader(authConfig.getCustomHeaderUsername());
+ request.getHeader(authConfig.getCustomHeaderUsername());
final String password =
- req.getHeader(authConfig.getCustomHeaderPassword());
+ request.getHeader(authConfig.getCustomHeaderPassword());
if (userName != null && password != null)
{
return new String[] { userName, password };
@@ -437,7 +468,7 @@
if (authConfig.isBasicAuthenticationSupported())
{
- String httpBasicAuthHeader = req.getHeader(HTTP_BASIC_AUTH_HEADER);
+ String httpBasicAuthHeader = request.getHeader(HTTP_BASIC_AUTH_HEADER);
if (httpBasicAuthHeader != null)
{
String[] userPassword = parseUsernamePassword(httpBasicAuthHeader);
@@ -463,26 +494,23 @@
* @param re
* the resource exception with the error response content
*/
- void sendErrorReponse(ServletResponse response, boolean prettyPrint,
+ void sendErrorReponse(HttpServletResponse response, boolean prettyPrint,
ResourceException re)
{
- HttpServletResponse resp = (HttpServletResponse) response;
- resp.setStatus(re.getCode());
+ response.setStatus(re.getCode());
if (re.getCode() == HttpServletResponse.SC_UNAUTHORIZED
&& authConfig.isBasicAuthenticationSupported())
{
- resp.setHeader("WWW-Authenticate",
+ response.setHeader("WWW-Authenticate",
"Basic realm=\"org.forgerock.opendj\"");
}
try
{
// Send error JSON document out
- resp.setHeader("Content-Type", "application/json");
-
- ServletOutputStream out = resp.getOutputStream();
- out.println(toJSON(prettyPrint, re));
+ response.setHeader("Content-Type", "application/json");
+ response.getOutputStream().println(toJSON(prettyPrint, re));
}
catch (IOException ignore)
{
diff --git a/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java b/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
index 04188dc..ff2a1d5 100644
--- a/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
+++ b/opends/src/server/org/opends/server/protocols/http/SdkConnectionAdapter.java
@@ -33,6 +33,8 @@
import java.util.LinkedHashSet;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.servlet.http.HttpServletResponse;
+
import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
import org.forgerock.opendj.ldap.ConnectionEventListener;
import org.forgerock.opendj.ldap.ErrorResultException;
@@ -68,6 +70,7 @@
import org.opends.server.core.QueueingStrategy;
import org.opends.server.core.SearchOperationBasis;
import org.opends.server.core.UnbindOperationBasis;
+import org.opends.server.loggers.HTTPRequestInfo;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
@@ -91,6 +94,9 @@
/** The HTTP client connection being "adapted". */
private final HTTPClientConnection clientConnection;
+ /** The HTTP request information to log. */
+ private final HTTPRequestInfo requestInfo;
+
/**
* The next message ID (and operation ID) that should be used for this
* connection.
@@ -111,10 +117,14 @@
*
* @param clientConnection
* the HTTP client connection being "adapted"
+ * @param requestInfo
+ * the HTTP request information to log
*/
- public SdkConnectionAdapter(HTTPClientConnection clientConnection)
+ public SdkConnectionAdapter(HTTPClientConnection clientConnection,
+ HTTPRequestInfo requestInfo)
{
this.clientConnection = clientConnection;
+ this.requestInfo = requestInfo;
this.queueingStrategy =
new BoundedWorkQueueStrategy(clientConnection.getConnectionHandler()
.getCurrentConfig().getMaxConcurrentOpsPerConnection());
@@ -223,6 +233,11 @@
{
this.clientConnection.disconnect(DisconnectReason.UNBIND, false, null);
}
+
+ // At this point, we try to log the request with OK status code.
+ // If it was already logged, it will be a no op.
+ this.requestInfo.log(HttpServletResponse.SC_OK);
+
isClosed = true;
}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
index a7577ff..f1904d7 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -51,7 +51,14 @@
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.LogManager;
@@ -78,8 +85,10 @@
import org.opends.server.extensions.ConfigFileHandler;
import org.opends.server.loggers.AccessLogger;
import org.opends.server.loggers.ErrorLogger;
+import org.opends.server.loggers.HTTPAccessLogger;
import org.opends.server.loggers.TextAccessLogPublisher;
import org.opends.server.loggers.TextErrorLogPublisher;
+import org.opends.server.loggers.TextHTTPAccessLogPublisher;
import org.opends.server.loggers.debug.DebugLogger;
import org.opends.server.loggers.debug.TextDebugLogPublisher;
import org.opends.server.plugins.InvocationCounterPlugin;
@@ -187,23 +196,17 @@
public static final String PROPERTY_OPENDMK_LOCATION =
"org.opends.server.snmp.opendmk";
- /**
- * The test text writer for the Debug Logger
- */
- public static TestTextWriter DEBUG_TEXT_WRITER =
- new TestTextWriter();
+ /** The test text writer for the Debug Logger */
+ public static TestTextWriter DEBUG_TEXT_WRITER = new TestTextWriter();
- /**
- * The test text writer for the Debug Logger
- */
- public static TestTextWriter ERROR_TEXT_WRITER =
- new TestTextWriter();
+ /** The test text writer for the Error Logger */
+ public static TestTextWriter ERROR_TEXT_WRITER = new TestTextWriter();
- /**
- * The test text writer for the Debug Logger
- */
- public static TestTextWriter ACCESS_TEXT_WRITER =
- new TestTextWriter();
+ /** The test text writer for the Access Logger */
+ public static TestTextWriter ACCESS_TEXT_WRITER = new TestTextWriter();
+
+ /** The test text writer for the HTTP Access Logger */
+ public static TestTextWriter HTTP_ACCESS_TEXT_WRITER = new TestTextWriter();
/**
* Indicates whether the server has already been started. The value of this
@@ -525,6 +528,9 @@
TextAccessLogPublisher.getStartupTextAccessPublisher(
ACCESS_TEXT_WRITER, false));
+ HTTPAccessLogger.addHTTPAccessLogPublisher(TextHTTPAccessLogPublisher
+ .getStartupTextHTTPAccessPublisher(HTTP_ACCESS_TEXT_WRITER));
+
// Use more verbose tool logger.
ErrorLogger.addErrorLogPublisher(
TextErrorLogPublisher.getToolStartupTextErrorPublisher(
--
Gitblit v1.10.0