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