fixed dependencies
This commit is contained in:
22
vendor/github.com/lestrrat-go/file-rotatelogs/.gitignore
generated
vendored
Normal file
22
vendor/github.com/lestrrat-go/file-rotatelogs/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
5
vendor/github.com/lestrrat-go/file-rotatelogs/.travis.yml
generated
vendored
Normal file
5
vendor/github.com/lestrrat-go/file-rotatelogs/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- "1.14"
|
||||
- tip
|
||||
8
vendor/github.com/lestrrat-go/file-rotatelogs/Changes
generated
vendored
Normal file
8
vendor/github.com/lestrrat-go/file-rotatelogs/Changes
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
v2.4.0 - 8 Sep 2020
|
||||
* Add WithRotationSize option to specify log ration when
|
||||
a certain file size is reached. (NOTE: this does not guarantee
|
||||
that the file size is exactly the size specified, but that it
|
||||
*exceeds* the specified size)
|
||||
20
vendor/github.com/lestrrat-go/file-rotatelogs/LICENSE
generated
vendored
Normal file
20
vendor/github.com/lestrrat-go/file-rotatelogs/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 lestrrat
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
233
vendor/github.com/lestrrat-go/file-rotatelogs/README.md
generated
vendored
Normal file
233
vendor/github.com/lestrrat-go/file-rotatelogs/README.md
generated
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
file-rotatelogs
|
||||
==================
|
||||
|
||||
Provide an `io.Writer` that periodically rotates log files from within the application. Port of [File::RotateLogs](https://metacpan.org/release/File-RotateLogs) from Perl to Go.
|
||||
|
||||
[](https://travis-ci.org/lestrrat-go/file-rotatelogs)
|
||||
|
||||
[](https://godoc.org/github.com/lestrrat-go/file-rotatelogs)
|
||||
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
```go
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
apachelog "github.com/lestrrat-go/apache-logformat"
|
||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ... })
|
||||
|
||||
logf, err := rotatelogs.New(
|
||||
"/path/to/access_log.%Y%m%d%H%M",
|
||||
rotatelogs.WithLinkName("/path/to/access_log"),
|
||||
rotatelogs.WithMaxAge(24 * time.Hour),
|
||||
rotatelogs.WithRotationTime(time.Hour),
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("failed to create rotatelogs: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Now you must write to logf. apache-logformat library can create
|
||||
// a http.Handler that only writes the approriate logs for the request
|
||||
// to the given handle
|
||||
http.ListenAndServe(":8080", apachelog.CombinedLog.Wrap(mux, logf))
|
||||
}
|
||||
```
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
When you integrate this to into your app, it automatically write to logs that
|
||||
are rotated from within the app: No more disk-full alerts because you forgot
|
||||
to setup logrotate!
|
||||
|
||||
To install, simply issue a `go get`:
|
||||
|
||||
```
|
||||
go get github.com/lestrrat-go/file-rotatelogs
|
||||
```
|
||||
|
||||
It's normally expected that this library is used with some other
|
||||
logging service, such as the built-in `log` library, or loggers
|
||||
such as `github.com/lestrrat-go/apache-logformat`.
|
||||
|
||||
```go
|
||||
import(
|
||||
"log"
|
||||
"github.com/lestrrat-go/file-rotatelogs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rl, _ := rotatelogs.New("/path/to/access_log.%Y%m%d%H%M")
|
||||
|
||||
log.SetOutput(rl)
|
||||
|
||||
/* elsewhere ... */
|
||||
log.Printf("Hello, World!")
|
||||
}
|
||||
```
|
||||
|
||||
OPTIONS
|
||||
====
|
||||
|
||||
## Pattern (Required)
|
||||
|
||||
The pattern used to generate actual log file names. You should use patterns
|
||||
using the strftime (3) format. For example:
|
||||
|
||||
```go
|
||||
rotatelogs.New("/var/log/myapp/log.%Y%m%d")
|
||||
```
|
||||
|
||||
## Clock (default: rotatelogs.Local)
|
||||
|
||||
You may specify an object that implements the roatatelogs.Clock interface.
|
||||
When this option is supplied, it's used to determine the current time to
|
||||
base all of the calculations on. For example, if you want to base your
|
||||
calculations in UTC, you may specify rotatelogs.UTC
|
||||
|
||||
```go
|
||||
rotatelogs.New(
|
||||
"/var/log/myapp/log.%Y%m%d",
|
||||
rotatelogs.WithClock(rotatelogs.UTC),
|
||||
)
|
||||
```
|
||||
|
||||
## Location
|
||||
|
||||
This is an alternative to the `WithClock` option. Instead of providing an
|
||||
explicit clock, you can provide a location for you times. We will create
|
||||
a Clock object that produces times in your specified location, and configure
|
||||
the rotatelog to respect it.
|
||||
|
||||
## LinkName (default: "")
|
||||
|
||||
Path where a symlink for the actual log file is placed. This allows you to
|
||||
always check at the same location for log files even if the logs were rotated
|
||||
|
||||
```go
|
||||
rotatelogs.New(
|
||||
"/var/log/myapp/log.%Y%m%d",
|
||||
rotatelogs.WithLinkName("/var/log/myapp/current"),
|
||||
)
|
||||
```
|
||||
|
||||
```
|
||||
// Else where
|
||||
$ tail -f /var/log/myapp/current
|
||||
```
|
||||
|
||||
Links that share the same parent directory with the main log path will get a
|
||||
special treatment: namely, linked paths will be *RELATIVE* to the main log file.
|
||||
|
||||
| Main log file name | Link name | Linked path |
|
||||
|---------------------|---------------------|-----------------------|
|
||||
| /path/to/log.%Y%m%d | /path/to/log | log.YYYYMMDD |
|
||||
| /path/to/log.%Y%m%d | /path/to/nested/log | ../log.YYYYMMDD |
|
||||
| /path/to/log.%Y%m%d | /foo/bar/baz/log | /path/to/log.YYYYMMDD |
|
||||
|
||||
If not provided, no link will be written.
|
||||
|
||||
## RotationTime (default: 86400 sec)
|
||||
|
||||
Interval between file rotation. By default logs are rotated every 86400 seconds.
|
||||
Note: Remember to use time.Duration values.
|
||||
|
||||
```go
|
||||
// Rotate every hour
|
||||
rotatelogs.New(
|
||||
"/var/log/myapp/log.%Y%m%d",
|
||||
rotatelogs.WithRotationTime(time.Hour),
|
||||
)
|
||||
```
|
||||
|
||||
## MaxAge (default: 7 days)
|
||||
|
||||
Time to wait until old logs are purged. By default no logs are purged, which
|
||||
certainly isn't what you want.
|
||||
Note: Remember to use time.Duration values.
|
||||
|
||||
```go
|
||||
// Purge logs older than 1 hour
|
||||
rotatelogs.New(
|
||||
"/var/log/myapp/log.%Y%m%d",
|
||||
rotatelogs.WithMaxAge(time.Hour),
|
||||
)
|
||||
```
|
||||
|
||||
## RotationCount (default: -1)
|
||||
|
||||
The number of files should be kept. By default, this option is disabled.
|
||||
|
||||
Note: MaxAge should be disabled by specifing `WithMaxAge(-1)` explicitly.
|
||||
|
||||
```go
|
||||
// Purge logs except latest 7 files
|
||||
rotatelogs.New(
|
||||
"/var/log/myapp/log.%Y%m%d",
|
||||
rotatelogs.WithMaxAge(-1),
|
||||
rotatelogs.WithRotationCount(7),
|
||||
)
|
||||
```
|
||||
|
||||
## Handler (default: nil)
|
||||
|
||||
Sets the event handler to receive event notifications from the RotateLogs
|
||||
object. Currently only supported event type is FiledRotated
|
||||
|
||||
```go
|
||||
rotatelogs.New(
|
||||
"/var/log/myapp/log.%Y%m%d",
|
||||
rotatelogs.Handler(rotatelogs.HandlerFunc(func(e Event) {
|
||||
if e.Type() != rotatelogs.FileRotatedEventType {
|
||||
return
|
||||
}
|
||||
|
||||
// Do what you want with the data. This is just an idea:
|
||||
storeLogFileToRemoteStorage(e.(*FileRotatedEvent).PreviousFile())
|
||||
})),
|
||||
)
|
||||
```
|
||||
|
||||
## ForceNewFile
|
||||
|
||||
Ensure a new file is created every time New() is called. If the base file name
|
||||
already exists, an implicit rotation is performed.
|
||||
|
||||
```go
|
||||
rotatelogs.New(
|
||||
"/var/log/myapp/log.%Y%m%d",
|
||||
rotatelogs.ForceNewFile(),
|
||||
)
|
||||
```
|
||||
|
||||
# Rotating files forcefully
|
||||
|
||||
If you want to rotate files forcefully before the actual rotation time has reached,
|
||||
you may use the `Rotate()` method. This method forcefully rotates the logs, but
|
||||
if the generated file name clashes, then a numeric suffix is added so that
|
||||
the new file will forcefully appear on disk.
|
||||
|
||||
For example, suppose you had a pattern of '%Y.log' with a rotation time of
|
||||
`86400` so that it only gets rotated every year, but for whatever reason you
|
||||
wanted to rotate the logs now, you could install a signal handler to
|
||||
trigger this rotation:
|
||||
|
||||
```go
|
||||
rl := rotatelogs.New(...)
|
||||
|
||||
signal.Notify(ch, syscall.SIGHUP)
|
||||
|
||||
go func(ch chan os.Signal) {
|
||||
<-ch
|
||||
rl.Rotate()
|
||||
}()
|
||||
```
|
||||
|
||||
And you will get a log file name in like `2018.log.1`, `2018.log.2`, etc.
|
||||
17
vendor/github.com/lestrrat-go/file-rotatelogs/event.go
generated
vendored
Normal file
17
vendor/github.com/lestrrat-go/file-rotatelogs/event.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package rotatelogs
|
||||
|
||||
func (h HandlerFunc) Handle(e Event) {
|
||||
h(e)
|
||||
}
|
||||
|
||||
func (e *FileRotatedEvent) Type() EventType {
|
||||
return FileRotatedEventType
|
||||
}
|
||||
|
||||
func (e *FileRotatedEvent) PreviousFile() string {
|
||||
return e.prev
|
||||
}
|
||||
|
||||
func (e *FileRotatedEvent) CurrentFile() string {
|
||||
return e.current
|
||||
}
|
||||
73
vendor/github.com/lestrrat-go/file-rotatelogs/interface.go
generated
vendored
Normal file
73
vendor/github.com/lestrrat-go/file-rotatelogs/interface.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package rotatelogs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
strftime "github.com/lestrrat-go/strftime"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
Handle(Event)
|
||||
}
|
||||
|
||||
type HandlerFunc func(Event)
|
||||
|
||||
type Event interface {
|
||||
Type() EventType
|
||||
}
|
||||
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
InvalidEventType EventType = iota
|
||||
FileRotatedEventType
|
||||
)
|
||||
|
||||
type FileRotatedEvent struct {
|
||||
prev string // previous filename
|
||||
current string // current, new filename
|
||||
}
|
||||
|
||||
// RotateLogs represents a log file that gets
|
||||
// automatically rotated as you write to it.
|
||||
type RotateLogs struct {
|
||||
clock Clock
|
||||
curFn string
|
||||
curBaseFn string
|
||||
globPattern string
|
||||
generation int
|
||||
linkName string
|
||||
maxAge time.Duration
|
||||
mutex sync.RWMutex
|
||||
eventHandler Handler
|
||||
outFh *os.File
|
||||
pattern *strftime.Strftime
|
||||
rotationTime time.Duration
|
||||
rotationSize int64
|
||||
rotationCount uint
|
||||
forceNewFile bool
|
||||
}
|
||||
|
||||
// Clock is the interface used by the RotateLogs
|
||||
// object to determine the current time
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
type clockFn func() time.Time
|
||||
|
||||
// UTC is an object satisfying the Clock interface, which
|
||||
// returns the current time in UTC
|
||||
var UTC = clockFn(func() time.Time { return time.Now().UTC() })
|
||||
|
||||
// Local is an object satisfying the Clock interface, which
|
||||
// returns the current time in the local timezone
|
||||
var Local = clockFn(time.Now)
|
||||
|
||||
// Option is used to pass optional arguments to
|
||||
// the RotateLogs constructor
|
||||
type Option interface {
|
||||
Name() string
|
||||
Value() interface{}
|
||||
}
|
||||
25
vendor/github.com/lestrrat-go/file-rotatelogs/internal/option/option.go
generated
vendored
Normal file
25
vendor/github.com/lestrrat-go/file-rotatelogs/internal/option/option.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package option
|
||||
|
||||
type Interface interface {
|
||||
Name() string
|
||||
Value() interface{}
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
name string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func New(name string, value interface{}) *Option {
|
||||
return &Option{
|
||||
name: name,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Option) Name() string {
|
||||
return o.name
|
||||
}
|
||||
func (o *Option) Value() interface{} {
|
||||
return o.value
|
||||
}
|
||||
89
vendor/github.com/lestrrat-go/file-rotatelogs/options.go
generated
vendored
Normal file
89
vendor/github.com/lestrrat-go/file-rotatelogs/options.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package rotatelogs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/file-rotatelogs/internal/option"
|
||||
)
|
||||
|
||||
const (
|
||||
optkeyClock = "clock"
|
||||
optkeyHandler = "handler"
|
||||
optkeyLinkName = "link-name"
|
||||
optkeyMaxAge = "max-age"
|
||||
optkeyRotationTime = "rotation-time"
|
||||
optkeyRotationSize = "rotation-size"
|
||||
optkeyRotationCount = "rotation-count"
|
||||
optkeyForceNewFile = "force-new-file"
|
||||
)
|
||||
|
||||
// WithClock creates a new Option that sets a clock
|
||||
// that the RotateLogs object will use to determine
|
||||
// the current time.
|
||||
//
|
||||
// By default rotatelogs.Local, which returns the
|
||||
// current time in the local time zone, is used. If you
|
||||
// would rather use UTC, use rotatelogs.UTC as the argument
|
||||
// to this option, and pass it to the constructor.
|
||||
func WithClock(c Clock) Option {
|
||||
return option.New(optkeyClock, c)
|
||||
}
|
||||
|
||||
// WithLocation creates a new Option that sets up a
|
||||
// "Clock" interface that the RotateLogs object will use
|
||||
// to determine the current time.
|
||||
//
|
||||
// This optin works by always returning the in the given
|
||||
// location.
|
||||
func WithLocation(loc *time.Location) Option {
|
||||
return option.New(optkeyClock, clockFn(func() time.Time {
|
||||
return time.Now().In(loc)
|
||||
}))
|
||||
}
|
||||
|
||||
// WithLinkName creates a new Option that sets the
|
||||
// symbolic link name that gets linked to the current
|
||||
// file name being used.
|
||||
func WithLinkName(s string) Option {
|
||||
return option.New(optkeyLinkName, s)
|
||||
}
|
||||
|
||||
// WithMaxAge creates a new Option that sets the
|
||||
// max age of a log file before it gets purged from
|
||||
// the file system.
|
||||
func WithMaxAge(d time.Duration) Option {
|
||||
return option.New(optkeyMaxAge, d)
|
||||
}
|
||||
|
||||
// WithRotationTime creates a new Option that sets the
|
||||
// time between rotation.
|
||||
func WithRotationTime(d time.Duration) Option {
|
||||
return option.New(optkeyRotationTime, d)
|
||||
}
|
||||
|
||||
// WithRotationSize creates a new Option that sets the
|
||||
// log file size between rotation.
|
||||
func WithRotationSize(s int64) Option {
|
||||
return option.New(optkeyRotationSize, s)
|
||||
}
|
||||
|
||||
// WithRotationCount creates a new Option that sets the
|
||||
// number of files should be kept before it gets
|
||||
// purged from the file system.
|
||||
func WithRotationCount(n uint) Option {
|
||||
return option.New(optkeyRotationCount, n)
|
||||
}
|
||||
|
||||
// WithHandler creates a new Option that specifies the
|
||||
// Handler object that gets invoked when an event occurs.
|
||||
// Currently `FileRotated` event is supported
|
||||
func WithHandler(h Handler) Option {
|
||||
return option.New(optkeyHandler, h)
|
||||
}
|
||||
|
||||
// ForceNewFile ensures a new file is created every time New()
|
||||
// is called. If the base file name already exists, an implicit
|
||||
// rotation is performed
|
||||
func ForceNewFile() Option {
|
||||
return option.New(optkeyForceNewFile, true)
|
||||
}
|
||||
407
vendor/github.com/lestrrat-go/file-rotatelogs/rotatelogs.go
generated
vendored
Normal file
407
vendor/github.com/lestrrat-go/file-rotatelogs/rotatelogs.go
generated
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
// package rotatelogs is a port of File-RotateLogs from Perl
|
||||
// (https://metacpan.org/release/File-RotateLogs), and it allows
|
||||
// you to automatically rotate output files when you write to them
|
||||
// according to the filename pattern that you can specify.
|
||||
package rotatelogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
strftime "github.com/lestrrat-go/strftime"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c clockFn) Now() time.Time {
|
||||
return c()
|
||||
}
|
||||
|
||||
// New creates a new RotateLogs object. A log filename pattern
|
||||
// must be passed. Optional `Option` parameters may be passed
|
||||
func New(p string, options ...Option) (*RotateLogs, error) {
|
||||
globPattern := p
|
||||
for _, re := range patternConversionRegexps {
|
||||
globPattern = re.ReplaceAllString(globPattern, "*")
|
||||
}
|
||||
|
||||
pattern, err := strftime.New(p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, `invalid strftime pattern`)
|
||||
}
|
||||
|
||||
var clock Clock = Local
|
||||
rotationTime := 24 * time.Hour
|
||||
var rotationSize int64
|
||||
var rotationCount uint
|
||||
var linkName string
|
||||
var maxAge time.Duration
|
||||
var handler Handler
|
||||
var forceNewFile bool
|
||||
|
||||
for _, o := range options {
|
||||
switch o.Name() {
|
||||
case optkeyClock:
|
||||
clock = o.Value().(Clock)
|
||||
case optkeyLinkName:
|
||||
linkName = o.Value().(string)
|
||||
case optkeyMaxAge:
|
||||
maxAge = o.Value().(time.Duration)
|
||||
if maxAge < 0 {
|
||||
maxAge = 0
|
||||
}
|
||||
case optkeyRotationTime:
|
||||
rotationTime = o.Value().(time.Duration)
|
||||
if rotationTime < 0 {
|
||||
rotationTime = 0
|
||||
}
|
||||
case optkeyRotationSize:
|
||||
rotationSize = o.Value().(int64)
|
||||
if rotationSize < 0 {
|
||||
rotationSize = 0
|
||||
}
|
||||
case optkeyRotationCount:
|
||||
rotationCount = o.Value().(uint)
|
||||
case optkeyHandler:
|
||||
handler = o.Value().(Handler)
|
||||
case optkeyForceNewFile:
|
||||
forceNewFile = true
|
||||
}
|
||||
}
|
||||
|
||||
if maxAge > 0 && rotationCount > 0 {
|
||||
return nil, errors.New("options MaxAge and RotationCount cannot be both set")
|
||||
}
|
||||
|
||||
if maxAge == 0 && rotationCount == 0 {
|
||||
// if both are 0, give maxAge a sane default
|
||||
maxAge = 7 * 24 * time.Hour
|
||||
}
|
||||
|
||||
return &RotateLogs{
|
||||
clock: clock,
|
||||
eventHandler: handler,
|
||||
globPattern: globPattern,
|
||||
linkName: linkName,
|
||||
maxAge: maxAge,
|
||||
pattern: pattern,
|
||||
rotationTime: rotationTime,
|
||||
rotationSize: rotationSize,
|
||||
rotationCount: rotationCount,
|
||||
forceNewFile: forceNewFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rl *RotateLogs) genFilename() string {
|
||||
now := rl.clock.Now()
|
||||
|
||||
// XXX HACK: Truncate only happens in UTC semantics, apparently.
|
||||
// observed values for truncating given time with 86400 secs:
|
||||
//
|
||||
// before truncation: 2018/06/01 03:54:54 2018-06-01T03:18:00+09:00
|
||||
// after truncation: 2018/06/01 03:54:54 2018-05-31T09:00:00+09:00
|
||||
//
|
||||
// This is really annoying when we want to truncate in local time
|
||||
// so we hack: we take the apparent local time in the local zone,
|
||||
// and pretend that it's in UTC. do our math, and put it back to
|
||||
// the local zone
|
||||
var base time.Time
|
||||
if now.Location() != time.UTC {
|
||||
base = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), time.UTC)
|
||||
base = base.Truncate(time.Duration(rl.rotationTime))
|
||||
base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), base.Minute(), base.Second(), base.Nanosecond(), base.Location())
|
||||
} else {
|
||||
base = now.Truncate(time.Duration(rl.rotationTime))
|
||||
}
|
||||
return rl.pattern.FormatString(base)
|
||||
}
|
||||
|
||||
// Write satisfies the io.Writer interface. It writes to the
|
||||
// appropriate file handle that is currently being used.
|
||||
// If we have reached rotation time, the target file gets
|
||||
// automatically rotated, and also purged if necessary.
|
||||
func (rl *RotateLogs) Write(p []byte) (n int, err error) {
|
||||
// Guard against concurrent writes
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
|
||||
out, err := rl.getWriter_nolock(false, false)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, `failed to acquite target io.Writer`)
|
||||
}
|
||||
|
||||
return out.Write(p)
|
||||
}
|
||||
|
||||
// must be locked during this operation
|
||||
func (rl *RotateLogs) getWriter_nolock(bailOnRotateFail, useGenerationalNames bool) (io.Writer, error) {
|
||||
generation := rl.generation
|
||||
previousFn := rl.curFn
|
||||
// This filename contains the name of the "NEW" filename
|
||||
// to log to, which may be newer than rl.currentFilename
|
||||
baseFn := rl.genFilename()
|
||||
filename := baseFn
|
||||
var forceNewFile bool
|
||||
|
||||
fi, err := os.Stat(rl.curFn)
|
||||
sizeRotation := false
|
||||
if err == nil && rl.rotationSize > 0 && rl.rotationSize <= fi.Size() {
|
||||
forceNewFile = true
|
||||
sizeRotation = true
|
||||
}
|
||||
|
||||
if baseFn != rl.curBaseFn {
|
||||
generation = 0
|
||||
// even though this is the first write after calling New(),
|
||||
// check if a new file needs to be created
|
||||
if rl.forceNewFile {
|
||||
forceNewFile = true
|
||||
}
|
||||
} else {
|
||||
if !useGenerationalNames && !sizeRotation {
|
||||
// nothing to do
|
||||
return rl.outFh, nil
|
||||
}
|
||||
forceNewFile = true
|
||||
generation++
|
||||
}
|
||||
if forceNewFile {
|
||||
// A new file has been requested. Instead of just using the
|
||||
// regular strftime pattern, we create a new file name using
|
||||
// generational names such as "foo.1", "foo.2", "foo.3", etc
|
||||
var name string
|
||||
for {
|
||||
if generation == 0 {
|
||||
name = filename
|
||||
} else {
|
||||
name = fmt.Sprintf("%s.%d", filename, generation)
|
||||
}
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
filename = name
|
||||
break
|
||||
}
|
||||
generation++
|
||||
}
|
||||
}
|
||||
// make sure the dir is existed, eg:
|
||||
// ./foo/bar/baz/hello.log must make sure ./foo/bar/baz is existed
|
||||
dirname := filepath.Dir(filename)
|
||||
if err := os.MkdirAll(dirname, 0755); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create directory %s", dirname)
|
||||
}
|
||||
// if we got here, then we need to create a file
|
||||
fh, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to open file %s: %s", rl.pattern, err)
|
||||
}
|
||||
|
||||
if err := rl.rotate_nolock(filename); err != nil {
|
||||
err = errors.Wrap(err, "failed to rotate")
|
||||
if bailOnRotateFail {
|
||||
// Failure to rotate is a problem, but it's really not a great
|
||||
// idea to stop your application just because you couldn't rename
|
||||
// your log.
|
||||
//
|
||||
// We only return this error when explicitly needed (as specified by bailOnRotateFail)
|
||||
//
|
||||
// However, we *NEED* to close `fh` here
|
||||
if fh != nil { // probably can't happen, but being paranoid
|
||||
fh.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
}
|
||||
|
||||
rl.outFh.Close()
|
||||
rl.outFh = fh
|
||||
rl.curBaseFn = baseFn
|
||||
rl.curFn = filename
|
||||
rl.generation = generation
|
||||
|
||||
if h := rl.eventHandler; h != nil {
|
||||
go h.Handle(&FileRotatedEvent{
|
||||
prev: previousFn,
|
||||
current: filename,
|
||||
})
|
||||
}
|
||||
return fh, nil
|
||||
}
|
||||
|
||||
// CurrentFileName returns the current file name that
|
||||
// the RotateLogs object is writing to
|
||||
func (rl *RotateLogs) CurrentFileName() string {
|
||||
rl.mutex.RLock()
|
||||
defer rl.mutex.RUnlock()
|
||||
return rl.curFn
|
||||
}
|
||||
|
||||
var patternConversionRegexps = []*regexp.Regexp{
|
||||
regexp.MustCompile(`%[%+A-Za-z]`),
|
||||
regexp.MustCompile(`\*+`),
|
||||
}
|
||||
|
||||
type cleanupGuard struct {
|
||||
enable bool
|
||||
fn func()
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (g *cleanupGuard) Enable() {
|
||||
g.mutex.Lock()
|
||||
defer g.mutex.Unlock()
|
||||
g.enable = true
|
||||
}
|
||||
func (g *cleanupGuard) Run() {
|
||||
g.fn()
|
||||
}
|
||||
|
||||
// Rotate forcefully rotates the log files. If the generated file name
|
||||
// clash because file already exists, a numeric suffix of the form
|
||||
// ".1", ".2", ".3" and so forth are appended to the end of the log file
|
||||
//
|
||||
// Thie method can be used in conjunction with a signal handler so to
|
||||
// emulate servers that generate new log files when they receive a
|
||||
// SIGHUP
|
||||
func (rl *RotateLogs) Rotate() error {
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
if _, err := rl.getWriter_nolock(true, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rl *RotateLogs) rotate_nolock(filename string) error {
|
||||
lockfn := filename + `_lock`
|
||||
fh, err := os.OpenFile(lockfn, os.O_CREATE|os.O_EXCL, 0644)
|
||||
if err != nil {
|
||||
// Can't lock, just return
|
||||
return err
|
||||
}
|
||||
|
||||
var guard cleanupGuard
|
||||
guard.fn = func() {
|
||||
fh.Close()
|
||||
os.Remove(lockfn)
|
||||
}
|
||||
defer guard.Run()
|
||||
|
||||
if rl.linkName != "" {
|
||||
tmpLinkName := filename + `_symlink`
|
||||
|
||||
// Change how the link name is generated based on where the
|
||||
// target location is. if the location is directly underneath
|
||||
// the main filename's parent directory, then we create a
|
||||
// symlink with a relative path
|
||||
linkDest := filename
|
||||
linkDir := filepath.Dir(rl.linkName)
|
||||
|
||||
baseDir := filepath.Dir(filename)
|
||||
if strings.Contains(rl.linkName, baseDir) {
|
||||
tmp, err := filepath.Rel(linkDir, filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `failed to evaluate relative path from %#v to %#v`, baseDir, rl.linkName)
|
||||
}
|
||||
|
||||
linkDest = tmp
|
||||
}
|
||||
|
||||
if err := os.Symlink(linkDest, tmpLinkName); err != nil {
|
||||
return errors.Wrap(err, `failed to create new symlink`)
|
||||
}
|
||||
|
||||
// the directory where rl.linkName should be created must exist
|
||||
_, err := os.Stat(linkDir)
|
||||
if err != nil { // Assume err != nil means the directory doesn't exist
|
||||
if err := os.MkdirAll(linkDir, 0755); err != nil {
|
||||
return errors.Wrapf(err, `failed to create directory %s`, linkDir)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpLinkName, rl.linkName); err != nil {
|
||||
return errors.Wrap(err, `failed to rename new symlink`)
|
||||
}
|
||||
}
|
||||
|
||||
if rl.maxAge <= 0 && rl.rotationCount <= 0 {
|
||||
return errors.New("panic: maxAge and rotationCount are both set")
|
||||
}
|
||||
|
||||
matches, err := filepath.Glob(rl.globPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cutoff := rl.clock.Now().Add(-1 * rl.maxAge)
|
||||
var toUnlink []string
|
||||
for _, path := range matches {
|
||||
// Ignore lock files
|
||||
if strings.HasSuffix(path, "_lock") || strings.HasSuffix(path, "_symlink") {
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fl, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if rl.maxAge > 0 && fi.ModTime().After(cutoff) {
|
||||
continue
|
||||
}
|
||||
|
||||
if rl.rotationCount > 0 && fl.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
}
|
||||
toUnlink = append(toUnlink, path)
|
||||
}
|
||||
|
||||
if rl.rotationCount > 0 {
|
||||
// Only delete if we have more than rotationCount
|
||||
if rl.rotationCount >= uint(len(toUnlink)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
toUnlink = toUnlink[:len(toUnlink)-int(rl.rotationCount)]
|
||||
}
|
||||
|
||||
if len(toUnlink) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard.Enable()
|
||||
go func() {
|
||||
// unlink files on a separate goroutine
|
||||
for _, path := range toUnlink {
|
||||
os.Remove(path)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close satisfies the io.Closer interface. You must
|
||||
// call this method if you performed any writes to
|
||||
// the object.
|
||||
func (rl *RotateLogs) Close() error {
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
|
||||
if rl.outFh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rl.outFh.Close()
|
||||
rl.outFh = nil
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user