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