/*
|
* 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 2006-2009 Sun Microsystems, Inc.
|
* Portions Copyright 2011 ForgeRock AS
|
*/
|
package org.opends.server.loggers;
|
|
|
|
import static org.opends.messages.ConfigMessages.*;
|
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
|
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
|
import static org.opends.server.util.StaticUtils.getFileForPath;
|
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
|
import static org.opends.server.util.StaticUtils.toLowerCase;
|
|
import java.io.File;
|
import java.io.IOException;
|
import java.net.InetAddress;
|
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.api.Group;
|
import org.opends.server.authorization.dseecompat.PatternDN;
|
import org.opends.server.config.ConfigException;
|
import org.opends.server.core.*;
|
import org.opends.server.loggers.debug.DebugTracer;
|
import org.opends.server.types.*;
|
import org.opends.server.util.TimeThread;
|
|
|
|
/**
|
* This class provides the implementation of the access logger used by the
|
* directory server.
|
*/
|
public class TextAccessLogPublisher extends
|
AccessLogPublisher<FileBasedAccessLogPublisherCfg> implements
|
ConfigurationChangeListener<FileBasedAccessLogPublisherCfg>
|
{
|
/**
|
* 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;
|
private final AddressMask[] clientAddressEqualTo;
|
private final AddressMask[] clientAddressNotEqualTo;
|
private final PatternDN[] userDNEqualTo;
|
private final PatternDN[] userDNNotEqualTo;
|
private final PatternDN[] targetDNEqualTo;
|
private final PatternDN[] targetDNNotEqualTo;
|
private final DN[] userIsMemberOf;
|
private final DN[] userIsNotMemberOf;
|
private final String attachmentName;
|
|
|
|
/**
|
* Creates a new criteria based filter.
|
*
|
* @param cfg
|
* The access log filter criteria.
|
* @throws DirectoryException
|
* If the configuration cannot be parsed.
|
*/
|
CriteriaFilter(final AccessLogFilteringCriteriaCfg cfg)
|
throws DirectoryException
|
{
|
this.cfg = cfg;
|
|
// Generate a unique identifier for attaching partial results to
|
// operations.
|
attachmentName = this.getClass().getName() + "#" + hashCode();
|
|
// 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;
|
}
|
}
|
}
|
|
clientAddressEqualTo = cfg.getClientAddressEqualTo().toArray(
|
new AddressMask[0]);
|
clientAddressNotEqualTo = cfg.getClientAddressNotEqualTo().toArray(
|
new AddressMask[0]);
|
|
userDNEqualTo = new PatternDN[cfg.getUserDNEqualTo().size()];
|
int i = 0;
|
for (final String s : cfg.getUserDNEqualTo())
|
{
|
userDNEqualTo[i++] = PatternDN.decode(s);
|
}
|
|
userDNNotEqualTo = new PatternDN[cfg.getUserDNNotEqualTo().size()];
|
i = 0;
|
for (final String s : cfg.getUserDNNotEqualTo())
|
{
|
userDNNotEqualTo[i++] = PatternDN.decode(s);
|
}
|
|
userIsMemberOf = cfg.getUserIsMemberOf().toArray(new DN[0]);
|
userIsNotMemberOf = cfg.getUserIsNotMemberOf().toArray(new DN[0]);
|
|
targetDNEqualTo = new PatternDN[cfg.getRequestTargetDNEqualTo().size()];
|
i = 0;
|
for (final String s : cfg.getRequestTargetDNEqualTo())
|
{
|
targetDNEqualTo[i++] = PatternDN.decode(s);
|
}
|
|
targetDNNotEqualTo = new PatternDN[cfg.getRequestTargetDNNotEqualTo()
|
.size()];
|
i = 0;
|
for (final String s : cfg.getRequestTargetDNNotEqualTo())
|
{
|
targetDNNotEqualTo[i++] = PatternDN.decode(s);
|
}
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isConnectLoggable(final ClientConnection connection)
|
{
|
if (!logConnectRecords)
|
{
|
return false;
|
}
|
|
if (!filterClientConnection(connection))
|
{
|
return false;
|
}
|
|
return true;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isDisconnectLoggable(final ClientConnection connection)
|
{
|
if (!logDisconnectRecords)
|
{
|
return false;
|
}
|
|
if (!filterClientConnection(connection))
|
{
|
return false;
|
}
|
|
if (!filterUser(connection))
|
{
|
return false;
|
}
|
|
return true;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isRequestLoggable(final Operation operation)
|
{
|
final ClientConnection connection = operation.getClientConnection();
|
final boolean matches = logOperationRecords.contains(operation
|
.getOperationType())
|
&& filterClientConnection(connection)
|
&& filterUser(connection) && filterRequest(operation);
|
|
// Cache the result so that it does not need to be recomputed for the
|
// response.
|
operation.setAttachment(attachmentName, matches);
|
|
return matches;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isResponseLoggable(final Operation operation)
|
{
|
// First check the result that was computed for the initial request.
|
Boolean requestMatched = (Boolean) operation
|
.getAttachment(attachmentName);
|
if (requestMatched == null)
|
{
|
// This should not happen.
|
if (debugEnabled())
|
{
|
TRACER.debugWarning(
|
"Operation attachment %s not found while logging response",
|
attachmentName);
|
}
|
requestMatched = isRequestLoggable(operation);
|
}
|
|
if (!requestMatched)
|
{
|
return false;
|
}
|
|
// Check the response parameters.
|
if (!filterResponse(operation))
|
{
|
return false;
|
}
|
|
return true;
|
}
|
|
|
|
private boolean filterClientConnection(final ClientConnection connection)
|
{
|
// Check client address.
|
final InetAddress ipAddr = connection.getRemoteAddress();
|
if (clientAddressNotEqualTo.length > 0)
|
{
|
if (AddressMask.maskListContains(ipAddr, clientAddressNotEqualTo))
|
{
|
return false;
|
}
|
}
|
if (clientAddressEqualTo.length > 0)
|
{
|
if (!AddressMask.maskListContains(ipAddr, clientAddressEqualTo))
|
{
|
return false;
|
}
|
}
|
|
// Check server port.
|
if (!cfg.getClientPortEqualTo().isEmpty())
|
{
|
if (!cfg.getClientPortEqualTo().contains(connection.getServerPort()))
|
{
|
return false;
|
}
|
}
|
|
// Check protocol.
|
if (!cfg.getClientProtocolEqualTo().isEmpty())
|
{
|
if (!cfg.getClientProtocolEqualTo().contains(
|
toLowerCase(connection.getProtocol())))
|
{
|
return false;
|
}
|
}
|
|
return true;
|
}
|
|
|
|
private boolean filterRequest(final Operation operation)
|
{
|
// Check target DN.
|
if (targetDNNotEqualTo.length > 0 || targetDNEqualTo.length > 0)
|
{
|
if (!filterRequestTargetDN(operation))
|
{
|
return false;
|
}
|
}
|
|
// TODO: check required controls.
|
|
return true;
|
}
|
|
|
|
private boolean filterRequestTargetDN(final Operation operation)
|
{
|
// Obtain both the parsed and unparsed target DNs. Requests are logged
|
// before parsing so usually only the raw unparsed target DN will be
|
// present, and it may even be invalid.
|
DN targetDN = null;
|
ByteString rawTargetDN = null;
|
|
switch (operation.getOperationType())
|
{
|
case ABANDON:
|
case UNBIND:
|
// These operations don't have parameters which we can filter so
|
// always match them.
|
return true;
|
case EXTENDED:
|
// These operations could have parameters which can be filtered but
|
// we'd need to decode the request in order to find out. This is
|
// beyond the scope of the access log. Therefore, treat extended
|
// operations like abandon/unbind.
|
return true;
|
case ADD:
|
targetDN = ((AddOperation) operation).getEntryDN();
|
rawTargetDN = ((AddOperation) operation).getRawEntryDN();
|
break;
|
case BIND:
|
// For SASL bind operations the bind DN, if provided, will require the
|
// SASL credentials to be decoded which is beyond the scope of the
|
// access log.
|
targetDN = ((BindOperation) operation).getBindDN();
|
rawTargetDN = ((BindOperation) operation).getRawBindDN();
|
break;
|
case COMPARE:
|
targetDN = ((CompareOperation) operation).getEntryDN();
|
rawTargetDN = ((CompareOperation) operation).getRawEntryDN();
|
break;
|
case DELETE:
|
targetDN = ((DeleteOperation) operation).getEntryDN();
|
rawTargetDN = ((DeleteOperation) operation).getRawEntryDN();
|
break;
|
case MODIFY:
|
targetDN = ((ModifyOperation) operation).getEntryDN();
|
rawTargetDN = ((ModifyOperation) operation).getRawEntryDN();
|
break;
|
case MODIFY_DN:
|
targetDN = ((ModifyDNOperation) operation).getEntryDN();
|
rawTargetDN = ((ModifyDNOperation) operation).getRawEntryDN();
|
break;
|
case SEARCH:
|
targetDN = ((SearchOperation) operation).getBaseDN();
|
rawTargetDN = ((SearchOperation) operation).getRawBaseDN();
|
break;
|
}
|
|
// Attempt to parse the raw target DN if needed.
|
if (targetDN == null)
|
{
|
try
|
{
|
targetDN = DN.decode(rawTargetDN);
|
}
|
catch (final DirectoryException e)
|
{
|
// The DN raw target DN was invalid. It will never match any
|
// not-equal-to nor equal-to patterns, so return appropriate result.
|
if (targetDNEqualTo.length != 0)
|
{
|
// Invalid DN will never match equal-to patterns.
|
return false;
|
}
|
else
|
{
|
// Invalid DN does not match any not-equal-to patterns.
|
return true;
|
}
|
}
|
}
|
|
if (targetDNNotEqualTo.length > 0)
|
{
|
for (final PatternDN pattern : targetDNNotEqualTo)
|
{
|
if (pattern.matchesDN(targetDN))
|
{
|
return false;
|
}
|
}
|
}
|
|
if (targetDNEqualTo.length > 0)
|
{
|
for (final PatternDN pattern : targetDNNotEqualTo)
|
{
|
if (pattern.matchesDN(targetDN))
|
{
|
return true;
|
}
|
}
|
}
|
|
// The target DN did not match.
|
return false;
|
}
|
|
|
|
private boolean filterResponse(final Operation operation)
|
{
|
// Check response code.
|
final Integer resultCode = operation.getResultCode().getIntValue();
|
|
if (!cfg.getResponseResultCodeNotEqualTo().isEmpty())
|
{
|
if (cfg.getResponseResultCodeNotEqualTo().contains(resultCode))
|
{
|
return false;
|
}
|
}
|
|
if (!cfg.getResponseResultCodeEqualTo().isEmpty())
|
{
|
if (!cfg.getResponseResultCodeNotEqualTo().contains(resultCode))
|
{
|
return false;
|
}
|
}
|
|
// Check etime.
|
final long etime = operation.getProcessingTime();
|
|
final Integer etimeGT = cfg.getResponseEtimeLessThan();
|
if (etimeGT != null)
|
{
|
if (etime <= ((long) etimeGT))
|
{
|
return false;
|
}
|
}
|
|
final Integer etimeLT = cfg.getResponseEtimeLessThan();
|
if (etimeLT != null)
|
{
|
if (etime >= ((long) etimeLT))
|
{
|
return false;
|
}
|
}
|
|
// Check search response fields.
|
if (operation instanceof SearchOperation)
|
{
|
final SearchOperation searchOperation = (SearchOperation) operation;
|
final Boolean isIndexed= cfg.isSearchResponseIsIndexed();
|
if (isIndexed != null)
|
{
|
boolean wasUnindexed = false;
|
for (final AdditionalLogItem item : operation.getAdditionalLogItems())
|
{
|
if (item.getKey().equals("unindexed"))
|
{
|
wasUnindexed = true;
|
break;
|
}
|
}
|
|
if (isIndexed)
|
{
|
if (wasUnindexed)
|
{
|
return false;
|
}
|
}
|
else
|
{
|
if (!wasUnindexed)
|
{
|
return false;
|
}
|
}
|
}
|
|
final int nentries = searchOperation.getEntriesSent();
|
|
final Integer nentriesGT = cfg.getSearchResponseNentriesGreaterThan();
|
if (nentriesGT != null)
|
{
|
if (nentries <= nentriesGT)
|
{
|
return false;
|
}
|
}
|
|
final Integer nentriesLT = cfg.getSearchResponseNentriesLessThan();
|
if (nentriesLT != null)
|
{
|
if (nentries >= nentriesLT)
|
{
|
return false;
|
}
|
}
|
}
|
|
return true;
|
}
|
|
|
|
private boolean filterUser(final ClientConnection connection)
|
{
|
// Check user DN.
|
if (userDNNotEqualTo.length > 0 || userDNEqualTo.length > 0)
|
{
|
if (!filterUserBindDN(connection))
|
{
|
return false;
|
}
|
}
|
|
// Check group membership.
|
if (userIsNotMemberOf.length > 0 || userIsNotMemberOf.length > 0)
|
{
|
if (!filterUserIsMemberOf(connection))
|
{
|
return false;
|
}
|
}
|
|
return true;
|
}
|
|
|
|
private boolean filterUserBindDN(final ClientConnection connection)
|
{
|
final DN userDN = connection.getAuthenticationInfo()
|
.getAuthenticationDN();
|
|
// Fast-path for unauthenticated clients.
|
if (userDN == null)
|
{
|
return userDNEqualTo.length == 0;
|
}
|
|
if (userDNNotEqualTo.length > 0)
|
{
|
for (final PatternDN pattern : userDNNotEqualTo)
|
{
|
if (pattern.matchesDN(userDN))
|
{
|
return false;
|
}
|
}
|
}
|
|
if (userDNEqualTo.length > 0)
|
{
|
for (final PatternDN pattern : userDNNotEqualTo)
|
{
|
if (pattern.matchesDN(userDN))
|
{
|
return true;
|
}
|
}
|
}
|
|
// The user DN did not match.
|
return false;
|
}
|
|
|
|
private boolean filterUserIsMemberOf(final ClientConnection connection)
|
{
|
final Entry userEntry = connection.getAuthenticationInfo()
|
.getAuthenticationEntry();
|
|
// Fast-path for unauthenticated clients.
|
if (userEntry == null)
|
{
|
return userIsMemberOf.length == 0;
|
}
|
|
final GroupManager groupManager = DirectoryServer.getGroupManager();
|
if (userIsNotMemberOf.length > 0)
|
{
|
for (final DN groupDN : userIsNotMemberOf)
|
{
|
final Group<?> group = groupManager.getGroupInstance(groupDN);
|
try
|
{
|
if ((group != null) && group.isMember(userEntry))
|
{
|
return false;
|
}
|
}
|
catch (final DirectoryException e)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
}
|
}
|
}
|
|
if (userIsMemberOf.length > 0)
|
{
|
for (final DN groupDN : userIsMemberOf)
|
{
|
final Group<?> group = groupManager.getGroupInstance(groupDN);
|
try
|
{
|
if ((group != null) && group.isMember(userEntry))
|
{
|
return true;
|
}
|
}
|
catch (final DirectoryException e)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
}
|
}
|
}
|
|
// The user entry did not match.
|
return false;
|
}
|
|
}
|
|
|
|
// TODO: update assigned OIDs WIKI page when complete.
|
|
/**
|
* 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
|
* The request.
|
* @return {@code true} if the provided request should be logged.
|
*/
|
boolean isRequestLoggable(Operation operation);
|
|
|
|
/**
|
* Returns {@code true} if the provided response should be logged.
|
*
|
* @param operation
|
* The response.
|
* @return {@code true} if the provided response should be logged.
|
*/
|
boolean isResponseLoggable(Operation operation);
|
}
|
|
|
|
/**
|
* A filter which performs a logical OR over a set of sub-filters.
|
*/
|
static final class OrFilter implements Filter
|
{
|
private final Filter[] subFilters;
|
|
|
|
/**
|
* Creates a new OR filter.
|
*
|
* @param subFilters
|
* The sub-filters.
|
*/
|
OrFilter(final Filter[] subFilters)
|
{
|
this.subFilters = subFilters;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isConnectLoggable(final ClientConnection connection)
|
{
|
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))
|
{
|
// Succeed fast.
|
return true;
|
}
|
}
|
return false;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isResponseLoggable(final Operation operation)
|
{
|
for (final Filter filter : subFilters)
|
{
|
if (filter.isResponseLoggable(operation))
|
{
|
// Succeed fast.
|
return true;
|
}
|
}
|
return false;
|
}
|
|
}
|
|
|
|
/**
|
* The root filter which first checks the logger configuration, delegating to
|
* a sub-filter if needed.
|
*/
|
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-filters.
|
*/
|
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;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isConnectLoggable(final ClientConnection connection)
|
{
|
final long connectionID = connection.getConnectionID();
|
if (connectionID >= 0 || !suppressInternalOperations)
|
{
|
switch (policy)
|
{
|
case INCLUSIVE:
|
return subFilter.isConnectLoggable(connection);
|
case EXCLUSIVE:
|
return !subFilter.isConnectLoggable(connection);
|
default: // NO_FILTERING:
|
return true;
|
}
|
}
|
else
|
{
|
return false;
|
}
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
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))
|
{
|
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
|
{
|
return false;
|
}
|
}
|
|
|
|
// Determines whether the provided operation should be logged.
|
private boolean isLoggable(final Operation operation)
|
{
|
final long connectionID = operation.getConnectionID();
|
if (connectionID < 0)
|
{
|
// This is an internal operation.
|
if (operation.isSynchronizationOperation())
|
{
|
return !suppressSynchronizationOperations;
|
}
|
else
|
{
|
return !suppressInternalOperations;
|
}
|
}
|
|
return true;
|
}
|
}
|
|
|
|
/**
|
* 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 tracer object for the debug logger.
|
*/
|
private static final DebugTracer TRACER = getTracer();
|
|
/**
|
* The category to use when logging responses.
|
*/
|
private static final String CATEGORY_RESPONSE = "RES";
|
|
/**
|
* The category to use when logging requests.
|
*/
|
private static final String CATEGORY_REQUEST = "REQ";
|
|
|
|
/**
|
* Returns an instance of the text 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.
|
* @param suppressInternal
|
* Indicates whether to suppress internal operations.
|
* @return The instance of the text error log publisher that will print all
|
* messages to standard out.
|
*/
|
public static TextAccessLogPublisher getStartupTextAccessPublisher(
|
final TextWriter writer, final boolean suppressInternal)
|
{
|
final TextAccessLogPublisher startupPublisher =
|
new TextAccessLogPublisher();
|
startupPublisher.writer = writer;
|
startupPublisher.buildFilters(suppressInternal, false,
|
FilteringPolicy.NO_FILTERING);
|
return startupPublisher;
|
}
|
|
|
|
private FileBasedAccessLogPublisherCfg currentConfig = null;
|
private TextWriter writer = null;
|
private Filter filter = null;
|
private FilterChangeListener filterChangeListener = null;
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public ConfigChangeResult applyConfigurationChange(
|
final FileBasedAccessLogPublisherCfg 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 ((currentConfig.isAsynchronous() && config.isAsynchronous())
|
&& (currentConfig.getQueueSize() != config.getQueueSize()))
|
{
|
adminActionRequired = true;
|
}
|
|
currentConfig = config;
|
|
// Rebuild the filter using the new configuration and criteria.
|
buildFilters();
|
}
|
}
|
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 close()
|
{
|
writer.shutdown();
|
|
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);
|
}
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public DN getDN()
|
{
|
if (currentConfig != null)
|
{
|
return currentConfig.dn();
|
}
|
else
|
{
|
return null;
|
}
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void initializeAccessLogPublisher(
|
final FileBasedAccessLogPublisherCfg 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 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 (final DN dn : cfg.getRotationPolicyDNs())
|
{
|
writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
|
}
|
|
for (final DN dn : cfg.getRetentionPolicyDNs())
|
{
|
writer.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(), writer);
|
}
|
else
|
{
|
this.writer = new ParallelTextWriter("Parallel Text Writer for "
|
+ cfg.dn().toNormalizedString(), cfg.isAutoFlush(), writer);
|
}
|
}
|
else
|
{
|
this.writer = writer;
|
}
|
}
|
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);
|
|
}
|
|
currentConfig = cfg;
|
|
// Rebuild the filter using the new configuration and criteria.
|
buildFilters();
|
|
// 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);
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public boolean isConfigurationAcceptable(
|
final AccessLogPublisherCfg configuration,
|
final List<Message> unacceptableReasons)
|
{
|
final FileBasedAccessLogPublisherCfg config =
|
(FileBasedAccessLogPublisherCfg) configuration;
|
return isConfigurationChangeAcceptable(config, unacceptableReasons);
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isConfigurationChangeAcceptable(
|
final FileBasedAccessLogPublisherCfg config,
|
final List<Message> unacceptableReasons)
|
{
|
// 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;
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the abandon
|
* request associated with the provided abandon operation.
|
*
|
* @param abandonOperation
|
* The abandon operation containing the information to use to log the
|
* abandon request.
|
*/
|
@Override
|
public void logAbandonRequest(final AbandonOperation abandonOperation)
|
{
|
if (!filter.isRequestLoggable(abandonOperation))
|
{
|
return;
|
}
|
|
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());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the result of
|
* the provided abandon operation.
|
*
|
* @param abandonOperation
|
* The abandon operation containing the information to use to log the
|
* abandon request.
|
*/
|
@Override
|
public void logAbandonResult(final AbandonOperation abandonOperation)
|
{
|
if (!filter.isResponseLoggable(abandonOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(abandonOperation, "ABANDON", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(abandonOperation.getResultCode().getIntValue());
|
final MessageBuilder msg = abandonOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(abandonOperation, buffer);
|
|
buffer.append(" etime=");
|
buffer.append(abandonOperation.getProcessingTime());
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the add
|
* request associated with the provided add operation.
|
*
|
* @param addOperation
|
* The add operation containing the information to use to log the add
|
* request.
|
*/
|
@Override
|
public void logAddRequest(final AddOperation addOperation)
|
{
|
if (!filter.isRequestLoggable(addOperation))
|
{
|
return;
|
}
|
|
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());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the add
|
* response associated with the provided add operation.
|
*
|
* @param addOperation
|
* The add operation containing the information to use to log the add
|
* response.
|
*/
|
@Override
|
public void logAddResponse(final AddOperation addOperation)
|
{
|
if (!filter.isResponseLoggable(addOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(addOperation, "ADD", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(addOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = addOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(addOperation, buffer);
|
|
final DN proxiedAuthDN = addOperation.getProxiedAuthorizationDN();
|
if (proxiedAuthDN != null)
|
{
|
buffer.append(" authzDN=\"");
|
proxiedAuthDN.toString(buffer);
|
buffer.append('\"');
|
}
|
|
buffer.append(" etime=");
|
long etime = addOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = addOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the bind
|
* request associated with the provided bind operation.
|
*
|
* @param bindOperation
|
* The bind operation with the information to use to log the bind
|
* request.
|
*/
|
@Override
|
public void logBindRequest(final BindOperation bindOperation)
|
{
|
if (!filter.isRequestLoggable(bindOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(bindOperation, "BIND", CATEGORY_REQUEST, buffer);
|
|
final String protocolVersion = bindOperation.getProtocolVersion();
|
if (protocolVersion != null)
|
{
|
buffer.append(" version=");
|
buffer.append(protocolVersion);
|
}
|
|
switch (bindOperation.getAuthenticationType())
|
{
|
case SIMPLE:
|
buffer.append(" type=SIMPLE");
|
break;
|
case SASL:
|
buffer.append(" type=SASL mechanism=");
|
buffer.append(bindOperation.getSASLMechanism());
|
break;
|
default:
|
buffer.append(" type=");
|
buffer.append(bindOperation.getAuthenticationType());
|
break;
|
}
|
|
buffer.append(" dn=\"");
|
buffer.append(bindOperation.getRawBindDN().toString());
|
buffer.append("\"");
|
if (bindOperation.isSynchronizationOperation())
|
{
|
buffer.append(" type=synchronization");
|
}
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the bind
|
* response associated with the provided bind operation.
|
*
|
* @param bindOperation
|
* The bind operation containing the information to use to log the
|
* bind response.
|
*/
|
@Override
|
public void logBindResponse(final BindOperation bindOperation)
|
{
|
if (!filter.isResponseLoggable(bindOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(bindOperation, "BIND", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(bindOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = bindOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
final Message failureMessage = bindOperation.getAuthFailureReason();
|
if (failureMessage != null)
|
{
|
buffer.append(" authFailureID=");
|
buffer.append(failureMessage.getDescriptor().getId());
|
buffer.append(" authFailureReason=\"");
|
buffer.append(failureMessage);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(bindOperation, buffer);
|
|
if (bindOperation.getResultCode() == ResultCode.SUCCESS)
|
{
|
final AuthenticationInfo authInfo = bindOperation.getAuthenticationInfo();
|
if (authInfo != null)
|
{
|
final DN authDN = authInfo.getAuthenticationDN();
|
if (authDN != null)
|
{
|
buffer.append(" authDN=\"");
|
authDN.toString(buffer);
|
buffer.append('\"');
|
|
final DN authzDN = authInfo.getAuthorizationDN();
|
if (!authDN.equals(authzDN))
|
{
|
buffer.append(" authzDN=\"");
|
if (authzDN != null)
|
{
|
authzDN.toString(buffer);
|
}
|
buffer.append('\"');
|
}
|
}
|
else
|
{
|
buffer.append(" authDN=\"\"");
|
}
|
}
|
}
|
|
buffer.append(" etime=");
|
long etime = bindOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = bindOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the compare
|
* request associated with the provided compare operation.
|
*
|
* @param compareOperation
|
* The compare operation containing the information to use to log the
|
* compare request.
|
*/
|
@Override
|
public void logCompareRequest(final CompareOperation compareOperation)
|
{
|
if (!filter.isRequestLoggable(compareOperation))
|
{
|
return;
|
}
|
|
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());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the compare
|
* response associated with the provided compare operation.
|
*
|
* @param compareOperation
|
* The compare operation containing the information to use to log the
|
* compare response.
|
*/
|
@Override
|
public void logCompareResponse(final CompareOperation compareOperation)
|
{
|
if (!filter.isResponseLoggable(compareOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(compareOperation, "COMPARE", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(compareOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = compareOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(compareOperation, buffer);
|
|
final DN proxiedAuthDN = compareOperation.getProxiedAuthorizationDN();
|
if (proxiedAuthDN != null)
|
{
|
buffer.append(" authzDN=\"");
|
proxiedAuthDN.toString(buffer);
|
buffer.append('\"');
|
}
|
|
buffer.append(" etime=");
|
long etime = compareOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = compareOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about a new client
|
* connection that has been established, regardless of whether it will be
|
* immediately terminated.
|
*
|
* @param clientConnection
|
* The client connection that has been established.
|
*/
|
@Override
|
public void logConnect(final ClientConnection clientConnection)
|
{
|
if (!filter.isConnectLoggable(clientConnection))
|
{
|
return;
|
}
|
|
final long connectionID = clientConnection.getConnectionID();
|
final StringBuilder buffer = new StringBuilder(100);
|
buffer.append("[");
|
buffer.append(TimeThread.getLocalTime());
|
buffer.append("]");
|
buffer.append(" CONNECT conn=");
|
buffer.append(connectionID);
|
buffer.append(" from=");
|
buffer.append(clientConnection.getClientHostPort());
|
buffer.append(" to=");
|
buffer.append(clientConnection.getServerHostPort());
|
buffer.append(" protocol=");
|
buffer.append(clientConnection.getProtocol());
|
|
writer.writeRecord(buffer.toString());
|
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the delete
|
* request associated with the provided delete operation.
|
*
|
* @param deleteOperation
|
* The delete operation with the information to use to log the delete
|
* request.
|
*/
|
@Override
|
public void logDeleteRequest(final DeleteOperation deleteOperation)
|
{
|
if (!filter.isRequestLoggable(deleteOperation))
|
{
|
return;
|
}
|
|
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());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the delete
|
* response associated with the provided delete operation.
|
*
|
* @param deleteOperation
|
* The delete operation containing the information to use to log the
|
* delete response.
|
*/
|
@Override
|
public void logDeleteResponse(final DeleteOperation deleteOperation)
|
{
|
if (!filter.isResponseLoggable(deleteOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(deleteOperation, "DELETE", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(deleteOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = deleteOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(deleteOperation, buffer);
|
|
final DN proxiedAuthDN = deleteOperation.getProxiedAuthorizationDN();
|
if (proxiedAuthDN != null)
|
{
|
buffer.append(" authzDN=\"");
|
proxiedAuthDN.toString(buffer);
|
buffer.append('\"');
|
}
|
|
buffer.append(" etime=");
|
long etime = deleteOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = deleteOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the
|
* termination of an existing client connection.
|
*
|
* @param clientConnection
|
* The client connection that has been terminated.
|
* @param disconnectReason
|
* A generic disconnect reason for the connection termination.
|
* @param message
|
* A human-readable message that can provide additional information
|
* about the disconnect.
|
*/
|
@Override
|
public void logDisconnect(final ClientConnection clientConnection,
|
final DisconnectReason disconnectReason, final Message message)
|
{
|
if (!filter.isDisconnectLoggable(clientConnection))
|
{
|
return;
|
}
|
|
final long connectionID = clientConnection.getConnectionID();
|
final StringBuilder buffer = new StringBuilder(100);
|
buffer.append("[");
|
buffer.append(TimeThread.getLocalTime());
|
buffer.append("]");
|
buffer.append(" DISCONNECT conn=");
|
buffer.append(connectionID);
|
buffer.append(" reason=\"");
|
buffer.append(disconnectReason);
|
|
if (message != null)
|
{
|
buffer.append("\" msg=\"");
|
buffer.append(message);
|
}
|
|
buffer.append("\"");
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the extended
|
* request associated with the provided extended operation.
|
*
|
* @param extendedOperation
|
* The extended operation containing the information to use to log
|
* the extended request.
|
*/
|
@Override
|
public void logExtendedRequest(final ExtendedOperation extendedOperation)
|
{
|
if (!filter.isRequestLoggable(extendedOperation))
|
{
|
return;
|
}
|
|
String name = null;
|
final String oid = extendedOperation.getRequestOID();
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(extendedOperation, "EXTENDED", CATEGORY_REQUEST, buffer);
|
final ExtendedOperationHandler<?> extOpHandler = DirectoryServer
|
.getExtendedOperationHandler(oid);
|
if (extOpHandler != null)
|
{
|
name = extOpHandler.getExtendedOperationName();
|
if (name != null)
|
{
|
buffer.append(" name=\"");
|
buffer.append(name);
|
buffer.append("\"");
|
}
|
}
|
buffer.append(" oid=\"");
|
buffer.append(oid);
|
buffer.append("\"");
|
if (extendedOperation.isSynchronizationOperation())
|
{
|
buffer.append(" type=synchronization");
|
}
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the extended
|
* response associated with the provided extended operation.
|
*
|
* @param extendedOperation
|
* The extended operation containing the info to use to log the
|
* extended response.
|
*/
|
@Override
|
public void logExtendedResponse(final ExtendedOperation extendedOperation)
|
{
|
if (!filter.isResponseLoggable(extendedOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(extendedOperation, "EXTENDED", CATEGORY_RESPONSE, buffer);
|
|
String name = null;
|
final String oid = extendedOperation.getResponseOID();
|
if (oid != null)
|
{
|
final ExtendedOperationHandler<?> extOpHandler = DirectoryServer
|
.getExtendedOperationHandler(oid);
|
if (extOpHandler != null)
|
{
|
name = extOpHandler.getExtendedOperationName();
|
if (name != null)
|
{
|
buffer.append(" name=\"");
|
buffer.append(name);
|
buffer.append("\"");
|
}
|
}
|
buffer.append(" oid=\"");
|
buffer.append(oid);
|
buffer.append('\"');
|
}
|
|
buffer.append(" result=");
|
buffer.append(extendedOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = extendedOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(extendedOperation, buffer);
|
|
buffer.append(" etime=");
|
long etime = extendedOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = extendedOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the modify DN
|
* request associated with the provided modify DN operation.
|
*
|
* @param modifyDNOperation
|
* The modify DN operation containing the info to use to log the
|
* modify DN request.
|
*/
|
@Override
|
public void logModifyDNRequest(final ModifyDNOperation modifyDNOperation)
|
{
|
if (!filter.isRequestLoggable(modifyDNOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(modifyDNOperation, "MODIFYDN", CATEGORY_REQUEST, buffer);
|
buffer.append(" dn=\"");
|
buffer.append(modifyDNOperation.getRawEntryDN().toString());
|
buffer.append("\" newRDN=\"");
|
buffer.append(modifyDNOperation.getRawNewRDN().toString());
|
buffer.append("\" deleteOldRDN=");
|
buffer.append(modifyDNOperation.deleteOldRDN());
|
|
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());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the modify DN
|
* response associated with the provided modify DN operation.
|
*
|
* @param modifyDNOperation
|
* The modify DN operation containing the information to use to log
|
* the modify DN response.
|
*/
|
@Override
|
public void logModifyDNResponse(final ModifyDNOperation modifyDNOperation)
|
{
|
if (!filter.isResponseLoggable(modifyDNOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(modifyDNOperation, "MODIFYDN", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(modifyDNOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = modifyDNOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(modifyDNOperation, buffer);
|
|
final DN proxiedAuthDN = modifyDNOperation.getProxiedAuthorizationDN();
|
if (proxiedAuthDN != null)
|
{
|
buffer.append(" authzDN=\"");
|
proxiedAuthDN.toString(buffer);
|
buffer.append('\"');
|
}
|
|
buffer.append(" etime=");
|
long etime = modifyDNOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = modifyDNOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the modify
|
* request associated with the provided modify operation.
|
*
|
* @param modifyOperation
|
* The modify operation containing the information to use to log the
|
* modify request.
|
*/
|
@Override
|
public void logModifyRequest(final ModifyOperation modifyOperation)
|
{
|
if (!filter.isRequestLoggable(modifyOperation))
|
{
|
return;
|
}
|
|
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());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the modify
|
* response associated with the provided modify operation.
|
*
|
* @param modifyOperation
|
* The modify operation containing the information to use to log the
|
* modify response.
|
*/
|
@Override
|
public void logModifyResponse(final ModifyOperation modifyOperation)
|
{
|
if (!filter.isResponseLoggable(modifyOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(modifyOperation, "MODIFY", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(modifyOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = modifyOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
logAdditionalLogItems(modifyOperation, buffer);
|
|
final DN proxiedAuthDN = modifyOperation.getProxiedAuthorizationDN();
|
if (proxiedAuthDN != null)
|
{
|
buffer.append(" authzDN=\"");
|
proxiedAuthDN.toString(buffer);
|
buffer.append('\"');
|
}
|
|
buffer.append(" etime=");
|
long etime = modifyOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = modifyOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the search
|
* request associated with the provided search operation.
|
*
|
* @param searchOperation
|
* The search operation containing the info to use to log the search
|
* request.
|
*/
|
@Override
|
public void logSearchRequest(final SearchOperation searchOperation)
|
{
|
if (!filter.isRequestLoggable(searchOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(192);
|
appendHeader(searchOperation, "SEARCH", CATEGORY_REQUEST, buffer);
|
buffer.append(" base=\"");
|
buffer.append(searchOperation.getRawBaseDN().toString());
|
buffer.append("\" scope=");
|
buffer.append(searchOperation.getScope());
|
buffer.append(" filter=\"");
|
searchOperation.getRawFilter().toString(buffer);
|
|
final LinkedHashSet<String> attrs = searchOperation.getAttributes();
|
if ((attrs == null) || attrs.isEmpty())
|
{
|
buffer.append("\" attrs=\"ALL\"");
|
}
|
else
|
{
|
buffer.append("\" attrs=\"");
|
|
final Iterator<String> iterator = attrs.iterator();
|
buffer.append(iterator.next());
|
while (iterator.hasNext())
|
{
|
buffer.append(",");
|
buffer.append(iterator.next());
|
}
|
|
buffer.append("\"");
|
}
|
if (searchOperation.isSynchronizationOperation())
|
{
|
buffer.append(" type=synchronization");
|
}
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the completion
|
* of the provided search operation.
|
*
|
* @param searchOperation
|
* The search operation containing the information to use to log the
|
* search result done message.
|
*/
|
@Override
|
public void logSearchResultDone(final SearchOperation searchOperation)
|
{
|
if (!filter.isResponseLoggable(searchOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(128);
|
appendHeader(searchOperation, "SEARCH", CATEGORY_RESPONSE, buffer);
|
buffer.append(" result=");
|
buffer.append(searchOperation.getResultCode().getIntValue());
|
|
final MessageBuilder msg = searchOperation.getErrorMessage();
|
if ((msg != null) && (msg.length() > 0))
|
{
|
buffer.append(" message=\"");
|
buffer.append(msg);
|
buffer.append('\"');
|
}
|
|
buffer.append(" nentries=");
|
buffer.append(searchOperation.getEntriesSent());
|
|
logAdditionalLogItems(searchOperation, buffer);
|
|
final DN proxiedAuthDN = searchOperation.getProxiedAuthorizationDN();
|
if (proxiedAuthDN != null)
|
{
|
buffer.append(" authzDN=\"");
|
proxiedAuthDN.toString(buffer);
|
buffer.append('\"');
|
}
|
|
buffer.append(" etime=");
|
long etime = searchOperation.getProcessingNanoTime();
|
if (etime <= -1)
|
{
|
etime = searchOperation.getProcessingTime();
|
}
|
buffer.append(etime);
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
/**
|
* Writes a message to the access logger with information about the unbind
|
* request associated with the provided unbind operation.
|
*
|
* @param unbindOperation
|
* The unbind operation containing the info to use to log the unbind
|
* request.
|
*/
|
@Override
|
public void logUnbind(final UnbindOperation unbindOperation)
|
{
|
// FIXME: ensure that these are logged in combined mode.
|
if (!filter.isRequestLoggable(unbindOperation))
|
{
|
return;
|
}
|
|
final StringBuilder buffer = new StringBuilder(100);
|
appendHeader(unbindOperation, "UNBIND", CATEGORY_REQUEST, buffer);
|
if (unbindOperation.isSynchronizationOperation())
|
{
|
buffer.append(" type=synchronization");
|
}
|
|
writer.writeRecord(buffer.toString());
|
}
|
|
|
|
// Appends the common log header information to the provided buffer.
|
private void appendHeader(final Operation operation, final String opType,
|
final String category, final StringBuilder buffer)
|
{
|
buffer.append('[');
|
buffer.append(TimeThread.getLocalTime());
|
buffer.append("] ");
|
buffer.append(opType);
|
buffer.append(' ');
|
buffer.append(category);
|
buffer.append(" conn=");
|
buffer.append(operation.getConnectionID());
|
buffer.append(" op=");
|
buffer.append(operation.getOperationID());
|
buffer.append(" msgID=");
|
buffer.append(operation.getMessageID());
|
}
|
|
|
|
// Build an appropriate set of filters based on the configuration.
|
private void buildFilters()
|
{
|
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.
|
}
|
catch (final DirectoryException 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);
|
}
|
}
|
|
}
|