/* * 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 2013 ForgeRock AS */ package org.opends.server.loggers.debug; import static org.opends.messages.ConfigMessages.*; 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.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.opends.messages.Message; 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.DebugLogPublisherCfgDefn; import org.opends.server.admin.std.server.DebugTargetCfg; import org.opends.server.admin.std.server.FileBasedDebugLogPublisherCfg; import org.opends.server.api.DebugLogPublisher; import org.opends.server.api.DirectoryThread; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.*; import org.opends.server.types.*; import org.opends.server.util.ServerConstants; import org.opends.server.util.StaticUtils; import org.opends.server.util.TimeThread; import com.sleepycat.je.*; /** * 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. This is used to print the messages * to the console when the server starts up. By default, only error level * messages are printed. Special debug targets are also parsed from * system properties if any are specified. * * @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. */ public static TextDebugLogPublisher getStartupTextDebugPublisher(TextWriter writer) { TextDebugLogPublisher startupPublisher = new TextDebugLogPublisher(); startupPublisher.writer = writer; Set<Map.Entry<Object, Object>> propertyEntries = System.getProperties().entrySet(); for(Map.Entry<Object, Object> entry : propertyEntries) { if(((String)entry.getKey()).startsWith(PROPERTY_DEBUG_TARGET)) { String value = (String)entry.getValue(); 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) { startupPublisher.addTraceSettings(scope, settings); } } } } return startupPublisher; } /** * {@inheritDoc} */ @Override public boolean isConfigurationAcceptable( FileBasedDebugLogPublisherCfg config, List<Message> unacceptableReasons) { return isConfigurationChangeAcceptable(config, unacceptableReasons); } /** * {@inheritDoc} */ @Override public void initializeLogPublisher(FileBasedDebugLogPublisherCfg config) throws ConfigException, InitializationException { File logFile = getFileForPath(config.getLogFile()); 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().toNormalizedString(), 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 = new AsynchronousTextWriter( "Asynchronous Text Writer for " + config.dn().toNormalizedString(), config.getQueueSize(), config.isAutoFlush(), writer); } else { this.writer = writer; } } catch(DirectoryException e) { Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( config.dn().toString(), String.valueOf(e)); throw new InitializationException(message, e); } catch(IOException e) { Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get( logFile.toString(), config.dn().toString(), String.valueOf(e)); throw new InitializationException(message, e); } config.addDebugTargetAddListener(this); config.addDebugTargetDeleteListener(this); //Get the default/global settings LogLevel logLevel = DebugLogLevel.parse(config.getDefaultDebugLevel().toString()); Set<LogCategory> logCategories = null; if(!config.getDefaultDebugCategory().isEmpty()) { logCategories = new HashSet<LogCategory>(config.getDefaultDebugCategory().size()); for(DebugLogPublisherCfgDefn.DefaultDebugCategory category : config.getDefaultDebugCategory()) { logCategories.add(DebugLogCategory.parse(category.toString())); } } TraceSettings defaultSettings = new TraceSettings(logLevel, logCategories, config.isDefaultOmitMethodEntryArguments(), config.isDefaultOmitMethodReturnValue(), config.getDefaultThrowableStackFrames(), config.isDefaultIncludeThrowableCause()); addTraceSettings(null, defaultSettings); for(String name : config.listDebugTargets()) { DebugTargetCfg targetCfg = config.getDebugTarget(name); addTraceSettings(targetCfg.getDebugScope(), new TraceSettings(targetCfg)); } currentConfig = config; config.addFileBasedDebugChangeListener(this); } /** * {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( FileBasedDebugLogPublisherCfg config, List<Message> unacceptableReasons) { // Make sure the permission is valid. try { FilePermission filePerm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); if(!filePerm.isOwnerWritable()) { Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get( config.getLogFilePermissions()); unacceptableReasons.add(message); return false; } } catch(DirectoryException e) { Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get( config.getLogFilePermissions(), String.valueOf(e)); unacceptableReasons.add(message); return false; } return true; } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( FileBasedDebugLogPublisherCfg config) { // Default result code. ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList<Message> messages = new ArrayList<Message>(); //Get the default/global settings LogLevel logLevel = DebugLogLevel.parse(config.getDefaultDebugLevel().toString()); Set<LogCategory> logCategories = null; if(!config.getDefaultDebugCategory().isEmpty()) { logCategories = new HashSet<LogCategory>(config.getDefaultDebugCategory().size()); for(DebugLogPublisherCfgDefn.DefaultDebugCategory category : config.getDefaultDebugCategory()) { logCategories.add(DebugLogCategory.parse(category.toString())); } } TraceSettings defaultSettings = new TraceSettings(logLevel, logCategories, config.isDefaultOmitMethodEntryArguments(), config.isDefaultOmitMethodReturnValue(), config.getDefaultThrowableStackFrames(), config.isDefaultIncludeThrowableCause()); addTraceSettings(null, defaultSettings); DebugLogger.updateTracerSettings(); File logFile = getFileForPath(config.getLogFile()); FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); try { FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous(); TextWriter currentWriter; // Determine the writer we are using. If we were writing asyncronously, // we need to modify the underlaying writer. if(writer instanceof AsynchronousTextWriter) { currentWriter = ((AsynchronousTextWriter)writer).getWrappedWriter(); } else { currentWriter = writer; } if(currentWriter instanceof MultifileTextWriter) { MultifileTextWriter mfWriter = (MultifileTextWriter)writer; 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)); } if(writer instanceof AsynchronousTextWriter && !config.isAsynchronous()) { // The asynchronous setting is being turned off. AsynchronousTextWriter asyncWriter = ((AsynchronousTextWriter)writer); writer = mfWriter; asyncWriter.shutdown(false); } if(!(writer instanceof AsynchronousTextWriter) && config.isAsynchronous()) { // The asynchronous setting is being turned on. writer = new AsynchronousTextWriter("Asynchronous Text Writer for " + config.dn().toNormalizedString(), config.getQueueSize(), config.isAutoFlush(), mfWriter); } if((currentConfig.isAsynchronous() && config.isAsynchronous()) && (currentConfig.getQueueSize() != config.getQueueSize())) { adminActionRequired = true; } currentConfig = config; } } catch(Exception e) { 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 boolean isConfigurationAddAcceptable(DebugTargetCfg config, List<Message> unacceptableReasons) { return getTraceSettings(config.getDebugScope()) == null; } /** * {@inheritDoc} */ @Override public boolean isConfigurationDeleteAcceptable(DebugTargetCfg config, List<Message> unacceptableReasons) { // A delete should always be acceptable. return true; } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationAdd(DebugTargetCfg config) { // Default result code. ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList<Message> messages = new ArrayList<Message>(); addTraceSettings(config.getDebugScope(), new TraceSettings(config)); DebugLogger.updateTracerSettings(); return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationDelete(DebugTargetCfg config) { // Default result code. ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList<Message> messages = new ArrayList<Message>(); removeTraceSettings(config.getDebugScope()); DebugLogger.updateTracerSettings(); return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** * {@inheritDoc} */ @Override public void traceConstructor(LogLevel level, TraceSettings settings, String signature, String sourceLocation, Object[] args, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.CONSTRUCTOR; String msg = ""; if(args != null) { msg = buildDefaultEntryMessage(args); } String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, msg, stack); } /** * {@inheritDoc} */ @Override public void traceMethodEntry(LogLevel level, TraceSettings settings, String signature, String sourceLocation, Object obj, Object[] args, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.ENTER; String msg = ""; if(args != null) { msg = buildDefaultEntryMessage(args); } String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, msg, stack); } /** * {@inheritDoc} */ @Override public void traceStaticMethodEntry(LogLevel level, TraceSettings settings, String signature, String sourceLocation, Object[] args, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.ENTER; String msg = ""; if(args != null) { msg = buildDefaultEntryMessage(args); } String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, msg, stack); } /** * {@inheritDoc} */ @Override public void traceReturn(LogLevel level, TraceSettings settings, String signature, String sourceLocation, Object ret, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.EXIT; String msg = ""; if(ret != null) { msg = DebugMessageFormatter.format("returned={%s}", new Object[] {ret}); } String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, msg, stack); } /** * {@inheritDoc} */ @Override public void traceThrown(LogLevel level, TraceSettings settings, String signature, String sourceLocation, Throwable ex, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.THROWN; String msg = DebugMessageFormatter.format("thrown={%s}", new Object[] {ex}); String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(ex, settings.stackDepth, settings.includeCause); } publish(category, level, signature, sourceLocation, msg, stack); } /** * {@inheritDoc} */ @Override public void traceMessage(LogLevel level, TraceSettings settings, String signature, String sourceLocation, String msg, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.MESSAGE; String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, msg, stack); } /** * {@inheritDoc} */ @Override public void traceCaught(LogLevel level, TraceSettings settings, String signature, String sourceLocation, Throwable ex, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.CAUGHT; String msg = DebugMessageFormatter.format("caught={%s}", new Object[] {ex}); String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(ex, settings.stackDepth, settings.includeCause); } publish(category, level, signature, sourceLocation, msg, stack); } /** * {@inheritDoc} */ @Override public void traceJEAccess(LogLevel level, TraceSettings settings, String signature, String sourceLocation, OperationStatus status, Database database, Transaction txn, DatabaseEntry key, DatabaseEntry data, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.DATABASE_ACCESS; // Build the string that is common to category DATABASE_ACCESS. StringBuilder builder = new StringBuilder(); builder.append(" ("); builder.append(status.toString()); builder.append(")"); builder.append(" db="); try { builder.append(database.getDatabaseName()); } catch(DatabaseException de) { builder.append(de.toString()); } if (txn != null) { builder.append(" txnid="); try { builder.append(txn.getId()); } catch(DatabaseException de) { builder.append(de.toString()); } } else { builder.append(" txnid=none"); } builder.append(ServerConstants.EOL); if(key != null) { builder.append("key:"); builder.append(ServerConstants.EOL); StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4); } // If the operation was successful we log the same common information // plus the data if (status == OperationStatus.SUCCESS && data != null) { builder.append("data(len="); builder.append(data.getSize()); builder.append("):"); builder.append(ServerConstants.EOL); StaticUtils.byteArrayToHexPlusAscii(builder, data.getData(), 4); } String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, builder.toString(), stack); } /** * {@inheritDoc} */ @Override public void traceData(LogLevel level, TraceSettings settings, String signature, String sourceLocation, byte[] data, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.DATA; if(data != null) { StringBuilder builder = new StringBuilder(); builder.append(ServerConstants.EOL); builder.append("data(len="); builder.append(data.length); builder.append("):"); builder.append(ServerConstants.EOL); StaticUtils.byteArrayToHexPlusAscii(builder, data, 4); String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, builder.toString(), stack); } } /** * {@inheritDoc} */ @Override public void traceProtocolElement(LogLevel level, TraceSettings settings, String signature, String sourceLocation, String decodedForm, StackTraceElement[] stackTrace) { LogCategory category = DebugLogCategory.PROTOCOL; StringBuilder builder = new StringBuilder(); builder.append(ServerConstants.EOL); builder.append(decodedForm); String stack = null; if(stackTrace != null) { stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, settings.stackDepth); } publish(category, level, signature, sourceLocation, builder.toString(), stack); } /** * {@inheritDoc} */ @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(LogCategory category, LogLevel level, 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 debug category. buf.append(category); buf.append(" "); // Emit the debug level. buf.append(level); buf.append(" "); // 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()); } private String buildDefaultEntryMessage(Object[] args) { StringBuilder format = new StringBuilder(); for (int i = 0; i < args.length; i++) { if (i != 0) format.append(", "); format.append("arg"); format.append(i + 1); format.append("={%s}"); } return DebugMessageFormatter.format(format.toString(), args); } /** * {@inheritDoc} */ @Override public DN getDN() { if(currentConfig != null) { return currentConfig.dn(); } else { return null; } } }