/*
|
* 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;
|
}
|
}
|