mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
11.34.2011 33092f68b1dfc3b9dae653b8b0b95cbab5381d5c
Fix OPENDJ-308: Implement access log filtering and configurable message format

* split access and audit log configuration, since filtering is not appropriate for the audit log
* add initial filtering capabilities to the access log (based on operation type only for now).

4 files added
6 files modified
1424 ■■■■ changed files
opends/resource/config/config.ldif 2 ●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 34 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/AccessLogFilteringCriteriaConfiguration.xml 98 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml 46 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/FileBasedAuditLogPublisherConfiguration.xml 288 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/AccessLogFilteringCriteriaCfgDefn.properties 17 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/FileBasedAccessLogPublisherCfgDefn.properties 7 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties 26 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java 883 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java 23 ●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -706,7 +706,7 @@
objectClass: top
objectClass: ds-cfg-log-publisher
objectClass: ds-cfg-access-log-publisher
objectClass: ds-cfg-file-based-access-log-publisher
objectClass: ds-cfg-file-based-audit-log-publisher
cn: File-Based Audit Logger
ds-cfg-java-class: org.opends.server.loggers.TextAuditLogPublisher
ds-cfg-enabled: false
opends/resource/schema/02-config.ldif
@@ -2668,6 +2668,14 @@
  NAME 'ds-cfg-rename-inbound-attributes'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.30
  NAME 'ds-cfg-filtering-policy'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.31
  NAME 'ds-cfg-log-record-type'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler'
  SUP top
@@ -2932,7 +2940,8 @@
        ds-cfg-buffer-size $
        ds-cfg-auto-flush $
        ds-cfg-append $
        ds-cfg-queue-size )
        ds-cfg-queue-size $
        ds-cfg-filtering-policy )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.26
  NAME 'ds-cfg-file-based-debug-log-publisher'
@@ -4442,3 +4451,26 @@
  MAY ( ds-cfg-remove-inbound-attributes $
        ds-cfg-rename-inbound-attributes )
  X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.6
  NAME 'ds-cfg-file-based-audit-log-publisher'
  SUP ds-cfg-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 )
  X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.7
  NAME 'ds-cfg-access-log-filtering-criteria'
  SUP top
  STRUCTURAL
  MUST ( cn )
  MAY ( ds-cfg-log-record-type )
  X-ORIGIN 'OpenDJ Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/AccessLogFilteringCriteriaConfiguration.xml
New file
@@ -0,0 +1,98 @@
<?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 2011 ForgeRock AS.
  ! -->
<adm:managed-object name="access-log-filtering-criteria"
  plural-name="access-log-filtering-criteria"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    A set of rules which together determine whether a log record should be
    logged or not.
  </adm:synopsis>
  <adm:tag name="core-server"/>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-access-log-filtering-criteria</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="log-record-type" multi-valued="true">
    <adm:synopsis>
    </adm:synopsis>
    <adm:default-behavior>
      <adm:alias>
        <adm:synopsis>All records</adm:synopsis>
      </adm:alias>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="abandon">
          <adm:synopsis>Abandon operations</adm:synopsis>
        </adm:value>
        <adm:value name="add">
          <adm:synopsis>Add operations</adm:synopsis>
        </adm:value>
        <adm:value name="bind">
          <adm:synopsis>Bind operations</adm:synopsis>
        </adm:value>
        <adm:value name="compare">
          <adm:synopsis>Compare operations</adm:synopsis>
        </adm:value>
        <adm:value name="delete">
          <adm:synopsis>Delete operations</adm:synopsis>
        </adm:value>
        <adm:value name="extended">
          <adm:synopsis>Extended operations</adm:synopsis>
        </adm:value>
        <adm:value name="modify">
          <adm:synopsis>Modify operations</adm:synopsis>
        </adm:value>
        <adm:value name="rename">
          <adm:synopsis>Rename operations</adm:synopsis>
        </adm:value>
        <adm:value name="search">
          <adm:synopsis>Search operations</adm:synopsis>
        </adm:value>
        <adm:value name="unbind">
          <adm:synopsis>Unbind operations</adm:synopsis>
        </adm:value>
        <adm:value name="connect">
          <adm:synopsis>Client connections</adm:synopsis>
        </adm:value>
        <adm:value name="disconnect">
          <adm:synopsis>Client disconnections</adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-log-record-type</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/FileBasedAccessLogPublisherConfiguration.xml
@@ -24,6 +24,7 @@
  !
  !
  !      Copyright 2007-2009 Sun Microsystems, Inc.
  !      Portions copyright 2011 ForgeRock AS.
  ! -->
<adm:managed-object name="file-based-access-log-publisher"
  plural-name="file-based-access-log-publishers"
@@ -40,6 +41,15 @@
      <ldap:superior>ds-cfg-access-log-publisher</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:relation name="access-log-filtering-criteria">
    <adm:synopsis>
      The set of criteria which will be used to filter log records.
    </adm:synopsis>
    <adm:one-to-many/>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>cn=Filtering Criteria</ldap:rdn-sequence>
    </adm:profile>
  </adm:relation>
  <adm:property-override name="java-class" advanced="true">
    <adm:default-behavior>
      <adm:defined>
@@ -285,4 +295,40 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="filtering-policy">
    <adm:synopsis>
      Specifies how filtering criteria should be applied to log records.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>no-filtering</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="no-filtering">
          <adm:synopsis>
            No filtering will be performed, and all records will be logged.
          </adm:synopsis>
        </adm:value>
        <adm:value name="inclusive">
          <adm:synopsis>
            Records must match at least one of the filtering criteria in order
            to be logged.
          </adm:synopsis>
        </adm:value>
        <adm:value name="exclusive">
          <adm:synopsis>
            Records must not match any of the filtering criteria in order to be
            logged.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-filtering-policy</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/FileBasedAuditLogPublisherConfiguration.xml
New file
@@ -0,0 +1,288 @@
<?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 2011 ForgeRock AS.
  ! -->
<adm:managed-object name="file-based-audit-log-publisher"
  plural-name="file-based-audit-log-publishers"
  package="org.opends.server.admin.std" extends="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 access messages to the file system.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-file-based-audit-log-publisher</ldap:name>
      <ldap:superior>ds-cfg-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.TextAuditLogPublisher
        </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:managed-object>
