Redesign logging for v2

remotes/r/develop
Guoqiang Chen 8 years ago
parent 9e5c21c45a
commit 628e833ea7

@ -2,14 +2,20 @@ package log
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
// AlwaysNewFileWriter create new log for every process
type AlwaysNewFileWriter struct {
Name string
MaxCount int
file *os.File
}
@ -38,5 +44,38 @@ func (w *AlwaysNewFileWriter) openFile() (err error) {
return err
}
if w.MaxCount > 0 {
go w.cleanFiles()
}
return nil
}
// clean old files
func (w *AlwaysNewFileWriter) cleanFiles() {
dir := path.Dir(w.Name)
fileList, err := ioutil.ReadDir(dir)
if err != nil {
return
}
prefix := path.Base(w.Name) + "."
var matches []string
for _, f := range fileList {
if !f.IsDir() && strings.HasPrefix(f.Name(), prefix) {
matches = append(matches, f.Name())
}
}
if len(matches) > w.MaxCount {
sort.Sort(sort.Reverse(sort.StringSlice(matches)))
fmt.Println(matches)
for _, f := range matches[w.MaxCount:] {
file := filepath.Join(dir, f)
os.Remove(file)
}
}
}

@ -1,16 +0,0 @@
package log
import (
"testing"
)
func TestAlwaysNewFileWriter(t *testing.T) {
log := New(&AlwaysNewFileWriter{
Name: "/tmp/test.log",
})
log.SetTimeLayout("15:04:05.999")
log.SetName("main")
for i := 0; i < 100; i++ {
log.Info("i = %d", i)
}
}

@ -1,14 +0,0 @@
package log
import (
"io"
)
type CompositeWriters []io.Writer
func (ws *CompositeWriters) Write(p []byte) (n int, err error) {
for _, w := range *ws {
n, err = w.Write(p)
}
return
}

@ -1,20 +0,0 @@
package log
import (
"os"
"testing"
)
func TestCompositeWriters(t *testing.T) {
log := New(&CompositeWriters{
os.Stdout,
&DailyFileWriter{
Name: "/tmp/test.log",
},
})
log.SetTimeLayout("15:04:05.999")
log.SetName("main")
for i := 0; i < 5; i++ {
log.Info("i = %d", i)
}
}

