低空智控平台 后端go
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

318 lines
6.1 KiB

package logger
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"gopkg.in/natefinch/lumberjack.v2"
)
const (
Level_T Level = iota
Level_D
Level_I
Level_W
Level_E
)
const (
Reset = "\033[0m"
Red = "\033[31m"
Green = "\033[32m"
Yellow = "\033[33m"
Blue = "\033[34m"
Magenta = "\033[35m"
Cyan = "\033[36m"
White = "\033[37m"
)
// LogConfig 日志配置
type LogConfig struct {
LogPath string
MaxSize int
MaxBackups int
MaxAge int
Compress bool
}
var defaultConfig = LogConfig{
LogPath: "./log",
MaxSize: 50,
MaxBackups: 20,
MaxAge: 30,
Compress: true,
}
type Level int
// TraceContext 存储跟踪信息
type TraceContext struct {
TraceID string
SpanID string
}
// goroutineID 到 traceID 的映射
var traceMap sync.Map
// 获取当前 goroutine 的 ID
func getGoroutineID() uint64 {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
b = bytes.TrimPrefix(b, []byte("goroutine "))
b = b[:bytes.IndexByte(b, ' ')]
n, _ := strconv.ParseUint(string(b), 10, 64)
return n
}
// SetTraceID 设置当前 goroutine 的 traceID
func SetTraceID(traceID string) {
traceMap.Store(getGoroutineID(), traceID)
}
// GetTraceID 获取当前 goroutine 的 traceID
func GetTraceID() string {
if id, ok := traceMap.Load(getGoroutineID()); ok {
return id.(string)
}
return ""
}
// CleanTraceID 清理当前 goroutine 的 traceID
func CleanTraceID() {
traceMap.Delete(getGoroutineID())
}
type customLogWriter struct {
prefix string
output io.Writer
}
var DefaultWriter io.Writer
var (
loglevel Level
_trace *log.Logger
_debug *log.Logger
_info *log.Logger
_warn *log.Logger
_error *log.Logger
)
func SetLevel(level Level) {
loglevel = level
}
func GetLevel() string {
return loglevel.String()
}
func formatLogWithTrace(data ...any) string {
logData := formatLogData(data...)
return fmt.Sprintf("===> %v", logData)
}
func formatLogData(data ...any) string {
var formattedData []string
for _, item := range data {
switch v := item.(type) {
case string:
formattedData = append(formattedData, v)
case int, int64, float64, bool:
formattedData = append(formattedData, fmt.Sprintf("%v", v))
case []interface{}:
for _, elem := range v {
formattedData = append(formattedData, fmt.Sprintf("%+v", elem))
}
default:
formattedData = append(formattedData, fmt.Sprintf("%+v", v))
}
}
return strings.Join(formattedData, " ")
}
func TRACE(data ...any) {
if loglevel <= Level_T {
_trace.Println(formatLogWithTrace(data...))
}
}
func INFO(data ...any) {
if loglevel <= Level_I {
_info.Println(formatLogWithTrace(data...))
}
}
func DEBUG(data ...any) {
if loglevel <= Level_D {
_debug.Println(formatLogWithTrace(data...))
}
}
func ERROR(data any, e error) {
if loglevel <= Level_E {
_error.Println(formatLogWithTrace(data), e)
}
}
func WARN(data ...any) {
if loglevel <= Level_W {
_warn.Println(formatLogWithTrace(data...))
}
}
// AsyncWriter 异步写入器
type AsyncWriter struct {
output io.Writer
ch chan []byte
wg sync.WaitGroup
}
func NewAsyncWriter(output io.Writer) *AsyncWriter {
w := &AsyncWriter{
output: output,
ch: make(chan []byte, 1000),
}
w.wg.Add(1)
go w.run()
return w
}
func NewAsyncWriterWithSize(output io.Writer, size int) *AsyncWriter {
w := &AsyncWriter{
output: output,
ch: make(chan []byte, size),
}
w.wg.Add(1)
go w.run()
return w
}
func (w *AsyncWriter) run() {
defer w.wg.Done()
for data := range w.ch {
w.output.Write(data)
}
}
func (w *AsyncWriter) Write(p []byte) (n int, err error) {
w.ch <- append([]byte(nil), p...)
return len(p), nil
}
func (w *AsyncWriter) Close() {
close(w.ch)
w.wg.Wait()
}
func InitCustomLog(level Level, config *LogConfig, logName string) {
if config == nil {
config = &defaultConfig
}
SetLevel(level)
if err := os.MkdirAll(config.LogPath, 0o755); err != nil {
panic(err)
}
infoLogger := newLevelLogger(fmt.Sprintf("%s-info", logName), config)
errorLogger := newLevelLogger(fmt.Sprintf("%s-error", logName), config)
env := os.Getenv("LAIC_ENV")
var output io.Writer
if env != "prod" && loglevel <= Level_D {
output = io.MultiWriter(os.Stdout, infoLogger)
} else {
output = infoLogger
}
DefaultWriter = NewAsyncWriter(output)
var errorOutput io.Writer
if loglevel <= Level_D {
errorOutput = NewAsyncWriter(io.MultiWriter(os.Stdout, errorLogger, infoLogger))
} else {
errorOutput = NewAsyncWriter(io.MultiWriter(errorLogger, infoLogger))
}
_trace = newLogger(Level_T, Magenta, DefaultWriter)
_debug = newLogger(Level_D, Blue, DefaultWriter)
_info = newLogger(Level_I, Green, DefaultWriter)
_warn = newLogger(Level_W, Yellow, DefaultWriter)
_error = newLogger(Level_E, Red, errorOutput)
log.SetOutput(DefaultWriter)
log.SetFlags(0)
}
func newLevelLogger(level string, config *LogConfig) io.Writer {
return &lumberjack.Logger{
Filename: filepath.Join(config.LogPath, level+".log"),
MaxSize: config.MaxSize,
MaxBackups: config.MaxBackups,
MaxAge: config.MaxAge,
Compress: config.Compress,
}
}
func newLogger(level Level, color string, output io.Writer) *log.Logger {
logger := log.New(output, "", 0)
logger.SetOutput(&customLogWriter{
prefix: fmt.Sprintf("%s[%s]: %s", color, strings.ToUpper(level.String()), Reset),
output: output,
})
return logger
}
// String 方法
func (l Level) String() string {
switch l {
case Level_T:
return "TRACE"
case Level_D:
return "DEBUG"
case Level_I:
return "INFO"
case Level_W:
return "WARN"
case Level_E:
return "ERROR"
default:
return "UNKNOWN"
}
}
func (c *customLogWriter) Write(p []byte) (n int, err error) {
buf := &bytes.Buffer{}
traceID := GetTraceID()
_, file, line, ok := runtime.Caller(4)
if !ok {
file = "unknown"
line = 0
}
file = file[strings.LastIndex(file, "/")+1:]
fmt.Fprintf(buf, "[%s] ", time.Now().Format("2006-01-02 15:04:05.999"))
fmt.Fprintf(buf, "%s", c.prefix)
if traceID != "" {
fmt.Fprintf(buf, " (%s) ", traceID)
}
fmt.Fprintf(buf, "[%s:%d] %s", file, line, p)
return c.output.Write(buf.Bytes())
}
func GetLogWriter() io.Writer {
return DefaultWriter
}