diff --git a/README.md b/README.md index e1419ca..0c15dc2 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,16 @@ w.Write([]byte("test")) // /path/to/example.log.UTC ``` +with Symlink +```go +w := writer.MustNew("/path/to/example.log.%Y%m%d", writer.WithSymlink("/path/to/example.log")) +w.Write([]byte("test")) + +// output file +// /path/to/example.log.20170204 +// /path/to/example.log -> /path/to/example.log.20170204 +``` + with Mutex ```go w := writer.MustNew("/path/to/example.log.%Y%m%d", writer.WithMutex()) diff --git a/log.go b/log.go new file mode 100644 index 0000000..21b845c --- /dev/null +++ b/log.go @@ -0,0 +1,55 @@ +package writer + +import ( + "fmt" + "io" + "os" +) + +type logger interface { + Write(b []byte) + Println(args ...interface{}) + Printf(format string, args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) +} + +type noopLogger struct{} + +type debugLogger struct { + stdout io.Writer + stderr io.Writer +} + +func newDebugLogger() *debugLogger { + return &debugLogger{ + stdout: os.Stdout, + stderr: os.Stderr, + } +} + +func (l *noopLogger) Write(b []byte) {} +func (l *noopLogger) Println(args ...interface{}) {} +func (l *noopLogger) Printf(format string, args ...interface{}) {} +func (l *noopLogger) Error(args ...interface{}) {} +func (l *noopLogger) Errorf(format string, args ...interface{}) {} + +func (l *debugLogger) Write(b []byte) { + fmt.Fprintf(l.stdout, "%s", b) +} + +func (l *debugLogger) Println(args ...interface{}) { + fmt.Fprintln(l.stdout, args...) +} + +func (l *debugLogger) Printf(format string, args ...interface{}) { + fmt.Fprintf(l.stdout, format, args...) +} + +func (l *debugLogger) Error(args ...interface{}) { + fmt.Fprintln(l.stderr, args...) +} + +func (l *debugLogger) Errorf(format string, args ...interface{}) { + fmt.Fprintf(l.stderr, format, args...) +} diff --git a/noop.go b/mutex.go similarity index 50% rename from noop.go rename to mutex.go index 72296b5..3953803 100644 --- a/noop.go +++ b/mutex.go @@ -1,11 +1,6 @@ package writer type noopMutex struct{} -type noopWriter struct{} func (*noopMutex) Lock() {} func (*noopMutex) Unlock() {} - -func (*noopWriter) Write([]byte) (int, error) { - return 0, nil // no-op -} diff --git a/writer.go b/writer.go index 2ae901b..16ae027 100644 --- a/writer.go +++ b/writer.go @@ -13,11 +13,11 @@ import ( type CronoWriter struct { pattern *strftime.Strftime // given pattern path string // current file path + symlink *strftime.Strftime // symbolic link to current file path fp *os.File // current file pointer loc *time.Location mux sync.Locker - stdout io.Writer - stderr io.Writer + debug logger init bool // if true, open the file when New() method is called } @@ -38,11 +38,11 @@ func New(pattern string, options ...Option) (*CronoWriter, error) { c := &CronoWriter{ pattern: p, path: "", + symlink: nil, fp: nil, loc: time.Local, mux: new(noopMutex), // default mutex off - stdout: &noopWriter{}, - stderr: &noopWriter{}, + debug: &noopLogger{}, init: false, } @@ -74,6 +74,16 @@ func WithLocation(loc *time.Location) Option { } } +func WithSymlink(pattern string) Option { + return func(c *CronoWriter) { + p, err := strftime.New(pattern) + if err != nil { + panic(err) + } + c.symlink = p + } +} + func WithMutex() Option { return func(c *CronoWriter) { c.mux = new(sync.Mutex) @@ -82,8 +92,7 @@ func WithMutex() Option { func WithDebug() Option { return func(c *CronoWriter) { - c.stdout = os.Stdout - c.stderr = os.Stderr + c.debug = newDebugLogger() } } @@ -97,7 +106,8 @@ func (c *CronoWriter) Write(b []byte) (int, error) { c.mux.Lock() defer c.mux.Unlock() - path := c.pattern.FormatString(now().In(c.loc)) + t := now().In(c.loc) + path := c.pattern.FormatString(t) if c.path != path { // close file @@ -117,6 +127,8 @@ func (c *CronoWriter) Write(b []byte) (int, error) { if err != nil { return c.write(nil, err) } + c.createSymlink(t, path) + c.path = path c.fp = fp } @@ -124,6 +136,28 @@ func (c *CronoWriter) Write(b []byte) (int, error) { return c.write(b, nil) } +func (c *CronoWriter) createSymlink(t time.Time, path string) { + if c.symlink == nil { + return + } + + symlink := c.symlink.FormatString(t) + if symlink == path { + c.debug.Error("Can't create symlink. same path is specified.") + return + } + + if err := os.Remove(symlink); err != nil { + c.debug.Error(err) + return + } + + if err := os.Symlink(path, symlink); err != nil { + c.debug.Error(err) + // ignore error + } +} + func (c *CronoWriter) Close() error { c.mux.Lock() defer c.mux.Unlock() @@ -133,10 +167,10 @@ func (c *CronoWriter) Close() error { func (c *CronoWriter) write(b []byte, err error) (int, error) { if err != nil { - c.stderr.Write([]byte(err.Error())) + c.debug.Error(err) return 0, err } - c.stdout.Write(b) + c.debug.Write(b) return c.fp.Write(b) } diff --git a/writer_benchmark_test.go b/writer_benchmark_test.go index 8689bdd..a0a55d6 100644 --- a/writer_benchmark_test.go +++ b/writer_benchmark_test.go @@ -40,8 +40,8 @@ func BenchmarkCronoWriter_WriteWithDebug(b *testing.B) { } c := MustNew(filepath.Join(tmpDir, "benchmark.log.%Y%m%d"), WithDebug()) - c.stdout = ioutil.Discard - c.stderr = ioutil.Discard + c.debug.(*debugLogger).stdout = ioutil.Discard + c.debug.(*debugLogger).stderr = ioutil.Discard for i := 0; i < b.N; i++ { c.Write([]byte("abcdefg")) }