@ -2,14 +2,20 @@ package log
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
// DailyFileWriter create new log for every day
type DailyFileWriter struct {
Name string
MaxCount int
file *os.File
nextDayTime int64
}
@ -28,7 +34,7 @@ func (w *DailyFileWriter) Write(p []byte) (n int, err error) {
}
func (w *DailyFileWriter) openFile(now *time.Time) (err error) {
name := fmt.Sprintf("%s.%s", w.Name, now.Format("2006-01-02"))
name := fmt.Sprintf("%s.%s", w.Name, now.Format("20060102"))
// remove symbol link if exist
os.Remove(w.Name)
@ -47,5 +53,37 @@ func (w *DailyFileWriter) openFile(now *time.Time) (err error) {
year, month, day := now.Date()
w.nextDayTime = time.Date(year, month, day+1, 0, 0, 0, 0, now.Location()).Unix()
if w.MaxCount > 0 {
go w.cleanFiles()
}
return nil
}
// clean old files
func (w *DailyFileWriter) cleanFiles() {
dir := path.Dir(w.Name)
fileList, err := ioutil.ReadDir(dir)
if err != nil {
return
}
prefix := path.Base(w.Name) + "."
var matches []string
for _, f := range fileList {
if !f.IsDir() && strings.HasPrefix(f.Name(), prefix) {
matches = append(matches, f.Name())
}
}
if len(matches) > w.MaxCount {
sort.Sort(sort.Reverse(sort.StringSlice(matches)))
for _, f := range matches[w.MaxCount:] {
file := filepath.Join(dir, f)
os.Remove(file)
}
}
}

@ -1,16 +0,0 @@
package log
import (
"testing"
)
func TestDailyFileWriter(t *testing.T) {
log := New(&DailyFileWriter{
Name: "/tmp/test.log",
})
log.SetTimeLayout("15:04:05.999")
log.SetName("main")
for i := 0; i < 100; i++ {
log.Info("i = %d", i)
}
}

@ -1,2 +0,0 @@
// Logging package similar to log4j for the Golang.
package log

@ -0,0 +1,40 @@
package log_test
import (
"github.com/subchen/go-log"
"os"
)
func ExampleNew() {
logger := log.New(os.Stdout)
logger.SetAppName("app")
logger.SetFlags(log.DEFAULT_FLAGS | log.F_GID)
logger.Info("testing ...")
logger.Errorf("err = %v", os.ErrInvalid)
// Outputs:
// 2001-10-10 12:00:00,000 1234 app 987 INFO main.go:13 testing ...
// 2001-10-10 12:00:00,000 1234 app 987 ERROR main.go:14 err = invalid argument
}
func ExampleAlwaysNewFileWriter() {
log.SetWriter(&log.AlwaysNewFileWriter{
Name: "/tmp/test.log",
MaxCount: 10,
})
}
func ExampleDailyFileWriter() {
log.SetWriter(&log.DailyFileWriter{
Name: "/tmp/test.log",
MaxCount: 10,
})
}
func ExampleFixedSizeFileWriter() {
log.SetWriter(&log.FixedSizeFileWriter{
Name: "/tmp/test.log",
MaxSize: 10 * 1024 * 1024, // 10M
MaxCount: 10,
})
}

@ -1,18 +0,0 @@
package log
import (
"testing"
)
func TestFixedSizeFileWriter(t *testing.T) {
log := New(&FixedSizeFileWriter{
Name: "/tmp/test.log",
MaxSize: 1024 * 3,
MaxCount: 5,
})
log.SetTimeLayout("15:04:05.999")
log.SetName("main")
for i := 0; i < 100; i++ {
log.Info("i = %d", i)
}
}

103
log.go

@ -1,12 +1,21 @@
// Package log implements a common logging like log4j.
package log
import (
"io"
"os"
"github.com/mattn/go-isatty"
)
// std is a default logger for console
var std = New(os.Stdout).EnableColorizedLevel(true).SkipCaller(3)
var std = New(os.Stdout).SkipCaller(3)
func init() {
if isatty.IsTerminal(os.Stdout.Fd()) {
std.SetFlags(DEFAULT_FLAGS | F_COLOR)
}
}
// Get log level
func GetLevel() int {
@ -18,29 +27,44 @@ func SetLevel(level int) {
std.SetLevel(level)
}
// Set a name to indicate a process
func SetName(name string) {
std.SetName(name)
// Get log level name
func GetLevelName() string {
return std.GetLevelName()
}
// Set log level name
func SetLevelName(level string) {
std.SetLevelName(level)
}
// Set time layout for log line
func SetTimeLayout(layout string) {
std.SetTimeLayout(layout)
// Get the flags for output format
func GetFlags() int {
return std.GetFlags()
}
// EnableGoroutineId output goroutinue id
func EnableGoroutineId(enable bool) {
std.EnableGoroutineId(enable)
// Set flags for output format
func SetFlags(flags int) {
std.SetFlags(flags)
}
// EnableLongFileFormat output long file name
func EnableLongFileFormat(enable bool) {
std.EnableLongFileFormat(enable)
// Get the process name
func GetAppName() string {
return std.GetAppName()
}
// EnableColorizedLevel output colorized level
func EnableColorizedLevel(enable bool) {
std.EnableColorizedLevel(enable)
// Set a process name
func SetAppName(name string) {
std.SetAppName(name)
}
// Get time format for log line
func GetTimeFormat() string {
return std.GetTimeFormat()
}
// Set time format for log line
func SetTimeFormat(format string) {
std.SetTimeFormat(format)
}
// Set a writer
@ -68,27 +92,52 @@ func IsErrorEnabled() bool {
return std.IsErrorEnabled()
}
// Output an debug message
func Debug(msg string, args ...interface{}) {
std.Debug(msg, args...)
// Output a debug message
func Debug(obj ...interface{}) {
std.Debug(obj...)
}
// Output an info message
func Info(obj ...interface{}) {
std.Info(obj...)
}
// Output a warning message
func Warn(obj ...interface{}) {
std.Warn(obj...)
}
// Output an error message
func Error(obj ...interface{}) {
std.Error(obj...)
}
// Output a fatal message with full stack
func Fatal(obj ...interface{}) {
std.Fatal(obj...)
}
// Output a debug message
func Debugf(msg string, args ...interface{}) {
std.Debugf(msg, args...)
}
// Output an info message
func Info(msg string, args ...interface{}) {
std.Info(msg, args...)
func Infof(msg string, args ...interface{}) {
std.Infof(msg, args...)
}
// Output a warning message
func Warn(msg string, args ...interface{}) {
std.Warn(msg, args...)
func Warnf(msg string, args ...interface{}) {
std.Warnf(msg, args...)
}
// Output an error message
func Error(msg string, args ...interface{}) {
std.Error(msg, args)
func Errorf(msg string, args ...interface{}) {
std.Errorf(msg, args...)
}
// Output a fatal message with full stack
func Fatal(msg string, args ...interface{}) {
std.Fatal(msg, args)
func Fatalf(msg string, args ...interface{}) {
std.Fatalf(msg, args...)
}

@ -14,13 +14,27 @@ import (
"github.com/subchen/gstack/gls"
)
// Log Level
const (
DEBUG = iota
INFO
WARN
ERROR
FATAL
// log level
L_DEBUG = iota
L_INFO
L_WARN
L_ERROR
L_FATAL
// Bits or'ed together to control what's printed.
F_TIME = 1 << iota
F_LONG_FILE
F_SHORT_FILE
F_PID
F_GID
F_COLOR
// default flags
DEFAULT_FLAGS = F_TIME | F_SHORT_FILE | F_PID
// default time format
DEFAULT_TIME_FORMAT = "2006-01-02 15:04:05,000"
)
var (
@ -38,32 +52,34 @@ var (
"\033[31mERROR\033[0m",
"\033[35mFATAL\033[0m",
}
buffer = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
)
func New(out io.Writer) *Logger {
return &Logger{
out: out,
level: INFO,
writer: out,
level: L_INFO,
pid: os.Getpid(),
name: "",
timeLayout: "2006-01-02 15:04:05.000",
goroutineId: false,
longFileFormat: false,
colorizedLevel: false,
timeFormat: DEFAULT_TIME_FORMAT,
flags: DEFAULT_FLAGS,
callerSkip: 2,
}
}
type Logger struct {
mu sync.Mutex
out io.Writer
m sync.Mutex
writer io.Writer
level int
pid int
name string
timeLayout string
goroutineId bool
longFileFormat bool
colorizedLevel bool
timeFormat string
flags int
callerSkip int
}
@ -72,130 +88,205 @@ func (l *Logger) GetLevel() int {
}
func (l *Logger) SetLevel(level int) *Logger {
if level < L_DEBUG || level > L_FATAL {
panic("unknown log level")
}
l.m.Lock()
l.level = level
l.m.Unlock()
return l
}
func (l *Logger) SetName(name string) *Logger {
l.name = name
return l
func (l *Logger) GetLevelName() string {
return levelStr[l.level]
}
func (l *Logger) SetTimeLayout(layout string) *Logger {
l.timeLayout = layout
return l
func (l *Logger) SetLevelName(level string) {
level = strings.ToUpper(level)
for i, v := range levelStr {
if v == level {
l.SetLevel(i)
return
}
}
panic("unknown log level: " + level)
}
func (l *Logger) GetFlags() int {
return l.flags
}
func (l *Logger) EnableGoroutineId(enable bool) *Logger {
l.goroutineId = enable
func (l *Logger) SetFlags(flags int) *Logger {
l.m.Lock()
l.flags = flags
l.m.Unlock()
return l
}
func (l *Logger) EnableLongFileFormat(enable bool) *Logger {
l.longFileFormat = enable
func (l *Logger) GetAppName() string {
return l.name
}
func (l *Logger) SetAppName(name string) *Logger {
l.m.Lock()
l.name = name
l.m.Unlock()
return l
}
func (l *Logger) EnableColorizedLevel(enable bool) *Logger {
l.colorizedLevel = enable
func (l *Logger) GetTimeFormat() string {
return l.timeFormat
}
func (l *Logger) SetTimeFormat(format string) *Logger {
l.m.Lock()
l.timeFormat = format
l.m.Unlock()
return l
}
func (l *Logger) SkipCaller(skip int) *Logger {
l.m.Lock()
defer l.m.Unlock()
l.callerSkip = skip
return l
}
func (l *Logger) SetWriter(w io.Writer) *Logger {
l.out = w
l.m.Lock()
defer l.m.Unlock()
l.writer = w
return l
}
func (l *Logger) IsDebugEnabled() bool {
return l.level <= DEBUG
return l.level <= L_DEBUG
}
func (l *Logger) IsInfoEnabled() bool {
return l.level <= INFO
return l.level <= L_INFO
}
func (l *Logger) IsWarnEnabled() bool {
return l.level <= WARN
return l.level <= L_WARN
}
func (l *Logger) IsErrorEnabled() bool {
return l.level <= ERROR
return l.level <= L_ERROR
}
func (l *Logger) Debug(msg string, args ...interface{}) {
if l.level <= DEBUG {
l.log(DEBUG, msg, args...)
func (l *Logger) Debug(obj ...interface{}) {
if l.level <= L_DEBUG {
l.log(L_DEBUG, fmt.Sprint(obj...))
}
}
func (l *Logger) Info(msg string, args ...interface{}) {
if l.level <= INFO {
l.log(INFO, msg, args...)
func (l *Logger) Info(obj ...interface{}) {
if l.level <= L_INFO {
l.log(L_INFO, fmt.Sprint(obj...))
}
}
func (l *Logger) Warn(msg string, args ...interface{}) {
if l.level <= WARN {
l.log(WARN, msg, args...)
func (l *Logger) Warn(obj ...interface{}) {
if l.level <= L_WARN {
l.log(L_WARN, fmt.Sprint(obj...))
}
}
func (l *Logger) Error(msg string, args ...interface{}) {
if l.level <= ERROR {
l.log(ERROR, msg, args...)
func (l *Logger) Error(obj ...interface{}) {
if l.level <= L_ERROR {
l.log(L_ERROR, fmt.Sprint(obj...))
}
}
func (l *Logger) Fatal(msg string, args ...interface{}) {
l.log(FATAL, msg, args...)
func (l *Logger) Fatal(obj ...interface{}) {
if l.level <= L_FATAL {
l.log(L_FATAL, fmt.Sprint(obj...))
}
}
func (l *Logger) log(level int, msg string, args ...interface{}) {
_, file, line, ok := runtime.Caller(l.callerSkip)
if !ok {
file = "???"
line = 0
} else if !l.longFileFormat {
if index := strings.LastIndex(file, "/"); index >= 0 {
file = file[index+1:]
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
file = file[index+1:]
func (l *Logger) Debugf(msg string, args ...interface{}) {
if l.level <= L_DEBUG {
l.log(L_DEBUG, fmt.Sprintf(msg, args...))
}
}
func (l *Logger) Infof(msg string, args ...interface{}) {
if l.level <= L_INFO {
l.log(L_INFO, fmt.Sprintf(msg, args...))
}
}
func (l *Logger) Warnf(msg string, args ...interface{}) {
if l.level <= L_WARN {
l.log(L_WARN, fmt.Sprintf(msg, args...))
}
}
func (l *Logger) Errorf(msg string, args ...interface{}) {
if l.level <= L_ERROR {
l.log(L_ERROR, fmt.Sprintf(msg, args...))
}
}
func (l *Logger) Fatalf(msg string, args ...interface{}) {
l.log(L_FATAL, fmt.Sprintf(msg, args...))
}
func (l *Logger) log(level int, msg string) {
// output format: DATE PID [NAME] [GID] LEVEL file:line message
// 2001-10-10 12:00:00,000+0800 1234 app 987 INFO main.go:1234 log message ...
buf := new(bytes.Buffer)
buf := buffer.Get().(*bytes.Buffer)
defer buffer.Put(buf)
if l.flags&F_TIME != 0 {
timeStr := time.Now().Format(l.timeFormat)
buf.WriteString(timeStr)
buf.WriteByte(' ')
}
if l.flags&F_PID != 0 {
buf.WriteString(strconv.Itoa(l.pid))
buf.WriteByte(' ')
}
if l.name != "" {
buf.WriteString(l.name)
buf.WriteByte(' ')
}
if l.goroutineId || l.level == DEBUG {
if l.flags&F_GID != 0 {
buf.WriteString(strconv.FormatUint(gls.GoroutineID(), 10))
buf.WriteByte(' ')
}
if l.colorizedLevel {
if l.flags&F_COLOR != 0 {
buf.WriteString(levelStrWithColor[level])
} else {
buf.WriteString(levelStr[level])
}
buf.WriteByte(' ')
if l.flags&(F_LONG_FILE|F_SHORT_FILE) != 0 {
_, file, line, ok := runtime.Caller(l.callerSkip)
if !ok {
file = "???"
line = 0
} else if l.flags&F_SHORT_FILE != 0 {
if index := strings.LastIndex(file, "/"); index >= 0 {
file = file[index+1:]
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
file = file[index+1:]
}
}
buf.WriteString(file)
buf.WriteByte(':')
buf.WriteString(strconv.Itoa(line))
buf.WriteByte(' ')
fmt.Fprintf(buf, msg, args...)
}
buf.WriteString(msg)
buf.WriteByte('\n')
if level == FATAL {
if level == L_FATAL {
for i := l.callerSkip; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
@ -205,11 +296,11 @@ func (l *Logger) log(level int, msg string, args ...interface{}) {
}
}
l.mu.Lock()
defer l.mu.Unlock()
line := buf.Bytes()
buf.Reset()
timeStr := time.Now().Format(l.timeLayout)
l.m.Lock()
defer l.m.Unlock()
l.out.Write([]byte(timeStr))
l.out.Write(buf.Bytes())
l.writer.Write(line)
}

@ -2,33 +2,38 @@ package log
import (
"os"
"sync"
"testing"
"time"
)
func TestLogger(t *testing.T) {
stdout := New(os.Stdout)
stdout.SetTimeLayout("15:04:05.999")
stdout.SetName("main")
stdout.SetLevel(DEBUG)
stdout.EnableColorizedLevel(true)
stdout.EnableGoroutineId(true)
stdout.SetTimeFormat("15:04:05.999")
stdout.SetAppName("main")
stdout.SetLevel(L_DEBUG)
stdout.SetFlags(DEFAULT_FLAGS | F_GID | F_COLOR)
wg := sync.WaitGroup{}
for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int) {
stdout.Debug("i = %d", i)
stdout.Info("i = %d", i)
stdout.Debugf("i = %d", i)
stdout.Infof("i = %d", i)
wg.Done()
}(i)
}
for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int) {
stdout.Debug("i = %d", i)
stdout.Info("i = %d", i)
stdout.Debugf("i = %d", i)
stdout.Infof("i = %d", i)
wg.Done()
}(i)
}
stdout.Warn("warning")
wg.Wait()
stdout.Warn("warning", "message")
stdout.Error("error")
stdout.Fatal("fatal")
time.Sleep(1 * time.Second)
}

Loading…
Cancel
Save