gf/g/os/glog/glog_logger.go

397 lines
12 KiB
Go
Raw Normal View History

// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
2018-04-19 16:24:48 +08:00
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
2018-04-19 16:24:48 +08:00
package glog
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gfpool"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/text/gregex"
2019-06-01 11:01:57 +08:00
"github.com/gogf/gf/g/util/gconv"
"io"
"os"
"runtime"
"strings"
"time"
2018-04-19 16:24:48 +08:00
)
type Logger struct {
parent *Logger // Parent logger.
writer io.Writer // Customized io.Writer.
flags int // Extra flags for logging output features.
path string // Logging directory path.
file string // Format for logging file.
level int // Output level.
prefix string // Prefix string for every logging content.
btSkip int // Skip count for backtrace.
btStatus int // Backtrace status(1: enabled - default; 0: disabled)
headerPrint bool // Print header or not(true in default).
stdoutPrint bool // Output to stdout or not(true in default).
}
2018-04-19 16:24:48 +08:00
const (
gDEFAULT_FILE_FORMAT = `{Y-m-d}.log`
2018-04-19 16:24:48 +08:00
gDEFAULT_FILE_POOL_FLAGS = os.O_CREATE|os.O_WRONLY|os.O_APPEND
2018-11-11 14:24:04 +08:00
gDEFAULT_FPOOL_PERM = os.FileMode(0666)
gDEFAULT_FPOOL_EXPIRE = 60000
2018-04-19 16:24:48 +08:00
)
const (
2019-06-01 19:34:03 +08:00
F_ASYNC = 1 << iota // Print logging content asynchronously。
F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23.
2019-05-21 23:57:03 +08:00
F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
F_TIME_DATE // Print the date in the local time zone: 2009-01-23.
F_TIME_TIME // Print the time in the local time zone: 01:23:23.
F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675.
F_TIME_STD = F_TIME_DATE | F_TIME_MILLI
)
var (
// Default line break.
ln = "\n"
)
func init() {
// Initialize log line breaks depending on underlying os.
if runtime.GOOS == "windows" {
ln = "\r\n"
}
}
// New creates and returns a custom logger.
2018-08-28 17:06:49 +08:00
func New() *Logger {
logger := &Logger {
file : gDEFAULT_FILE_FORMAT,
2019-05-21 23:57:03 +08:00
flags : F_TIME_STD,
2019-05-22 09:19:21 +08:00
level : LEVEL_ALL,
btStatus : 1,
headerPrint : true,
stdoutPrint : true,
2018-08-24 23:41:58 +08:00
}
return logger
2018-08-24 23:41:58 +08:00
}
2019-02-20 11:16:10 +08:00
// Clone returns a new logger, which is the clone the current logger.
2018-08-28 17:06:49 +08:00
func (l *Logger) Clone() *Logger {
2019-06-01 11:01:57 +08:00
logger := Logger{}
logger = *l
logger.parent = l
return &logger
2018-08-24 23:41:58 +08:00
}
2019-02-20 11:16:10 +08:00
// SetLevel sets the logging level.
2018-08-30 13:00:49 +08:00
func (l *Logger) SetLevel(level int) {
l.level = level
2018-08-30 13:00:49 +08:00
}
2019-02-20 11:16:10 +08:00
// GetLevel returns the logging level value.
func (l *Logger) GetLevel() int {
return l.level
}
2019-02-20 11:16:10 +08:00
// SetDebug enables/disables the debug level for logger.
// The debug level is enabled in default.
2018-08-30 13:00:49 +08:00
func (l *Logger) SetDebug(debug bool) {
if debug {
l.level = l.level | LEVEL_DEBU
} else {
l.level = l.level & ^LEVEL_DEBU
}
2018-08-30 13:00:49 +08:00
}
2019-06-01 19:34:03 +08:00
// SetAsync enables/disables async logging output feature.
func (l *Logger) SetAsync(enabled bool) {
if enabled {
l.flags = l.flags | F_ASYNC
} else {
l.flags = l.flags & ^F_ASYNC
}
}
2019-05-21 23:57:03 +08:00
// SetFlags sets extra flags for logging output features.
func (l *Logger) SetFlags(flags int) {
l.flags = flags
}
// GetFlags returns the flags of logger.
func (l *Logger) GetFlags() int {
return l.flags
}
2019-02-20 11:16:10 +08:00
// SetBacktrace enables/disables the backtrace feature in failure logging outputs.
2018-08-30 13:00:49 +08:00
func (l *Logger) SetBacktrace(enabled bool) {
if enabled {
l.btStatus = 1
} else {
l.btStatus = 0
}
2018-08-30 13:00:49 +08:00
}
2019-02-20 11:16:10 +08:00
// SetBacktraceSkip sets the backtrace offset from the end point.
func (l *Logger) SetBacktraceSkip(skip int) {
l.btSkip = skip
}
// SetWriter sets the customized logging <writer> for logging.
2019-02-20 11:16:10 +08:00
// The <writer> object should implements the io.Writer interface.
// Developer can use customized logging <writer> to redirect logging output to another service,
2019-02-20 11:16:10 +08:00
// eg: kafka, mysql, mongodb, etc.
func (l *Logger) SetWriter(writer io.Writer) {
2019-04-18 09:11:14 +08:00
l.writer = writer
2018-04-19 16:24:48 +08:00
}
// GetWriter returns the customized writer object, which implements the io.Writer interface.
2019-05-23 20:34:06 +08:00
// It returns nil if no writer previously set.
func (l *Logger) GetWriter() io.Writer {
return l.writer
2018-04-19 16:24:48 +08:00
}
2019-02-20 11:16:10 +08:00
// getFilePointer returns the file pinter for file logging.
// It returns nil if file logging is disabled, or file opening fails.
2018-11-11 14:21:22 +08:00
func (l *Logger) getFilePointer() *gfpool.File {
if path := l.path; path != "" {
// Content containing "{}" in the file name is formatted using gtime
file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file, func(s string) string {
return gtime.Now().Format(strings.Trim(s, "{}"))
})
// Create path if it does not exist。
if !gfile.Exists(path) {
if err := gfile.Mkdir(path); err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error()))
return nil
}
}
2019-05-22 09:19:21 +08:00
if fp, err := gfpool.Open(
path + gfile.Separator + file,
gDEFAULT_FILE_POOL_FLAGS,
gDEFAULT_FPOOL_PERM,
gDEFAULT_FPOOL_EXPIRE); err == nil {
2018-04-19 16:24:48 +08:00
return fp
} else {
fmt.Fprintln(os.Stderr, err)
}
}
return nil
}
2019-02-20 11:16:10 +08:00
// SetPath sets the directory path for file logging.
2018-04-19 16:24:48 +08:00
func (l *Logger) SetPath(path string) error {
2019-01-31 13:00:17 +08:00
if path == "" {
return errors.New("path is empty")
}
if !gfile.Exists(path) {
if err := gfile.Mkdir(path); err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error()))
return err
}
}
l.path = strings.TrimRight(path, gfile.Separator)
2018-04-19 16:24:48 +08:00
return nil
}
2019-02-20 11:16:10 +08:00
// GetPath returns the logging directory path for file logging.
// It returns empty string if no directory path set.
func (l *Logger) GetPath() string {
return l.path
}
2019-02-20 11:16:10 +08:00
// SetFile sets the file name <pattern> for file logging.
// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log.
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
func (l *Logger) SetFile(pattern string) {
l.file = pattern
}
2019-05-23 11:49:33 +08:00
// SetStdoutPrint sets whether output the logging contents to stdout, which is true in default.
func (l *Logger) SetStdoutPrint(enabled bool) {
l.stdoutPrint = enabled
}
// SetHeaderPrint sets whether output header of the logging contents, which is true in default.
func (l *Logger) SetHeaderPrint(enabled bool) {
l.headerPrint = enabled
}
// SetPrefix sets prefix string for every logging content.
// Prefix is part of header, which means if header output is shut, no prefix will be output.
func (l *Logger) SetPrefix(prefix string) {
l.prefix = prefix
2018-05-24 12:37:55 +08:00
}
// print prints <s> to defined writer, logging file or passed <std>.
2019-06-01 22:36:12 +08:00
func (l *Logger) print(std io.Writer, lead string, value...interface{}) {
2019-06-01 11:01:57 +08:00
buffer := bytes.NewBuffer(nil)
if l.headerPrint {
2019-06-01 11:01:57 +08:00
// Time.
timeFormat := ""
if l.flags & F_TIME_DATE > 0 {
timeFormat += "2006-01-02 "
}
if l.flags & F_TIME_TIME > 0 {
timeFormat += "15:04:05 "
}
if l.flags & F_TIME_MILLI > 0 {
timeFormat += "15:04:05.000 "
}
if len(timeFormat) > 0 {
buffer.WriteString(time.Now().Format(timeFormat))
}
2019-06-07 23:00:34 +08:00
// Lead string.
if len(lead) > 0 {
buffer.WriteString(lead)
if len(value) > 0 {
buffer.WriteByte(' ')
}
}
2019-06-01 11:01:57 +08:00
// Caller path.
callerPath := ""
if l.flags & F_FILE_LONG > 0 {
callerPath = l.getLongFile() + ": "
}
if l.flags & F_FILE_SHORT > 0 {
callerPath = gfile.Basename(l.getLongFile()) + ": "
}
if len(callerPath) > 0 {
buffer.WriteString(callerPath)
}
// Prefix.
if len(l.prefix) > 0 {
buffer.WriteString(l.prefix + " ")
}
}
2019-06-01 22:36:12 +08:00
for k, v := range value {
if k > 0 {
buffer.WriteByte(' ')
2019-06-01 11:01:57 +08:00
}
2019-06-01 22:36:12 +08:00
buffer.WriteString(gconv.String(v))
2019-06-01 11:01:57 +08:00
}
buffer.WriteString(ln)
2019-06-01 19:34:03 +08:00
if l.flags & F_ASYNC > 0 {
asyncPool.Add(func() {
l.printToWriter(std, buffer)
})
} else {
l.printToWriter(std, buffer)
}
}
2019-06-01 11:01:57 +08:00
2019-06-01 19:34:03 +08:00
// printToWriter writes buffer to writer.
func (l *Logger) printToWriter(std io.Writer, buffer *bytes.Buffer) {
if l.writer == nil {
if f := l.getFilePointer(); f != nil {
defer f.Close()
if _, err := io.WriteString(f, buffer.String()); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
}
// Allow output to stdout?
if l.stdoutPrint {
if _, err := std.Write(buffer.Bytes()); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
}
} else {
if _, err := l.writer.Write(buffer.Bytes()); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
}
2018-04-19 16:24:48 +08:00
}
// printStd prints content <s> without backtrace.
2019-06-01 22:36:12 +08:00
func (l *Logger) printStd(lead string, value...interface{}) {
l.print(os.Stdout, lead, value...)
2018-04-19 16:24:48 +08:00
}
// printStd prints content <s> with backtrace check.
2019-06-01 22:36:12 +08:00
func (l *Logger) printErr(lead string, value...interface{}) {
if l.btStatus == 1 {
2019-06-01 11:01:57 +08:00
if s := l.GetBacktrace(); s != "" {
value = append(value, ln + "Backtrace:" + ln + s)
}
2018-04-19 16:24:48 +08:00
}
// In matter of sequence, do not use stderr here, but use the same stdout.
2019-06-01 22:36:12 +08:00
l.print(os.Stdout, lead, value...)
}
// format formats <values> using fmt.Sprintf.
func (l *Logger) format(format string, value...interface{}) string {
return fmt.Sprintf(format, value...)
}
2019-02-20 11:16:10 +08:00
// PrintBacktrace prints the caller backtrace,
// the optional parameter <skip> specify the skipped backtrace offset from the end point.
func (l *Logger) PrintBacktrace(skip...int) {
2019-06-01 11:01:57 +08:00
if s := l.GetBacktrace(skip...); s != "" {
l.Println("Backtrace:" + ln + s)
} else {
l.Println()
}
}
2019-02-20 11:16:10 +08:00
// GetBacktrace returns the caller backtrace content,
// the optional parameter <skip> specify the skipped backtrace offset from the end point.
func (l *Logger) GetBacktrace(skip...int) string {
customSkip := 0
if len(skip) > 0 {
customSkip = skip[0]
}
backtrace := ""
from := 0
2019-05-23 19:14:16 +08:00
// Find the caller position exclusive of the glog file.
for i := 0; i < 1000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
2019-05-23 20:34:06 +08:00
from = i
break
}
}
}
2019-05-23 19:14:16 +08:00
// Find the true caller file path using custom skip.
index := 1
goRoot := runtime.GOROOT()
for i := from + customSkip + l.btSkip; i < 1000; i++ {
2019-06-01 19:34:03 +08:00
if _, file, cline, ok := runtime.Caller(i); ok && len(file) > 2 {
if (goRoot == "" || !gregex.IsMatchString("^" + goRoot, file)) && !gregex.IsMatchString(`<autogenerated>`, file) {
backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, file, cline, ln)
2018-04-20 17:53:34 +08:00
index++
2018-04-19 16:24:48 +08:00
}
} else {
break
}
}
return backtrace
}
2019-05-23 19:14:16 +08:00
// getLongFile returns the absolute file path along with its line number of the caller.
func (l *Logger) getLongFile() string {
from := 0
// Find the caller position exclusive of the glog file.
for i := 0; i < 1000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
2019-05-23 19:14:16 +08:00
if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
2019-05-23 20:34:06 +08:00
from = i
2019-05-23 19:14:16 +08:00
break
}
}
}
// Find the true caller file path using custom skip.
goRoot := runtime.GOROOT()
for i := from + l.btSkip; i < 1000; i++ {
2019-06-01 19:34:03 +08:00
if _, file, line, ok := runtime.Caller(i); ok && len(file) > 2 {
2019-05-23 19:14:16 +08:00
if (goRoot == "" || !gregex.IsMatchString("^" + goRoot, file)) && !gregex.IsMatchString(`<autogenerated>`, file) {
return fmt.Sprintf(`%s:%d`, file, line)
}
} else {
break
}
}
return ""
}