Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log rotation by time interval #8349

Merged
merged 6 commits into from
Sep 28, 2018

Conversation

danhermann
Copy link
Contributor

@danhermann danhermann commented Sep 18, 2018

Adds optional log rotation by time interval in addition to size-based log rotation. It's modeled after ES's log rotation which defaults to daily log rotation in which the current log file is rotated to filename-yyyy-MM-dd-n where n is incremented if either the max file size is reached or a beat is started more than once per day. The rotation interval is configurable down to 1s. Values of 1m, 1h, 24h, 7*24h, 30*24h, and 365*24h are treated specially by being boundary-aligned with minutes, hours, days, weeks, months, and years as reported by the local system clock.

The keepfiles setting is also honored as the oldest backup files are removed during rotation.

Fixes #7990.

@@ -263,6 +293,81 @@ func (r *Rotator) closeFile() error {
}

func (r *Rotator) purgeOldBackups() error {
if (r.daily) {
return r.purgeOldDailyBackups()
} else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if block ends with a return statement, so drop this else and outdent its block

@danhermann danhermann added in progress Pull request is currently in progress. libbeat labels Sep 18, 2018
@vjsamuel
Copy link
Contributor

this can be time based instead of daily where the time is defaulted to 1d

@danhermann danhermann added review and removed in progress Pull request is currently in progress. labels Sep 18, 2018
@@ -1101,6 +1101,10 @@ logging.files:
# Must be a valid Unix-style file permissions mask expressed in octal notation.
#permissions: 0600

# Enable daily log file rotation in addition to size-based rotation. Defaults to
# disabled.
#daily: true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please set this value to false as it is the default value.

return errors.Wrap(err, "failed to rotate backups")
}

if r.log != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not move logging in rotateByDay and rotateBySize?

type Rotator struct {
filename string
maxSizeBytes uint
maxBackups uint
permissions os.FileMode
log Logger // Optional Logger (may be nil).
daily bool
lastYear int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you store date in separately instead of a time.Time struct? It would make easier to check if the log needs to be rotated or not by using Since.

Copy link
Contributor

@kvch kvch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool feature! However, I am not sure if it 100% addresses the initial issue. I would prefer to have time based log rotation where the rotation interval is configurable. For example adding a new option rotation_interval which is 0 by default, meaning there is no rotation. If someone wants to use it, he/she can specify an time interval. So the daily rotation would look like rotation_interval: 1d, weekly could be rotation_interval: 7d, and so on.

@kvch
Copy link
Contributor

kvch commented Sep 19, 2018

Also, please add a new entry to CHANGELOG.asciidoc.

@@ -108,13 +115,22 @@ func WithLogger(l Logger) RotatorOption {
}
}

// Daily enables or disables log rotation by time interval in addition to log

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on exported function Interval should be of the form "Interval ..."

return ""
}

// Given a log filename in the form [prefix]-[formattedDate]-n, returns n as int

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on exported function IntervalLogIndex should be of the form "IntervalLogIndex ..."

)
}

// Given a log filename in the form [prefix]-[formattedDate]-n, returns the filename after

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on exported function OrderIntervalLogs should be of the form "OrderIntervalLogs ..."

return time.Now()
}

func NewIntervalRotator(interval time.Duration) (*intervalRotator, error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exported function NewIntervalRotator should have comment or be unexported
exported func NewIntervalRotator returns unexported type *file.intervalRotator, which can be annoying to use

@danhermann danhermann changed the title Daily log rotation Log rotation by time interval Sep 21, 2018
@danhermann
Copy link
Contributor Author

@kvch, thanks for the review. I've updated the PR to allow for rotation on arbitrary time intervals as explained in the edited PR description above.

@ruflin
Copy link
Contributor

ruflin commented Sep 24, 2018

My concern about this feature is that we add more logging logic to our code but are in the progress of more moving to popular libraries at least for the internal logging. I wonder if we could do the same for writing to file/stdout etc. too? @andrewkroh Thoughts?

@andrewkroh
Copy link
Member

If there is some popular well-tested library for this sort of thing then I would consider using it, but I didn't find much last time I looked. So I think we have to roll our own here.

Is there a use-case for enabling it on the file output? I've never used the file output for anything other than testing and debugging.

@danhermann
Copy link
Contributor Author

danhermann commented Sep 25, 2018

I also looked around for a library that did this and didn't see one. The https://github.com/natefinch/lumberjack library seems to be the most popular library for log rotation, but it does not support time-interval rotation and will not based on the authors comments in issue 17. This PR doesn't suffer from his main criticisms of time-interval rotation in that both size limits and time intervals are considered for rotation, so running out of disk space wouldn't be an issue.

newInterval func(lastTime time.Time, currentTime time.Time) bool
}

type clock interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this interface exists? What's the reason of having a realClock? Why not simply call time.Now directly instead of through an interface?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it's used during unit testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only for testing. I'm happy to do it differently if there's a better or more idiomatic way in Go.

return u64, i, err
}

func newSecond(lastTime time.Time, currentTime time.Time) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit1: It would be more readable if you called these interval function from each other.
Example:

func newSecond(lastTime, currentTime time.Time) bool {
    return lastTime.Second() != currentTime.Second || newMinute(lastTime, currentTime)
}

Nit2: It's a bit confusing/requires a bit more effort to read the code, because the order of the parameters are differs from the order during comparison.

func ...(lastTime, currentTime time.Time) bool {
    return lastTime.Seconds() != currentTime.Seconds()
}

I am willing to let the second nit go, because it might be a personal preference.

return u64, i, err
}

func newSecond(lastTime time.Time, currentTime time.Time) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be sure that nothing can go wrong here, you could add a sanity check to see if currentTime is indeed later in time than lastTime. Otherwise the checks only check if the two times are different, not later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was to accommodate cases where the system clock goes backward in calendar time which may occur during daylight savings transitions or when the clock is updated. If that is not desirable, I can change it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I also had concerns about that. I'm fine with this.

@danhermann danhermann added the needs_backport PR is waiting to be backported to other branches. label Sep 28, 2018
@danhermann
Copy link
Contributor Author

The unrelated failing tests are all due to an updated geoip database.

@danhermann danhermann merged commit 7b4cb29 into elastic:master Sep 28, 2018
danhermann added a commit to danhermann/beats that referenced this pull request Sep 28, 2018
@danhermann danhermann removed the needs_backport PR is waiting to be backported to other branches. label Sep 28, 2018
danhermann added a commit that referenced this pull request Oct 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants