/** * Add the provided record at the end of this log. * * <p>The record must have a key strictly higher than the key of the last record added. If it is * not the case, the record is not appended and the method returns immediately. * * <p>In order to ensure that record is written out of buffers and persisted to file system, it is * necessary to explicitely call the {@code syncToFileSystem()} method. * * @param record The record to add. * @throws ChangelogException If an error occurs while adding the record to the log. */ public void append(final Record<K, V> record) throws ChangelogException { // If this exclusive lock happens to be a bottleneck : // 1. use a shared lock for appending the record first // 2. switch to an exclusive lock only if rotation is needed // See http://sources.forgerock.org/cru/CR-3548#c27521 for full detail exclusiveLock.lock(); try { if (isClosed) { return; } if (recordIsBreakingKeyOrdering(record)) { logger.info( LocalizableMessage.raw( "Rejecting append to log '%s' for record: [%s], last key appended: [%s]", logPath.getPath(), record, lastAppendedKey != null ? lastAppendedKey : "null")); return; } LogFile<K, V> headLogFile = getHeadLogFile(); if (mustRotate(headLogFile)) { logger.trace( INFO_CHANGELOG_LOG_FILE_ROTATION.get(logPath.getPath(), headLogFile.getSizeInBytes())); rotateHeadLogFile(); headLogFile = getHeadLogFile(); } headLogFile.append(record); lastAppendedKey = record.getKey(); } finally { exclusiveLock.unlock(); } }
private boolean mustRotate(LogFile<K, V> headLogFile) { if (lastAppendedKey == null) { // never rotate an empty file return false; } if (headLogFile.getSizeInBytes() > sizeLimitPerLogFileInBytes) { // rotate because file size exceeded threshold logger.trace( "Rotate log %s due to size: %s", logPath.getPath(), headLogFile.getSizeInBytes()); return true; } if (rotationIntervalInMillis > 0) { // rotate if time limit is reached final long timeElapsed = timeService.since(lastRotationTime); boolean shouldRotate = timeElapsed > rotationIntervalInMillis; if (shouldRotate) { logger.trace( "Rotate log %s due to time: time elapsed %s, rotation interval: %s", logPath.getPath(), timeElapsed, rotationIntervalInMillis); } return shouldRotate; } return false; }