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 2012-2015 ForgeRock AS. 026 */ 027package org.opends.server.loggers; 028 029import static org.opends.messages.ConfigMessages.*; 030import static org.opends.messages.LoggerMessages.*; 031import static org.opends.server.util.ServerConstants.*; 032import static org.opends.server.util.StaticUtils.*; 033 034import java.io.File; 035import java.io.IOException; 036import java.util.Arrays; 037import java.util.HashSet; 038import java.util.List; 039import java.util.Set; 040import java.util.StringTokenizer; 041 042import org.forgerock.i18n.LocalizableMessage; 043import org.forgerock.opendj.config.server.ConfigException; 044import org.opends.messages.Severity; 045import org.opends.server.admin.server.ConfigurationChangeListener; 046import org.opends.server.admin.std.meta.ErrorLogPublisherCfgDefn; 047import org.opends.server.admin.std.server.FileBasedErrorLogPublisherCfg; 048import org.opends.server.core.DirectoryServer; 049import org.opends.server.core.ServerContext; 050import org.forgerock.opendj.config.server.ConfigChangeResult; 051import org.opends.server.types.DN; 052import org.opends.server.types.DirectoryException; 053import org.opends.server.types.FilePermission; 054import org.opends.server.types.InitializationException; 055import org.opends.server.util.StaticUtils; 056import org.opends.server.util.TimeThread; 057 058/** 059 * This class provides an implementation of an error log publisher. 060 */ 061public class TextErrorLogPublisher 062 extends ErrorLogPublisher<FileBasedErrorLogPublisherCfg> 063 implements ConfigurationChangeListener<FileBasedErrorLogPublisherCfg> 064{ 065 private TextWriter writer; 066 private FileBasedErrorLogPublisherCfg currentConfig; 067 068 /** 069 * Returns a new text error log publisher which will print all messages to the 070 * provided writer. This publisher should be used by tools. 071 * 072 * @param writer 073 * The text writer where the message will be written to. 074 * @return A new text error log publisher which will print all messages to the 075 * provided writer. 076 */ 077 public static TextErrorLogPublisher getToolStartupTextErrorPublisher(TextWriter writer) 078 { 079 TextErrorLogPublisher startupPublisher = new TextErrorLogPublisher(); 080 startupPublisher.writer = writer; 081 startupPublisher.defaultSeverities.addAll(Arrays.asList(Severity.values())); 082 return startupPublisher; 083 } 084 085 086 087 /** 088 * Returns a new text error log publisher which will print only notices, 089 * severe warnings and errors, and fatal errors messages to the provided 090 * writer. This less verbose publisher should be used by the directory server 091 * during startup. 092 * 093 * @param writer 094 * The text writer where the message will be written to. 095 * @return A new text error log publisher which will print only notices, 096 * severe warnings and errors, and fatal errors messages to the 097 * provided writer. 098 */ 099 public static TextErrorLogPublisher getServerStartupTextErrorPublisher( 100 TextWriter writer) 101 { 102 TextErrorLogPublisher startupPublisher = new TextErrorLogPublisher(); 103 startupPublisher.writer = writer; 104 startupPublisher.defaultSeverities.addAll(Arrays.asList( 105 Severity.ERROR, Severity.WARNING, Severity.NOTICE)); 106 return startupPublisher; 107 } 108 109 110 111 /** {@inheritDoc} */ 112 @Override 113 public void initializeLogPublisher(FileBasedErrorLogPublisherCfg config, ServerContext serverContext) 114 throws ConfigException, InitializationException 115 { 116 File logFile = getFileForPath(config.getLogFile()); 117 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 118 119 try 120 { 121 FilePermission perm = 122 FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 123 124 LogPublisherErrorHandler errorHandler = 125 new LogPublisherErrorHandler(config.dn()); 126 127 boolean writerAutoFlush = 128 config.isAutoFlush() && !config.isAsynchronous(); 129 130 MultifileTextWriter writer = new MultifileTextWriter("Multifile Text Writer for " + config.dn(), 131 config.getTimeInterval(), 132 fnPolicy, 133 perm, 134 errorHandler, 135 "UTF-8", 136 writerAutoFlush, 137 config.isAppend(), 138 (int)config.getBufferSize()); 139 140 // Validate retention and rotation policies. 141 for(DN dn : config.getRotationPolicyDNs()) 142 { 143 writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 144 } 145 146 for(DN dn: config.getRetentionPolicyDNs()) 147 { 148 writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 149 } 150 151 if(config.isAsynchronous()) 152 { 153 this.writer = new AsynchronousTextWriter("Asynchronous Text Writer for " + config.dn(), 154 config.getQueueSize(), config.isAutoFlush(), writer); 155 } 156 else 157 { 158 this.writer = writer; 159 } 160 } 161 catch(DirectoryException e) 162 { 163 throw new InitializationException( 164 ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(config.dn(), e), e); 165 } 166 catch(IOException e) 167 { 168 throw new InitializationException( 169 ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile, config.dn(), e), e); 170 } 171 172 setDefaultSeverities(config.getDefaultSeverity()); 173 174 for(String overrideSeverity : config.getOverrideSeverity()) 175 { 176 if(overrideSeverity != null) 177 { 178 int equalPos = overrideSeverity.indexOf('='); 179 if (equalPos < 0) 180 { 181 throw new ConfigException(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity)); 182 } 183 184 String category = overrideSeverity.substring(0, equalPos); 185 category = category.replace("-", "_").toUpperCase(); 186 try 187 { 188 Set<Severity> severities = new HashSet<>(); 189 StringTokenizer sevTokenizer = new StringTokenizer(overrideSeverity.substring(equalPos + 1), ","); 190 while (sevTokenizer.hasMoreElements()) 191 { 192 String severityName = sevTokenizer.nextToken(); 193 severityName = severityName.replace("-", "_").toUpperCase(); 194 if (LOG_SEVERITY_ALL.equalsIgnoreCase(severityName)) 195 { 196 severities.add(Severity.ERROR); 197 severities.add(Severity.WARNING); 198 severities.add(Severity.NOTICE); 199 severities.add(Severity.INFORMATION); 200 } 201 else 202 { 203 try 204 { 205 severities.add(Severity.parseString(severityName)); 206 } 207 catch (Exception e) 208 { 209 throw new ConfigException(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName)); 210 } 211 } 212 } 213 definedSeverities.put(category, severities); 214 } 215 catch (Exception e) 216 { 217 throw new ConfigException(WARN_ERROR_LOGGER_INVALID_CATEGORY.get(category)); 218 } 219 } 220 } 221 222 currentConfig = config; 223 224 config.addFileBasedErrorChangeListener(this); 225 } 226 227 228 229 /** {@inheritDoc} */ 230 @Override 231 public boolean isConfigurationAcceptable( 232 FileBasedErrorLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons) 233 { 234 return isConfigurationChangeAcceptable(config, unacceptableReasons); 235 } 236 237 /** {@inheritDoc} */ 238 @Override 239 public boolean isConfigurationChangeAcceptable( 240 FileBasedErrorLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons) 241 { 242 // Make sure the permission is valid. 243 try 244 { 245 FilePermission filePerm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 246 if(!filePerm.isOwnerWritable()) 247 { 248 unacceptableReasons.add(ERR_CONFIG_LOGGING_INSANE_MODE.get(config.getLogFilePermissions())); 249 return false; 250 } 251 } 252 catch(DirectoryException e) 253 { 254 unacceptableReasons.add(ERR_CONFIG_LOGGING_MODE_INVALID.get(config.getLogFilePermissions(), e)); 255 return false; 256 } 257 258 for(String overrideSeverity : config.getOverrideSeverity()) 259 { 260 if(overrideSeverity != null) 261 { 262 int equalPos = overrideSeverity.indexOf('='); 263 if (equalPos < 0) 264 { 265 unacceptableReasons.add(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity)); 266 return false; 267 } 268 269 // No check on category because it can be any value 270 StringTokenizer sevTokenizer = new StringTokenizer(overrideSeverity.substring(equalPos + 1), ","); 271 while (sevTokenizer.hasMoreElements()) 272 { 273 String severityName = sevTokenizer.nextToken(); 274 severityName = severityName.replace("-", "_").toUpperCase(); 275 if (!LOG_SEVERITY_ALL.equalsIgnoreCase(severityName)) 276 { 277 try 278 { 279 Severity.parseString(severityName); 280 } 281 catch (Exception e) 282 { 283 unacceptableReasons.add(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName)); 284 return false; 285 } 286 } 287 } 288 } 289 } 290 return true; 291 } 292 293 /** {@inheritDoc} */ 294 @Override 295 public ConfigChangeResult applyConfigurationChange(FileBasedErrorLogPublisherCfg config) 296 { 297 final ConfigChangeResult ccr = new ConfigChangeResult(); 298 299 setDefaultSeverities(config.getDefaultSeverity()); 300 301 definedSeverities.clear(); 302 for(String overrideSeverity : config.getOverrideSeverity()) 303 { 304 if(overrideSeverity != null) 305 { 306 int equalPos = overrideSeverity.indexOf('='); 307 if (equalPos < 0) 308 { 309 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 310 ccr.addMessage(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity)); 311 } else 312 { 313 String category = overrideSeverity.substring(0, equalPos); 314 category = category.replace("-", "_").toUpperCase(); 315 try 316 { 317 Set<Severity> severities = new HashSet<>(); 318 StringTokenizer sevTokenizer = 319 new StringTokenizer(overrideSeverity.substring(equalPos+1), ","); 320 while (sevTokenizer.hasMoreElements()) 321 { 322 String severityName = sevTokenizer.nextToken(); 323 severityName = severityName.replace("-", "_").toUpperCase(); 324 if(LOG_SEVERITY_ALL.equalsIgnoreCase(severityName)) 325 { 326 severities.add(Severity.ERROR); 327 severities.add(Severity.INFORMATION); 328 severities.add(Severity.WARNING); 329 severities.add(Severity.NOTICE); 330 } 331 else 332 { 333 try 334 { 335 severities.add(Severity.parseString(severityName)); 336 } 337 catch(Exception e) 338 { 339 throw new ConfigException(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName)); 340 } 341 } 342 } 343 definedSeverities.put(category, severities); 344 } 345 catch(Exception e) 346 { 347 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 348 ccr.addMessage(WARN_ERROR_LOGGER_INVALID_CATEGORY.get(category)); 349 } 350 } 351 } 352 } 353 354 File logFile = getFileForPath(config.getLogFile()); 355 FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 356 try 357 { 358 FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 359 360 boolean writerAutoFlush = 361 config.isAutoFlush() && !config.isAsynchronous(); 362 363 TextWriter currentWriter; 364 // Determine the writer we are using. If we were writing asynchronously, 365 // we need to modify the underlying writer. 366 if(writer instanceof AsynchronousTextWriter) 367 { 368 currentWriter = ((AsynchronousTextWriter)writer).getWrappedWriter(); 369 } 370 else 371 { 372 currentWriter = writer; 373 } 374 375 if(currentWriter instanceof MultifileTextWriter) 376 { 377 MultifileTextWriter mfWriter = (MultifileTextWriter)currentWriter; 378 379 mfWriter.setNamingPolicy(fnPolicy); 380 mfWriter.setFilePermissions(perm); 381 mfWriter.setAppend(config.isAppend()); 382 mfWriter.setAutoFlush(writerAutoFlush); 383 mfWriter.setBufferSize((int)config.getBufferSize()); 384 mfWriter.setInterval(config.getTimeInterval()); 385 386 mfWriter.removeAllRetentionPolicies(); 387 mfWriter.removeAllRotationPolicies(); 388 389 for(DN dn : config.getRotationPolicyDNs()) 390 { 391 mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 392 } 393 394 for(DN dn: config.getRetentionPolicyDNs()) 395 { 396 mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 397 } 398 399 if(writer instanceof AsynchronousTextWriter && !config.isAsynchronous()) 400 { 401 // The asynchronous setting is being turned off. 402 AsynchronousTextWriter asyncWriter = (AsynchronousTextWriter)writer; 403 writer = mfWriter; 404 asyncWriter.shutdown(false); 405 } 406 407 if (!(writer instanceof AsynchronousTextWriter) && config.isAsynchronous()) 408 { 409 // The asynchronous setting is being turned on. 410 writer = new AsynchronousTextWriter("Asynchronous Text Writer for " + config.dn(), 411 config.getQueueSize(), config.isAutoFlush(), mfWriter); 412 } 413 414 if (currentConfig.isAsynchronous() 415 && config.isAsynchronous() 416 && currentConfig.getQueueSize() != config.getQueueSize()) 417 { 418 ccr.setAdminActionRequired(true); 419 } 420 421 currentConfig = config; 422 } 423 } 424 catch(Exception e) 425 { 426 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 427 ccr.addMessage(ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( 428 config.dn(), stackTraceToSingleLineString(e))); 429 } 430 431 return ccr; 432 } 433 434 private void setDefaultSeverities(Set<ErrorLogPublisherCfgDefn.DefaultSeverity> defSevs) 435 { 436 defaultSeverities.clear(); 437 if (defSevs.isEmpty()) 438 { 439 defaultSeverities.add(Severity.ERROR); 440 defaultSeverities.add(Severity.WARNING); 441 } 442 else 443 { 444 for (ErrorLogPublisherCfgDefn.DefaultSeverity defSev : defSevs) 445 { 446 String defaultSeverity = defSev.toString(); 447 if (LOG_SEVERITY_ALL.equalsIgnoreCase(defaultSeverity)) 448 { 449 defaultSeverities.add(Severity.ERROR); 450 defaultSeverities.add(Severity.WARNING); 451 defaultSeverities.add(Severity.INFORMATION); 452 defaultSeverities.add(Severity.NOTICE); 453 } 454 else if (LOG_SEVERITY_NONE.equalsIgnoreCase(defaultSeverity)) 455 { 456 // don't add any severity 457 } 458 else 459 { 460 Severity errorSeverity = Severity.parseString(defSev.name()); 461 if (errorSeverity != null) 462 { 463 defaultSeverities.add(errorSeverity); 464 } 465 } 466 } 467 } 468 } 469 470 /** {@inheritDoc} */ 471 @Override 472 public void close() 473 { 474 writer.shutdown(); 475 476 if(currentConfig != null) 477 { 478 currentConfig.removeFileBasedErrorChangeListener(this); 479 } 480 } 481 482 /** {@inheritDoc} */ 483 @Override 484 public void log(String category, Severity severity, LocalizableMessage message, Throwable exception) 485 { 486 if (isEnabledFor(category, severity)) 487 { 488 StringBuilder sb = new StringBuilder(); 489 sb.append("["); 490 sb.append(TimeThread.getLocalTime()); 491 sb.append("] category=").append(category). 492 append(" severity=").append(severity). 493 append(" msgID=").append(message.resourceName()) 494 .append('.') 495 .append(message.ordinal()). 496 append(" msg=").append(message); 497 if (exception != null) 498 { 499 sb.append(" exception=").append( 500 StaticUtils.stackTraceToSingleLineString(exception)); 501 } 502 503 writer.writeRecord(sb.toString()); 504 } 505 } 506 507 /** {@inheritDoc} */ 508 @Override 509 public boolean isEnabledFor(String category, Severity severity) 510 { 511 Set<Severity> severities = definedSeverities.get(category); 512 if (severities == null) 513 { 514 severities = defaultSeverities; 515 } 516 return severities.contains(severity); 517 } 518 519 /** {@inheritDoc} */ 520 @Override 521 public DN getDN() 522 { 523 if(currentConfig != null) 524 { 525 return currentConfig.dn(); 526 } 527 return null; 528 } 529} 530