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