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