gf/os/glog/glog_logger.go

320 lines
8.5 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"
2020-05-08 17:12:37 +08:00
"context"
"fmt"
"github.com/gogf/gf/container/gtype"
2019-11-01 20:36:09 +08:00
"github.com/gogf/gf/internal/intlog"
2020-03-25 00:03:52 +08:00
"github.com/gogf/gf/os/gfpool"
"github.com/gogf/gf/os/gmlock"
"github.com/gogf/gf/os/gtimer"
"io"
"os"
"strings"
"sync"
"time"
"github.com/gogf/gf/debug/gdebug"
2019-07-29 21:01:19 +08:00
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/util/gconv"
2018-04-19 16:24:48 +08:00
)
2020-02-14 21:57:35 +08:00
// Logger is the struct for logging management.
type Logger struct {
2020-05-08 17:12:37 +08:00
rmu sync.Mutex // Mutex for rotation feature.
ctx context.Context // Context for logging.
init *gtype.Bool // Initialized.
2020-05-08 17:12:37 +08:00
parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
config Config // Logger configuration.
}
2018-04-19 16:24:48 +08:00
const (
gDEFAULT_FILE_FORMAT = `{Y-m-d}.log`
gDEFAULT_FILE_FLAGS = os.O_CREATE | os.O_WRONLY | os.O_APPEND
gDEFAULT_FILE_PERM = os.FileMode(0666)
gDEFAULT_FILE_EXPIRE = time.Minute
gPATH_FILTER_KEY = "/os/glog/glog"
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.
2020-06-22 11:04:57 +08:00
F_CALLER_FN // Print Caller function name and package: main.main
2019-06-19 09:06:52 +08:00
F_TIME_STD = F_TIME_DATE | F_TIME_MILLI
)
// New creates and returns a custom logger.
2018-08-28 17:06:49 +08:00
func New() *Logger {
logger := &Logger{
init: gtype.NewBool(),
2019-10-26 10:58:07 +08:00
config: DefaultConfig(),
2019-06-19 09:06:52 +08:00
}
return logger
2018-08-24 23:41:58 +08:00
}
2020-02-26 23:26:24 +08:00
// NewWithWriter creates and returns a custom logger with io.Writer.
func NewWithWriter(writer io.Writer) *Logger {
l := New()
l.SetWriter(writer)
return l
}
2019-02-20 11:16:10 +08:00
// Clone returns a new logger, which is the clone the current logger.
// It's commonly used for chaining operations.
2018-08-28 17:06:49 +08:00
func (l *Logger) Clone() *Logger {
logger := New()
logger.ctx = l.ctx
logger.config = l.config
2019-06-01 11:01:57 +08:00
logger.parent = l
return logger
2018-08-24 23:41:58 +08:00
}
// getFilePath returns the logging file path.
// The logging file name must have extension name of "log".
func (l *Logger) getFilePath(now time.Time) string {
// Content containing "{}" in the file name is formatted using gtime.
file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.config.File, func(s string) string {
return gtime.New(now).Format(strings.Trim(s, "{}"))
})
file = gfile.Join(l.config.Path, file)
if gfile.ExtName(file) != "log" {
file += ".log"
}
return file
}
// print prints <s> to defined writer, logging file or passed <std>.
2020-05-08 17:12:37 +08:00
func (l *Logger) print(std io.Writer, lead string, values ...interface{}) {
// Lazy initialize for rotation feature.
// It uses atomic reading operation to enhance the checking performance.
// It here uses CAP for performance and concurrent safety.
if !l.init.Val() && l.init.Cas(false, true) {
// It just initializes once for each logger.
if l.config.RotateSize > 0 || l.config.RotateExpire > 0 {
gtimer.AddOnce(l.config.RotateCheckInterval, l.rotateChecksTimely)
intlog.Printf("logger rotation initialized: every %s", l.config.RotateCheckInterval.String())
}
}
var (
now = time.Now()
buffer = bytes.NewBuffer(nil)
)
2019-10-26 10:58:07 +08:00
if l.config.HeaderPrint {
2019-06-19 09:06:52 +08:00
// Time.
timeFormat := ""
2019-10-26 10:58:07 +08:00
if l.config.Flags&F_TIME_DATE > 0 {
2019-06-19 09:06:52 +08:00
timeFormat += "2006-01-02 "
}
2019-10-26 10:58:07 +08:00
if l.config.Flags&F_TIME_TIME > 0 {
2019-06-19 09:06:52 +08:00
timeFormat += "15:04:05 "
}
2019-10-26 10:58:07 +08:00
if l.config.Flags&F_TIME_MILLI > 0 {
2019-06-19 09:06:52 +08:00
timeFormat += "15:04:05.000 "
}
if len(timeFormat) > 0 {
buffer.WriteString(now.Format(timeFormat))
2019-06-19 09:06:52 +08:00
}
// Lead string.
if len(lead) > 0 {
buffer.WriteString(lead)
2020-05-08 17:12:37 +08:00
if len(values) > 0 {
2019-06-19 09:06:52 +08:00
buffer.WriteByte(' ')
}
}
2020-06-22 11:04:57 +08:00
// Caller path and Fn name.
if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
callerPath := ""
callerFnName, path, line := gdebug.CallerWithFilter(gPATH_FILTER_KEY, l.config.StSkip)
if l.config.Flags&F_CALLER_FN > 0 {
buffer.WriteString(fmt.Sprintf(`[%s] `, callerFnName))
}
if l.config.Flags&F_FILE_LONG > 0 {
callerPath = fmt.Sprintf(`%s:%d: `, path, line)
}
if l.config.Flags&F_FILE_SHORT > 0 {
callerPath = fmt.Sprintf(`%s:%d: `, gfile.Basename(path), line)
}
2019-06-19 09:06:52 +08:00
buffer.WriteString(callerPath)
2020-06-22 11:04:57 +08:00
2019-06-19 09:06:52 +08:00
}
// Prefix.
2019-10-26 10:58:07 +08:00
if len(l.config.Prefix) > 0 {
buffer.WriteString(l.config.Prefix + " ")
2019-06-19 09:06:52 +08:00
}
}
// Convert value to string.
2020-05-08 17:12:37 +08:00
var (
tempStr = ""
valueStr = ""
)
// Context values.
if l.ctx != nil && len(l.config.CtxKeys) > 0 {
ctxStr := ""
for _, key := range l.config.CtxKeys {
if v := l.ctx.Value(key); v != nil {
if ctxStr != "" {
ctxStr += ", "
}
ctxStr += fmt.Sprintf("%s: %+v", key, v)
}
}
if ctxStr != "" {
buffer.WriteString(fmt.Sprintf("{%s} ", ctxStr))
}
}
for _, v := range values {
if err, ok := v.(error); ok {
tempStr = fmt.Sprintf("%+v", err)
} else {
tempStr = gconv.String(v)
}
2019-06-29 23:35:32 +08:00
if len(valueStr) > 0 {
if valueStr[len(valueStr)-1] == '\n' {
// Remove one blank line(\n\n).
2019-06-29 23:35:32 +08:00
if tempStr[0] == '\n' {
valueStr += tempStr[1:]
} else {
valueStr += tempStr
}
} else {
2019-06-30 12:54:06 +08:00
valueStr += " " + tempStr
2019-06-29 23:35:32 +08:00
}
} else {
valueStr = tempStr
2019-06-01 11:01:57 +08:00
}
}
2019-06-29 23:35:32 +08:00
buffer.WriteString(valueStr + "\n")
2019-10-26 10:58:07 +08:00
if l.config.Flags&F_ASYNC > 0 {
2019-11-01 20:36:09 +08:00
err := asyncPool.Add(func() {
l.printToWriter(now, std, buffer)
2019-06-01 19:34:03 +08:00
})
2019-11-01 20:36:09 +08:00
if err != nil {
intlog.Error(err)
}
2019-06-01 19:34:03 +08:00
} else {
l.printToWriter(now, std, buffer)
2019-06-01 19:34:03 +08:00
}
}
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(now time.Time, std io.Writer, buffer *bytes.Buffer) {
2019-10-26 10:58:07 +08:00
if l.config.Writer == nil {
2020-03-25 00:03:52 +08:00
// Output content to disk file.
if l.config.Path != "" {
l.printToFile(now, buffer)
2019-06-01 19:34:03 +08:00
}
// Allow output to stdout?
2019-10-26 10:58:07 +08:00
if l.config.StdoutPrint {
2019-06-01 19:34:03 +08:00
if _, err := std.Write(buffer.Bytes()); err != nil {
intlog.Error(err)
2019-06-01 19:34:03 +08:00
}
}
} else {
2019-10-26 10:58:07 +08:00
if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
2020-03-25 00:03:52 +08:00
panic(err)
2019-06-01 19:34:03 +08:00
}
}
2018-04-19 16:24:48 +08:00
}
// printToFile outputs logging content to disk file.
func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
var (
logFilePath = l.getFilePath(now)
memoryLockKey = "glog.file.lock:" + logFilePath
)
gmlock.Lock(memoryLockKey)
defer gmlock.Unlock(memoryLockKey)
file := l.getFilePointer(logFilePath)
// Rotation file size checks.
if l.config.RotateSize > 0 {
stat, err := file.Stat()
if err != nil {
2020-07-20 21:32:28 +08:00
file.Close()
panic(err)
}
if stat.Size() > l.config.RotateSize {
l.rotateFileBySize(now)
file = l.getFilePointer(logFilePath)
}
}
if _, err := file.Write(buffer.Bytes()); err != nil {
2020-07-20 21:32:28 +08:00
file.Close()
panic(err)
}
2020-07-20 21:32:28 +08:00
file.Close()
}
// getFilePointer retrieves and returns a file pointer from file pool.
func (l *Logger) getFilePointer(path string) *gfpool.File {
file, err := gfpool.Open(
path,
gDEFAULT_FILE_FLAGS,
gDEFAULT_FILE_PERM,
gDEFAULT_FILE_EXPIRE,
)
if err != nil {
panic(err)
}
return file
}
// printStd prints content <s> without stack.
2019-06-19 09:06:52 +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 stack check.
2019-06-19 09:06:52 +08:00
func (l *Logger) printErr(lead string, value ...interface{}) {
2019-10-26 10:58:07 +08:00
if l.config.StStatus == 1 {
if s := l.GetStack(); s != "" {
2019-06-29 23:35:32 +08:00
value = append(value, "\nStack:\n"+s)
2019-06-19 09:06:52 +08:00
}
}
// In matter of sequence, do not use stderr here, but use the same stdout.
l.print(os.Stdout, lead, value...)
2019-06-01 22:36:12 +08:00
}
// format formats <values> using fmt.Sprintf.
2019-06-19 09:06:52 +08:00
func (l *Logger) format(format string, value ...interface{}) string {
2019-06-01 22:36:12 +08:00
return fmt.Sprintf(format, value...)
}
// PrintStack prints the caller stack,
// the optional parameter <skip> specify the skipped stack offset from the end point.
func (l *Logger) PrintStack(skip ...int) {
if s := l.GetStack(skip...); s != "" {
2019-06-29 23:35:32 +08:00
l.Println("Stack:\n" + s)
2019-06-01 11:01:57 +08:00
} else {
l.Println()
}
}
// GetStack returns the caller stack content,
// the optional parameter <skip> specify the skipped stack offset from the end point.
func (l *Logger) GetStack(skip ...int) string {
2019-10-26 10:58:07 +08:00
stackSkip := l.config.StSkip
if len(skip) > 0 {
stackSkip += skip[0]
}
filters := []string{gPATH_FILTER_KEY}
2019-10-26 10:58:07 +08:00
if l.config.StFilter != "" {
filters = append(filters, l.config.StFilter)
}
return gdebug.StackWithFilters(filters, stackSkip)
2019-05-23 19:14:16 +08:00
}