/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.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 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2012-2015 ForgeRock AS.
*/
package org.opends.server.loggers;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.LoggerMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.messages.Severity;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.ErrorLogPublisherCfgDefn;
import org.opends.server.admin.std.server.FileBasedErrorLogPublisherCfg;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ServerContext;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.FilePermission;
import org.opends.server.types.InitializationException;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.TimeThread;
/** This class provides an implementation of an error log publisher. */
public class TextErrorLogPublisher
extends ErrorLogPublisher<FileBasedErrorLogPublisherCfg>
implements ConfigurationChangeListener<FileBasedErrorLogPublisherCfg>
{
private TextWriter writer;
private FileBasedErrorLogPublisherCfg currentConfig;
/**
* Returns a new text error log publisher which will print all messages to the
* provided writer. This publisher should be used by tools.
*
* @param writer
* The text writer where the message will be written to.
* @return A new text error log publisher which will print all messages to the
* provided writer.
*/
public static TextErrorLogPublisher getToolStartupTextErrorPublisher(TextWriter writer)
{
TextErrorLogPublisher startupPublisher = new TextErrorLogPublisher();
startupPublisher.writer = writer;
startupPublisher.defaultSeverities.addAll(Arrays.asList(Severity.values()));
return startupPublisher;
}
/**
* Returns a new text error log publisher which will print only notices,
* severe warnings and errors, and fatal errors messages to the provided
* writer. This less verbose publisher should be used by the directory server
* during startup.
*
* @param writer
* The text writer where the message will be written to.
* @return A new text error log publisher which will print only notices,
* severe warnings and errors, and fatal errors messages to the
* provided writer.
*/
public static TextErrorLogPublisher getServerStartupTextErrorPublisher(TextWriter writer)
{
TextErrorLogPublisher startupPublisher = new TextErrorLogPublisher();
startupPublisher.writer = writer;
startupPublisher.defaultSeverities.addAll(Arrays.asList(
Severity.ERROR, Severity.WARNING, Severity.NOTICE));
return startupPublisher;
}
@Override
public void initializeLogPublisher(FileBasedErrorLogPublisherCfg config, ServerContext serverContext)
throws ConfigException, InitializationException
{
File logFile = getLogFile(config);
FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
try
{
FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions());
LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler(config.dn());
boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous();
MultifileTextWriter writer = new MultifileTextWriter("Multifile Text Writer for " + config.dn(),
config.getTimeInterval(),
fnPolicy,
perm,
errorHandler,
"UTF-8",
writerAutoFlush,
config.isAppend(),
(int)config.getBufferSize());
// Validate retention and rotation policies.
for(DN dn : config.getRotationPolicyDNs())
{
writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
}
for(DN dn: config.getRetentionPolicyDNs())
{
writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
}
if(config.isAsynchronous())
{
this.writer = newAsyncWriter(writer, config);
}
else
{
this.writer = writer;
}
}
catch(DirectoryException e)
{
throw new InitializationException(
ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(config.dn(), e), e);
}
catch(IOException e)
{
throw new InitializationException(
ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile, config.dn(), e), e);
}
setDefaultSeverities(config.getDefaultSeverity());
ConfigChangeResult ccr = new ConfigChangeResult();
setDefinedSeverities(config, ccr);
if (!ccr.getMessages().isEmpty())
{
throw new ConfigException(ccr.getMessages().iterator().next());
}
currentConfig = config;
config.addFileBasedErrorChangeListener(this);
}
private void setDefinedSeverities(FileBasedErrorLogPublisherCfg config, final ConfigChangeResult ccr)
{
for (String overrideSeverity : config.getOverrideSeverity())
{
if (overrideSeverity != null)
{
int equalPos = overrideSeverity.indexOf('=');
if (equalPos < 0)
{
ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
ccr.addMessage(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity));
return;
}
String category = overrideSeverity.substring(0, equalPos);
category = category.replace("-", "_").toUpperCase();
try
{
Set<Severity> severities = new HashSet<>();
StringTokenizer sevTokenizer = new StringTokenizer(overrideSeverity.substring(equalPos + 1), ",");
while (sevTokenizer.hasMoreElements())
{
String severityName = sevTokenizer.nextToken();
severityName = severityName.replace("-", "_").toUpperCase();
if (LOG_SEVERITY_ALL.equalsIgnoreCase(severityName))
{
addAllSeverities(severities);
}
else
{
try
{
severities.add(Severity.parseString(severityName));
}
catch (Exception e)
{
ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
ccr.addMessage(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName));
return;
}
}
}
definedSeverities.put(category, severities);
}
catch (Exception e)
{
ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
ccr.addMessage(WARN_ERROR_LOGGER_INVALID_CATEGORY.get(category));
return;
}
}
}
}
@Override
public boolean isConfigurationAcceptable(
FileBasedErrorLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons)
{
return isConfigurationChangeAcceptable(config, unacceptableReasons);
}
@Override
public boolean isConfigurationChangeAcceptable(
FileBasedErrorLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons)
{
// Make sure the permission is valid.
try
{
FilePermission filePerm = FilePermission.decodeUNIXMode(config.getLogFilePermissions());
if(!filePerm.isOwnerWritable())
{
unacceptableReasons.add(ERR_CONFIG_LOGGING_INSANE_MODE.get(config.getLogFilePermissions()));
return false;
}
}
catch(DirectoryException e)
{
unacceptableReasons.add(ERR_CONFIG_LOGGING_MODE_INVALID.get(config.getLogFilePermissions(), e));
return false;
}
for(String overrideSeverity : config.getOverrideSeverity())
{
if(overrideSeverity != null)
{
int equalPos = overrideSeverity.indexOf('=');
if (equalPos < 0)
{
unacceptableReasons.add(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity));
return false;
}
// No check on category because it can be any value
StringTokenizer sevTokenizer = new StringTokenizer(overrideSeverity.substring(equalPos + 1), ",");
while (sevTokenizer.hasMoreElements())
{
String severityName = sevTokenizer.nextToken();
severityName = severityName.replace("-", "_").toUpperCase();
if (!LOG_SEVERITY_ALL.equalsIgnoreCase(severityName))
{
try
{
Severity.parseString(severityName);
}
catch (Exception e)
{
unacceptableReasons.add(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName));
return false;
}
}
}
}
}
return true;
}
@Override
public ConfigChangeResult applyConfigurationChange(FileBasedErrorLogPublisherCfg config)
{
final ConfigChangeResult ccr = new ConfigChangeResult();
setDefaultSeverities(config.getDefaultSeverity());
definedSeverities.clear();
setDefinedSeverities(config, ccr);
try
{
// Determine the writer we are using. If we were writing asynchronously,
// we need to modify the underlying writer.
TextWriter currentWriter;
if(writer instanceof AsynchronousTextWriter)
{
currentWriter = ((AsynchronousTextWriter)writer).getWrappedWriter();
}
else
{
currentWriter = writer;
}
if(currentWriter instanceof MultifileTextWriter)
{
MultifileTextWriter mfWriter = (MultifileTextWriter)currentWriter;
configure(mfWriter, config);
if (config.isAsynchronous())
{
if (writer instanceof AsynchronousTextWriter)
{
if (hasAsyncConfigChanged(config))
{
// reinstantiate
final AsynchronousTextWriter previousWriter = (AsynchronousTextWriter) writer;
writer = newAsyncWriter(mfWriter, config);
previousWriter.shutdown(false);
}
}
else
{
// turn async text writer on
writer = newAsyncWriter(mfWriter, config);
}
}
else
{
if (writer instanceof AsynchronousTextWriter)
{
// asynchronous is being turned off, remove async text writers.
final AsynchronousTextWriter previousWriter = (AsynchronousTextWriter) writer;
writer = mfWriter;
previousWriter.shutdown(false);
}
}
if (currentConfig.isAsynchronous() && config.isAsynchronous()
&& currentConfig.getQueueSize() != config.getQueueSize())
{
ccr.setAdminActionRequired(true);
}
currentConfig = config;
}
}
catch(Exception e)
{
ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
ccr.addMessage(ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(
config.dn(), stackTraceToSingleLineString(e)));
}
return ccr;
}
private void configure(MultifileTextWriter mfWriter, FileBasedErrorLogPublisherCfg config) throws DirectoryException
{
FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions());
boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous();
File logFile = getLogFile(config);
FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
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 (DN dn : config.getRotationPolicyDNs())
{
mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
}
for (DN dn : config.getRetentionPolicyDNs())
{
mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
}
}
private File getLogFile(FileBasedErrorLogPublisherCfg config)
{
return getFileForPath(config.getLogFile());
}
private boolean hasAsyncConfigChanged(FileBasedErrorLogPublisherCfg newConfig)
{
return !currentConfig.dn().equals(newConfig.dn())
&& currentConfig.isAutoFlush() != newConfig.isAutoFlush()
&& currentConfig.getQueueSize() != newConfig.getQueueSize();
}
private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter mfWriter, FileBasedErrorLogPublisherCfg config)
{
String name = "Asynchronous Text Writer for " + config.dn();
return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter);
}
private void setDefaultSeverities(Set<ErrorLogPublisherCfgDefn.DefaultSeverity> defSevs)
{
defaultSeverities.clear();
if (defSevs.isEmpty())
{
defaultSeverities.add(Severity.ERROR);
defaultSeverities.add(Severity.WARNING);
}
else
{
for (ErrorLogPublisherCfgDefn.DefaultSeverity defSev : defSevs)
{
String defaultSeverity = defSev.toString();
if (LOG_SEVERITY_ALL.equalsIgnoreCase(defaultSeverity))
{
addAllSeverities(defaultSeverities);
}
else if (!LOG_SEVERITY_NONE.equalsIgnoreCase(defaultSeverity))
{
Severity errorSeverity = Severity.parseString(defSev.name());
if (errorSeverity != null)
{
defaultSeverities.add(errorSeverity);
}
}
}
}
}
private void addAllSeverities(Set<Severity> severities)
{
severities.add(Severity.ERROR);
severities.add(Severity.WARNING);
severities.add(Severity.INFORMATION);
severities.add(Severity.NOTICE);
}
@Override
public void close()
{
writer.shutdown();
if(currentConfig != null)
{
currentConfig.removeFileBasedErrorChangeListener(this);
}
}
@Override
public void log(String category, Severity severity, LocalizableMessage message, Throwable exception)
{
if (isEnabledFor(category, severity))
{
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(TimeThread.getLocalTime());
sb.append("] category=").append(category).
append(" severity=").append(severity).
append(" msgID=").append(message.resourceName())
.append('.')
.append(message.ordinal()).
append(" msg=").append(message);
if (exception != null)
{
sb.append(" exception=").append(
StaticUtils.stackTraceToSingleLineString(exception));
}
writer.writeRecord(sb.toString());
}
}
@Override
public boolean isEnabledFor(String category, Severity severity)
{
Set<Severity> severities = definedSeverities.get(category);
if (severities == null)
{
severities = defaultSeverities;
}
return severities.contains(severity);
}
@Override
public DN getDN()
{
if(currentConfig != null)
{
return currentConfig.dn();
}
return null;
}
}