Skip to content

Name direct write cron files after the rollover period they cover - #4227

Open
tupelo-schneck wants to merge 2 commits into
apache:2.xfrom
tupelo-schneck:fix/cron-direct-write-current-file-name
Open

Name direct write cron files after the rollover period they cover#4227
tupelo-schneck wants to merge 2 commits into
apache:2.xfrom
tupelo-schneck:fix/cron-direct-write-current-file-name

Conversation

@tupelo-schneck

Copy link
Copy Markdown

This changes the names of direct write files for cron based appenders. That is the point of the change, but it is user visible on upgrade, so it deserves a decision rather than a rubber stamp. Details below.

An appender configured without a fileName writes directly to the file its pattern resolves to. That file covers a whole rollover period, but it is currently named after the moment the appender started rather than after the period it covers.

With a weekly schedule and a day-granularity pattern:

<RollingFile name="errorLogAppender" filePattern="${LOG_DIR}/error.log-%d{yyyyMMdd}">
    <PatternLayout pattern="%m%n"/>
    <CronTriggeringPolicy schedule="0 0 0 ? * SUN" evaluateOnStartup="true"/>
</RollingFile>

starting on Tuesday 2026-07-28 writes to error.log-20260728. The period began on Sunday 2026-07-26, so a restart on Wednesday opens error.log-20260729 instead of continuing the same file. A week with daily restarts leaves seven files where the schedule implies one, and rollover only ever closes out whichever fragment happens to be current.

Cause

CronTriggeringPolicy.initialize() computes the period start and records it correctly:

final Date lastRegularRoll = cronExpression.getPrevFireTime(new Date());
aManager.getPatternProcessor().setCurrentFileTime(lastRegularRoll.getTime());

DirectWriteRolloverStrategy.getCurrentFileName() then discards it:

// LOG4J2-3339 - Always use the current time for new direct write files.
manager.getPatternProcessor().setCurrentFileTime(System.currentTimeMillis());

That override compensates for a different problem. CronTriggeringPolicy.rollover() passes lastRollDate, the previous period's start, as the new file's time:

manager.rollover(cronExpression.getPrevFireTime(rollTime), lastRollDate);

and RollingFileManager.rollover(prevFileTime, prevRollTime) assigns that to the current file time, so each replacement file was named after the file just rolled. Substituting the current time hid this, and works only because at the instant of a rollover "now" is approximately the new period's start. At startup "now" is an arbitrary point inside the period, which is where it breaks.

Fix

Pass the roll time rather than the previous roll date, so the replacement file is named after the period it opens, and let getCurrentFileName() use the period start the policy already recorded.

Blast radius is limited to policies that track a period. PatternProcessor.formatFileName() already falls back to the current time when the current file time is 0:

final long time = useCurrentTime
        ? currentFileTime != 0 ? currentFileTime : System.currentTimeMillis()
        : prevFileTime != 0 ? prevFileTime : System.currentTimeMillis();

and PatternProcessor.updateTime() resets it to 0, so TimeBasedTriggeringPolicy, which never sets it, keeps its existing behaviour unchanged. Only cron, which knows its period start, behaves differently.

Compatibility

Direct write files for cron based appenders change name, from the start time of the process to the start of the rollover period. For the example above that is error.log-20260726 rather than error.log-20260728. Existing files are untouched and are neither read nor renamed; a restart after upgrading simply begins writing to the period's file. The changelog entry calls this out.

Appenders configured with a fileName are unaffected, since getCurrentFileName() is only consulted on the direct write path.

Testing

CronTriggeringPolicyTest#testDirectWriteFileNameUsesPeriodStart asserts the manager's file name matches the period start computed independently from the same cron expression. Against 2.x without the fix it fails with:

expected: <target/testcmd5.log-20260726> but was: <target/testcmd5.log-20260728>

RollingAppenderDirectCronTest, the regression test added with LOG4J2-3339, still passes; it asserts that a rolled file's name matches the timestamp of its first line, which this change preserves.

./mvnw verify passes across all 41 modules: 11088 tests, 0 failures, 0 errors, 40 pre-existing environment-conditional skips.

Relationship to the other PR

Independent of the startup-delay fix in the companion PR; the two touch different code paths and can merge in either order. They surfaced from the same investigation, which is why both concern CronTriggeringPolicy.

Checklist

  • Based on 2.x
  • ./mvnw verify succeeds
  • Changelog entry in src/changelog/.2.x.x
  • Tests are provided

An appender configured without a `fileName` writes directly to the file its
pattern resolves to. `CronTriggeringPolicy.initialize()` records the start of
the current rollover period as the pattern processor's current file time, but
`DirectWriteRolloverStrategy.getCurrentFileName()` overwrote it with the
current time, so the file was named after the moment the appender started
rather than after the period it covers.

For a weekly schedule and a `%d{yyyyMMdd}` pattern that means every restart on
a new day opens another file, leaving a week with as many files as there were
restart days rather than the single file the schedule implies.

That override (LOG4J2-3339) compensated for `rollover()` passing the previous
roll date as the new file's time, which named each new file after the file just
rolled. Pass the roll time instead, so the replacement file is named after the
period it opens, and let `getCurrentFileName()` use the recorded period start.

Policies that do not track a period, such as `TimeBasedTriggeringPolicy`, leave
the current file time at 0, and `PatternProcessor.formatFileName()` already
falls back to the current time in that case, so their behaviour is unchanged.

Note that this changes direct write file names for cron based appenders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant