001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2009 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.loggers; 018 019import java.io.File; 020import java.io.IOException; 021import java.util.List; 022import java.util.Map; 023 024import org.forgerock.i18n.LocalizableMessage; 025import org.forgerock.opendj.config.server.ConfigChangeResult; 026import org.forgerock.opendj.config.server.ConfigException; 027import org.forgerock.opendj.config.server.ConfigurationAddListener; 028import org.forgerock.opendj.config.server.ConfigurationChangeListener; 029import org.forgerock.opendj.config.server.ConfigurationDeleteListener; 030import org.forgerock.opendj.server.config.server.DebugTargetCfg; 031import org.forgerock.opendj.server.config.server.FileBasedDebugLogPublisherCfg; 032import org.opends.server.api.DirectoryThread; 033import org.opends.server.core.DirectoryServer; 034import org.opends.server.core.ServerContext; 035import org.forgerock.opendj.ldap.DN; 036import org.opends.server.types.DirectoryException; 037import org.opends.server.types.FilePermission; 038import org.opends.server.types.InitializationException; 039import org.opends.server.util.TimeThread; 040 041import static org.opends.messages.ConfigMessages.*; 042import static org.opends.server.util.StaticUtils.*; 043 044/** 045 * The debug log publisher implementation that writes debug messages to files 046 * on disk. It also maintains the rotation and retention polices of the log 047 * files. 048 */ 049public class TextDebugLogPublisher 050 extends DebugLogPublisher<FileBasedDebugLogPublisherCfg> 051 implements ConfigurationChangeListener<FileBasedDebugLogPublisherCfg>, 052 ConfigurationAddListener<DebugTargetCfg>, 053 ConfigurationDeleteListener<DebugTargetCfg> 054{ 055 private static long globalSequenceNumber; 056 private TextWriter writer; 057 private FileBasedDebugLogPublisherCfg currentConfig; 058 059 /** 060 * Returns an instance of the text debug log publisher that will print all 061 * messages to the provided writer, based on the provided debug targets. 062 * 063 * @param debugTargets 064 * The targets defining which and how debug events are logged. 065 * @param writer 066 * The text writer where the message will be written to. 067 * @return The instance of the text error log publisher that will print all 068 * messages to standard out. May be {@code null} if no debug target is 069 * valid. 070 */ 071 static TextDebugLogPublisher getStartupTextDebugPublisher(List<String> debugTargets, TextWriter writer) 072 { 073 TextDebugLogPublisher startupPublisher = null; 074 for (String value : debugTargets) 075 { 076 int settingsStart = value.indexOf(":"); 077 078 //See if the scope and settings exists 079 if (settingsStart > 0) 080 { 081 String scope = value.substring(0, settingsStart); 082 TraceSettings settings = TraceSettings.parseTraceSettings(value.substring(settingsStart + 1)); 083 if (settings != null) 084 { 085 if (startupPublisher == null) { 086 startupPublisher = new TextDebugLogPublisher(); 087 startupPublisher.writer = writer; 088 } 089 startupPublisher.addTraceSettings(scope, settings); 090 } 091 } 092 } 093 return startupPublisher; 094 } 095 096 @Override 097 public boolean isConfigurationAcceptable( 098 FileBasedDebugLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons) 099 { 100 return isConfigurationChangeAcceptable(config, unacceptableReasons); 101 } 102 103 @Override 104 public void initializeLogPublisher(FileBasedDebugLogPublisherCfg config, ServerContext serverContext) 105 throws ConfigException, InitializationException 106 { 107 File logFile = getFileForPath(config.getLogFile(), serverContext); 108 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 109 110 try 111 { 112 FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 113 LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler(config.dn()); 114 boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous(); 115 116 MultifileTextWriter writer = new MultifileTextWriter("Multifile Text Writer for " + config.dn(), 117 config.getTimeInterval(), 118 fnPolicy, 119 perm, 120 errorHandler, 121 "UTF-8", 122 writerAutoFlush, 123 config.isAppend(), 124 (int)config.getBufferSize()); 125 126 // Validate retention and rotation policies. 127 for(DN dn : config.getRotationPolicyDNs()) 128 { 129 writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 130 } 131 for(DN dn: config.getRetentionPolicyDNs()) 132 { 133 writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 134 } 135 136 if(config.isAsynchronous()) 137 { 138 this.writer = newAsyncWriter(writer, config); 139 } 140 else 141 { 142 this.writer = writer; 143 } 144 } 145 catch(DirectoryException e) 146 { 147 throw new InitializationException( 148 ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(config.dn(), e), e); 149 } 150 catch(IOException e) 151 { 152 throw new InitializationException( 153 ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile, config.dn(), e), e); 154 } 155 156 config.addDebugTargetAddListener(this); 157 config.addDebugTargetDeleteListener(this); 158 159 addTraceSettings(null, getDefaultSettings(config)); 160 161 for(String name : config.listDebugTargets()) 162 { 163 final DebugTargetCfg targetCfg = config.getDebugTarget(name); 164 addTraceSettings(targetCfg.getDebugScope(), new TraceSettings(targetCfg)); 165 } 166 167 currentConfig = config; 168 169 config.addFileBasedDebugChangeListener(this); 170 } 171 172 @Override 173 public boolean isConfigurationChangeAcceptable( 174 FileBasedDebugLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons) 175 { 176 // Make sure the permission is valid. 177 try 178 { 179 FilePermission filePerm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 180 if (!filePerm.isOwnerWritable()) 181 { 182 LocalizableMessage message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config.getLogFilePermissions()); 183 unacceptableReasons.add(message); 184 return false; 185 } 186 } 187 catch (DirectoryException e) 188 { 189 unacceptableReasons.add(ERR_CONFIG_LOGGING_MODE_INVALID.get(config.getLogFilePermissions(), e)); 190 return false; 191 } 192 193 return true; 194 } 195 196 @Override 197 public ConfigChangeResult applyConfigurationChange(FileBasedDebugLogPublisherCfg config) 198 { 199 final ConfigChangeResult ccr = new ConfigChangeResult(); 200 201 addTraceSettings(null, getDefaultSettings(config)); 202 DebugLogger.updateTracerSettings(); 203 204 try 205 { 206 // Determine the writer we are using. If we were writing asynchronously, 207 // we need to modify the underlying writer. 208 TextWriter currentWriter; 209 if(writer instanceof AsynchronousTextWriter) 210 { 211 currentWriter = ((AsynchronousTextWriter)writer).getWrappedWriter(); 212 } 213 else 214 { 215 currentWriter = writer; 216 } 217 218 if(currentWriter instanceof MultifileTextWriter) 219 { 220 MultifileTextWriter mfWriter = (MultifileTextWriter)writer; 221 configure(mfWriter, config); 222 223 if (config.isAsynchronous()) 224 { 225 if (writer instanceof AsynchronousTextWriter) 226 { 227 if (hasAsyncConfigChanged(config)) 228 { 229 // reinstantiate 230 final AsynchronousTextWriter previousWriter = (AsynchronousTextWriter) writer; 231 writer = newAsyncWriter(mfWriter, config); 232 previousWriter.shutdown(false); 233 } 234 } 235 else 236 { 237 // turn async text writer on 238 writer = newAsyncWriter(mfWriter, config); 239 } 240 } 241 else 242 { 243 if (writer instanceof AsynchronousTextWriter) 244 { 245 // asynchronous is being turned off, remove async text writers. 246 final AsynchronousTextWriter previousWriter = (AsynchronousTextWriter) writer; 247 writer = mfWriter; 248 previousWriter.shutdown(false); 249 } 250 } 251 252 if(currentConfig.isAsynchronous() && config.isAsynchronous() && 253 currentConfig.getQueueSize() != config.getQueueSize()) 254 { 255 ccr.setAdminActionRequired(true); 256 } 257 258 currentConfig = config; 259 } 260 } 261 catch(Exception e) 262 { 263 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 264 ccr.addMessage(ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( 265 config.dn(), stackTraceToSingleLineString(e))); 266 } 267 268 return ccr; 269 } 270 271 private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter writer, FileBasedDebugLogPublisherCfg config) 272 { 273 String name = "Asynchronous Text Writer for " + config.dn(); 274 return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), writer); 275 } 276 277 private void configure(MultifileTextWriter mfWriter, FileBasedDebugLogPublisherCfg config) throws DirectoryException 278 { 279 FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 280 boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous(); 281 282 File logFile = getLogFile(config); 283 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 284 285 mfWriter.setNamingPolicy(fnPolicy); 286 mfWriter.setFilePermissions(perm); 287 mfWriter.setAppend(config.isAppend()); 288 mfWriter.setAutoFlush(writerAutoFlush); 289 mfWriter.setBufferSize((int)config.getBufferSize()); 290 mfWriter.setInterval(config.getTimeInterval()); 291 292 mfWriter.removeAllRetentionPolicies(); 293 mfWriter.removeAllRotationPolicies(); 294 for(DN dn : config.getRotationPolicyDNs()) 295 { 296 mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 297 } 298 for(DN dn: config.getRetentionPolicyDNs()) 299 { 300 mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 301 } 302 } 303 304 private File getLogFile(FileBasedDebugLogPublisherCfg config) 305 { 306 return getFileForPath(config.getLogFile()); 307 } 308 309 private boolean hasAsyncConfigChanged(FileBasedDebugLogPublisherCfg newConfig) 310 { 311 return !currentConfig.dn().equals(newConfig.dn()) 312 && currentConfig.isAutoFlush() != newConfig.isAutoFlush() 313 && currentConfig.getQueueSize() != newConfig.getQueueSize(); 314 } 315 316 private TraceSettings getDefaultSettings(FileBasedDebugLogPublisherCfg config) 317 { 318 return new TraceSettings( 319 TraceSettings.Level.getLevel(true, config.isDefaultDebugExceptionsOnly()), 320 config.isDefaultOmitMethodEntryArguments(), 321 config.isDefaultOmitMethodReturnValue(), 322 config.getDefaultThrowableStackFrames(), 323 config.isDefaultIncludeThrowableCause()); 324 } 325 326 @Override 327 public boolean isConfigurationAddAcceptable(DebugTargetCfg config, 328 List<LocalizableMessage> unacceptableReasons) 329 { 330 return !hasTraceSettings(config.getDebugScope()); 331 } 332 333 @Override 334 public boolean isConfigurationDeleteAcceptable(DebugTargetCfg config, 335 List<LocalizableMessage> unacceptableReasons) 336 { 337 // A delete should always be acceptable. 338 return true; 339 } 340 341 @Override 342 public ConfigChangeResult applyConfigurationAdd(DebugTargetCfg config) 343 { 344 addTraceSettings(config.getDebugScope(), new TraceSettings(config)); 345 346 DebugLogger.updateTracerSettings(); 347 348 return new ConfigChangeResult(); 349 } 350 351 @Override 352 public ConfigChangeResult applyConfigurationDelete(DebugTargetCfg config) 353 { 354 removeTraceSettings(config.getDebugScope()); 355 356 DebugLogger.updateTracerSettings(); 357 358 return new ConfigChangeResult(); 359 } 360 361 @Override 362 public void trace(TraceSettings settings, String signature, 363 String sourceLocation, String msg, StackTraceElement[] stackTrace) 364 { 365 String stack = null; 366 if (stackTrace != null) 367 { 368 stack = DebugStackTraceFormatter.formatStackTrace(stackTrace, 369 settings.getStackDepth()); 370 } 371 publish(signature, sourceLocation, msg, stack); 372 } 373 374 @Override 375 public void traceException(TraceSettings settings, String signature, 376 String sourceLocation, String msg, Throwable ex, 377 StackTraceElement[] stackTrace) 378 { 379 String message = DebugMessageFormatter.format("%s caught={%s}", new Object[] { msg, ex }); 380 381 String stack = null; 382 if (stackTrace != null) 383 { 384 stack = DebugStackTraceFormatter.formatStackTrace(ex, settings.getStackDepth(), 385 settings.isIncludeCause()); 386 } 387 publish(signature, sourceLocation, message, stack); 388 } 389 390 @Override 391 public void close() 392 { 393 writer.shutdown(); 394 395 if(currentConfig != null) 396 { 397 currentConfig.removeFileBasedDebugChangeListener(this); 398 } 399 } 400 401 /** 402 * Publishes a record, optionally performing some "special" work: 403 * - injecting a stack trace into the message 404 * - format the message with argument values 405 */ 406 private void publish(String signature, String sourceLocation, String msg, 407 String stack) 408 { 409 Thread thread = Thread.currentThread(); 410 411 StringBuilder buf = new StringBuilder(); 412 // Emit the timestamp. 413 buf.append("["); 414 buf.append(TimeThread.getLocalTime()); 415 buf.append("] "); 416 417 // Emit the seq num 418 buf.append(globalSequenceNumber++); 419 buf.append(" "); 420 421 // Emit the debug level. 422 buf.append("trace "); 423 424 // Emit thread info. 425 buf.append("thread={"); 426 buf.append(thread.getName()); 427 buf.append("("); 428 buf.append(thread.getId()); 429 buf.append(")} "); 430 431 if(thread instanceof DirectoryThread) 432 { 433 buf.append("threadDetail={"); 434 for (Map.Entry<String, String> entry : 435 ((DirectoryThread) thread).getDebugProperties().entrySet()) 436 { 437 buf.append(entry.getKey()); 438 buf.append("="); 439 buf.append(entry.getValue()); 440 buf.append(" "); 441 } 442 buf.append("} "); 443 } 444 445 // Emit method info. 446 buf.append("method={"); 447 buf.append(signature); 448 buf.append("("); 449 buf.append(sourceLocation); 450 buf.append(")} "); 451 452 // Emit message. 453 buf.append(msg); 454 455 // Emit Stack Trace. 456 if(stack != null) 457 { 458 buf.append("\nStack Trace:\n"); 459 buf.append(stack); 460 } 461 462 writer.writeRecord(buf.toString()); 463 } 464 465 @Override 466 public DN getDN() 467 { 468 if(currentConfig != null) 469 { 470 return currentConfig.dn(); 471 } 472 return null; 473 } 474}