/*
* 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
* docs/licenses/cddl.txt
* or http://www.opensource.org/licenses/cddl1.php.
* 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
* docs/licenses/cddl.txt. 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 2010-2016 UnboundID Corp.
*/
package com.unboundid.directory.sdk.examples;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import com.unboundid.directory.sdk.common.api.DiskSpaceConsumer;
import com.unboundid.directory.sdk.common.api.ErrorLogger;
import com.unboundid.directory.sdk.common.api.MonitorProvider;
import com.unboundid.directory.sdk.common.config.ErrorLoggerConfig;
import com.unboundid.directory.sdk.common.types.LogCategory;
import com.unboundid.directory.sdk.common.types.LogSeverity;
import com.unboundid.directory.sdk.common.types.RegisteredDiskSpaceConsumer;
import com.unboundid.directory.sdk.common.types.RegisteredMonitorProvider;
import com.unboundid.directory.sdk.common.types.ServerContext;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.FileArgument;
/**
* This class provides a simple example of an error logger that will append a
* message to a specified file about any errors, warnings, or events in the
* server. It takes a single configuration argument:
* <UL>
* <LI>log-file -- The path to the log file that will be written. This must
* be provided.</LI>
* </UL>
* This example also demonstrates how to register an ad-hoc monitor provider
* with the server, without having to add any server-side configuration.
*/
public final class ExampleErrorLogger
extends ErrorLogger
implements DiskSpaceConsumer
{
/**
* The name of the argument that will be used for the argument used to specify
* the path to the log file.
*/
private static final String ARG_NAME_LOG_FILE = "log-file";
// The general configuration for this error logger.
private volatile ErrorLoggerConfig config;
// The path to the log file to be written.
private volatile File logFile;
// The lock that will be used to synchronize logging activity.
private final Object loggerLock;
// The print writer that will be used to actually write the log messages.
private volatile PrintWriter writer;
// A counter used to keep track of how many messages have been logged.
private AtomicLong messageCount;
// The registered disk space consumer for this error logger.
private volatile RegisteredDiskSpaceConsumer registeredConsumer;
//The registered monitor provider for this error logger.
private RegisteredMonitorProvider monitorProvider;
// The server context for the server in which this extension is running.
private ServerContext serverContext;
/**
* Creates a new instance of this error logger. All error logger
* implementations must include a default constructor, but any initialization
* should generally be done in the {@code initializeErrorLogger} method.
*/
public ExampleErrorLogger()
{
loggerLock = new Object();
}
/**
* Retrieves a human-readable name for this extension.
*
* @return A human-readable name for this extension.
*/
@Override()
public String getExtensionName()
{
return "Example Error Logger";
}
/**
* Retrieves a human-readable description for this extension. Each element
* of the array that is returned will be considered a separate paragraph in
* generated documentation.
*
* @return A human-readable description for this extension, or {@code null}
* or an empty array if no description should be available.
*/
@Override()
public String[] getExtensionDescription()
{
return new String[]
{
"This error logger serves an example that may be used to demonstrate " +
"the process for creating a third-party error logger. It will " +
"write a line to a specified file for each error log message " +
"generated by the server.",
"For simplicity, this example logger does not take care of all tasks " +
"that would be necessary for production use. For example, it " +
"does not include any rotation or retention functionality, so the " +
"file will grow without bounds. Further, while it is threadsafe, " +
"it does so using simple Java synchronized blocks which is a " +
"simple way to achieve the necessary safety, but does not " +
"necessarily allow for the highest performance or concurrency.",
"Because this error logger writes to disk, it also serves as an " +
"example of a disk space consumer so that the server may track " +
"available space on the disk containing the log file and " +
"potentially warn administrators if usable space becomes too low."
};
}
/**
* Updates the provided argument parser to define any configuration arguments
* which may be used by this error logger. The argument parser may also
* be updated to define relationships between arguments (e.g., to specify
* required, exclusive, or dependent argument sets).
*
* @param parser The argument parser to be updated with the configuration
* arguments which may be used by this error logger.
*
* @throws ArgumentException If a problem is encountered while updating the
* provided argument parser.
*/
@Override()
public void defineConfigArguments(final ArgumentParser parser)
throws ArgumentException
{
// Add an argument that allows you to specify the path to the log file.
Character shortIdentifier = null;
String longIdentifier = ARG_NAME_LOG_FILE;
boolean required = true;
int maxOccurrences = 1;
String placeholder = "{path}";
String description = "The path to the log file to be written. " +
"Non-absolute paths will be treated as relative to the server root.";
boolean fileMustExist = false;
boolean parentMustExist = true;
boolean mustBeFile = true;
boolean mustBeDirectory = false;
parser.addArgument(new FileArgument(shortIdentifier, longIdentifier,
required, maxOccurrences, placeholder, description, fileMustExist,
parentMustExist, mustBeFile, mustBeDirectory));
}
/**
* Initializes this error logger.
*
* @param serverContext A handle to the server context for the server in
* which this extension is running.
* @param config The general configuration for this error logger.
* @param parser The argument parser which has been initialized from
* the configuration for this error logger.
*
* @throws LDAPException If a problem occurs while initializing this error
* logger.
*/
@Override()
public void initializeErrorLogger(final ServerContext serverContext,
final ErrorLoggerConfig config,
final ArgumentParser parser)
throws LDAPException
{
this.serverContext = serverContext;
this.config = config;
this.messageCount = new AtomicLong();
// Create the logger and open the log file.
try
{
final FileArgument arg =
(FileArgument) parser.getNamedArgument(ARG_NAME_LOG_FILE);
logFile = arg.getValue();
writer = new PrintWriter(new FileWriter(logFile, true));
}
catch (final Exception e)
{
serverContext.debugCaught(e);
throw new LDAPException(ResultCode.OTHER,
"Unable to open file " + logFile.getAbsolutePath() +
" for writing: " + StaticUtils.getExceptionMessage(e),
e);
}
// Register as a disk space consumer since we will be writing to the log
// file.
registeredConsumer = serverContext.registerDiskSpaceConsumer(this);
// Register a custom monitor provider to expose the number of messages
// logged and the size of the log file.
monitorProvider = serverContext.registerMonitorProvider(
new SimpleMonitorProvider(), config);
}
/**
* Indicates whether the configuration contained in the provided argument
* parser represents a valid configuration for this extension.
*
* @param config The general configuration for this error
* logger.
* @param parser The argument parser which has been initialized
* with the proposed configuration.
* @param unacceptableReasons A list that can be updated with reasons that
* the proposed configuration is not acceptable.
*
* @return {@code true} if the proposed configuration is acceptable, or
* {@code false} if not.
*/
@Override()
public boolean isConfigurationAcceptable(final ErrorLoggerConfig config,
final ArgumentParser parser,
final List<String> unacceptableReasons)
{
// The argument parser will handle all of the necessary validation, so
// we don't need to do anything here.
return true;
}
/**
* Attempts to apply the configuration contained in the provided argument
* parser.
*
* @param config The general configuration for this error
* logger.
* @param parser The argument parser which has been
* initialized with the new configuration.
* @param adminActionsRequired A list that can be updated with information
* about any administrative actions that may be
* required before one or more of the
* configuration changes will be applied.
* @param messages A list that can be updated with information
* about the result of applying the new
* configuration.
*
* @return A result code that provides information about the result of
* attempting to apply the configuration change.
*/
@Override()
public ResultCode applyConfiguration(final ErrorLoggerConfig config,
final ArgumentParser parser,
final List<String> adminActionsRequired,
final List<String> messages)
{
this.config = config;
// Get the path to the log file from the new config.
final FileArgument arg =
(FileArgument) parser.getNamedArgument(ARG_NAME_LOG_FILE);
final File newFile = arg.getValue();
// If the log file path hasn't changed, then we don't need to do anything.
if (newFile.equals(logFile))
{
return ResultCode.SUCCESS;
}
// Create a print writer that can be used to write to the new log file.
final PrintWriter newWriter;
try
{
newWriter = new PrintWriter(new FileWriter(newFile, true));
}
catch (final Exception e)
{
serverContext.debugCaught(e);
messages.add("Unable to open new log file " + newFile.getAbsolutePath() +
" for writing: " + StaticUtils.getExceptionMessage(e));
return ResultCode.OTHER;
}
// Swap the new logger into place.
final PrintWriter oldWriter;
synchronized (loggerLock)
{
oldWriter = writer;
writer = newWriter;
logFile = newFile;
}
// Close the old logger.
oldWriter.close();
// Re-register the monitor provider with the new config.
serverContext.deregisterMonitorProvider(monitorProvider);
monitorProvider = serverContext.registerMonitorProvider(
new SimpleMonitorProvider(), config);
return ResultCode.SUCCESS;
}
/**
* Performs any cleanup which may be necessary when this error logger is
* to be taken out of service.
*/
@Override()
public void finalizeErrorLogger()
{
synchronized (loggerLock)
{
if (registeredConsumer != null)
{
serverContext.deregisterDiskSpaceConsumer(registeredConsumer);
}
writer.close();
writer = null;
logFile = null;
}
serverContext.deregisterMonitorProvider(monitorProvider);
}
/**
* Records information about the provided message, if appropriate.
*
* @param category The category for the message to be logged.
* @param severity The severity for the message to be logged.
* @param messageID The unique identifier with which the message text is
* associated.
* @param message The message to be logged.
*/
@Override()
public void logError(final LogCategory category, final LogSeverity severity,
final long messageID, final String message)
{
final StringBuilder buffer = new StringBuilder();
buffer.append(new Date());
buffer.append(" severity=");
buffer.append(severity.name());
buffer.append(" message=\"");
buffer.append(message);
buffer.append('"');
synchronized (loggerLock)
{
if (writer == null)
{
// This should only happen if the logger has been shut down.
return;
}
writer.println(buffer.toString());
}
// Increment the message counter.
messageCount.incrementAndGet();
}
/**
* Retrieves a map containing examples of configurations that may be used for
* this extension. The map key should be a list of sample arguments, and the
* corresponding value should be a description of the behavior that will be
* exhibited by the extension when used with that configuration.
*
* @return A map containing examples of configurations that may be used for
* this extension. It may be {@code null} or empty if there should
* not be any example argument sets.
*/
@Override()
public Map<List<String>,String> getExamplesArgumentSets()
{
final LinkedHashMap<List<String>,String> exampleMap =
new LinkedHashMap<List<String>,String>(1);
exampleMap.put(
Arrays.asList(ARG_NAME_LOG_FILE + "=logs/example-error.log"),
"Write error log messages to the logs/example-error.log file below " +
"the server root.");
return exampleMap;
}
/**
* Retrieves the name that should be used to identify this disk space
* consumer.
*
* @return The name that should be used to identify this disk space consumer.
*/
public String getDiskSpaceConsumerName()
{
return "Example Error Logger " + config.getConfigObjectName();
}
/**
* Retrieves a list of filesystem paths in which this disk space consumer may
* store files which may consume a significant amount of space. It is
* generally recommended that the paths be directories, but they may also be
* individual files.
*
* @return A list of filesystem paths in which this disk space consumer may
* store files which may consume a significant amount of space.
*/
public List<File> getDiskSpaceConsumerPaths()
{
return Arrays.asList(logFile);
}
/**
* A simple implementation of MonitorProvider that we can register with the
* server through the ServerContext object. This exposes the number of
* messages that have been logged and the current size of the log file.
*/
private class SimpleMonitorProvider extends MonitorProvider
{
/**
* Retrieves a human-readable name for this extension. This is not used
* since we are registering this MonitorProvider through the ServerContext
* object (e.g. it is not a stand-alone extension).
*
* @return A human-readable name for this extension.
*/
@Override
public String getExtensionName()
{
return "SimpleMonitorProvider";
}
/**
* Retrieves a human-readable description for this extension. Each element
* of the array that is returned will be considered a separate paragraph in
* generated documentation. This is not used since we are registering this
* MonitorProvider through the ServerContext object (e.g. it is not a
* stand-alone extension).
*
* @return A human-readable description for this extension, or {@code null}
* or an empty array if no description should be available.
*/
@Override
public String[] getExtensionDescription()
{
return null;
}
/**
* Retrieves the name that identifies this monitor provider instance. It
* will be used as the value of the naming attribute for monitor entries.
* Each monitor provider instance must have a unique name.
*
* @return The name that identifies this monitor provider instance.
*/
@Override
public String getMonitorInstanceName()
{
return "ExampleErrorLogger Monitor";
}
/**
* Retrieves the name of the object class that will be used for monitor
* entries created from this monitor provider. This does not need to be
* defined in the server schema. It may be {@code null} if a default object
* class should be used.
*
* @return The name of the object class that will be used for monitor
* entries created from this monitor provider.
*/
@Override
public String getMonitorObjectClass()
{
return "error-logger-monitor-entry";
}
/**
* Retrieves a list of attributes containing the data to include in the
* monitor entry generated from this monitor provider.
*
* @return A list of attributes containing the data to include in the
* monitor entry generated from this monitor provider.
*/
@Override
public List<Attribute> getMonitorAttributes()
{
List<Attribute> attributes = new ArrayList<Attribute>(2);
attributes.add(new Attribute("num-messages-logged",
String.valueOf(messageCount.get())));
attributes.add(new Attribute("log-file-size",
logFile.getTotalSpace() + " bytes"));
return attributes;
}
}
}
|