[
https://issues.apache.org/jira/browse/QPID-8770?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18122008#comment-18122008
]
ASF GitHub Bot commented on QPID-8770:
--------------------------------------
dakirily commented on code in PR #451:
URL: https://github.com/apache/qpid-broker-j/pull/451#discussion_r4167158361
##########
broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/DailyTriggeringPolicy.java:
##########
@@ -0,0 +1,281 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.logging.logback;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.time.Instant;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import ch.qos.logback.core.rolling.LengthCounter;
+import ch.qos.logback.core.rolling.LengthCounterBase;
+import ch.qos.logback.core.rolling.TimeBasedFileNamingAndTriggeringPolicy;
+import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
+import ch.qos.logback.core.rolling.helper.ArchiveRemover;
+import ch.qos.logback.core.rolling.helper.Compressor;
+import ch.qos.logback.core.rolling.helper.DateTokenConverter;
+import ch.qos.logback.core.rolling.helper.FileNamePattern;
+import ch.qos.logback.core.rolling.helper.RollingCalendar;
+import ch.qos.logback.core.rolling.helper.SizeAndTimeBasedArchiveRemover;
+import ch.qos.logback.core.spi.ContextAwareBase;
+import ch.qos.logback.core.util.FileSize;
+
+final class DailyTriggeringPolicy<E> extends ContextAwareBase implements
TimeBasedFileNamingAndTriggeringPolicy<E>
+{
+ private static final long RETRY_DELAY_MILLIS =
TimeUnit.SECONDS.toMillis(30);
+
+ private final FileSize _maxFileSize;
+ private final boolean _rollOnRestart;
+ private final LengthCounter _lengthCounter = new LengthCounterBase();
+ private TimeBasedRollingPolicy<E> _rollingPolicy;
+ private FileNamePattern _uncompressedPattern;
+ private RollingCalendar _calendar;
+ private ArchiveRemover _archiveRemover;
+ private String _compressionSuffix;
+ private Instant _period;
+ private long _nextCheck;
+ private long _retryAfter;
+ private int _nextArchiveIndex;
+ private String _elapsedFileName;
+ private boolean _restartPending;
+ private boolean _started;
+ private long _currentTime = -1;
+
+ DailyTriggeringPolicy(final FileSize maxFileSize, final boolean
rollOnRestart)
+ {
+ _maxFileSize = Objects.requireNonNull(maxFileSize, "maxFileSize");
+ _rollOnRestart = rollOnRestart;
+ }
+
+ @Override
+ public void start()
+ {
+ if (_started)
+ {
+ return;
+ }
+ if (_rollingPolicy == null ||
_rollingPolicy.getParentsRawFileProperty() == null || _maxFileSize.getSize() <=
0)
+ {
+ addError("Daily rolling requires a rolling policy, an active
filename and a positive maximum file size");
+ return;
+ }
+
+ final String pattern = _rollingPolicy.getFileNamePattern();
+ final FileNamePattern archivePattern = new FileNamePattern(pattern,
getContext());
+ final String uncompressedPattern = Compressor
+ .computeFileNameStrWithoutCompSuffix(pattern,
_rollingPolicy.getCompressionMode());
+ _uncompressedPattern = new FileNamePattern(uncompressedPattern,
getContext());
+ _compressionSuffix = pattern.substring(uncompressedPattern.length());
+ final DateTokenConverter<Object> dateConverter =
archivePattern.getPrimaryDateTokenConverter();
+ if (dateConverter == null || archivePattern.getIntegerTokenConverter()
== null ||
+ !uncompressedPattern.endsWith(".%i"))
+ {
+ addError("Daily rolling requires a date token and a final .%i
archive index");
+ return;
+ }
+ _calendar = dateConverter.getZoneId() == null
+ ? new RollingCalendar(dateConverter.getDatePattern())
+ : new RollingCalendar(dateConverter.getDatePattern(),
TimeZone.getTimeZone(dateConverter.getZoneId()),
+ Locale.getDefault());
+ if (!_calendar.isCollisionFree())
+ {
+ addError("The archive date pattern does not distinguish successive
periods");
+ return;
+ }
+
+ try
+ {
+ final BasicFileAttributes attributes = readActiveFileAttributes();
+ final boolean nonempty = attributes != null && attributes.size() >
0;
+ _period = nonempty ? attributes.lastModifiedTime().toInstant() :
Instant.ofEpochMilli(getCurrentTime());
+ _nextCheck =
_calendar.getNextTriggeringDate(_period).toEpochMilli();
+ _nextArchiveIndex = nextArchiveIndex(_period);
+ _restartPending = _rollOnRestart && nonempty;
+ _elapsedFileName = null;
+ _retryAfter = 0;
+ _lengthCounter.reset();
+ final SizeAndTimeBasedArchiveRemover remover =
+ new SizeAndTimeBasedArchiveRemover(archivePattern,
(RollingCalendar) _calendar.clone());
+ remover.setContext(getContext());
+ _archiveRemover = remover;
+ _started = true;
+ }
+ catch (IOException | DirectoryIteratorException | ArithmeticException
e)
+ {
+ addError("Cannot initialize daily log archives", e);
+ }
+ }
+
+ private BasicFileAttributes readActiveFileAttributes() throws IOException
+ {
+ try
+ {
+ return
Files.readAttributes(Path.of(_rollingPolicy.getParentsRawFileProperty()),
BasicFileAttributes.class);
+ }
+ catch (NoSuchFileException ignore)
+ {
+ // A new active file will be opened by RollingFileAppender after
this policy has started
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isTriggeringEvent(final File activeFile, final E event)
+ {
+ final long now = getCurrentTime();
+ if (!_started || now < _retryAfter)
+ {
+ return false;
+ }
+ try
+ {
+ if (now >= _nextCheck)
+ {
+ final Instant nextPeriod = Instant.ofEpochMilli(now);
+ final int nextIndex = nextArchiveIndex(nextPeriod);
+ _elapsedFileName =
getCurrentPeriodsFileNameWithoutCompressionSuffix();
+ _period = nextPeriod;
+ _nextArchiveIndex = nextIndex;
+ _nextCheck =
_calendar.getNextTriggeringDate(nextPeriod).toEpochMilli();
+ _restartPending = false;
+ _retryAfter = 0;
+ final boolean nonempty = _lengthCounter.getLength() > 0;
+ _lengthCounter.reset();
+ return nonempty;
+ }
+ if (_restartPending || _lengthCounter.getLength() >=
_maxFileSize.getSize())
+ {
+ final int nextIndex = Math.incrementExact(_nextArchiveIndex);
+ _elapsedFileName =
getCurrentPeriodsFileNameWithoutCompressionSuffix();
+ _nextArchiveIndex = nextIndex;
+ _restartPending = false;
+ _retryAfter = 0;
+ _lengthCounter.reset();
+ return true;
+ }
+ }
+ catch (IOException | DirectoryIteratorException | ArithmeticException
e)
Review Comment:
Please set `_retryAfter` before calling `DailyTriggeringPolicy#addError()`
in this catch block.
`addError()` synchronously notifies `BrokerLoggerStatusListener`, which logs
through the same appender. When archive scanning throws an `IOException`, this
re-enters `DailyTriggeringPolicy#isTriggeringEvent()` while `_retryAfter` is
still zero, repeating the failed scan and error notification. This can recurse
until `StackOverflowError`.
With the actual listener and `broker.failOnLoggerIOError=false`, a bounded
reproduction reached six nested notifications before its safety limit. Moving
the assignment before `addError()` reduced this to one notification and
preserved backoff and recovery.
Please capture whether `_retryAfter == 0`, assign the retry deadline, and
then report the error using the captured flag.
Add regression coverage with the real listener attached. The existing
scan-failure test does not exercise this interaction.
> [Broker-J] FileLogger does not roll on restart when daily rolling is enabled
> ----------------------------------------------------------------------------
>
> Key: QPID-8770
> URL: https://issues.apache.org/jira/browse/QPID-8770
> Project: Qpid
> Issue Type: Bug
> Components: Broker-J
> Affects Versions: qpid-java-broker-10.1.0, qpid-java-broker-10.1.1
> Reporter: Takahashi Kiyoshi
> Priority: Minor
>
> When both rollDaily and rollOnRestart are enabled, restarting the broker on
> the same day can append to the existing log instead of rolling it over.
> Expected behavior: archive the existing nonempty log before writing the first
> new log event, using the daily archive filename pattern.
> This appears to be a regression of QPID-8081, introduced during the Logback
> upgrade in QPID-8626.
> The fix should replace the daily triggering policy to restore restart
> rollover, preserve archive numbering, and avoid archiving empty files.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]