/* * 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 2013-2015 ForgeRock AS */ package org.opends.server.loggers; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; 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.server.DebugTargetCfg; import org.opends.server.admin.std.server.FileBasedDebugLogPublisherCfg; import org.opends.server.api.DirectoryThread; 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.TimeThread; import static org.opends.messages.ConfigMessages.*; import static org.opends.server.util.StaticUtils.*; /** * The debug log publisher implementation that writes debug messages to files * on disk. It also maintains the rotation and retention polices of the log * files. */ public class TextDebugLogPublisher extends DebugLogPublisher<FileBasedDebugLogPublisherCfg> implements ConfigurationChangeListener<FileBasedDebugLogPublisherCfg>, ConfigurationAddListener<DebugTargetCfg>, ConfigurationDeleteListener<DebugTargetCfg> { private static long globalSequenceNumber; private TextWriter writer; private FileBasedDebugLogPublisherCfg currentConfig; /** * Returns an instance of the text debug log publisher that will print all * messages to the provided writer, based on the provided debug targets. * * @param debugTargets * The targets defining which and how debug events are logged. * @param writer * The text writer where the message will be written to. * @return The instance of the text error log publisher that will print all * messages to standard out. May be {@code null} if no debug target is * valid. */ static TextDebugLogPublisher getStartupTextDebugPublisher(List<String> debugTargets, TextWriter writer) { TextDebugLogPublisher startupPublisher = null; for (String value : debugTargets) { int settingsStart = value.indexOf(":"); //See if the scope and settings exists if (settingsStart > 0) { String scope = value.substring(0, settingsStart); TraceSettings settings = TraceSettings.parseTraceSettings(value.substring(settingsStart + 1)); if (settings != null) { if (startupPublisher == null) { startupPublisher = new TextDebugLogPublisher(); startupPublisher.writer = writer; } startupPublisher.addTraceSettings(scope, settings); } } } return startupPublisher; } @Override public boolean isConfigurationAcceptable( FileBasedDebugLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons) { return isConfigurationChangeAcceptable(config, unacceptableReasons); } @Override public void initializeLogPublisher(FileBasedDebugLogPublisherCfg config, ServerContext serverContext) throws ConfigException, InitializationException { File logFile = getFileForPath(config.getLogFile(), serverContext); 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); } config.addDebugTargetAddListener(this); config.addDebugTargetDeleteListener(this); addTraceSettings(null, getDefaultSettings(config)); for(String name : config.listDebugTargets()) { final DebugTargetCfg targetCfg = config.getDebugTarget(name); addTraceSettings(targetCfg.getDebugScope(), new TraceSettings(targetCfg)); } currentConfig = config; config.addFileBasedDebugChangeListener(this); } @Override public boolean isConfigurationChangeAcceptable( FileBasedDebugLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons) { // Make sure the permission is valid. try { FilePermission filePerm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); if (!filePerm.isOwnerWritable()) { LocalizableMessage message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config.getLogFilePermissions()); unacceptableReasons.add(message); return false; } } catch (DirectoryException e) { unacceptableReasons.add(ERR_CONFIG_LOGGING_MODE_INVALID.get(config.getLogFilePermissions(), e)); return false; } return true; } @Override public ConfigChangeResult applyConfigurationChange(FileBasedDebugLogPublisherCfg config) { final ConfigChangeResult ccr = new ConfigChangeResult(); addTraceSettings(null, getDefaultSettings(config)); DebugLogger.updateTracerSettings(); 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)writer; 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 AsynchronousTextWriter newAsyncWriter(MultifileTextWriter writer, FileBasedDebugLogPublisherCfg config) { String name = "Asynchronous Text Writer for " + config.dn(); return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), writer); } private void configure(MultifileTextWriter mfWriter, FileBasedDebugLogPublisherCfg 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(FileBasedDebugLogPublisherCfg config) { return getFileForPath(config.getLogFile()); } private boolean hasAsyncConfigChanged(FileBasedDebugLogPublisherCfg newConfig) { return !currentConfig.dn().equals(newConfig.dn()) && currentConfig.isAutoFlush() != newConfig.isAutoFlush() && currentConfig.getQueueSize() != newConfig.getQueueSize(); } private TraceSettings getDefaultSettings(FileBasedDebugLogPublisherCfg config) { return new TraceSettings( TraceSettings.Level.getLevel(true, config.isDefaultDebugExceptionsOnly()), config.isDefaultOmitMethodEntryArguments(), config.isDefaultOmitMethodReturnValue(), config.getDefaultThrowableStackFrames(), config.isDefaultIncludeThrowableCause()); } @Override public boolean isConfigurationAddAcceptable(DebugTargetCfg config, List<LocalizableMessage> unacceptableReasons) { return !hasTraceSettings(config.getDebugScope()); } @Override public boolean isConfigurationDeleteAcceptable(DebugTargetCfg config, List<LocalizableMessage> unacceptableReasons) { // A delete should always be acceptable. return true; } @Override public ConfigChangeResult applyConfigurationAdd(DebugTargetCfg config) { addTraceSettings(config.getDebugScope(), new TraceSettings(config)); DebugLogger.updateTracerSettings(); return new ConfigChangeResult(); } @Override public ConfigChangeResult applyConfigurationDelete(DebugTargetCfg config) { removeTraceSettings(config.getDebugScope()); DebugLogger.updateTracerSettings(); return new ConfigChangeResult(); } @Override public void trace(TraceSettings settings, String signature, String sourceLocation, String msg, StackTraceElement[] stackTrace) { String stack = null; if (stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.getStackDepth()); } publish(signature, sourceLocation, msg, stack); } @Override public void traceException(TraceSettings settings, String signature, String sourceLocation, String msg, Throwable ex, StackTraceElement[] stackTrace) { String message = DebugMessageFormatter.format("%s caught={%s}", new Object[] { msg, ex }); String stack = null; if (stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(ex, settings.getStackDepth(), settings.isIncludeCause()); } publish(signature, sourceLocation, message, stack); } @Override public void close() { writer.shutdown(); if(currentConfig != null) { currentConfig.removeFileBasedDebugChangeListener(this); } } /** * Publishes a record, optionally performing some "special" work: * - injecting a stack trace into the message * - format the message with argument values */ private void publish(String signature, String sourceLocation, String msg, String stack) { Thread thread = Thread.currentThread(); StringBuilder buf = new StringBuilder(); // Emit the timestamp. buf.append("["); buf.append(TimeThread.getLocalTime()); buf.append("] "); // Emit the seq num buf.append(globalSequenceNumber++); buf.append(" "); // Emit the debug level. buf.append("trace "); // Emit thread info. buf.append("thread={"); buf.append(thread.getName()); buf.append("("); buf.append(thread.getId()); buf.append(")} "); if(thread instanceof DirectoryThread) { buf.append("threadDetail={"); for (Map.Entry<String, String> entry : ((DirectoryThread) thread).getDebugProperties().entrySet()) { buf.append(entry.getKey()); buf.append("="); buf.append(entry.getValue()); buf.append(" "); } buf.append("} "); } // Emit method info. buf.append("method={"); buf.append(signature); buf.append("("); buf.append(sourceLocation); buf.append(")} "); // Emit message. buf.append(msg); // Emit Stack Trace. if(stack != null) { buf.append("\nStack Trace:\n"); buf.append(stack); } writer.writeRecord(buf.toString()); } @Override public DN getDN() { if(currentConfig != null) { return currentConfig.dn(); } return null; } }