opends/src/admin/messages/AccessLogFilteringCriteriaCfgDefn.properties
New file
@@ -0,0 +1,17 @@
user-friendly-name=Access Log Filtering Criteria
user-friendly-plural-name=Access Log Filtering Criteria
synopsis=A set of rules which together determine whether a log record should be logged or not.
property.log-record-type.synopsis=
property.log-record-type.default-behavior.alias.synopsis=All records
property.log-record-type.syntax.enumeration.value.abandon.synopsis=Abandon operations
property.log-record-type.syntax.enumeration.value.add.synopsis=Add operations
property.log-record-type.syntax.enumeration.value.bind.synopsis=Bind operations
property.log-record-type.syntax.enumeration.value.compare.synopsis=Compare operations
property.log-record-type.syntax.enumeration.value.connect.synopsis=Client connections
property.log-record-type.syntax.enumeration.value.delete.synopsis=Delete operations
property.log-record-type.syntax.enumeration.value.disconnect.synopsis=Client disconnections
property.log-record-type.syntax.enumeration.value.extended.synopsis=Extended operations
property.log-record-type.syntax.enumeration.value.modify.synopsis=Modify operations
property.log-record-type.syntax.enumeration.value.rename.synopsis=Rename operations
property.log-record-type.syntax.enumeration.value.search.synopsis=Search operations
property.log-record-type.syntax.enumeration.value.unbind.synopsis=Unbind operations
opends/src/admin/messages/FileBasedAccessLogPublisherCfgDefn.properties
@@ -7,6 +7,10 @@
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 Access Log Publisher is enabled for use.
property.filtering-policy.synopsis=Specifies how filtering criteria should be applied to log records.
property.filtering-policy.syntax.enumeration.value.exclusive.synopsis=Records must not match any of the filtering criteria in order to be logged.
property.filtering-policy.syntax.enumeration.value.inclusive.synopsis=Records must match at least one of the filtering criteria in order to be logged.
property.filtering-policy.syntax.enumeration.value.no-filtering.synopsis=No filtering will be performed, and all records will be logged.
property.java-class.synopsis=The fully-qualified name of the Java class that provides the File Based Access Log Publisher implementation.
property.log-file.synopsis=The file name to use for the log files generated by the File Based 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.
@@ -24,3 +28,6 @@
property.suppress-internal-operations.synopsis=Indicates whether internal operations (for example, operations that are initiated by plugins) should be logged along with the operations that are requested by users.
property.suppress-synchronization-operations.synopsis=Indicates whether access messages that are generated by synchronization operations should be suppressed.
property.time-interval.synopsis=Specifies the interval at which to check whether the log files need to be rotated.
relation.access-log-filtering-criteria.user-friendly-name=Access Log Filtering Criteria
relation.access-log-filtering-criteria.user-friendly-plural-name=Access Log Filtering Criteria
relation.access-log-filtering-criteria.synopsis=The set of criteria which will be used to filter log records.
opends/src/admin/messages/FileBasedAuditLogPublisherCfgDefn.properties
New file
@@ -0,0 +1,26 @@
user-friendly-name=File Based Audit Log Publisher
user-friendly-plural-name=File Based Audit Log Publishers
synopsis=File Based Audit Log Publishers publish 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 Audit 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 Audit Log Publisher is enabled for use.
property.java-class.synopsis=The fully-qualified name of the Java class that provides the File Based Audit Log Publisher implementation.
property.log-file.synopsis=The file name to use for the log files generated by the File Based Audit 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 Audit 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.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 Audit 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 Audit 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 Audit 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.suppress-internal-operations.synopsis=Indicates whether internal operations (for example, operations that are initiated by plugins) should be logged along with the operations that are requested by users.
property.suppress-synchronization-operations.synopsis=Indicates whether access messages that are generated by synchronization operations should be suppressed.
property.time-interval.synopsis=Specifies the interval at which to check whether the log files need to be rotated.
opends/src/server/org/opends/server/loggers/TextAccessLogPublisher.java
@@ -30,35 +30,28 @@
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.StaticUtils.getFileForPath;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.*;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.std.meta.AccessLogFilteringCriteriaCfgDefn.*;
import org.opends.server.admin.std.meta.FileBasedAccessLogPublisherCfgDefn.*;
import org.opends.server.admin.std.server.AccessLogFilteringCriteriaCfg;
import org.opends.server.admin.std.server.AccessLogPublisherCfg;
import org.opends.server.admin.std.server.FileBasedAccessLogPublisherCfg;
import org.opends.server.api.AccessLogPublisher;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ExtendedOperationHandler;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AbandonOperation;
import org.opends.server.core.AddOperation;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ExtendedOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.UnbindOperation;
import org.opends.server.core.*;
import org.opends.server.types.*;
import org.opends.server.util.TimeThread;
@@ -74,11 +67,184 @@
{
  /**
   * Criteria based filter.
   */
  static final class CriteriaFilter implements Filter
  {
    private final AccessLogFilteringCriteriaCfg cfg;
    private final boolean logConnectRecords;
    private final boolean logDisconnectRecords;
    private final EnumSet<OperationType> logOperationRecords;
    /**
     * Creates a new criteria based filter.
     *
     * @param cfg
     *          The access log filter criteria.
     */
    CriteriaFilter(final AccessLogFilteringCriteriaCfg cfg)
    {
      this.cfg = cfg;
      // Pre-parse the log record types for more efficient queries.
      if (cfg.getLogRecordType().isEmpty())
      {
        logConnectRecords = true;
        logDisconnectRecords = true;
        logOperationRecords = EnumSet.allOf(OperationType.class);
      }
      else
      {
        logConnectRecords =
          cfg.getLogRecordType().contains(LogRecordType.CONNECT);
        logDisconnectRecords =
          cfg.getLogRecordType().contains(LogRecordType.DISCONNECT);
        logOperationRecords = EnumSet.noneOf(OperationType.class);
        for (final LogRecordType type : cfg.getLogRecordType())
        {
          switch (type)
          {
          case ABANDON:
            logOperationRecords.add(OperationType.ABANDON);
            break;
          case ADD:
            logOperationRecords.add(OperationType.ADD);
            break;
          case BIND:
            logOperationRecords.add(OperationType.BIND);
            break;
          case COMPARE:
            logOperationRecords.add(OperationType.COMPARE);
            break;
          case DELETE:
            logOperationRecords.add(OperationType.DELETE);
            break;
          case EXTENDED:
            logOperationRecords.add(OperationType.EXTENDED);
            break;
          case MODIFY:
            logOperationRecords.add(OperationType.MODIFY);
            break;
          case RENAME:
            logOperationRecords.add(OperationType.MODIFY_DN);
            break;
          case SEARCH:
            logOperationRecords.add(OperationType.SEARCH);
            break;
          case UNBIND:
            logOperationRecords.add(OperationType.UNBIND);
            break;
          default: // Ignore CONNECT/DISCONNECT
            break;
          }
        }
      }
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      if (!logConnectRecords)
      {
        return false;
      }
      // TODO: other checks.
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      if (!logDisconnectRecords)
      {
        return false;
      }
      // TODO: other checks.
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(final Operation operation)
    {
      if (!logOperationRecords.contains(operation.getOperationType()))
      {
        return false;
      }
      // TODO: other checks.
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(final Operation operation)
    {
      if (!logOperationRecords.contains(operation.getOperationType()))
      {
        return false;
      }
      // TODO: other checks.
      return true;
    }
  }
  /**
   * Log message filter predicate.
   */
  static interface Filter
  {
    /**
     * Returns {@code true} if the provided client connect should be logged.
     *
     * @param connection
     *          The client connection.
     * @return {@code true} if the provided client connect should be logged.
     */
    boolean isConnectLoggable(ClientConnection connection);
    /**
     * Returns {@code true} if the provided client disconnect should be logged.
     *
     * @param connection
     *          The client connection.
     * @return {@code true} if the provided client disconnect should be logged.
     */
    boolean isDisconnectLoggable(ClientConnection connection);
    /**
     * Returns {@code true} if the provided request should be logged.
     *
     * @param operation
@@ -102,66 +268,6 @@
  /**
   * A filter which performs a logical AND over a set of sub-filters.
   */
  static final class AndFilter implements Filter
  {
    private final Filter[] subFilters;
    /**
     * Creates a new AND filter.
     *
     * @param subFilters
     *          The sub-filters.
     */
    AndFilter(Filter[] subFilters)
    {
      this.subFilters = subFilters;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(Operation operation)
    {
      for (Filter filter : subFilters)
      {
        if (!filter.isRequestLoggable(operation))
        {
          // Fail fast.
          return false;
        }
      }
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(Operation operation)
    {
      for (Filter filter : subFilters)
      {
        if (!filter.isResponseLoggable(operation))
        {
          // Fail fast.
          return false;
        }
      }
      return true;
    }
  }
  /**
   * A filter which performs a logical OR over a set of sub-filters.
   */
  static final class OrFilter implements Filter
@@ -176,7 +282,7 @@
     * @param subFilters
     *          The sub-filters.
     */
    OrFilter(Filter[] subFilters)
    OrFilter(final Filter[] subFilters)
    {
      this.subFilters = subFilters;
    }
@@ -186,9 +292,45 @@
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(Operation operation)
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      for (Filter filter : subFilters)
      for (final Filter filter : subFilters)
      {
        if (filter.isConnectLoggable(connection))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isDisconnectLoggable(connection))
        {
          // Succeed fast.
          return true;
        }
      }
      return false;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(final Operation operation)
    {
      for (final Filter filter : subFilters)
      {
        if (filter.isRequestLoggable(operation))
        {
@@ -204,9 +346,9 @@
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(Operation operation)
    public boolean isResponseLoggable(final Operation operation)
    {
      for (Filter filter : subFilters)
      for (final Filter filter : subFilters)
      {
        if (filter.isResponseLoggable(operation))
        {
@@ -225,20 +367,34 @@
   * The root filter which first checks the logger configuration, delegating to
   * a sub-filter if needed.
   */
  final class RootFilter implements Filter
  static final class RootFilter implements Filter
  {
    private final Filter subFilter;
    private final boolean suppressInternalOperations;
    private final boolean suppressSynchronizationOperations;
    private final FilteringPolicy policy;
    /**
     * Creates a new root filter.
     *
     * @param suppressInternal
     *          Indicates whether internal operations should be suppressed.
     * @param suppressSynchronization
     *          Indicates whether sync operations should be suppressed.
     * @param policy
     *          The filtering policy.
     * @param subFilter
     *          The sub-filter.
     *          The sub-filters.
     */
    RootFilter(Filter subFilter)
    RootFilter(final boolean suppressInternal,
        final boolean suppressSynchronization, final FilteringPolicy policy,
        final Filter subFilter)
    {
      this.suppressInternalOperations = suppressInternal;
      this.suppressSynchronizationOperations = suppressSynchronization;
      this.policy = policy;
      this.subFilter = subFilter;
    }
@@ -247,12 +403,20 @@
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(Operation operation)
    public boolean isConnectLoggable(final ClientConnection connection)
    {
      if (isLoggable(operation))
      final long connectionID = connection.getConnectionID();
      if (connectionID >= 0 || !suppressInternalOperations)
      {
        // FIXME: actual behavior determined by default filter policy.
        return subFilter.isRequestLoggable(operation);
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isConnectLoggable(connection);
        case EXCLUSIVE:
          return !subFilter.isConnectLoggable(connection);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
@@ -265,12 +429,70 @@
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(Operation operation)
    public boolean isDisconnectLoggable(final ClientConnection connection)
    {
      final long connectionID = connection.getConnectionID();
      if (connectionID >= 0 || !suppressInternalOperations)
      {
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isDisconnectLoggable(connection);
        case EXCLUSIVE:
          return !subFilter.isDisconnectLoggable(connection);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    public boolean isRequestLoggable(final Operation operation)
    {
      if (isLoggable(operation))
      {
        // FIXME: actual behavior determined by default filter policy.
        switch (policy)
        {
        case INCLUSIVE:
          return subFilter.isRequestLoggable(operation);
        case EXCLUSIVE:
          return !subFilter.isRequestLoggable(operation);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
        return false;
      }
    }
    /**
     * {@inheritDoc}
     */
    public boolean isResponseLoggable(final Operation operation)
    {
      if (isLoggable(operation))
      {
        switch (policy)
        {
        case INCLUSIVE:
        return subFilter.isResponseLoggable(operation);
        case EXCLUSIVE:
          return !subFilter.isResponseLoggable(operation);
        default: // NO_FILTERING:
          return true;
        }
      }
      else
      {
@@ -281,9 +503,9 @@
    // Determines whether the provided operation should be logged.
    private boolean isLoggable(Operation operation)
    private boolean isLoggable(final Operation operation)
    {
      long connectionID = operation.getConnectionID();
      final long connectionID = operation.getConnectionID();
      if (connectionID < 0)
      {
        // This is an internal operation.
@@ -304,6 +526,92 @@
  /**
   * Filter criteria configuration listener.
   */
  private final class FilterChangeListener implements
      ConfigurationChangeListener<AccessLogFilteringCriteriaCfg>,
      ConfigurationAddListener<AccessLogFilteringCriteriaCfg>,
      ConfigurationDeleteListener<AccessLogFilteringCriteriaCfg>
  {
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationAdd(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      configuration.addChangeListener(this);
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationChange(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationDelete(
        final AccessLogFilteringCriteriaCfg configuration)
    {
      // Rebuild the filter using the new configuration and criteria.
      buildFilters();
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationAddAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationChangeAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationDeleteAcceptable(
        final AccessLogFilteringCriteriaCfg configuration,
        final List<Message> unacceptableReasons)
    {
      return true;
    }
  }
  /**
   * The category to use when logging responses.
   */
  private static final String CATEGORY_RESPONSE = "RES";
@@ -328,26 +636,22 @@
   *         messages to standard out.
   */
  public static TextAccessLogPublisher getStartupTextAccessPublisher(
      TextWriter writer, boolean suppressInternal)
      final TextWriter writer, final boolean suppressInternal)
  {
    TextAccessLogPublisher startupPublisher = new TextAccessLogPublisher();
    final TextAccessLogPublisher startupPublisher =
      new TextAccessLogPublisher();
    startupPublisher.writer = writer;
    startupPublisher.suppressInternalOperations = suppressInternal;
    startupPublisher.setSubFilter(new AndFilter(new Filter[0])); // Always true.
    startupPublisher.buildFilters(suppressInternal, false,
        FilteringPolicy.NO_FILTERING);
    return startupPublisher;
  }
  private FileBasedAccessLogPublisherCfg currentConfig;
  private boolean suppressInternalOperations = true;
  private boolean suppressSynchronizationOperations = false;
  private TextWriter writer;
  private Filter filter;
  private FileBasedAccessLogPublisherCfg currentConfig = null;
  private TextWriter writer = null;
  private Filter filter = null;
  private FilterChangeListener filterChangeListener = null;
@@ -355,26 +659,21 @@
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      FileBasedAccessLogPublisherCfg config)
      final FileBasedAccessLogPublisherCfg config)
  {
    // Default result code.
    ResultCode resultCode = ResultCode.SUCCESS;
    boolean adminActionRequired = false;
    ArrayList<Message> messages = new ArrayList<Message>();
    final ArrayList<Message> messages = new ArrayList<Message>();
    suppressInternalOperations = config.isSuppressInternalOperations();
    suppressSynchronizationOperations = config
        .isSuppressSynchronizationOperations();
    File logFile = getFileForPath(config.getLogFile());
    FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
    final File logFile = getFileForPath(config.getLogFile());
    final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
    try
    {
      FilePermission perm = FilePermission.decodeUNIXMode(config
      final FilePermission perm = FilePermission.decodeUNIXMode(config
          .getLogFilePermissions());
      boolean writerAutoFlush = config.isAutoFlush()
      final boolean writerAutoFlush = config.isAutoFlush()
          && !config.isAsynchronous();
      TextWriter currentWriter;
@@ -395,7 +694,8 @@
      if (currentWriter instanceof MultifileTextWriter)
      {
        MultifileTextWriter mfWriter = (MultifileTextWriter) currentWriter;
        final MultifileTextWriter mfWriter =
          (MultifileTextWriter) currentWriter;
        mfWriter.setNamingPolicy(fnPolicy);
        mfWriter.setFilePermissions(perm);
@@ -407,12 +707,12 @@
        mfWriter.removeAllRetentionPolicies();
        mfWriter.removeAllRotationPolicies();
        for (DN dn : config.getRotationPolicyDNs())
        for (final DN dn : config.getRotationPolicyDNs())
        {
          mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
        }
        for (DN dn : config.getRetentionPolicyDNs())
        for (final DN dn : config.getRetentionPolicyDNs())
        {
          mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
        }
@@ -420,7 +720,8 @@
        if (writer instanceof AsyncronousTextWriter && !config.isAsynchronous())
        {
          // The asynchronous setting is being turned off.
          AsyncronousTextWriter asyncWriter = ((AsyncronousTextWriter) writer);
          final AsyncronousTextWriter asyncWriter =
            ((AsyncronousTextWriter) writer);
          writer = mfWriter;
          asyncWriter.shutdown(false);
        }
@@ -428,7 +729,7 @@
        if (writer instanceof ParallelTextWriter && !config.isAsynchronous())
        {
          // The asynchronous setting is being turned off.
          ParallelTextWriter asyncWriter = ((ParallelTextWriter) writer);
          final ParallelTextWriter asyncWriter = ((ParallelTextWriter) writer);
          writer = mfWriter;
          asyncWriter.shutdown(false);
        }
@@ -437,7 +738,7 @@
            && config.isAsynchronous())
        {
          // The asynchronous setting is being turned on.
          AsyncronousTextWriter asyncWriter = new AsyncronousTextWriter(
          final AsyncronousTextWriter asyncWriter = new AsyncronousTextWriter(
              "Asyncronous Text Writer for " + config.dn().toNormalizedString(),
              config.getQueueSize(), config.isAutoFlush(), mfWriter);
          writer = asyncWriter;
@@ -446,7 +747,7 @@
        if (!(writer instanceof ParallelTextWriter) && config.isAsynchronous())
        {
          // The asynchronous setting is being turned on.
          ParallelTextWriter asyncWriter = new ParallelTextWriter(
          final ParallelTextWriter asyncWriter = new ParallelTextWriter(
              "Parallel Text Writer for " + config.dn().toNormalizedString(),
              config.isAutoFlush(), mfWriter);
          writer = asyncWriter;
@@ -458,16 +759,16 @@
          adminActionRequired = true;
        }
        // FIXME: use a dummy set of sub-filters for now.
        setSubFilter(new AndFilter(new Filter[0])); // Always true.
        currentConfig = config;
        // Rebuild the filter using the new configuration and criteria.
        buildFilters();
      }
    }
    catch (Exception e)
    catch (final Exception e)
    {
      Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(config.dn()
          .toString(), stackTraceToSingleLineString(e));
      final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(
          config.dn().toString(), stackTraceToSingleLineString(e));
      resultCode = DirectoryServer.getServerErrorResultCode();
      messages.add(message);
@@ -489,6 +790,24 @@
    if (currentConfig != null)
    {
      currentConfig.removeFileBasedAccessChangeListener(this);
      for (final String criteriaName : currentConfig
          .listAccessLogFilteringCriteria())
      {
        try
        {
          currentConfig.getAccessLogFilteringCriteria(criteriaName)
              .removeChangeListener(filterChangeListener);
        }
        catch (final ConfigException e)
        {
          // Ignore.
        }
      }
      currentConfig
          .removeAccessLogFilteringCriteriaAddListener(filterChangeListener);
      currentConfig
          .removeAccessLogFilteringCriteriaDeleteListener(filterChangeListener);
    }
  }
@@ -516,35 +835,36 @@
   * {@inheritDoc}
   */
  @Override
  public void initializeAccessLogPublisher(FileBasedAccessLogPublisherCfg cfg)
      throws ConfigException, InitializationException
  public void initializeAccessLogPublisher(
      final FileBasedAccessLogPublisherCfg cfg) throws ConfigException,
      InitializationException
  {
    File logFile = getFileForPath(cfg.getLogFile());
    FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
    final File logFile = getFileForPath(cfg.getLogFile());
    final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
    try
    {
      FilePermission perm = FilePermission.decodeUNIXMode(cfg
      final FilePermission perm = FilePermission.decodeUNIXMode(cfg
          .getLogFilePermissions());
      LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler(
          cfg.dn());
      final LogPublisherErrorHandler errorHandler =
        new LogPublisherErrorHandler(cfg.dn());
      boolean writerAutoFlush = cfg.isAutoFlush()
      final boolean writerAutoFlush = cfg.isAutoFlush()
          && !cfg.isAsynchronous();
      MultifileTextWriter writer = new MultifileTextWriter(
      final MultifileTextWriter writer = 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 (DN dn : cfg.getRotationPolicyDNs())
      for (final DN dn : cfg.getRotationPolicyDNs())
      {
        writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
      }
      for (DN dn : cfg.getRetentionPolicyDNs())
      for (final DN dn : cfg.getRetentionPolicyDNs())
      {
        writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
      }
@@ -568,30 +888,47 @@
        this.writer = writer;
      }
    }
    catch (DirectoryException e)
    catch (final DirectoryException e)
    {
      Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg.dn()
          .toString(), String.valueOf(e));
      final Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg
          .dn().toString(), String.valueOf(e));
      throw new InitializationException(message, e);
    }
    catch (IOException e)
    catch (final IOException e)
    {
      Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(
      final Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(
          logFile.toString(), cfg.dn().toString(), String.valueOf(e));
      throw new InitializationException(message, e);
    }
    suppressInternalOperations = cfg.isSuppressInternalOperations();
    suppressSynchronizationOperations = cfg
        .isSuppressSynchronizationOperations();
    currentConfig = cfg;
    // FIXME: use a dummy set of sub-filters for now.
    setSubFilter(new AndFilter(new Filter[0])); // Always true.
    // Rebuild the filter using the new configuration and criteria.
    buildFilters();
    cfg.addFileBasedAccessChangeListener(this);
    // Add change listeners.
    filterChangeListener = new FilterChangeListener();
    for (final String criteriaName : currentConfig
        .listAccessLogFilteringCriteria())
    {
      try
      {
        currentConfig.getAccessLogFilteringCriteria(criteriaName)
            .addChangeListener(filterChangeListener);
      }
      catch (final ConfigException e)
      {
        // Ignore.
      }
    }
    currentConfig
        .addAccessLogFilteringCriteriaAddListener(filterChangeListener);
    currentConfig
        .addAccessLogFilteringCriteriaDeleteListener(filterChangeListener);
    currentConfig.addFileBasedAccessChangeListener(this);
  }
@@ -600,10 +937,11 @@
   * {@inheritDoc}
   */
  @Override
  public boolean isConfigurationAcceptable(AccessLogPublisherCfg configuration,
      List<Message> unacceptableReasons)
  public boolean isConfigurationAcceptable(
      final AccessLogPublisherCfg configuration,
      final List<Message> unacceptableReasons)
  {
    FileBasedAccessLogPublisherCfg config =
    final FileBasedAccessLogPublisherCfg config =
      (FileBasedAccessLogPublisherCfg) configuration;
    return isConfigurationChangeAcceptable(config, unacceptableReasons);
  }
@@ -614,24 +952,25 @@
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      FileBasedAccessLogPublisherCfg config, List<Message> unacceptableReasons)
      final FileBasedAccessLogPublisherCfg config,
      final List<Message> unacceptableReasons)
  {
    // Make sure the permission is valid.
    try
    {
      FilePermission filePerm = FilePermission.decodeUNIXMode(config
      final FilePermission filePerm = FilePermission.decodeUNIXMode(config
          .getLogFilePermissions());
      if (!filePerm.isOwnerWritable())
      {
        Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config
        final Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config
            .getLogFilePermissions());
        unacceptableReasons.add(message);
        return false;
      }
    }
    catch (DirectoryException e)
    catch (final DirectoryException e)
    {
      Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get(
      final Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get(
          config.getLogFilePermissions(), String.valueOf(e));
      unacceptableReasons.add(message);
      return false;
@@ -651,19 +990,21 @@
   *          abandon request.
   */
  @Override
  public void logAbandonRequest(AbandonOperation abandonOperation)
  public void logAbandonRequest(final AbandonOperation abandonOperation)
  {
    if (!filter.isRequestLoggable(abandonOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(abandonOperation, "ABANDON", CATEGORY_REQUEST, buffer);
    buffer.append(" idToAbandon=");
    buffer.append(abandonOperation.getIDToAbandon());
    if (abandonOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -679,18 +1020,18 @@
   *          abandon request.
   */
  @Override
  public void logAbandonResult(AbandonOperation abandonOperation)
  public void logAbandonResult(final AbandonOperation abandonOperation)
  {
    if (!filter.isResponseLoggable(abandonOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(abandonOperation, "ABANDON", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(abandonOperation.getResultCode().getIntValue());
    MessageBuilder msg = abandonOperation.getErrorMessage();
    final MessageBuilder msg = abandonOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -717,20 +1058,22 @@
   *          request.
   */
  @Override
  public void logAddRequest(AddOperation addOperation)
  public void logAddRequest(final AddOperation addOperation)
  {
    if (!filter.isRequestLoggable(addOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(addOperation, "ADD", CATEGORY_REQUEST, buffer);
    buffer.append(" dn=\"");
    buffer.append(addOperation.getRawEntryDN().toString());
    buffer.append("\"");
    if (addOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -746,19 +1089,19 @@
   *          response.
   */
  @Override
  public void logAddResponse(AddOperation addOperation)
  public void logAddResponse(final AddOperation addOperation)
  {
    if (!filter.isResponseLoggable(addOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(addOperation, "ADD", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(addOperation.getResultCode().getIntValue());
    MessageBuilder msg = addOperation.getErrorMessage();
    final MessageBuilder msg = addOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -768,7 +1111,7 @@
    logAdditionalLogItems(addOperation, buffer);
    DN proxiedAuthDN = addOperation.getProxiedAuthorizationDN();
    final DN proxiedAuthDN = addOperation.getProxiedAuthorizationDN();
    if (proxiedAuthDN != null)
    {
      buffer.append(" authzDN=\"");
@@ -798,14 +1141,14 @@
   *          request.
   */
  @Override
  public void logBindRequest(BindOperation bindOperation)
  public void logBindRequest(final BindOperation bindOperation)
  {
    if (!filter.isRequestLoggable(bindOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(bindOperation, "BIND", CATEGORY_REQUEST, buffer);
    final String protocolVersion = bindOperation.getProtocolVersion();
@@ -834,7 +1177,9 @@
    buffer.append(bindOperation.getRawBindDN().toString());
    buffer.append("\"");
    if (bindOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -850,19 +1195,19 @@
   *          bind response.
   */
  @Override
  public void logBindResponse(BindOperation bindOperation)
  public void logBindResponse(final BindOperation bindOperation)
  {
    if (!filter.isResponseLoggable(bindOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(bindOperation, "BIND", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(bindOperation.getResultCode().getIntValue());
    MessageBuilder msg = bindOperation.getErrorMessage();
    final MessageBuilder msg = bindOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -870,7 +1215,7 @@
      buffer.append('\"');
    }
    Message failureMessage = bindOperation.getAuthFailureReason();
    final Message failureMessage = bindOperation.getAuthFailureReason();
    if (failureMessage != null)
    {
      buffer.append(" authFailureID=");
@@ -884,17 +1229,17 @@
    if (bindOperation.getResultCode() == ResultCode.SUCCESS)
    {
      AuthenticationInfo authInfo = bindOperation.getAuthenticationInfo();
      final AuthenticationInfo authInfo = bindOperation.getAuthenticationInfo();
      if (authInfo != null)
      {
        DN authDN = authInfo.getAuthenticationDN();
        final DN authDN = authInfo.getAuthenticationDN();
        if (authDN != null)
        {
          buffer.append(" authDN=\"");
          authDN.toString(buffer);
          buffer.append('\"');
          DN authzDN = authInfo.getAuthorizationDN();
          final DN authzDN = authInfo.getAuthorizationDN();
          if (!authDN.equals(authzDN))
          {
            buffer.append(" authzDN=\"");
@@ -934,21 +1279,23 @@
   *          compare request.
   */
  @Override
  public void logCompareRequest(CompareOperation compareOperation)
  public void logCompareRequest(final CompareOperation compareOperation)
  {
    if (!filter.isRequestLoggable(compareOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(compareOperation, "COMPARE", CATEGORY_REQUEST, buffer);
    buffer.append(" dn=\"");
    buffer.append(compareOperation.getRawEntryDN().toString());
    buffer.append("\" attr=");
    buffer.append(compareOperation.getAttributeType().getNameOrOID());
    if (compareOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -964,19 +1311,19 @@
   *          compare response.
   */
  @Override
  public void logCompareResponse(CompareOperation compareOperation)
  public void logCompareResponse(final CompareOperation compareOperation)
  {
    if (!filter.isResponseLoggable(compareOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(compareOperation, "COMPARE", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(compareOperation.getResultCode().getIntValue());
    MessageBuilder msg = compareOperation.getErrorMessage();
    final MessageBuilder msg = compareOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -986,7 +1333,7 @@
    logAdditionalLogItems(compareOperation, buffer);
    DN proxiedAuthDN = compareOperation.getProxiedAuthorizationDN();
    final DN proxiedAuthDN = compareOperation.getProxiedAuthorizationDN();
    if (proxiedAuthDN != null)
    {
      buffer.append(" authzDN=\"");
@@ -1016,16 +1363,15 @@
   *          The client connection that has been established.
   */
  @Override
  public void logConnect(ClientConnection clientConnection)
  public void logConnect(final ClientConnection clientConnection)
  {
    // FIXME: implement filtering.
    long connectionID = clientConnection.getConnectionID();
    if (connectionID < 0 && suppressInternalOperations)
    if (!filter.isConnectLoggable(clientConnection))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final long connectionID = clientConnection.getConnectionID();
    final StringBuilder buffer = new StringBuilder(100);
    buffer.append("[");
    buffer.append(TimeThread.getLocalTime());
    buffer.append("]");
@@ -1053,20 +1399,22 @@
   *          request.
   */
  @Override
  public void logDeleteRequest(DeleteOperation deleteOperation)
  public void logDeleteRequest(final DeleteOperation deleteOperation)
  {
    if (!filter.isRequestLoggable(deleteOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(deleteOperation, "DELETE", CATEGORY_REQUEST, buffer);
    buffer.append(" dn=\"");
    buffer.append(deleteOperation.getRawEntryDN().toString());
    buffer.append("\"");
    if (deleteOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -1082,19 +1430,19 @@
   *          delete response.
   */
  @Override
  public void logDeleteResponse(DeleteOperation deleteOperation)
  public void logDeleteResponse(final DeleteOperation deleteOperation)
  {
    if (!filter.isResponseLoggable(deleteOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(deleteOperation, "DELETE", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(deleteOperation.getResultCode().getIntValue());
    MessageBuilder msg = deleteOperation.getErrorMessage();
    final MessageBuilder msg = deleteOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -1104,7 +1452,7 @@
    logAdditionalLogItems(deleteOperation, buffer);
    DN proxiedAuthDN = deleteOperation.getProxiedAuthorizationDN();
    final DN proxiedAuthDN = deleteOperation.getProxiedAuthorizationDN();
    if (proxiedAuthDN != null)
    {
      buffer.append(" authzDN=\"");
@@ -1138,16 +1486,16 @@
   *          about the disconnect.
   */
  @Override
  public void logDisconnect(ClientConnection clientConnection,
      DisconnectReason disconnectReason, Message message)
  public void logDisconnect(final ClientConnection clientConnection,
      final DisconnectReason disconnectReason, final Message message)
  {
    // FIXME: implement filtering.
    long connectionID = clientConnection.getConnectionID();
    if (connectionID < 0 && suppressInternalOperations)
    if (!filter.isDisconnectLoggable(clientConnection))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final long connectionID = clientConnection.getConnectionID();
    final StringBuilder buffer = new StringBuilder(100);
    buffer.append("[");
    buffer.append(TimeThread.getLocalTime());
    buffer.append("]");
@@ -1178,7 +1526,7 @@
   *          the extended request.
   */
  @Override
  public void logExtendedRequest(ExtendedOperation extendedOperation)
  public void logExtendedRequest(final ExtendedOperation extendedOperation)
  {
    if (!filter.isRequestLoggable(extendedOperation))
    {
@@ -1186,10 +1534,10 @@
    }
    String name = null;
    String oid = extendedOperation.getRequestOID();
    StringBuilder buffer = new StringBuilder(100);
    final String oid = extendedOperation.getRequestOID();
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(extendedOperation, "EXTENDED", CATEGORY_REQUEST, buffer);
    ExtendedOperationHandler<?> extOpHandler = DirectoryServer
    final ExtendedOperationHandler<?> extOpHandler = DirectoryServer
        .getExtendedOperationHandler(oid);
    if (extOpHandler != null)
    {
@@ -1223,21 +1571,21 @@
   *          extended response.
   */
  @Override
  public void logExtendedResponse(ExtendedOperation extendedOperation)
  public void logExtendedResponse(final ExtendedOperation extendedOperation)
  {
    if (!filter.isResponseLoggable(extendedOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(extendedOperation, "EXTENDED", CATEGORY_RESPONSE, buffer);
    String name = null;
    String oid = extendedOperation.getResponseOID();
    final String oid = extendedOperation.getResponseOID();
    if (oid != null)
    {
      ExtendedOperationHandler<?> extOpHandler = DirectoryServer
      final ExtendedOperationHandler<?> extOpHandler = DirectoryServer
          .getExtendedOperationHandler(oid);
      if (extOpHandler != null)
      {
@@ -1257,7 +1605,7 @@
    buffer.append(" result=");
    buffer.append(extendedOperation.getResultCode().getIntValue());
    MessageBuilder msg = extendedOperation.getErrorMessage();
    final MessageBuilder msg = extendedOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -1289,14 +1637,14 @@
   *          modify DN request.
   */
  @Override
  public void logModifyDNRequest(ModifyDNOperation modifyDNOperation)
  public void logModifyDNRequest(final ModifyDNOperation modifyDNOperation)
  {
    if (!filter.isRequestLoggable(modifyDNOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(modifyDNOperation, "MODIFYDN", CATEGORY_REQUEST, buffer);
    buffer.append(" dn=\"");
    buffer.append(modifyDNOperation.getRawEntryDN().toString());
@@ -1305,14 +1653,16 @@
    buffer.append("\" deleteOldRDN=");
    buffer.append(modifyDNOperation.deleteOldRDN());
    ByteString newSuperior = modifyDNOperation.getRawNewSuperior();
    final ByteString newSuperior = modifyDNOperation.getRawNewSuperior();
    if (newSuperior != null)
    {
      buffer.append(" newSuperior=\"");
      buffer.append(newSuperior.toString());
    }
    if (modifyDNOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -1328,19 +1678,19 @@
   *          the modify DN response.
   */
  @Override
  public void logModifyDNResponse(ModifyDNOperation modifyDNOperation)
  public void logModifyDNResponse(final ModifyDNOperation modifyDNOperation)
  {
    if (!filter.isResponseLoggable(modifyDNOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(modifyDNOperation, "MODIFYDN", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(modifyDNOperation.getResultCode().getIntValue());
    MessageBuilder msg = modifyDNOperation.getErrorMessage();
    final MessageBuilder msg = modifyDNOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -1350,7 +1700,7 @@
    logAdditionalLogItems(modifyDNOperation, buffer);
    DN proxiedAuthDN = modifyDNOperation.getProxiedAuthorizationDN();
    final DN proxiedAuthDN = modifyDNOperation.getProxiedAuthorizationDN();
    if (proxiedAuthDN != null)
    {
      buffer.append(" authzDN=\"");
@@ -1380,20 +1730,22 @@
   *          modify request.
   */
  @Override
  public void logModifyRequest(ModifyOperation modifyOperation)
  public void logModifyRequest(final ModifyOperation modifyOperation)
  {
    if (!filter.isRequestLoggable(modifyOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(modifyOperation, "MODIFY", CATEGORY_REQUEST, buffer);
    buffer.append(" dn=\"");
    buffer.append(modifyOperation.getRawEntryDN().toString());
    buffer.append("\"");
    if (modifyOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -1409,19 +1761,19 @@
   *          modify response.
   */
  @Override
  public void logModifyResponse(ModifyOperation modifyOperation)
  public void logModifyResponse(final ModifyOperation modifyOperation)
  {
    if (!filter.isResponseLoggable(modifyOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(modifyOperation, "MODIFY", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(modifyOperation.getResultCode().getIntValue());
    MessageBuilder msg = modifyOperation.getErrorMessage();
    final MessageBuilder msg = modifyOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -1431,7 +1783,7 @@
    logAdditionalLogItems(modifyOperation, buffer);
    DN proxiedAuthDN = modifyOperation.getProxiedAuthorizationDN();
    final DN proxiedAuthDN = modifyOperation.getProxiedAuthorizationDN();
    if (proxiedAuthDN != null)
    {
      buffer.append(" authzDN=\"");
@@ -1461,14 +1813,14 @@
   *          request.
   */
  @Override
  public void logSearchRequest(SearchOperation searchOperation)
  public void logSearchRequest(final SearchOperation searchOperation)
  {
    if (!filter.isRequestLoggable(searchOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(192);
    final StringBuilder buffer = new StringBuilder(192);
    appendHeader(searchOperation, "SEARCH", CATEGORY_REQUEST, buffer);
    buffer.append(" base=\"");
    buffer.append(searchOperation.getRawBaseDN().toString());
@@ -1477,7 +1829,7 @@
    buffer.append(" filter=\"");
    searchOperation.getRawFilter().toString(buffer);
    LinkedHashSet<String> attrs = searchOperation.getAttributes();
    final LinkedHashSet<String> attrs = searchOperation.getAttributes();
    if ((attrs == null) || attrs.isEmpty())
    {
      buffer.append("\" attrs=\"ALL\"");
@@ -1486,7 +1838,7 @@
    {
      buffer.append("\" attrs=\"");
      Iterator<String> iterator = attrs.iterator();
      final Iterator<String> iterator = attrs.iterator();
      buffer.append(iterator.next());
      while (iterator.hasNext())
      {
@@ -1497,7 +1849,9 @@
      buffer.append("\"");
    }
    if (searchOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -1513,19 +1867,19 @@
   *          search result done message.
   */
  @Override
  public void logSearchResultDone(SearchOperation searchOperation)
  public void logSearchResultDone(final SearchOperation searchOperation)
  {
    if (!filter.isResponseLoggable(searchOperation))
    {
      return;
    }
    StringBuilder buffer = new StringBuilder(128);
    final StringBuilder buffer = new StringBuilder(128);
    appendHeader(searchOperation, "SEARCH", CATEGORY_RESPONSE, buffer);
    buffer.append(" result=");
    buffer.append(searchOperation.getResultCode().getIntValue());
    MessageBuilder msg = searchOperation.getErrorMessage();
    final MessageBuilder msg = searchOperation.getErrorMessage();
    if ((msg != null) && (msg.length() > 0))
    {
      buffer.append(" message=\"");
@@ -1538,7 +1892,7 @@
    logAdditionalLogItems(searchOperation, buffer);
    DN proxiedAuthDN = searchOperation.getProxiedAuthorizationDN();
    final DN proxiedAuthDN = searchOperation.getProxiedAuthorizationDN();
    if (proxiedAuthDN != null)
    {
      buffer.append(" authzDN=\"");
@@ -1568,7 +1922,7 @@
   *          request.
   */
  @Override
  public void logUnbind(UnbindOperation unbindOperation)
  public void logUnbind(final UnbindOperation unbindOperation)
  {
    // FIXME: ensure that these are logged in combined mode.
    if (!filter.isRequestLoggable(unbindOperation))
@@ -1576,10 +1930,12 @@
      return;
    }
    StringBuilder buffer = new StringBuilder(100);
    final StringBuilder buffer = new StringBuilder(100);
    appendHeader(unbindOperation, "UNBIND", CATEGORY_REQUEST, buffer);
    if (unbindOperation.isSynchronizationOperation())
    {
      buffer.append(" type=synchronization");
    }
    writer.writeRecord(buffer.toString());
  }
@@ -1587,8 +1943,8 @@
  // Appends the common log header information to the provided buffer.
  private void appendHeader(Operation operation, String opType,
      String category, StringBuilder buffer)
  private void appendHeader(final Operation operation, final String opType,
      final String category, final StringBuilder buffer)
  {
    buffer.append('[');
    buffer.append(TimeThread.getLocalTime());
@@ -1606,21 +1962,54 @@
  // Appends additional log items to the provided builder.
  private void logAdditionalLogItems(Operation operation, StringBuilder builder)
  // Build an appropriate set of filters based on the configuration.
  private void buildFilters()
  {
    for (AdditionalLogItem item : operation.getAdditionalLogItems())
    buildFilters(currentConfig.isSuppressInternalOperations(),
        currentConfig.isSuppressSynchronizationOperations(),
        currentConfig.getFilteringPolicy());
  }
  private void buildFilters(final boolean suppressInternal,
      final boolean suppressSynchronization, final FilteringPolicy policy)
  {
    final ArrayList<Filter> subFilters = new ArrayList<Filter>();
    if (currentConfig != null)
    {
      for (final String criteriaName : currentConfig
          .listAccessLogFilteringCriteria())
      {
        try
        {
          final AccessLogFilteringCriteriaCfg cfg = currentConfig
              .getAccessLogFilteringCriteria(criteriaName);
          subFilters.add(new CriteriaFilter(cfg));
        }
        catch (final ConfigException e)
        {
          // TODO: Unable to decode this access log criteria, so log a warning
          // and continue.
        }
      }
    }
    final Filter orFilter = new OrFilter(subFilters.toArray(new Filter[0]));
    filter = new RootFilter(suppressInternal, suppressSynchronization, policy,
        orFilter);
  }
  // Appends additional log items to the provided builder.
  private void logAdditionalLogItems(final Operation operation,
      final StringBuilder builder)
  {
    for (final AdditionalLogItem item : operation.getAdditionalLogItems())
    {
      builder.append(' ');
      item.toString(builder);
    }
  }
  // Sets the sub-filter.
  private void setSubFilter(Filter subFilter)
  {
    this.filter = new RootFilter(subFilter);
  }
}
opends/src/server/org/opends/server/loggers/TextAuditLogPublisher.java
@@ -23,6 +23,7 @@
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS.
 */
package org.opends.server.loggers;
@@ -41,7 +42,7 @@
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.AccessLogPublisherCfg;
import org.opends.server.admin.std.server.FileBasedAccessLogPublisherCfg;
import org.opends.server.admin.std.server.FileBasedAuditLogPublisherCfg;
import org.opends.server.api.AccessLogPublisher;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
@@ -61,8 +62,8 @@
 * the directory server.
 */
public class TextAuditLogPublisher extends
    AccessLogPublisher<FileBasedAccessLogPublisherCfg> implements
    ConfigurationChangeListener<FileBasedAccessLogPublisherCfg>
    AccessLogPublisher<FileBasedAuditLogPublisherCfg> implements
    ConfigurationChangeListener<FileBasedAuditLogPublisherCfg>
{
  private boolean suppressInternalOperations = true;
@@ -71,7 +72,7 @@
  private TextWriter writer;
  private FileBasedAccessLogPublisherCfg currentConfig;
  private FileBasedAuditLogPublisherCfg currentConfig;
@@ -79,7 +80,7 @@
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      FileBasedAccessLogPublisherCfg config)
      FileBasedAuditLogPublisherCfg config)
  {
    // Default result code.
    ResultCode resultCode = ResultCode.SUCCESS;
@@ -186,7 +187,7 @@
  public void close()
  {
    writer.shutdown();
    currentConfig.removeFileBasedAccessChangeListener(this);
    currentConfig.removeFileBasedAuditChangeListener(this);
  }
@@ -214,7 +215,7 @@
   */
  @Override()
  public void initializeAccessLogPublisher(
      FileBasedAccessLogPublisherCfg config)
      FileBasedAuditLogPublisherCfg config)
      throws ConfigException, InitializationException
  {
    File logFile = getFileForPath(config.getLogFile());
@@ -279,7 +280,7 @@
    currentConfig = config;
    config.addFileBasedAccessChangeListener(this);
    config.addFileBasedAuditChangeListener(this);
  }
@@ -291,8 +292,8 @@
  public boolean isConfigurationAcceptable(AccessLogPublisherCfg configuration,
      List<Message> unacceptableReasons)
  {
    FileBasedAccessLogPublisherCfg config =
      (FileBasedAccessLogPublisherCfg) configuration;
    FileBasedAuditLogPublisherCfg config =
      (FileBasedAuditLogPublisherCfg) configuration;
    return isConfigurationChangeAcceptable(config, unacceptableReasons);
  }
@@ -302,7 +303,7 @@
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      FileBasedAccessLogPublisherCfg config, List<Message> unacceptableReasons)
      FileBasedAuditLogPublisherCfg config, List<Message> unacceptableReasons)
  {
    // Make sure the permission is valid.
    try