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
|
||||
}
|
||||
24
vendor/github.com/lestrrat-go/strftime/.gitignore
generated
vendored
Normal file
24
vendor/github.com/lestrrat-go/strftime/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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
|
||||
*.test
|
||||
*.prof
|
||||
5
vendor/github.com/lestrrat-go/strftime/.golangci.yml
generated
vendored
Normal file
5
vendor/github.com/lestrrat-go/strftime/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
28
vendor/github.com/lestrrat-go/strftime/Changes
generated
vendored
Normal file
28
vendor/github.com/lestrrat-go/strftime/Changes
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
v1.0.6 - 20 Apr 2022
|
||||
[Miscellaneous]
|
||||
* Minimum go version is now go 1.13
|
||||
* github.com/pkg/errors is going to be phased out in steps. In this release,
|
||||
users may opt-in to using native errors using `fmt.Errorf("%w")` by
|
||||
specifying the tag `strftime_native_errors`. In the next release, the default
|
||||
will be to use native errors, but users will be able to opt-in to using
|
||||
github.com/pkg/errors using a tag. The version after will remove github.com/pkg/errors.
|
||||
|
||||
This is something that we normally would do over a major version upgrade
|
||||
but since we do not expect this library to receive API breaking changes in the
|
||||
near future and thus no v2 is expected, we have decided to do this over few
|
||||
non-major releases.
|
||||
|
||||
v1.0.5
|
||||
[New features]
|
||||
* `(strftime.Strftime).FormatBuffer([]byte, time.Time) []byte` has been added.
|
||||
This allows the user to provide the same underlying `[]byte` buffer for each
|
||||
call to `FormatBuffer`, which avoid allocation per call.
|
||||
* `%I` formatted midnight as `00`, where it should have been using `01`
|
||||
|
||||
|
||||
before v1.0.4
|
||||
|
||||
Apparently we have failed to provide Changes prior to v1.0.5 :(
|
||||
21
vendor/github.com/lestrrat-go/strftime/LICENSE
generated
vendored
Normal file
21
vendor/github.com/lestrrat-go/strftime/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 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.
|
||||
29
vendor/github.com/lestrrat-go/strftime/Makefile
generated
vendored
Normal file
29
vendor/github.com/lestrrat-go/strftime/Makefile
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
.PHONY: bench realclean cover viewcover test lint
|
||||
|
||||
bench:
|
||||
go test -tags bench -benchmem -bench .
|
||||
@git checkout go.mod
|
||||
@rm go.sum
|
||||
|
||||
realclean:
|
||||
rm coverage.out
|
||||
|
||||
test:
|
||||
go test -v -race ./...
|
||||
|
||||
cover:
|
||||
ifeq ($(strip $(STRFTIME_TAGS)),)
|
||||
go test -v -race -coverpkg=./... -coverprofile=coverage.out ./...
|
||||
else
|
||||
go test -v -tags $(STRFTIME_TAGS) -race -coverpkg=./... -coverprofile=coverage.out ./...
|
||||
endif
|
||||
|
||||
viewcover:
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
|
||||
imports:
|
||||
goimports -w ./
|
||||
|
||||
227
vendor/github.com/lestrrat-go/strftime/README.md
generated
vendored
Normal file
227
vendor/github.com/lestrrat-go/strftime/README.md
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
# strftime
|
||||
|
||||
Fast strftime for Go
|
||||
|
||||
[](https://travis-ci.org/lestrrat-go/strftime)
|
||||
|
||||
[](https://godoc.org/github.com/lestrrat-go/strftime)
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
```go
|
||||
f, err := strftime.New(`.... pattern ...`)
|
||||
if err := f.Format(buf, time.Now()); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
```
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The goals for this library are
|
||||
|
||||
* Optimized for the same pattern being called repeatedly
|
||||
* Be flexible about destination to write the results out
|
||||
* Be as complete as possible in terms of conversion specifications
|
||||
|
||||
# API
|
||||
|
||||
## Format(string, time.Time) (string, error)
|
||||
|
||||
Takes the pattern and the time, and formats it. This function is a utility function that recompiles the pattern every time the function is called. If you know beforehand that you will be formatting the same pattern multiple times, consider using `New` to create a `Strftime` object and reuse it.
|
||||
|
||||
## New(string) (\*Strftime, error)
|
||||
|
||||
Takes the pattern and creates a new `Strftime` object.
|
||||
|
||||
## obj.Pattern() string
|
||||
|
||||
Returns the pattern string used to create this `Strftime` object
|
||||
|
||||
## obj.Format(io.Writer, time.Time) error
|
||||
|
||||
Formats the time according to the pre-compiled pattern, and writes the result to the specified `io.Writer`
|
||||
|
||||
## obj.FormatString(time.Time) string
|
||||
|
||||
Formats the time according to the pre-compiled pattern, and returns the result string.
|
||||
|
||||
# SUPPORTED CONVERSION SPECIFICATIONS
|
||||
|
||||
| pattern | description |
|
||||
|:--------|:------------|
|
||||
| %A | national representation of the full weekday name |
|
||||
| %a | national representation of the abbreviated weekday |
|
||||
| %B | national representation of the full month name |
|
||||
| %b | national representation of the abbreviated month name |
|
||||
| %C | (year / 100) as decimal number; single digits are preceded by a zero |
|
||||
| %c | national representation of time and date |
|
||||
| %D | equivalent to %m/%d/%y |
|
||||
| %d | day of the month as a decimal number (01-31) |
|
||||
| %e | the day of the month as a decimal number (1-31); single digits are preceded by a blank |
|
||||
| %F | equivalent to %Y-%m-%d |
|
||||
| %H | the hour (24-hour clock) as a decimal number (00-23) |
|
||||
| %h | same as %b |
|
||||
| %I | the hour (12-hour clock) as a decimal number (01-12) |
|
||||
| %j | the day of the year as a decimal number (001-366) |
|
||||
| %k | the hour (24-hour clock) as a decimal number (0-23); single digits are preceded by a blank |
|
||||
| %l | the hour (12-hour clock) as a decimal number (1-12); single digits are preceded by a blank |
|
||||
| %M | the minute as a decimal number (00-59) |
|
||||
| %m | the month as a decimal number (01-12) |
|
||||
| %n | a newline |
|
||||
| %p | national representation of either "ante meridiem" (a.m.) or "post meridiem" (p.m.) as appropriate. |
|
||||
| %R | equivalent to %H:%M |
|
||||
| %r | equivalent to %I:%M:%S %p |
|
||||
| %S | the second as a decimal number (00-60) |
|
||||
| %T | equivalent to %H:%M:%S |
|
||||
| %t | a tab |
|
||||
| %U | the week number of the year (Sunday as the first day of the week) as a decimal number (00-53) |
|
||||
| %u | the weekday (Monday as the first day of the week) as a decimal number (1-7) |
|
||||
| %V | the week number of the year (Monday as the first day of the week) as a decimal number (01-53) |
|
||||
| %v | equivalent to %e-%b-%Y |
|
||||
| %W | the week number of the year (Monday as the first day of the week) as a decimal number (00-53) |
|
||||
| %w | the weekday (Sunday as the first day of the week) as a decimal number (0-6) |
|
||||
| %X | national representation of the time |
|
||||
| %x | national representation of the date |
|
||||
| %Y | the year with century as a decimal number |
|
||||
| %y | the year without century as a decimal number (00-99) |
|
||||
| %Z | the time zone name |
|
||||
| %z | the time zone offset from UTC |
|
||||
| %% | a '%' |
|
||||
|
||||
# EXTENSIONS / CUSTOM SPECIFICATIONS
|
||||
|
||||
This library in general tries to be POSIX compliant, but sometimes you just need that
|
||||
extra specification or two that is relatively widely used but is not included in the
|
||||
POSIX specification.
|
||||
|
||||
For example, POSIX does not specify how to print out milliseconds,
|
||||
but popular implementations allow `%f` or `%L` to achieve this.
|
||||
|
||||
For those instances, `strftime.Strftime` can be configured to use a custom set of
|
||||
specifications:
|
||||
|
||||
```
|
||||
ss := strftime.NewSpecificationSet()
|
||||
ss.Set('L', ...) // provide implementation for `%L`
|
||||
|
||||
// pass this new specification set to the strftime instance
|
||||
p, err := strftime.New(`%L`, strftime.WithSpecificationSet(ss))
|
||||
p.Format(..., time.Now())
|
||||
```
|
||||
|
||||
The implementation must implement the `Appender` interface, which is
|
||||
|
||||
```
|
||||
type Appender interface {
|
||||
Append([]byte, time.Time) []byte
|
||||
}
|
||||
```
|
||||
|
||||
For commonly used extensions such as the millisecond example and Unix timestamp, we provide a default
|
||||
implementation so the user can do one of the following:
|
||||
|
||||
```
|
||||
// (1) Pass a specification byte and the Appender
|
||||
// This allows you to pass arbitrary Appenders
|
||||
p, err := strftime.New(
|
||||
`%L`,
|
||||
strftime.WithSpecification('L', strftime.Milliseconds),
|
||||
)
|
||||
|
||||
// (2) Pass an option that knows to use strftime.Milliseconds
|
||||
p, err := strftime.New(
|
||||
`%L`,
|
||||
strftime.WithMilliseconds('L'),
|
||||
)
|
||||
```
|
||||
|
||||
Similarly for Unix Timestamp:
|
||||
```
|
||||
// (1) Pass a specification byte and the Appender
|
||||
// This allows you to pass arbitrary Appenders
|
||||
p, err := strftime.New(
|
||||
`%s`,
|
||||
strftime.WithSpecification('s', strftime.UnixSeconds),
|
||||
)
|
||||
|
||||
// (2) Pass an option that knows to use strftime.UnixSeconds
|
||||
p, err := strftime.New(
|
||||
`%s`,
|
||||
strftime.WithUnixSeconds('s'),
|
||||
)
|
||||
```
|
||||
|
||||
If a common specification is missing, please feel free to submit a PR
|
||||
(but please be sure to be able to defend how "common" it is)
|
||||
|
||||
## List of available extensions
|
||||
|
||||
- [`Milliseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#Milliseconds) (related option: [`WithMilliseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#WithMilliseconds));
|
||||
|
||||
- [`Microseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#Microseconds) (related option: [`WithMicroseconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#WithMicroseconds));
|
||||
|
||||
- [`UnixSeconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#UnixSeconds) (related option: [`WithUnixSeconds`](https://pkg.go.dev/github.com/lestrrat-go/strftime?tab=doc#WithUnixSeconds)).
|
||||
|
||||
|
||||
# PERFORMANCE / OTHER LIBRARIES
|
||||
|
||||
The following benchmarks were run separately because some libraries were using cgo on specific platforms (notabley, the fastly version)
|
||||
|
||||
```
|
||||
// On my OS X 10.14.6, 2.3 GHz Intel Core i5, 16GB memory.
|
||||
// go version go1.13.4 darwin/amd64
|
||||
hummingbird% go test -tags bench -benchmem -bench .
|
||||
<snip>
|
||||
BenchmarkTebeka-4 297471 3905 ns/op 257 B/op 20 allocs/op
|
||||
BenchmarkJehiah-4 818444 1773 ns/op 256 B/op 17 allocs/op
|
||||
BenchmarkFastly-4 2330794 550 ns/op 80 B/op 5 allocs/op
|
||||
BenchmarkLestrrat-4 916365 1458 ns/op 80 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedString-4 2527428 546 ns/op 128 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedWriter-4 537422 2155 ns/op 192 B/op 3 allocs/op
|
||||
PASS
|
||||
ok github.com/lestrrat-go/strftime 25.618s
|
||||
```
|
||||
|
||||
```
|
||||
// On a host on Google Cloud Platform, machine-type: f1-micro (vCPU x 1, memory: 0.6GB)
|
||||
// (Yes, I was being skimpy)
|
||||
// Linux <snip> 4.9.0-11-amd64 #1 SMP Debian 4.9.189-3+deb9u1 (2019-09-20) x86_64 GNU/Linux
|
||||
// go version go1.13.4 linux/amd64
|
||||
hummingbird% go test -tags bench -benchmem -bench .
|
||||
<snip>
|
||||
BenchmarkTebeka 254997 4726 ns/op 256 B/op 20 allocs/op
|
||||
BenchmarkJehiah 659289 1882 ns/op 256 B/op 17 allocs/op
|
||||
BenchmarkFastly 389150 3044 ns/op 224 B/op 13 allocs/op
|
||||
BenchmarkLestrrat 699069 1780 ns/op 80 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedString 2081594 589 ns/op 128 B/op 2 allocs/op
|
||||
BenchmarkLestrratCachedWriter 825763 1480 ns/op 192 B/op 3 allocs/op
|
||||
PASS
|
||||
ok github.com/lestrrat-go/strftime 11.355s
|
||||
```
|
||||
|
||||
This library is much faster than other libraries *IF* you can reuse the format pattern.
|
||||
|
||||
Here's the annotated list from the benchmark results. You can clearly see that (re)using a `Strftime` object
|
||||
and producing a string is the fastest. Writing to an `io.Writer` seems a bit sluggish, but since
|
||||
the one producing the string is doing almost exactly the same thing, we believe this is purely the overhead of
|
||||
writing to an `io.Writer`
|
||||
|
||||
| Import Path | Score | Note |
|
||||
|:------------------------------------|--------:|:--------------------------------|
|
||||
| github.com/lestrrat-go/strftime | 3000000 | Using `FormatString()` (cached) |
|
||||
| github.com/fastly/go-utils/strftime | 2000000 | Pure go version on OS X |
|
||||
| github.com/lestrrat-go/strftime | 1000000 | Using `Format()` (NOT cached) |
|
||||
| github.com/jehiah/go-strftime | 1000000 | |
|
||||
| github.com/fastly/go-utils/strftime | 1000000 | cgo version on Linux |
|
||||
| github.com/lestrrat-go/strftime | 500000 | Using `Format()` (cached) |
|
||||
| github.com/tebeka/strftime | 300000 | |
|
||||
|
||||
However, depending on your pattern, this speed may vary. If you find a particular pattern that seems sluggish,
|
||||
please send in patches or tests.
|
||||
|
||||
Please also note that this benchmark only uses the subset of conversion specifications that are supported by *ALL* of the libraries compared.
|
||||
|
||||
Somethings to consider when making performance comparisons in the future:
|
||||
|
||||
* Can it write to io.Writer?
|
||||
* Which `%specification` does it handle?
|
||||
348
vendor/github.com/lestrrat-go/strftime/appenders.go
generated
vendored
Normal file
348
vendor/github.com/lestrrat-go/strftime/appenders.go
generated
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These are all of the standard, POSIX compliant specifications.
|
||||
// Extensions should be in extensions.go
|
||||
var (
|
||||
fullWeekDayName = StdlibFormat("Monday")
|
||||
abbrvWeekDayName = StdlibFormat("Mon")
|
||||
fullMonthName = StdlibFormat("January")
|
||||
abbrvMonthName = StdlibFormat("Jan")
|
||||
centuryDecimal = AppendFunc(appendCentury)
|
||||
timeAndDate = StdlibFormat("Mon Jan _2 15:04:05 2006")
|
||||
mdy = StdlibFormat("01/02/06")
|
||||
dayOfMonthZeroPad = StdlibFormat("02")
|
||||
dayOfMonthSpacePad = StdlibFormat("_2")
|
||||
ymd = StdlibFormat("2006-01-02")
|
||||
twentyFourHourClockZeroPad = &hourPadded{twelveHour: false, pad: '0'}
|
||||
twelveHourClockZeroPad = &hourPadded{twelveHour: true, pad: '0'}
|
||||
dayOfYear = AppendFunc(appendDayOfYear)
|
||||
twentyFourHourClockSpacePad = &hourPadded{twelveHour: false, pad: ' '}
|
||||
twelveHourClockSpacePad = &hourPadded{twelveHour: true, pad: ' '}
|
||||
minutesZeroPad = StdlibFormat("04")
|
||||
monthNumberZeroPad = StdlibFormat("01")
|
||||
newline = Verbatim("\n")
|
||||
ampm = StdlibFormat("PM")
|
||||
hm = StdlibFormat("15:04")
|
||||
imsp = hmsWAMPM{}
|
||||
secondsNumberZeroPad = StdlibFormat("05")
|
||||
hms = StdlibFormat("15:04:05")
|
||||
tab = Verbatim("\t")
|
||||
weekNumberSundayOrigin = weeknumberOffset(0) // week number of the year, Sunday first
|
||||
weekdayMondayOrigin = weekday(1)
|
||||
// monday as the first day, and 01 as the first value
|
||||
weekNumberMondayOriginOneOrigin = AppendFunc(appendWeekNumber)
|
||||
eby = StdlibFormat("_2-Jan-2006")
|
||||
// monday as the first day, and 00 as the first value
|
||||
weekNumberMondayOrigin = weeknumberOffset(1) // week number of the year, Monday first
|
||||
weekdaySundayOrigin = weekday(0)
|
||||
natReprTime = StdlibFormat("15:04:05") // national representation of the time XXX is this correct?
|
||||
natReprDate = StdlibFormat("01/02/06") // national representation of the date XXX is this correct?
|
||||
year = StdlibFormat("2006") // year with century
|
||||
yearNoCentury = StdlibFormat("06") // year w/o century
|
||||
timezone = StdlibFormat("MST") // time zone name
|
||||
timezoneOffset = StdlibFormat("-0700") // time zone ofset from UTC
|
||||
percent = Verbatim("%")
|
||||
)
|
||||
|
||||
// Appender is the interface that must be fulfilled by components that
|
||||
// implement the translation of specifications to actual time value.
|
||||
//
|
||||
// The Append method takes the accumulated byte buffer, and the time to
|
||||
// use to generate the textual representation. The resulting byte
|
||||
// sequence must be returned by this method, normally by using the
|
||||
// append() builtin function.
|
||||
type Appender interface {
|
||||
Append([]byte, time.Time) []byte
|
||||
}
|
||||
|
||||
// AppendFunc is an utility type to allow users to create a
|
||||
// function-only version of an Appender
|
||||
type AppendFunc func([]byte, time.Time) []byte
|
||||
|
||||
func (af AppendFunc) Append(b []byte, t time.Time) []byte {
|
||||
return af(b, t)
|
||||
}
|
||||
|
||||
type appenderList []Appender
|
||||
|
||||
type dumper interface {
|
||||
dump(io.Writer)
|
||||
}
|
||||
|
||||
func (l appenderList) dump(out io.Writer) {
|
||||
var buf bytes.Buffer
|
||||
ll := len(l)
|
||||
for i, a := range l {
|
||||
if dumper, ok := a.(dumper); ok {
|
||||
dumper.dump(&buf)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%#v", a)
|
||||
}
|
||||
|
||||
if i < ll-1 {
|
||||
fmt.Fprintf(&buf, ",\n")
|
||||
}
|
||||
}
|
||||
if _, err := buf.WriteTo(out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// does the time.Format thing
|
||||
type stdlibFormat struct {
|
||||
s string
|
||||
}
|
||||
|
||||
// StdlibFormat returns an Appender that simply goes through `time.Format()`
|
||||
// For example, if you know you want to display the abbreviated month name for %b,
|
||||
// you can create a StdlibFormat with the pattern `Jan` and register that
|
||||
// for specification `b`:
|
||||
//
|
||||
// a := StdlibFormat(`Jan`)
|
||||
// ss := NewSpecificationSet()
|
||||
// ss.Set('b', a) // does %b -> abbreviated month name
|
||||
func StdlibFormat(s string) Appender {
|
||||
return &stdlibFormat{s: s}
|
||||
}
|
||||
|
||||
func (v stdlibFormat) Append(b []byte, t time.Time) []byte {
|
||||
return t.AppendFormat(b, v.s)
|
||||
}
|
||||
|
||||
func (v stdlibFormat) str() string {
|
||||
return v.s
|
||||
}
|
||||
|
||||
func (v stdlibFormat) canCombine() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v stdlibFormat) combine(w combiner) Appender {
|
||||
return StdlibFormat(v.s + w.str())
|
||||
}
|
||||
|
||||
func (v stdlibFormat) dump(out io.Writer) {
|
||||
fmt.Fprintf(out, "stdlib: %s", v.s)
|
||||
}
|
||||
|
||||
type verbatimw struct {
|
||||
s string
|
||||
}
|
||||
|
||||
// Verbatim returns an Appender suitable for generating static text.
|
||||
// For static text, this method is slightly favorable than creating
|
||||
// your own appender, as adjacent verbatim blocks will be combined
|
||||
// at compile time to produce more efficient Appenders
|
||||
func Verbatim(s string) Appender {
|
||||
return &verbatimw{s: s}
|
||||
}
|
||||
|
||||
func (v verbatimw) Append(b []byte, _ time.Time) []byte {
|
||||
return append(b, v.s...)
|
||||
}
|
||||
|
||||
func (v verbatimw) canCombine() bool {
|
||||
return canCombine(v.s)
|
||||
}
|
||||
|
||||
func (v verbatimw) combine(w combiner) Appender {
|
||||
if _, ok := w.(*stdlibFormat); ok {
|
||||
return StdlibFormat(v.s + w.str())
|
||||
}
|
||||
return Verbatim(v.s + w.str())
|
||||
}
|
||||
|
||||
func (v verbatimw) str() string {
|
||||
return v.s
|
||||
}
|
||||
|
||||
func (v verbatimw) dump(out io.Writer) {
|
||||
fmt.Fprintf(out, "verbatim: %s", v.s)
|
||||
}
|
||||
|
||||
// These words below, as well as any decimal character
|
||||
var combineExclusion = []string{
|
||||
"Mon",
|
||||
"Monday",
|
||||
"Jan",
|
||||
"January",
|
||||
"MST",
|
||||
"PM",
|
||||
"pm",
|
||||
}
|
||||
|
||||
func canCombine(s string) bool {
|
||||
if strings.ContainsAny(s, "0123456789") {
|
||||
return false
|
||||
}
|
||||
for _, word := range combineExclusion {
|
||||
if strings.Contains(s, word) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type combiner interface {
|
||||
canCombine() bool
|
||||
combine(combiner) Appender
|
||||
str() string
|
||||
}
|
||||
|
||||
// this is container for the compiler to keep track of appenders,
|
||||
// and combine them as we parse and compile the pattern
|
||||
type combiningAppend struct {
|
||||
list appenderList
|
||||
prev Appender
|
||||
prevCanCombine bool
|
||||
}
|
||||
|
||||
func (ca *combiningAppend) Append(w Appender) {
|
||||
if ca.prevCanCombine {
|
||||
if wc, ok := w.(combiner); ok && wc.canCombine() {
|
||||
ca.prev = ca.prev.(combiner).combine(wc)
|
||||
ca.list[len(ca.list)-1] = ca.prev
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ca.list = append(ca.list, w)
|
||||
ca.prev = w
|
||||
ca.prevCanCombine = false
|
||||
if comb, ok := w.(combiner); ok {
|
||||
if comb.canCombine() {
|
||||
ca.prevCanCombine = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appendCentury(b []byte, t time.Time) []byte {
|
||||
n := t.Year() / 100
|
||||
if n < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
type weekday int
|
||||
|
||||
func (v weekday) Append(b []byte, t time.Time) []byte {
|
||||
n := int(t.Weekday())
|
||||
if n < int(v) {
|
||||
n += 7
|
||||
}
|
||||
return append(b, byte(n+48))
|
||||
}
|
||||
|
||||
type weeknumberOffset int
|
||||
|
||||
func (v weeknumberOffset) Append(b []byte, t time.Time) []byte {
|
||||
yd := t.YearDay()
|
||||
offset := int(t.Weekday()) - int(v)
|
||||
if offset < 0 {
|
||||
offset += 7
|
||||
}
|
||||
|
||||
if yd < offset {
|
||||
return append(b, '0', '0')
|
||||
}
|
||||
|
||||
n := ((yd - offset) / 7) + 1
|
||||
if n < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
func appendWeekNumber(b []byte, t time.Time) []byte {
|
||||
_, n := t.ISOWeek()
|
||||
if n < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
func appendDayOfYear(b []byte, t time.Time) []byte {
|
||||
n := t.YearDay()
|
||||
if n < 10 {
|
||||
b = append(b, '0', '0')
|
||||
} else if n < 100 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(n)...)
|
||||
}
|
||||
|
||||
type hourPadded struct {
|
||||
pad byte
|
||||
twelveHour bool
|
||||
}
|
||||
|
||||
func (v hourPadded) Append(b []byte, t time.Time) []byte {
|
||||
h := t.Hour()
|
||||
if v.twelveHour && h > 12 {
|
||||
h = h - 12
|
||||
}
|
||||
if v.twelveHour && h == 0 {
|
||||
h = 12
|
||||
}
|
||||
|
||||
if h < 10 {
|
||||
b = append(b, v.pad)
|
||||
b = append(b, byte(h+48))
|
||||
} else {
|
||||
b = unrollTwoDigits(b, h)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func unrollTwoDigits(b []byte, v int) []byte {
|
||||
b = append(b, byte((v/10)+48))
|
||||
b = append(b, byte((v%10)+48))
|
||||
return b
|
||||
}
|
||||
|
||||
type hmsWAMPM struct{}
|
||||
|
||||
func (v hmsWAMPM) Append(b []byte, t time.Time) []byte {
|
||||
h := t.Hour()
|
||||
var am bool
|
||||
|
||||
if h == 0 {
|
||||
b = append(b, '1')
|
||||
b = append(b, '2')
|
||||
am = true
|
||||
} else {
|
||||
switch {
|
||||
case h == 12:
|
||||
// no op
|
||||
case h > 12:
|
||||
h = h - 12
|
||||
default:
|
||||
am = true
|
||||
}
|
||||
b = unrollTwoDigits(b, h)
|
||||
}
|
||||
b = append(b, ':')
|
||||
b = unrollTwoDigits(b, t.Minute())
|
||||
b = append(b, ':')
|
||||
b = unrollTwoDigits(b, t.Second())
|
||||
|
||||
b = append(b, ' ')
|
||||
if am {
|
||||
b = append(b, 'A')
|
||||
} else {
|
||||
b = append(b, 'P')
|
||||
}
|
||||
b = append(b, 'M')
|
||||
|
||||
return b
|
||||
}
|
||||
67
vendor/github.com/lestrrat-go/strftime/extension.go
generated
vendored
Normal file
67
vendor/github.com/lestrrat-go/strftime/extension.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NOTE: declare private variable and iniitalize once in init(),
|
||||
// and leave the Milliseconds() function as returning static content.
|
||||
// This way, `go doc -all` does not show the contents of the
|
||||
// milliseconds function
|
||||
var milliseconds Appender
|
||||
var microseconds Appender
|
||||
var unixseconds Appender
|
||||
|
||||
func init() {
|
||||
milliseconds = AppendFunc(func(b []byte, t time.Time) []byte {
|
||||
millisecond := int(t.Nanosecond()) / int(time.Millisecond)
|
||||
if millisecond < 100 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if millisecond < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(millisecond)...)
|
||||
})
|
||||
microseconds = AppendFunc(func(b []byte, t time.Time) []byte {
|
||||
microsecond := int(t.Nanosecond()) / int(time.Microsecond)
|
||||
if microsecond < 100000 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 10000 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 1000 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 100 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
if microsecond < 10 {
|
||||
b = append(b, '0')
|
||||
}
|
||||
return append(b, strconv.Itoa(microsecond)...)
|
||||
})
|
||||
unixseconds = AppendFunc(func(b []byte, t time.Time) []byte {
|
||||
return append(b, strconv.FormatInt(t.Unix(), 10)...)
|
||||
})
|
||||
}
|
||||
|
||||
// Milliseconds returns the Appender suitable for creating a zero-padded,
|
||||
// 3-digit millisecond textual representation.
|
||||
func Milliseconds() Appender {
|
||||
return milliseconds
|
||||
}
|
||||
|
||||
// Microsecond returns the Appender suitable for creating a zero-padded,
|
||||
// 6-digit microsecond textual representation.
|
||||
func Microseconds() Appender {
|
||||
return microseconds
|
||||
}
|
||||
|
||||
// UnixSeconds returns the Appender suitable for creating
|
||||
// unix timestamp textual representation.
|
||||
func UnixSeconds() Appender {
|
||||
return unixseconds
|
||||
}
|
||||
18
vendor/github.com/lestrrat-go/strftime/internal/errors/errors_fmt.go
generated
vendored
Normal file
18
vendor/github.com/lestrrat-go/strftime/internal/errors/errors_fmt.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build strftime_native_errors
|
||||
// +build strftime_native_errors
|
||||
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
func New(s string) error {
|
||||
return fmt.Errorf(s)
|
||||
}
|
||||
|
||||
func Errorf(s string, args ...interface{}) error {
|
||||
return fmt.Errorf(s, args...)
|
||||
}
|
||||
|
||||
func Wrap(err error, s string) error {
|
||||
return fmt.Errorf(s+`: %w`, err)
|
||||
}
|
||||
18
vendor/github.com/lestrrat-go/strftime/internal/errors/errors_pkg.go
generated
vendored
Normal file
18
vendor/github.com/lestrrat-go/strftime/internal/errors/errors_pkg.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build !strftime_native_errors
|
||||
// +build !strftime_native_errors
|
||||
|
||||
package errors
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
func New(s string) error {
|
||||
return errors.New(s)
|
||||
}
|
||||
|
||||
func Errorf(s string, args ...interface{}) error {
|
||||
return errors.Errorf(s, args...)
|
||||
}
|
||||
|
||||
func Wrap(err error, s string) error {
|
||||
return errors.Wrap(err, s)
|
||||
}
|
||||
67
vendor/github.com/lestrrat-go/strftime/options.go
generated
vendored
Normal file
67
vendor/github.com/lestrrat-go/strftime/options.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
package strftime
|
||||
|
||||
type Option interface {
|
||||
Name() string
|
||||
Value() interface{}
|
||||
}
|
||||
|
||||
type option struct {
|
||||
name string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (o *option) Name() string { return o.name }
|
||||
func (o *option) Value() interface{} { return o.value }
|
||||
|
||||
const optSpecificationSet = `opt-specification-set`
|
||||
|
||||
// WithSpecification allows you to specify a custom specification set
|
||||
func WithSpecificationSet(ds SpecificationSet) Option {
|
||||
return &option{
|
||||
name: optSpecificationSet,
|
||||
value: ds,
|
||||
}
|
||||
}
|
||||
|
||||
type optSpecificationPair struct {
|
||||
name byte
|
||||
appender Appender
|
||||
}
|
||||
|
||||
const optSpecification = `opt-specification`
|
||||
|
||||
// WithSpecification allows you to create a new specification set on the fly,
|
||||
// to be used only for that invocation.
|
||||
func WithSpecification(b byte, a Appender) Option {
|
||||
return &option{
|
||||
name: optSpecification,
|
||||
value: &optSpecificationPair{
|
||||
name: b,
|
||||
appender: a,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithMilliseconds is similar to WithSpecification, and specifies that
|
||||
// the Strftime object should interpret the pattern `%b` (where b
|
||||
// is the byte that you specify as the argument)
|
||||
// as the zero-padded, 3 letter milliseconds of the time.
|
||||
func WithMilliseconds(b byte) Option {
|
||||
return WithSpecification(b, Milliseconds())
|
||||
}
|
||||
|
||||
// WithMicroseconds is similar to WithSpecification, and specifies that
|
||||
// the Strftime object should interpret the pattern `%b` (where b
|
||||
// is the byte that you specify as the argument)
|
||||
// as the zero-padded, 3 letter microseconds of the time.
|
||||
func WithMicroseconds(b byte) Option {
|
||||
return WithSpecification(b, Microseconds())
|
||||
}
|
||||
|
||||
// WithUnixSeconds is similar to WithSpecification, and specifies that
|
||||
// the Strftime object should interpret the pattern `%b` (where b
|
||||
// is the byte that you specify as the argument)
|
||||
// as the unix timestamp in seconds
|
||||
func WithUnixSeconds(b byte) Option {
|
||||
return WithSpecification(b, UnixSeconds())
|
||||
}
|
||||
152
vendor/github.com/lestrrat-go/strftime/specifications.go
generated
vendored
Normal file
152
vendor/github.com/lestrrat-go/strftime/specifications.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/lestrrat-go/strftime/internal/errors"
|
||||
)
|
||||
|
||||
// because there is no such thing was a sync.RWLocker
|
||||
type rwLocker interface {
|
||||
RLock()
|
||||
RUnlock()
|
||||
sync.Locker
|
||||
}
|
||||
|
||||
// SpecificationSet is a container for patterns that Strftime uses.
|
||||
// If you want a custom strftime, you can copy the default
|
||||
// SpecificationSet and tweak it
|
||||
type SpecificationSet interface {
|
||||
Lookup(byte) (Appender, error)
|
||||
Delete(byte) error
|
||||
Set(byte, Appender) error
|
||||
}
|
||||
|
||||
type specificationSet struct {
|
||||
mutable bool
|
||||
lock rwLocker
|
||||
store map[byte]Appender
|
||||
}
|
||||
|
||||
// The default specification set does not need any locking as it is never
|
||||
// accessed from the outside, and is never mutated.
|
||||
var defaultSpecificationSet SpecificationSet
|
||||
|
||||
func init() {
|
||||
defaultSpecificationSet = newImmutableSpecificationSet()
|
||||
}
|
||||
|
||||
func newImmutableSpecificationSet() SpecificationSet {
|
||||
// Create a mutable one so that populateDefaultSpecifications work through
|
||||
// its magic, then copy the associated map
|
||||
// (NOTE: this is done this way because there used to be
|
||||
// two struct types for specification set, united under an interface.
|
||||
// it can now be removed, but we would need to change the entire
|
||||
// populateDefaultSpecifications method, and I'm currently too lazy
|
||||
// PRs welcome)
|
||||
tmp := NewSpecificationSet()
|
||||
|
||||
ss := &specificationSet{
|
||||
mutable: false,
|
||||
lock: nil, // never used, so intentionally not initialized
|
||||
store: tmp.(*specificationSet).store,
|
||||
}
|
||||
|
||||
return ss
|
||||
}
|
||||
|
||||
// NewSpecificationSet creates a specification set with the default specifications.
|
||||
func NewSpecificationSet() SpecificationSet {
|
||||
ds := &specificationSet{
|
||||
mutable: true,
|
||||
lock: &sync.RWMutex{},
|
||||
store: make(map[byte]Appender),
|
||||
}
|
||||
populateDefaultSpecifications(ds)
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
var defaultSpecifications = map[byte]Appender{
|
||||
'A': fullWeekDayName,
|
||||
'a': abbrvWeekDayName,
|
||||
'B': fullMonthName,
|
||||
'b': abbrvMonthName,
|
||||
'C': centuryDecimal,
|
||||
'c': timeAndDate,
|
||||
'D': mdy,
|
||||
'd': dayOfMonthZeroPad,
|
||||
'e': dayOfMonthSpacePad,
|
||||
'F': ymd,
|
||||
'H': twentyFourHourClockZeroPad,
|
||||
'h': abbrvMonthName,
|
||||
'I': twelveHourClockZeroPad,
|
||||
'j': dayOfYear,
|
||||
'k': twentyFourHourClockSpacePad,
|
||||
'l': twelveHourClockSpacePad,
|
||||
'M': minutesZeroPad,
|
||||
'm': monthNumberZeroPad,
|
||||
'n': newline,
|
||||
'p': ampm,
|
||||
'R': hm,
|
||||
'r': imsp,
|
||||
'S': secondsNumberZeroPad,
|
||||
'T': hms,
|
||||
't': tab,
|
||||
'U': weekNumberSundayOrigin,
|
||||
'u': weekdayMondayOrigin,
|
||||
'V': weekNumberMondayOriginOneOrigin,
|
||||
'v': eby,
|
||||
'W': weekNumberMondayOrigin,
|
||||
'w': weekdaySundayOrigin,
|
||||
'X': natReprTime,
|
||||
'x': natReprDate,
|
||||
'Y': year,
|
||||
'y': yearNoCentury,
|
||||
'Z': timezone,
|
||||
'z': timezoneOffset,
|
||||
'%': percent,
|
||||
}
|
||||
|
||||
func populateDefaultSpecifications(ds SpecificationSet) {
|
||||
for c, handler := range defaultSpecifications {
|
||||
if err := ds.Set(c, handler); err != nil {
|
||||
panic(fmt.Sprintf("failed to set default specification for %c: %s", c, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ds *specificationSet) Lookup(b byte) (Appender, error) {
|
||||
if ds.mutable {
|
||||
ds.lock.RLock()
|
||||
defer ds.lock.RLock()
|
||||
}
|
||||
v, ok := ds.store[b]
|
||||
if !ok {
|
||||
return nil, errors.Errorf(`lookup failed: '%%%c' was not found in specification set`, b)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (ds *specificationSet) Delete(b byte) error {
|
||||
if !ds.mutable {
|
||||
return errors.New(`delete failed: this specification set is marked immutable`)
|
||||
}
|
||||
|
||||
ds.lock.Lock()
|
||||
defer ds.lock.Unlock()
|
||||
delete(ds.store, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *specificationSet) Set(b byte, a Appender) error {
|
||||
if !ds.mutable {
|
||||
return errors.New(`set failed: this specification set is marked immutable`)
|
||||
}
|
||||
|
||||
ds.lock.Lock()
|
||||
defer ds.lock.Unlock()
|
||||
ds.store[b] = a
|
||||
return nil
|
||||
}
|
||||
228
vendor/github.com/lestrrat-go/strftime/strftime.go
generated
vendored
Normal file
228
vendor/github.com/lestrrat-go/strftime/strftime.go
generated
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/strftime/internal/errors"
|
||||
)
|
||||
|
||||
type compileHandler interface {
|
||||
handle(Appender)
|
||||
}
|
||||
|
||||
// compile, and create an appender list
|
||||
type appenderListBuilder struct {
|
||||
list *combiningAppend
|
||||
}
|
||||
|
||||
func (alb *appenderListBuilder) handle(a Appender) {
|
||||
alb.list.Append(a)
|
||||
}
|
||||
|
||||
// compile, and execute the appenders on the fly
|
||||
type appenderExecutor struct {
|
||||
t time.Time
|
||||
dst []byte
|
||||
}
|
||||
|
||||
func (ae *appenderExecutor) handle(a Appender) {
|
||||
ae.dst = a.Append(ae.dst, ae.t)
|
||||
}
|
||||
|
||||
func compile(handler compileHandler, p string, ds SpecificationSet) error {
|
||||
for l := len(p); l > 0; l = len(p) {
|
||||
// This is a really tight loop, so we don't even calls to
|
||||
// Verbatim() to cuase extra stuff
|
||||
var verbatim verbatimw
|
||||
|
||||
i := strings.IndexByte(p, '%')
|
||||
if i < 0 {
|
||||
verbatim.s = p
|
||||
handler.handle(&verbatim)
|
||||
// this is silly, but I don't trust break keywords when there's a
|
||||
// possibility of this piece of code being rearranged
|
||||
p = p[l:]
|
||||
continue
|
||||
}
|
||||
if i == l-1 {
|
||||
return errors.New(`stray % at the end of pattern`)
|
||||
}
|
||||
|
||||
// we found a '%'. we need the next byte to decide what to do next
|
||||
// we already know that i < l - 1
|
||||
// everything up to the i is verbatim
|
||||
if i > 0 {
|
||||
verbatim.s = p[:i]
|
||||
handler.handle(&verbatim)
|
||||
p = p[i:]
|
||||
}
|
||||
|
||||
specification, err := ds.Lookup(p[1])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, `pattern compilation failed`)
|
||||
}
|
||||
|
||||
handler.handle(specification)
|
||||
p = p[2:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSpecificationSetFor(options ...Option) (SpecificationSet, error) {
|
||||
var ds SpecificationSet = defaultSpecificationSet
|
||||
var extraSpecifications []*optSpecificationPair
|
||||
for _, option := range options {
|
||||
switch option.Name() {
|
||||
case optSpecificationSet:
|
||||
ds = option.Value().(SpecificationSet)
|
||||
case optSpecification:
|
||||
extraSpecifications = append(extraSpecifications, option.Value().(*optSpecificationPair))
|
||||
}
|
||||
}
|
||||
|
||||
if len(extraSpecifications) > 0 {
|
||||
// If ds is immutable, we're going to need to create a new
|
||||
// one. oh what a waste!
|
||||
if raw, ok := ds.(*specificationSet); ok && !raw.mutable {
|
||||
ds = NewSpecificationSet()
|
||||
}
|
||||
for _, v := range extraSpecifications {
|
||||
if err := ds.Set(v.name, v.appender); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
var fmtAppendExecutorPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
var h appenderExecutor
|
||||
h.dst = make([]byte, 0, 32)
|
||||
return &h
|
||||
},
|
||||
}
|
||||
|
||||
func getFmtAppendExecutor() *appenderExecutor {
|
||||
return fmtAppendExecutorPool.Get().(*appenderExecutor)
|
||||
}
|
||||
|
||||
func releasdeFmtAppendExecutor(v *appenderExecutor) {
|
||||
// TODO: should we discard the buffer if it's too long?
|
||||
v.dst = v.dst[:0]
|
||||
fmtAppendExecutorPool.Put(v)
|
||||
}
|
||||
|
||||
// Format takes the format `s` and the time `t` to produce the
|
||||
// format date/time. Note that this function re-compiles the
|
||||
// pattern every time it is called.
|
||||
//
|
||||
// If you know beforehand that you will be reusing the pattern
|
||||
// within your application, consider creating a `Strftime` object
|
||||
// and reusing it.
|
||||
func Format(p string, t time.Time, options ...Option) (string, error) {
|
||||
// TODO: this may be premature optimization
|
||||
ds, err := getSpecificationSetFor(options...)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, `failed to get specification set`)
|
||||
}
|
||||
h := getFmtAppendExecutor()
|
||||
defer releasdeFmtAppendExecutor(h)
|
||||
|
||||
h.t = t
|
||||
if err := compile(h, p, ds); err != nil {
|
||||
return "", errors.Wrap(err, `failed to compile format`)
|
||||
}
|
||||
|
||||
return string(h.dst), nil
|
||||
}
|
||||
|
||||
// Strftime is the object that represents a compiled strftime pattern
|
||||
type Strftime struct {
|
||||
pattern string
|
||||
compiled appenderList
|
||||
}
|
||||
|
||||
// New creates a new Strftime object. If the compilation fails, then
|
||||
// an error is returned in the second argument.
|
||||
func New(p string, options ...Option) (*Strftime, error) {
|
||||
// TODO: this may be premature optimization
|
||||
ds, err := getSpecificationSetFor(options...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, `failed to get specification set`)
|
||||
}
|
||||
|
||||
var h appenderListBuilder
|
||||
h.list = &combiningAppend{}
|
||||
|
||||
if err := compile(&h, p, ds); err != nil {
|
||||
return nil, errors.Wrap(err, `failed to compile format`)
|
||||
}
|
||||
|
||||
return &Strftime{
|
||||
pattern: p,
|
||||
compiled: h.list.list,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Pattern returns the original pattern string
|
||||
func (f *Strftime) Pattern() string {
|
||||
return f.pattern
|
||||
}
|
||||
|
||||
// Format takes the destination `dst` and time `t`. It formats the date/time
|
||||
// using the pre-compiled pattern, and outputs the results to `dst`
|
||||
func (f *Strftime) Format(dst io.Writer, t time.Time) error {
|
||||
const bufSize = 64
|
||||
var b []byte
|
||||
max := len(f.pattern) + 10
|
||||
if max < bufSize {
|
||||
var buf [bufSize]byte
|
||||
b = buf[:0]
|
||||
} else {
|
||||
b = make([]byte, 0, max)
|
||||
}
|
||||
if _, err := dst.Write(f.format(b, t)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormatBuffer is equivalent to Format, but appends the result directly to
|
||||
// supplied slice dst, returning the updated slice. This avoids any internal
|
||||
// memory allocation.
|
||||
func (f *Strftime) FormatBuffer(dst []byte, t time.Time) []byte {
|
||||
return f.format(dst, t)
|
||||
}
|
||||
|
||||
// Dump outputs the internal structure of the formatter, for debugging purposes.
|
||||
// Please do NOT assume the output format to be fixed: it is expected to change
|
||||
// in the future.
|
||||
func (f *Strftime) Dump(out io.Writer) {
|
||||
f.compiled.dump(out)
|
||||
}
|
||||
|
||||
func (f *Strftime) format(b []byte, t time.Time) []byte {
|
||||
for _, w := range f.compiled {
|
||||
b = w.Append(b, t)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// FormatString takes the time `t` and formats it, returning the
|
||||
// string containing the formated data.
|
||||
func (f *Strftime) FormatString(t time.Time) string {
|
||||
const bufSize = 64
|
||||
var b []byte
|
||||
max := len(f.pattern) + 10
|
||||
if max < bufSize {
|
||||
var buf [bufSize]byte
|
||||
b = buf[:0]
|
||||
} else {
|
||||
b = make([]byte, 0, max)
|
||||
}
|
||||
return string(f.format(b, t))
|
||||
}
|
||||
Reference in New Issue
Block a user