Skip to content
This repository was archived by the owner on Jul 19, 2021. It is now read-only.

Commit e0a88b0

Browse files
authored
Merge pull request #39 from bobmcn/hotfix/force-new-file
Add an option to New that will always create a new file.
2 parents cf4bc19 + e92e07d commit e0a88b0

6 files changed

+214
-6
lines changed

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,30 @@ object. Currently only supported event type is FiledRotated
186186
)
187187
```
188188

189+
## ForceNewFile
190+
191+
Ensure a new file is created every time New() is called. If the base file name
192+
already exists, an implicit rotation is performed.
193+
194+
```go
195+
rotatelogs.New(
196+
"/var/log/myapp/log.%Y%m%d",
197+
rotatelogs.ForceNewFile(),
198+
)
199+
```
200+
201+
## ForceNewFile
202+
203+
Ensure a new file is created every time New() is called. If the base file name
204+
already exists, an implicit rotation is performed.
205+
206+
```go
207+
rotatelogs.New(
208+
"/var/log/myapp/log.%Y%m%d",
209+
rotatelogs.ForceNewFile(),
210+
)
211+
```
212+
189213
# Rotating files forcefully
190214

191215
If you want to rotate files forcefully before the actual rotation time has reached,

example_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package rotatelogs_test
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
8+
)
9+
10+
func ExampleForceNewFile () {
11+
logDir, err := ioutil.TempDir("", "rotatelogs_test")
12+
if err != nil {
13+
fmt.Println("could not create log directory ", err)
14+
return
15+
}
16+
logPath := fmt.Sprintf("%s/test.log", logDir)
17+
18+
for i := 0; i < 2; i++ {
19+
writer, err := rotatelogs.New(logPath,
20+
rotatelogs.ForceNewFile(),
21+
)
22+
if err != nil {
23+
fmt.Println("Could not open log file ", err)
24+
return
25+
}
26+
27+
n, err := writer.Write([]byte("test"))
28+
if err != nil || n != 4 {
29+
fmt.Println("Write failed ", err, " number written ", n)
30+
return
31+
}
32+
err = writer.Close()
33+
if err != nil {
34+
fmt.Println("Close failed ", err)
35+
return
36+
}
37+
}
38+
39+
files, err := ioutil.ReadDir(logDir)
40+
if err != nil {
41+
fmt.Println("ReadDir failed ", err)
42+
return
43+
}
44+
for _, file := range files {
45+
fmt.Println(file.Name(), file.Size())
46+
}
47+
48+
err = os.RemoveAll(logDir)
49+
if err != nil {
50+
fmt.Println("RemoveAll failed ", err)
51+
return
52+
}
53+
// OUTPUT:
54+
// test.log 4
55+
// test.log.1 4
56+
}

interface.go

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type RotateLogs struct {
4646
pattern *strftime.Strftime
4747
rotationTime time.Duration
4848
rotationCount uint
49+
forceNewFile bool
4950
}
5051

5152
// Clock is the interface used by the RotateLogs

options.go

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const (
1313
optkeyMaxAge = "max-age"
1414
optkeyRotationTime = "rotation-time"
1515
optkeyRotationCount = "rotation-count"
16+
optkeyForceNewFile = "force-new-file"
1617
)
1718

1819
// WithClock creates a new Option that sets a clock
@@ -72,3 +73,10 @@ func WithRotationCount(n uint) Option {
7273
func WithHandler(h Handler) Option {
7374
return option.New(optkeyHandler, h)
7475
}
76+
77+
// ForceNewFile ensures a new file is created every time New()
78+
// is called. If the base file name already exists, an implicit
79+
// rotation is performed
80+
func ForceNewFile() Option {
81+
return option.New(optkeyForceNewFile, true)
82+
}

rotatelogs.go

+24-6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func New(p string, options ...Option) (*RotateLogs, error) {
4141
var linkName string
4242
var maxAge time.Duration
4343
var handler Handler
44+
var forceNewFile bool
4445

4546
for _, o := range options {
4647
switch o.Name() {
@@ -62,6 +63,8 @@ func New(p string, options ...Option) (*RotateLogs, error) {
6263
rotationCount = o.Value().(uint)
6364
case optkeyHandler:
6465
handler = o.Value().(Handler)
66+
case optkeyForceNewFile:
67+
forceNewFile = true
6568
}
6669
}
6770

@@ -83,6 +86,7 @@ func New(p string, options ...Option) (*RotateLogs, error) {
8386
pattern: pattern,
8487
rotationTime: rotationTime,
8588
rotationCount: rotationCount,
89+
forceNewFile: forceNewFile,
8690
}, nil
8791
}
8892

@@ -135,24 +139,38 @@ func (rl *RotateLogs) getWriter_nolock(bailOnRotateFail, useGenerationalNames bo
135139
// to log to, which may be newer than rl.currentFilename
136140
baseFn := rl.genFilename()
137141
filename := baseFn
142+
var forceNewFile bool
138143
if baseFn != rl.curBaseFn {
139144
generation = 0
145+
// even though this is the first write after calling New(),
146+
// check if a new file needs to be created
147+
if rl.forceNewFile {
148+
forceNewFile = true
149+
}
140150
} else {
141151
if !useGenerationalNames {
142152
// nothing to do
143153
return rl.outFh, nil
144154
}
145-
// This is used when we *REALLY* want to rotate a log.
146-
// instead of just using the regular strftime pattern, we
147-
// create a new file name using generational names such as
148-
// "foo.1", "foo.2", "foo.3", etc
155+
forceNewFile = true
156+
generation++
157+
}
158+
if forceNewFile {
159+
// A new file has been requested. Instead of just using the
160+
// regular strftime pattern, we create a new file name using
161+
// generational names such as "foo.1", "foo.2", "foo.3", etc
162+
var name string
149163
for {
150-
generation++
151-
name := fmt.Sprintf("%s.%d", filename, generation)
164+
if generation == 0 {
165+
name = filename
166+
} else {
167+
name = fmt.Sprintf("%s.%d", filename, generation)
168+
}
152169
if _, err := os.Stat(name); err != nil {
153170
filename = name
154171
break
155172
}
173+
generation++
156174
}
157175
}
158176
// make sure the dir is existed, eg:

rotatelogs_test.go

+101
Original file line numberDiff line numberDiff line change
@@ -428,3 +428,104 @@ func TestGHIssue23(t *testing.T) {
428428
}
429429
}
430430
}
431+
432+
func TestForceNewFile(t *testing.T) {
433+
dir, err := ioutil.TempDir("", "file-rotatelogs-force-new-file")
434+
if !assert.NoError(t, err, `creating temporary directory should succeed`) {
435+
return
436+
}
437+
defer os.RemoveAll(dir)
438+
439+
t.Run("Force a new file", func(t *testing.T) {
440+
441+
rl, err := rotatelogs.New(
442+
filepath.Join(dir, "force-new-file.log"),
443+
rotatelogs.ForceNewFile(),
444+
)
445+
if !assert.NoError(t, err, "rotatelogs.New should succeed") {
446+
return
447+
}
448+
rl.Write([]byte("Hello, World!"))
449+
rl.Close()
450+
451+
for i := 0; i < 10; i++ {
452+
baseFn := filepath.Join(dir, "force-new-file.log")
453+
rl, err := rotatelogs.New(
454+
baseFn,
455+
rotatelogs.ForceNewFile(),
456+
)
457+
if !assert.NoError(t, err, "rotatelogs.New should succeed") {
458+
return
459+
}
460+
rl.Write([]byte("Hello, World"))
461+
rl.Write([]byte(fmt.Sprintf("%d", i)))
462+
rl.Close()
463+
464+
fn := filepath.Base(rl.CurrentFileName())
465+
suffix := strings.TrimPrefix(fn, "force-new-file.log")
466+
expectedSuffix := fmt.Sprintf(".%d", i+1)
467+
if !assert.True(t, suffix == expectedSuffix, "expected suffix %s found %s", expectedSuffix, suffix) {
468+
return
469+
}
470+
assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName())
471+
content, err := ioutil.ReadFile(rl.CurrentFileName())
472+
if !assert.NoError(t, err, "ioutil.ReadFile %s should succeed", rl.CurrentFileName()) {
473+
return
474+
}
475+
str := fmt.Sprintf("Hello, World%d", i)
476+
if !assert.Equal(t, str, string(content), "read %s from file %s, not expected %s", string(content), rl.CurrentFileName(), str) {
477+
return
478+
}
479+
480+
assert.FileExists(t, baseFn, "file does not exist %s", baseFn)
481+
content, err = ioutil.ReadFile(baseFn)
482+
if !assert.NoError(t, err, "ioutil.ReadFile should succeed") {
483+
return
484+
}
485+
if !assert.Equal(t, "Hello, World!", string(content), "read %s from file %s, not expected Hello, World!", string(content), baseFn) {
486+
return
487+
}
488+
}
489+
490+
})
491+
492+
t.Run("Force a new file with Rotate", func(t *testing.T) {
493+
494+
baseFn := filepath.Join(dir, "force-new-file-rotate.log")
495+
rl, err := rotatelogs.New(
496+
baseFn,
497+
rotatelogs.ForceNewFile(),
498+
)
499+
if !assert.NoError(t, err, "rotatelogs.New should succeed") {
500+
return
501+
}
502+
rl.Write([]byte("Hello, World!"))
503+
504+
for i := 0; i < 10; i++ {
505+
if !assert.NoError(t, rl.Rotate(), "rl.Rotate should succeed") {
506+
return
507+
}
508+
rl.Write([]byte("Hello, World"))
509+
rl.Write([]byte(fmt.Sprintf("%d", i)))
510+
assert.FileExists(t, rl.CurrentFileName(), "file does not exist %s", rl.CurrentFileName())
511+
content, err := ioutil.ReadFile(rl.CurrentFileName())
512+
if !assert.NoError(t, err, "ioutil.ReadFile %s should succeed", rl.CurrentFileName()) {
513+
return
514+
}
515+
str := fmt.Sprintf("Hello, World%d", i)
516+
if !assert.Equal(t, str, string(content), "read %s from file %s, not expected %s", string(content), rl.CurrentFileName(), str) {
517+
return
518+
}
519+
520+
assert.FileExists(t, baseFn, "file does not exist %s", baseFn)
521+
content, err = ioutil.ReadFile(baseFn)
522+
if !assert.NoError(t, err, "ioutil.ReadFile should succeed") {
523+
return
524+
}
525+
if !assert.Equal(t, "Hello, World!", string(content), "read %s from file %s, not expected Hello, World!", string(content), baseFn) {
526+
return
527+
}
528+
}
529+
})
530+
}
531+

0 commit comments

Comments
 (0)