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 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.loggers; 028 029import static org.opends.messages.LoggerMessages.ERR_COMMON_AUDIT_INVALID_TRANSACTION_ID; 030import static org.opends.messages.ConfigMessages.*; 031import static org.opends.server.util.StaticUtils.*; 032 033import java.io.File; 034import java.io.IOException; 035import java.text.SimpleDateFormat; 036import java.util.List; 037import java.util.Set; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.forgerock.i18n.LocalizableMessageBuilder; 041import org.forgerock.opendj.config.server.ConfigChangeResult; 042import org.forgerock.opendj.config.server.ConfigException; 043import org.forgerock.opendj.ldap.ByteString; 044import org.forgerock.opendj.ldap.ResultCode; 045import org.forgerock.util.Utils; 046import org.opends.server.admin.server.ConfigurationChangeListener; 047import org.opends.server.admin.std.meta.FileBasedAccessLogPublisherCfgDefn.LogFormat; 048import org.opends.server.admin.std.server.FileBasedAccessLogPublisherCfg; 049import org.opends.server.api.ClientConnection; 050import org.opends.server.api.ExtendedOperationHandler; 051import org.opends.server.controls.TransactionIdControl; 052import org.opends.server.core.*; 053import org.opends.server.types.*; 054import org.opends.server.util.StaticUtils; 055import org.opends.server.util.TimeThread; 056 057/** This class provides the implementation of the access logger used by the directory server. */ 058public final class TextAccessLogPublisher extends 059 AbstractTextAccessLogPublisher<FileBasedAccessLogPublisherCfg> implements 060 ConfigurationChangeListener<FileBasedAccessLogPublisherCfg> 061{ 062 /** The category to use when logging responses. */ 063 private static final String CATEGORY_RESPONSE = "RES"; 064 /** The category to use when logging requests. */ 065 private static final String CATEGORY_REQUEST = "REQ"; 066 067 /** 068 * Returns an instance of the text access log publisher that will print all 069 * messages to the provided writer. This is used to print the messages to the 070 * console when the server starts up. 071 * 072 * @param writer 073 * The text writer where the message will be written to. 074 * @param suppressInternal 075 * Indicates whether to suppress internal operations. 076 * @return The instance of the text error log publisher that will print all 077 * messages to standard out. 078 */ 079 public static TextAccessLogPublisher getStartupTextAccessPublisher( 080 final TextWriter writer, final boolean suppressInternal) 081 { 082 final TextAccessLogPublisher startupPublisher = new TextAccessLogPublisher(); 083 startupPublisher.writer = writer; 084 startupPublisher.buildFilters(suppressInternal); 085 return startupPublisher; 086 } 087 088 private TextWriter writer; 089 private FileBasedAccessLogPublisherCfg cfg; 090 private boolean isCombinedMode; 091 private boolean includeControlOIDs; 092 private String timeStampFormat = "dd/MMM/yyyy:HH:mm:ss Z"; 093 private ServerContext serverContext; 094 095 @Override 096 public ConfigChangeResult applyConfigurationChange(FileBasedAccessLogPublisherCfg config) 097 { 098 final ConfigChangeResult ccr = new ConfigChangeResult(); 099 100 try 101 { 102 // Determine the writer we are using. If we were writing asynchronously, 103 // we need to modify the underlying writer. 104 TextWriter currentWriter; 105 if (writer instanceof AsynchronousTextWriter) 106 { 107 currentWriter = ((AsynchronousTextWriter) writer).getWrappedWriter(); 108 } 109 else 110 { 111 currentWriter = writer; 112 } 113 114 if (currentWriter instanceof MultifileTextWriter) 115 { 116 final MultifileTextWriter mfWriter = (MultifileTextWriter) currentWriter; 117 configure(mfWriter, config); 118 119 if (config.isAsynchronous()) 120 { 121 if (writer instanceof AsynchronousTextWriter) 122 { 123 if (hasAsyncConfigChanged(config)) 124 { 125 // reinstantiate 126 final AsynchronousTextWriter previousWriter = (AsynchronousTextWriter) writer; 127 writer = newAsyncWriter(mfWriter, config); 128 previousWriter.shutdown(false); 129 } 130 } 131 else 132 { 133 // turn async text writer on 134 writer = newAsyncWriter(mfWriter, config); 135 } 136 } 137 else 138 { 139 if (writer instanceof AsynchronousTextWriter) 140 { 141 // asynchronous is being turned off, remove async text writers. 142 final AsynchronousTextWriter previousWriter = (AsynchronousTextWriter) writer; 143 writer = mfWriter; 144 previousWriter.shutdown(false); 145 } 146 } 147 148 if (cfg.isAsynchronous() && config.isAsynchronous() 149 && cfg.getQueueSize() != config.getQueueSize()) 150 { 151 ccr.setAdminActionRequired(true); 152 } 153 154 if (!config.getLogRecordTimeFormat().equals(timeStampFormat)) 155 { 156 TimeThread.removeUserDefinedFormatter(timeStampFormat); 157 timeStampFormat = config.getLogRecordTimeFormat(); 158 } 159 160 cfg = config; 161 isCombinedMode = cfg.getLogFormat() == LogFormat.COMBINED; 162 includeControlOIDs = cfg.isLogControlOids(); 163 } 164 } 165 catch (final Exception e) 166 { 167 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 168 ccr.addMessage(ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get( 169 config.dn(), stackTraceToSingleLineString(e))); 170 } 171 172 return ccr; 173 } 174 175 private void configure(MultifileTextWriter mfWriter, FileBasedAccessLogPublisherCfg config) throws DirectoryException 176 { 177 final FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 178 final boolean writerAutoFlush = config.isAutoFlush() && !config.isAsynchronous(); 179 180 final File logFile = getLogFile(config); 181 final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 182 183 mfWriter.setNamingPolicy(fnPolicy); 184 mfWriter.setFilePermissions(perm); 185 mfWriter.setAppend(config.isAppend()); 186 mfWriter.setAutoFlush(writerAutoFlush); 187 mfWriter.setBufferSize((int) config.getBufferSize()); 188 mfWriter.setInterval(config.getTimeInterval()); 189 190 mfWriter.removeAllRetentionPolicies(); 191 mfWriter.removeAllRotationPolicies(); 192 for (final DN dn : config.getRotationPolicyDNs()) 193 { 194 mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 195 } 196 for (final DN dn : config.getRetentionPolicyDNs()) 197 { 198 mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 199 } 200 } 201 202 private File getLogFile(final FileBasedAccessLogPublisherCfg config) 203 { 204 return getFileForPath(config.getLogFile()); 205 } 206 207 @Override 208 public void initializeLogPublisher(final FileBasedAccessLogPublisherCfg cfg, ServerContext serverContext) 209 throws ConfigException, InitializationException 210 { 211 this.serverContext = serverContext; 212 final File logFile = getLogFile(cfg); 213 final FileNamingPolicy fnPolicy = new TimeStampNaming(logFile); 214 215 try 216 { 217 final FilePermission perm = FilePermission.decodeUNIXMode(cfg.getLogFilePermissions()); 218 final LogPublisherErrorHandler errorHandler = new LogPublisherErrorHandler(cfg.dn()); 219 final boolean writerAutoFlush = cfg.isAutoFlush() && !cfg.isAsynchronous(); 220 221 final MultifileTextWriter theWriter = new MultifileTextWriter( 222 "Multifile Text Writer for " + cfg.dn(), 223 cfg.getTimeInterval(), fnPolicy, perm, errorHandler, "UTF-8", 224 writerAutoFlush, cfg.isAppend(), (int) cfg.getBufferSize()); 225 226 // Validate retention and rotation policies. 227 for (final DN dn : cfg.getRotationPolicyDNs()) 228 { 229 theWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn)); 230 } 231 for (final DN dn : cfg.getRetentionPolicyDNs()) 232 { 233 theWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn)); 234 } 235 236 if (cfg.isAsynchronous()) 237 { 238 this.writer = newAsyncWriter(theWriter, cfg); 239 } 240 else 241 { 242 this.writer = theWriter; 243 } 244 } 245 catch (final DirectoryException e) 246 { 247 throw new InitializationException( 248 ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(cfg.dn(), e), e); 249 } 250 catch (final IOException e) 251 { 252 throw new InitializationException( 253 ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile, cfg.dn(), e), e); 254 } 255 256 initializeFilters(cfg); 257 258 this.cfg = cfg; 259 isCombinedMode = cfg.getLogFormat() == LogFormat.COMBINED; 260 includeControlOIDs = cfg.isLogControlOids(); 261 timeStampFormat = cfg.getLogRecordTimeFormat(); 262 263 cfg.addFileBasedAccessChangeListener(this); 264 } 265 266 private boolean hasAsyncConfigChanged(FileBasedAccessLogPublisherCfg newConfig) 267 { 268 return hasParallelConfigChanged(newConfig) && cfg.getQueueSize() != newConfig.getQueueSize(); 269 } 270 271 private boolean hasParallelConfigChanged(FileBasedAccessLogPublisherCfg newConfig) 272 { 273 return !cfg.dn().equals(newConfig.dn()) && cfg.isAutoFlush() != newConfig.isAutoFlush(); 274 } 275 276 private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter mfWriter, FileBasedAccessLogPublisherCfg config) 277 { 278 String name = "Asynchronous Text Writer for " + config.dn(); 279 return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter); 280 } 281 282 @Override 283 public boolean isConfigurationAcceptable( 284 final FileBasedAccessLogPublisherCfg configuration, 285 final List<LocalizableMessage> unacceptableReasons) 286 { 287 return isFilterConfigurationAcceptable(configuration, unacceptableReasons) 288 && isConfigurationChangeAcceptable(configuration, unacceptableReasons); 289 } 290 291 @Override 292 public boolean isConfigurationChangeAcceptable( 293 final FileBasedAccessLogPublisherCfg config, 294 final List<LocalizableMessage> unacceptableReasons) 295 { 296 // Validate the time-stamp formatter. 297 final String formatString = config.getLogRecordTimeFormat(); 298 try 299 { 300 new SimpleDateFormat(formatString); 301 } 302 catch (final Exception e) 303 { 304 final LocalizableMessage message = ERR_CONFIG_LOGGING_INVALID_TIME_FORMAT.get(formatString); 305 unacceptableReasons.add(message); 306 return false; 307 } 308 309 // Make sure the permission is valid. 310 try 311 { 312 final FilePermission filePerm = FilePermission.decodeUNIXMode(config.getLogFilePermissions()); 313 if (!filePerm.isOwnerWritable()) 314 { 315 final LocalizableMessage message = ERR_CONFIG_LOGGING_INSANE_MODE.get(config.getLogFilePermissions()); 316 unacceptableReasons.add(message); 317 return false; 318 } 319 } 320 catch (final DirectoryException e) 321 { 322 unacceptableReasons.add(ERR_CONFIG_LOGGING_MODE_INVALID.get(config.getLogFilePermissions(), e)); 323 return false; 324 } 325 326 return true; 327 } 328 329 @Override 330 public void logAbandonRequest(final AbandonOperation abandonOperation) 331 { 332 if (isCombinedMode || !isRequestLoggable(abandonOperation)) 333 { 334 return; 335 } 336 337 final StringBuilder buffer = new StringBuilder(100); 338 appendHeader(abandonOperation, "ABANDON", CATEGORY_REQUEST, buffer); 339 appendAbandonRequest(abandonOperation, buffer); 340 writer.writeRecord(buffer.toString()); 341 } 342 343 @Override 344 public void logAbandonResult(final AbandonOperation abandonOperation) 345 { 346 if (!isResponseLoggable(abandonOperation)) 347 { 348 return; 349 } 350 351 final StringBuilder buffer = new StringBuilder(100); 352 appendHeader(abandonOperation, "ABANDON", CATEGORY_RESPONSE, buffer); 353 if (isCombinedMode) 354 { 355 appendAbandonRequest(abandonOperation, buffer); 356 } 357 appendResultCodeAndMessage(buffer, abandonOperation); 358 logAdditionalLogItems(abandonOperation, buffer); 359 appendEtime(buffer, abandonOperation); 360 writer.writeRecord(buffer.toString()); 361 } 362 363 @Override 364 public void logAddRequest(final AddOperation addOperation) 365 { 366 if (isCombinedMode || !isRequestLoggable(addOperation)) 367 { 368 return; 369 } 370 371 final StringBuilder buffer = new StringBuilder(100); 372 appendHeader(addOperation, "ADD", CATEGORY_REQUEST, buffer); 373 appendAddRequest(addOperation, buffer); 374 writer.writeRecord(buffer.toString()); 375 } 376 377 @Override 378 public void logAddResponse(final AddOperation addOperation) 379 { 380 if (!isResponseLoggable(addOperation)) 381 { 382 return; 383 } 384 385 final StringBuilder buffer = new StringBuilder(100); 386 appendHeader(addOperation, "ADD", CATEGORY_RESPONSE, buffer); 387 if (isCombinedMode) 388 { 389 appendAddRequest(addOperation, buffer); 390 } 391 appendResultCodeAndMessage(buffer, addOperation); 392 logAdditionalLogItems(addOperation, buffer); 393 appendLabelIfNotNull(buffer, "authzDN", addOperation.getProxiedAuthorizationDN()); 394 appendEtime(buffer, addOperation); 395 writer.writeRecord(buffer.toString()); 396 } 397 398 @Override 399 public void logBindRequest(final BindOperation bindOperation) 400 { 401 if (isCombinedMode || !isRequestLoggable(bindOperation)) 402 { 403 return; 404 } 405 406 final StringBuilder buffer = new StringBuilder(100); 407 appendHeader(bindOperation, "BIND", CATEGORY_REQUEST, buffer); 408 appendBindRequest(bindOperation, buffer); 409 writer.writeRecord(buffer.toString()); 410 } 411 412 @Override 413 public void logBindResponse(final BindOperation bindOperation) 414 { 415 if (!isResponseLoggable(bindOperation)) 416 { 417 return; 418 } 419 420 final StringBuilder buffer = new StringBuilder(100); 421 appendHeader(bindOperation, "BIND", CATEGORY_RESPONSE, buffer); 422 if (isCombinedMode) 423 { 424 appendBindRequest(bindOperation, buffer); 425 } 426 appendResultCodeAndMessage(buffer, bindOperation); 427 428 final LocalizableMessage failureMessage = bindOperation.getAuthFailureReason(); 429 if (failureMessage != null) 430 { 431 // this code path is mutually exclusive with the if result code is success down below 432 appendLabel(buffer, "authFailureReason", failureMessage); 433 if (bindOperation.getSASLMechanism() != null 434 && bindOperation.getSASLAuthUserEntry() != null) 435 { // SASL bind and we have successfully found a user entry for auth 436 appendLabel(buffer, "authDN", bindOperation.getSASLAuthUserEntry().getName()); 437 } 438 else 439 { // SASL bind failed to find user entry for auth or simple bind 440 appendLabel(buffer, "authDN", bindOperation.getRawBindDN()); 441 } 442 } 443 444 logAdditionalLogItems(bindOperation, buffer); 445 446 if (bindOperation.getResultCode() == ResultCode.SUCCESS) 447 { 448 // this code path is mutually exclusive with the if failure message that exists just above 449 final AuthenticationInfo authInfo = bindOperation.getAuthenticationInfo(); 450 if (authInfo != null) 451 { 452 final DN authDN = authInfo.getAuthenticationDN(); 453 if (authDN != null) 454 { 455 appendLabel(buffer, "authDN", authDN); 456 457 final DN authzDN = authInfo.getAuthorizationDN(); 458 if (!authDN.equals(authzDN)) 459 { 460 appendLabel(buffer, "authzDN", authzDN); 461 } 462 } 463 else 464 { 465 buffer.append(" authDN=\"\""); 466 } 467 } 468 } 469 470 appendEtime(buffer, bindOperation); 471 472 writer.writeRecord(buffer.toString()); 473 } 474 475 @Override 476 public void logCompareRequest(final CompareOperation compareOperation) 477 { 478 if (isCombinedMode || !isRequestLoggable(compareOperation)) 479 { 480 return; 481 } 482 483 final StringBuilder buffer = new StringBuilder(100); 484 appendHeader(compareOperation, "COMPARE", CATEGORY_REQUEST, buffer); 485 appendCompareRequest(compareOperation, buffer); 486 writer.writeRecord(buffer.toString()); 487 } 488 489 @Override 490 public void logCompareResponse(final CompareOperation compareOperation) 491 { 492 if (!isResponseLoggable(compareOperation)) 493 { 494 return; 495 } 496 497 final StringBuilder buffer = new StringBuilder(100); 498 appendHeader(compareOperation, "COMPARE", CATEGORY_RESPONSE, buffer); 499 if (isCombinedMode) 500 { 501 appendCompareRequest(compareOperation, buffer); 502 } 503 appendResultCodeAndMessage(buffer, compareOperation); 504 logAdditionalLogItems(compareOperation, buffer); 505 appendLabelIfNotNull(buffer, "authzDN", compareOperation.getProxiedAuthorizationDN()); 506 appendEtime(buffer, compareOperation); 507 writer.writeRecord(buffer.toString()); 508 } 509 510 @Override 511 public void logConnect(final ClientConnection clientConnection) 512 { 513 if (!isConnectLoggable(clientConnection)) 514 { 515 return; 516 } 517 518 final long connectionID = clientConnection.getConnectionID(); 519 final StringBuilder buffer = new StringBuilder(100); 520 buffer.append('[').append(TimeThread.getUserDefinedTime(timeStampFormat)).append(']'); 521 buffer.append(" CONNECT conn=").append(connectionID); 522 buffer.append(" from=").append(clientConnection.getClientHostPort()); 523 buffer.append(" to=").append(clientConnection.getServerHostPort()); 524 buffer.append(" protocol=").append(clientConnection.getProtocol()); 525 526 writer.writeRecord(buffer.toString()); 527 } 528 529 @Override 530 public void logDeleteRequest(final DeleteOperation deleteOperation) 531 { 532 if (isCombinedMode || !isRequestLoggable(deleteOperation)) 533 { 534 return; 535 } 536 537 final StringBuilder buffer = new StringBuilder(100); 538 appendHeader(deleteOperation, "DELETE", CATEGORY_REQUEST, buffer); 539 appendDeleteRequest(deleteOperation, buffer); 540 writer.writeRecord(buffer.toString()); 541 } 542 543 @Override 544 public void logDeleteResponse(final DeleteOperation deleteOperation) 545 { 546 if (!isResponseLoggable(deleteOperation)) 547 { 548 return; 549 } 550 551 final StringBuilder buffer = new StringBuilder(100); 552 appendHeader(deleteOperation, "DELETE", CATEGORY_RESPONSE, buffer); 553 if (isCombinedMode) 554 { 555 appendDeleteRequest(deleteOperation, buffer); 556 } 557 appendResultCodeAndMessage(buffer, deleteOperation); 558 logAdditionalLogItems(deleteOperation, buffer); 559 appendLabelIfNotNull(buffer, "authzDN", deleteOperation.getProxiedAuthorizationDN()); 560 appendEtime(buffer, deleteOperation); 561 writer.writeRecord(buffer.toString()); 562 } 563 564 @Override 565 public void logDisconnect(final ClientConnection clientConnection, 566 final DisconnectReason disconnectReason, final LocalizableMessage message) 567 { 568 if (!isDisconnectLoggable(clientConnection)) 569 { 570 return; 571 } 572 573 final long connectionID = clientConnection.getConnectionID(); 574 final StringBuilder buffer = new StringBuilder(100); 575 buffer.append('[').append(TimeThread.getUserDefinedTime(timeStampFormat)).append(']'); 576 buffer.append(" DISCONNECT conn=").append(connectionID); 577 appendLabel(buffer, "reason", disconnectReason); 578 appendLabelIfNotNull(buffer, "msg", message); 579 580 writer.writeRecord(buffer.toString()); 581 } 582 583 @Override 584 public void logExtendedRequest(final ExtendedOperation extendedOperation) 585 { 586 if (isCombinedMode || !isRequestLoggable(extendedOperation)) 587 { 588 return; 589 } 590 591 final StringBuilder buffer = new StringBuilder(100); 592 appendHeader(extendedOperation, "EXTENDED", CATEGORY_REQUEST, buffer); 593 appendExtendedRequest(extendedOperation, buffer); 594 writer.writeRecord(buffer.toString()); 595 } 596 597 @Override 598 public void logExtendedResponse(final ExtendedOperation extendedOperation) 599 { 600 if (!isResponseLoggable(extendedOperation)) 601 { 602 return; 603 } 604 605 final StringBuilder buffer = new StringBuilder(100); 606 appendHeader(extendedOperation, "EXTENDED", CATEGORY_RESPONSE, buffer); 607 if (isCombinedMode) 608 { 609 appendExtendedRequest(extendedOperation, buffer); 610 } 611 612 final String oid = extendedOperation.getResponseOID(); 613 if (oid != null) 614 { 615 final ExtendedOperationHandler<?> extOpHandler = DirectoryServer 616 .getExtendedOperationHandler(oid); 617 if (extOpHandler != null) 618 { 619 String name = extOpHandler.getExtendedOperationName(); 620 appendLabelIfNotNull(buffer, "name", name); 621 } 622 appendLabel(buffer, "oid", oid); 623 } 624 appendResultCodeAndMessage(buffer, extendedOperation); 625 logAdditionalLogItems(extendedOperation, buffer); 626 appendEtime(buffer, extendedOperation); 627 628 writer.writeRecord(buffer.toString()); 629 } 630 631 @Override 632 public void logModifyDNRequest(final ModifyDNOperation modifyDNOperation) 633 { 634 if (isCombinedMode || !isRequestLoggable(modifyDNOperation)) 635 { 636 return; 637 } 638 639 final StringBuilder buffer = new StringBuilder(100); 640 appendHeader(modifyDNOperation, "MODIFYDN", CATEGORY_REQUEST, buffer); 641 appendModifyDNRequest(modifyDNOperation, buffer); 642 writer.writeRecord(buffer.toString()); 643 } 644 645 @Override 646 public void logModifyDNResponse(final ModifyDNOperation modifyDNOperation) 647 { 648 if (!isResponseLoggable(modifyDNOperation)) 649 { 650 return; 651 } 652 653 final StringBuilder buffer = new StringBuilder(100); 654 appendHeader(modifyDNOperation, "MODIFYDN", CATEGORY_RESPONSE, buffer); 655 if (isCombinedMode) 656 { 657 appendModifyDNRequest(modifyDNOperation, buffer); 658 } 659 appendResultCodeAndMessage(buffer, modifyDNOperation); 660 logAdditionalLogItems(modifyDNOperation, buffer); 661 appendLabelIfNotNull(buffer, "authzDN", modifyDNOperation.getProxiedAuthorizationDN()); 662 appendEtime(buffer, modifyDNOperation); 663 writer.writeRecord(buffer.toString()); 664 } 665 666 @Override 667 public void logModifyRequest(final ModifyOperation modifyOperation) 668 { 669 if (isCombinedMode || !isRequestLoggable(modifyOperation)) 670 { 671 return; 672 } 673 674 final StringBuilder buffer = new StringBuilder(100); 675 appendHeader(modifyOperation, "MODIFY", CATEGORY_REQUEST, buffer); 676 appendModifyRequest(modifyOperation, buffer); 677 writer.writeRecord(buffer.toString()); 678 } 679 680 @Override 681 public void logModifyResponse(final ModifyOperation modifyOperation) 682 { 683 if (!isResponseLoggable(modifyOperation)) 684 { 685 return; 686 } 687 688 final StringBuilder buffer = new StringBuilder(100); 689 appendHeader(modifyOperation, "MODIFY", CATEGORY_RESPONSE, buffer); 690 if (isCombinedMode) 691 { 692 appendModifyRequest(modifyOperation, buffer); 693 } 694 appendResultCodeAndMessage(buffer, modifyOperation); 695 logAdditionalLogItems(modifyOperation, buffer); 696 appendLabelIfNotNull(buffer, "authzDN", modifyOperation.getProxiedAuthorizationDN()); 697 appendEtime(buffer, modifyOperation); 698 writer.writeRecord(buffer.toString()); 699 } 700 701 @Override 702 public void logSearchRequest(final SearchOperation searchOperation) 703 { 704 if (isCombinedMode || !isRequestLoggable(searchOperation)) 705 { 706 return; 707 } 708 709 final StringBuilder buffer = new StringBuilder(192); 710 appendHeader(searchOperation, "SEARCH", CATEGORY_REQUEST, buffer); 711 appendSearchRequest(searchOperation, buffer); 712 writer.writeRecord(buffer.toString()); 713 } 714 715 @Override 716 public void logSearchResultDone(final SearchOperation searchOperation) 717 { 718 if (!isResponseLoggable(searchOperation)) 719 { 720 return; 721 } 722 723 final StringBuilder buffer = new StringBuilder(128); 724 appendHeader(searchOperation, "SEARCH", CATEGORY_RESPONSE, buffer); 725 if (isCombinedMode) 726 { 727 appendSearchRequest(searchOperation, buffer); 728 } 729 appendResultCodeAndMessage(buffer, searchOperation); 730 buffer.append(" nentries=").append(searchOperation.getEntriesSent()); 731 logAdditionalLogItems(searchOperation, buffer); 732 appendLabelIfNotNull(buffer, "authzDN", searchOperation.getProxiedAuthorizationDN()); 733 appendEtime(buffer, searchOperation); 734 writer.writeRecord(buffer.toString()); 735 } 736 737 @Override 738 public void logUnbind(final UnbindOperation unbindOperation) 739 { 740 if (!isRequestLoggable(unbindOperation)) 741 { 742 return; 743 } 744 745 final StringBuilder buffer = new StringBuilder(100); 746 appendHeader(unbindOperation, "UNBIND", CATEGORY_REQUEST, buffer); 747 if (unbindOperation.isSynchronizationOperation()) 748 { 749 buffer.append(" type=synchronization"); 750 } 751 752 writer.writeRecord(buffer.toString()); 753 } 754 755 @Override 756 protected void close0() 757 { 758 writer.shutdown(); 759 TimeThread.removeUserDefinedFormatter(timeStampFormat); 760 if (cfg != null) 761 { 762 cfg.removeFileBasedAccessChangeListener(this); 763 } 764 } 765 766 private void appendAbandonRequest(final AbandonOperation abandonOperation, final StringBuilder buffer) 767 { 768 buffer.append(" idToAbandon="); 769 buffer.append(abandonOperation.getIDToAbandon()); 770 appendRequestControls(abandonOperation, buffer); 771 if (abandonOperation.isSynchronizationOperation()) 772 { 773 buffer.append(" type=synchronization"); 774 } 775 } 776 777 private void appendAddRequest(final AddOperation addOperation, final StringBuilder buffer) 778 { 779 appendLabel(buffer, "dn", addOperation.getRawEntryDN()); 780 appendRequestControls(addOperation, buffer); 781 if (addOperation.isSynchronizationOperation()) 782 { 783 buffer.append(" type=synchronization"); 784 } 785 } 786 787 private void appendBindRequest(final BindOperation bindOperation, final StringBuilder buffer) 788 { 789 final String protocolVersion = bindOperation.getProtocolVersion(); 790 if (protocolVersion != null) 791 { 792 buffer.append(" version="); 793 buffer.append(protocolVersion); 794 } 795 796 switch (bindOperation.getAuthenticationType()) 797 { 798 case SIMPLE: 799 buffer.append(" type=SIMPLE"); 800 break; 801 case SASL: 802 buffer.append(" type=SASL mechanism=").append(bindOperation.getSASLMechanism()); 803 break; 804 default: 805 buffer.append(" type=").append(bindOperation.getAuthenticationType()); 806 break; 807 } 808 809 appendLabel(buffer, "dn", bindOperation.getRawBindDN()); 810 appendRequestControls(bindOperation, buffer); 811 if (bindOperation.isSynchronizationOperation()) 812 { 813 buffer.append(" type=synchronization"); 814 } 815 } 816 817 private void appendCompareRequest(final CompareOperation compareOperation, final StringBuilder buffer) 818 { 819 appendLabel(buffer, "dn", compareOperation.getRawEntryDN()); 820 buffer.append(" attr="); 821 buffer.append(compareOperation.getAttributeType().getNameOrOID()); 822 appendRequestControls(compareOperation, buffer); 823 if (compareOperation.isSynchronizationOperation()) 824 { 825 buffer.append(" type=synchronization"); 826 } 827 } 828 829 private void appendDeleteRequest(final DeleteOperation deleteOperation, final StringBuilder buffer) 830 { 831 appendLabel(buffer, "dn", deleteOperation.getRawEntryDN()); 832 appendRequestControls(deleteOperation, buffer); 833 if (deleteOperation.isSynchronizationOperation()) 834 { 835 buffer.append(" type=synchronization"); 836 } 837 } 838 839 private void appendExtendedRequest(final ExtendedOperation extendedOperation, 840 final StringBuilder buffer) 841 { 842 final String oid = extendedOperation.getRequestOID(); 843 final ExtendedOperationHandler<?> extOpHandler = DirectoryServer.getExtendedOperationHandler(oid); 844 if (extOpHandler != null) 845 { 846 final String name = extOpHandler.getExtendedOperationName(); 847 appendLabelIfNotNull(buffer, "name", name); 848 } 849 appendLabel(buffer, "oid", oid); 850 appendRequestControls(extendedOperation, buffer); 851 if (extendedOperation.isSynchronizationOperation()) 852 { 853 buffer.append(" type=synchronization"); 854 } 855 } 856 857 private void appendLabel(final StringBuilder buffer, final String label, final Object obj) 858 { 859 buffer.append(' ').append(label).append("=\""); 860 if (obj != null) 861 { 862 buffer.append(obj); 863 } 864 buffer.append('\"'); 865 } 866 867 private void appendLabelIfNotNull(final StringBuilder buffer, final String label, final Object obj) 868 { 869 if (obj != null) 870 { 871 appendLabel(buffer, label, obj); 872 } 873 } 874 875 private void appendResultCodeAndMessage(StringBuilder buffer, Operation operation) 876 { 877 buffer.append(" result="); 878 buffer.append(operation.getResultCode().intValue()); 879 880 final LocalizableMessageBuilder msg = operation.getErrorMessage(); 881 if (msg != null && msg.length() > 0) 882 { 883 appendLabel(buffer, "message", msg); 884 } 885 886 if (operation.getMaskedResultCode() != null) 887 { 888 buffer.append(" maskedResult="); 889 buffer.append(operation.getMaskedResultCode().intValue()); 890 } 891 final LocalizableMessageBuilder maskedMsg = operation.getMaskedErrorMessage(); 892 if (maskedMsg != null && maskedMsg.length() > 0) 893 { 894 appendLabel(buffer, "maskedMessage", maskedMsg); 895 } 896 } 897 898 private void appendEtime(final StringBuilder buffer, final Operation operation) 899 { 900 buffer.append(" etime="); 901 // the server can be configured to log processing time as nanos xor millis 902 long etime = operation.getProcessingNanoTime(); 903 if (etime <= -1) 904 { 905 // if it is not configured for nanos, then use millis. 906 etime = operation.getProcessingTime(); 907 } 908 buffer.append(etime); 909 } 910 911 /** Appends the common log header information to the provided buffer. */ 912 private void appendHeader(final Operation operation, final String opType, 913 final String category, final StringBuilder buffer) 914 { 915 buffer.append('[').append(TimeThread.getUserDefinedTime(timeStampFormat)).append("] "); 916 buffer.append(opType); 917 if (!isCombinedMode) 918 { 919 buffer.append(' ').append(category); 920 } 921 buffer.append(" conn=").append(operation.getConnectionID()); 922 buffer.append(" op=").append(operation.getOperationID()); 923 buffer.append(" msgID=").append(operation.getMessageID()); 924 appendTransactionId(operation, buffer); 925 } 926 927 private void appendModifyDNRequest(final ModifyDNOperation modifyDNOperation, 928 final StringBuilder buffer) 929 { 930 appendLabel(buffer, "dn", modifyDNOperation.getRawEntryDN()); 931 appendLabel(buffer, "newRDN", modifyDNOperation.getRawNewRDN()); 932 appendLabel(buffer, "deleteOldRDN", modifyDNOperation.deleteOldRDN()); 933 934 final ByteString newSuperior = modifyDNOperation.getRawNewSuperior(); 935 appendLabelIfNotNull(buffer, "newSuperior", newSuperior); 936 appendRequestControls(modifyDNOperation, buffer); 937 if (modifyDNOperation.isSynchronizationOperation()) 938 { 939 buffer.append(" type=synchronization"); 940 } 941 } 942 943 private void appendModifyRequest(final ModifyOperation modifyOperation, final StringBuilder buffer) 944 { 945 appendLabel(buffer, "dn", modifyOperation.getRawEntryDN()); 946 appendRequestControls(modifyOperation, buffer); 947 if (modifyOperation.isSynchronizationOperation()) 948 { 949 buffer.append(" type=synchronization"); 950 } 951 } 952 953 private void appendTransactionId(Operation operation, final StringBuilder buffer) 954 { 955 // In test context, serverContext may be null 956 if (serverContext != null && serverContext.getCommonAudit().shouldTrustTransactionIds()) 957 { 958 String transactionId = getTransactionIdFromControl(operation); 959 if (transactionId != null) 960 { 961 buffer.append(" transactionId=").append(transactionId); 962 } 963 } 964 } 965 966 private String getTransactionIdFromControl(Operation operation) 967 { 968 try 969 { 970 TransactionIdControl control = operation.getRequestControl(TransactionIdControl.DECODER); 971 return control != null ? control.getTransactionId() : null; 972 } 973 catch (DirectoryException e) 974 { 975 logger.error(ERR_COMMON_AUDIT_INVALID_TRANSACTION_ID.get(StaticUtils.stackTraceToSingleLineString(e))); 976 } 977 return null; 978 } 979 980 private void appendRequestControls(final Operation operation, final StringBuilder buffer) 981 { 982 appendControls(buffer, " requestControls=", operation.getRequestControls()); 983 } 984 985 private void appendResponseControls(final Operation operation, final StringBuilder buffer) 986 { 987 appendControls(buffer, " responseControls=", operation.getResponseControls()); 988 } 989 990 private void appendControls(final StringBuilder buffer, String label, List<Control> controls) 991 { 992 if (includeControlOIDs && !controls.isEmpty()) 993 { 994 buffer.append(label); 995 boolean isFirst = true; 996 for (final Control control : controls) 997 { 998 if (!isFirst) 999 { 1000 buffer.append(","); 1001 } 1002 buffer.append(control.getOID()); 1003 isFirst = false; 1004 } 1005 } 1006 } 1007 1008 private void appendSearchRequest(final SearchOperation searchOperation, final StringBuilder buffer) 1009 { 1010 appendLabel(buffer, "base", searchOperation.getRawBaseDN()); 1011 buffer.append(" scope="); 1012 buffer.append(searchOperation.getScope()); 1013 buffer.append(" filter=\""); 1014 searchOperation.getRawFilter().toString(buffer); 1015 1016 final Set<String> attrs = searchOperation.getAttributes(); 1017 if (attrs == null || attrs.isEmpty()) 1018 { 1019 buffer.append("\" attrs=\"ALL\""); 1020 } 1021 else 1022 { 1023 buffer.append("\" attrs=\""); 1024 Utils.joinAsString(buffer, ",", attrs); 1025 buffer.append("\""); 1026 } 1027 appendRequestControls(searchOperation, buffer); 1028 if (searchOperation.isSynchronizationOperation()) 1029 { 1030 buffer.append(" type=synchronization"); 1031 } 1032 } 1033 1034 /** Appends additional log items to the provided builder. */ 1035 private void logAdditionalLogItems(final Operation operation, final StringBuilder builder) 1036 { 1037 appendResponseControls(operation, builder); 1038 for (final AdditionalLogItem item : operation.getAdditionalLogItems()) 1039 { 1040 builder.append(' '); 1041 item.toString(builder); 1042 } 1043 } 1044}