mirror of
https://gitee.ltd/lxh/logger.git
synced 2025-10-23 14:56:16 +08:00
🎉logger的v2版本,暂未支持环境变量配置
This commit is contained in:
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
### Go template
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# env file
|
||||||
|
.env
|
||||||
|
|
202
README.md
Normal file
202
README.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# zap_logger 日志组件库使用文档
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
`zap_logger` 是一个基于 [Zap](https://github.com/uber-go/zap) 的日志组件库,提供灵活的多输出目标支持、可定制的日志格式和级别控制,以及与 Loki 日志系统的集成能力。该库旨在简化 Go 应用程序的日志配置流程,满足不同环境下的日志收集需求。
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
- **多输出目标**:同时支持控制台输出、文件输出和 Loki 日志系统
|
||||||
|
- **灵活配置**:通过 YAML 配置文件或代码选项动态调整日志行为
|
||||||
|
- **日志轮转**:文件日志自动轮转,支持大小限制、备份数量和压缩
|
||||||
|
- **日志编码**:支持 JSON 和 Console 两种编码格式,可按输出目标单独配置
|
||||||
|
- **级别控制**:支持细粒度的日志级别设置(Debug, Info, Warn, Error 等)
|
||||||
|
- **默认配置**:内置合理默认值,零配置快速启动
|
||||||
|
|
||||||
|
## 安装方法
|
||||||
|
```go
|
||||||
|
go get gitea.mrx.ltd/pkg/zap_logger
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 默认配置
|
||||||
|
无需任何配置文件,直接初始化即可使用默认配置(控制台输出,Info级别,Console编码):
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.mrx.ltd/pkg/zap_logger"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 使用默认配置初始化
|
||||||
|
if err := zap_logger.NewZapLogger(""); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 使用全局日志器
|
||||||
|
log.Info("这是一条info级别的日志")
|
||||||
|
log.Error("这是一条error级别的日志")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义配置文件
|
||||||
|
创建 YAML 配置文件(如 `logger.yaml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
logger:
|
||||||
|
encoder: "json" # 全局编码器 (json/console)
|
||||||
|
level: "info" # 全局日志级别
|
||||||
|
|
||||||
|
file:
|
||||||
|
enable: true # 启用文件输出,默认关闭
|
||||||
|
filename: "app.log" # 日志文件名,不填写则自动生成
|
||||||
|
max_size: 10 # 单个文件最大尺寸(MB)
|
||||||
|
max_age: 7 # 日志保留天数
|
||||||
|
max_backups: 5 # 最大备份数量
|
||||||
|
local_time: true # 使用本地时间命名备份文件
|
||||||
|
compress: true # 压缩备份文件
|
||||||
|
|
||||||
|
console:
|
||||||
|
enable: true # 启用控制台输出,默认开启
|
||||||
|
color: true # 启用彩色输出
|
||||||
|
|
||||||
|
loki:
|
||||||
|
enable: true # 启用Loki输出,默认关闭
|
||||||
|
host: "localhost" # Loki服务地址
|
||||||
|
port: 3100 # Loki服务端口
|
||||||
|
source: "app" # 日志来源标识
|
||||||
|
service: "api" # 服务名称
|
||||||
|
job: "backend" # 任务名称
|
||||||
|
environment: "prod" # 环境标识
|
||||||
|
```
|
||||||
|
|
||||||
|
通过配置文件初始化:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := zap_logger.NewZapLogger("logger.yaml"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 全局配置 (logger)
|
||||||
|
| 字段名 | 类型 | 说明 | 可选值 | 默认值 |
|
||||||
|
|----------|--------|-------------------------------|------------------------|-----------|
|
||||||
|
| encoder | string | 全局日志编码器 | "json", "console" | "console" |
|
||||||
|
| level | string | 全局日志级别 | "debug", "info", "warn", "error", "dpanic", "panic", "fatal" | "info" |
|
||||||
|
|
||||||
|
### 文件输出配置 (file)
|
||||||
|
| 字段名 | 类型 | 说明 | 默认值 |
|
||||||
|
|---------------|--------|-------------------------------|---------------------------------|
|
||||||
|
| enable | bool | 是否启用文件输出 | false |
|
||||||
|
| encoder | string | 文件日志编码器(覆盖全局) | 继承 logger.encoder |
|
||||||
|
| level | string | 文件日志级别(覆盖全局) | 继承 logger.level |
|
||||||
|
| filename | string | 日志文件路径 | "app-{hostname}-{date}.log" |
|
||||||
|
| max_size | int | 单个文件最大尺寸(MB) | 10 |
|
||||||
|
| max_age | int | 日志保留天数 | 7 |
|
||||||
|
| max_backups | int | 最大备份数量 | 5 |
|
||||||
|
| local_time | bool | 备份文件使用本地时间命名 | true |
|
||||||
|
| compress | bool | 是否压缩备份文件 | false |
|
||||||
|
|
||||||
|
### 控制台输出配置 (console)
|
||||||
|
| 字段名 | 类型 | 说明 | 默认值 |
|
||||||
|
|----------|--------|-------------------------------|-------------------|
|
||||||
|
| enable | bool | 是否启用控制台输出 | true |
|
||||||
|
| encoder | string | 控制台日志编码器(覆盖全局) | 继承 logger.encoder |
|
||||||
|
| level | string | 控制台日志级别(覆盖全局) | 继承 logger.level |
|
||||||
|
| color | bool | 是否启用彩色输出 | false |
|
||||||
|
|
||||||
|
### Loki输出配置 (loki)
|
||||||
|
| 字段名 | 类型 | 说明 | 默认值 |
|
||||||
|
|---------------|--------|-------------------------------|---------------------------------|
|
||||||
|
| enable | bool | 是否启用Loki输出 | false |
|
||||||
|
| encoder | string | Loki日志编码器(覆盖全局) | 继承 logger.encoder |
|
||||||
|
| level | string | Loki日志级别(覆盖全局) | 继承 logger.level |
|
||||||
|
| host | string | Loki服务主机地址 | "localhost" |
|
||||||
|
| port | int | Loki服务端口 | 3100 |
|
||||||
|
| source | string | 日志来源标识 | "" |
|
||||||
|
| service | string | 服务名称标签 | "" |
|
||||||
|
| job | string | 任务名称标签 | "" |
|
||||||
|
| environment | string | 环境标识标签 | "" |
|
||||||
|
|
||||||
|
## 代码配置选项
|
||||||
|
除了配置文件外,还可以通过代码选项动态调整配置:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"gitea.mrx.ltd/pkg/zap_logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 使用代码选项自定义配置
|
||||||
|
err := zap_logger.NewZapLogger("",
|
||||||
|
zap_logger.WithEncoder(zap_logger.JsonEncoder), // 设置全局编码器为JSON
|
||||||
|
zap_logger.WithLevel("debug"), // 设置全局级别为Debug
|
||||||
|
zap_logger.WithEnableFile(true), // 启用文件输出
|
||||||
|
zap_logger.WithFilename("app.log"), // 设置日志文件名
|
||||||
|
zap_logger.WithLokiEnable(true), // 启用Loki输出
|
||||||
|
zap_logger.WithLokiHost("loki.example.com"), // 设置Loki主机
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
完整选项列表:
|
||||||
|
|
||||||
|
| 函数名 | 说明 | 参数类型/示例 |
|
||||||
|
|----------------------------|-------------------------------|--------------------------------|
|
||||||
|
| WithEncoder | 设置全局编码器 | JsonEncoder/ConsoleEncoder |
|
||||||
|
| WithLevel | 设置全局日志级别 | "debug", "info", "warn"等 |
|
||||||
|
| WithEnableFile | 启用/禁用文件输出 | true/false |
|
||||||
|
| WithFilename | 设置日志文件名 | "app.log" |
|
||||||
|
| WithFileMaxSize | 设置文件最大尺寸(MB) | 20 |
|
||||||
|
| WithFileMaxAge | 设置日志保留天数 | 15 |
|
||||||
|
| WithFileMaxBackups | 设置最大备份数量 | 10 |
|
||||||
|
| WithFileLocaltime | 备份文件使用本地时间 | true/false |
|
||||||
|
| WithFileCompress | 启用/禁用备份压缩 | true/false |
|
||||||
|
| WithConsoleEnable | 启用/禁用控制台输出 | true/false |
|
||||||
|
| WithConsoleEnableColor | 启用/禁用控制台彩色输出 | true/false |
|
||||||
|
| WithLokiEnable | 启用/禁用Loki输出 | true/false |
|
||||||
|
| WithLokiHost | 设置Loki服务主机 | "loki.example.com" |
|
||||||
|
| WithLokiPort | 设置Loki服务端口 | 3100 |
|
||||||
|
| WithLokiSource | 设置日志来源标识 | "payment-service" |
|
||||||
|
| WithLokiService | 设置服务名称标签 | "api" |
|
||||||
|
| WithLokiJob | 设置任务名称标签 | "backend" |
|
||||||
|
| WithLokiEnvironment | 设置环境标识标签 | "production" |
|
||||||
|
|
||||||
|
## 高级使用示例
|
||||||
|
|
||||||
|
### 多输出组合
|
||||||
|
同时启用文件和Loki输出,禁用控制台:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
logger:
|
||||||
|
encoder: "json"
|
||||||
|
level: "info"
|
||||||
|
|
||||||
|
file:
|
||||||
|
enable: true
|
||||||
|
filename: "app.log"
|
||||||
|
|
||||||
|
console:
|
||||||
|
enable: false
|
||||||
|
|
||||||
|
loki:
|
||||||
|
enable: true
|
||||||
|
host: "loki.internal"
|
||||||
|
port: 3100
|
||||||
|
service: "user-service"
|
||||||
|
environment: "prod"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
1. 配置优先级:代码选项 > 配置文件 > 默认配置
|
||||||
|
2. 日志级别:如果全局级别高于输出特定级别,以全局级别为准
|
||||||
|
3. Loki依赖:使用Loki输出时需确保Loki服务可访问
|
||||||
|
4. 错误处理:NewZapLogger返回的错误需妥善处理,避免日志初始化失败
|
44
config.go
Normal file
44
config.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package zap_logger
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Logger *Logger `yaml:"logger"`
|
||||||
|
File *File `yaml:"file"`
|
||||||
|
Console *Console `yaml:"console"`
|
||||||
|
Loki *Loki `yaml:"loki"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
Encoder string `yaml:"encoder"`
|
||||||
|
Level string `yaml:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Encoder string `yaml:"encoder"`
|
||||||
|
Level string `yaml:"level"`
|
||||||
|
Filename string `yaml:"name"`
|
||||||
|
MaxSize int `yaml:"max_size"`
|
||||||
|
MaxAge int `yaml:"max_age"`
|
||||||
|
MaxBackups int `yaml:"max_backups"`
|
||||||
|
LocalTime bool `yaml:"local_time"`
|
||||||
|
Compress bool `yaml:"compress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Console struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Encoder string `yaml:"encoder"`
|
||||||
|
Level string `yaml:"level"`
|
||||||
|
Color bool `yaml:"color"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Loki struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Encoder string `yaml:"encoder"`
|
||||||
|
Level string `yaml:"level"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Source string `yaml:"source"`
|
||||||
|
Service string `yaml:"service"`
|
||||||
|
Job string `yaml:"job"`
|
||||||
|
Environment string `yaml:"environment"`
|
||||||
|
}
|
49
console.go
Normal file
49
console.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package zap_logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
customencoder "gitee.ltd/lxh/logger/v2/encoder"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type consoleLogger struct {
|
||||||
|
Encoder string
|
||||||
|
Level string
|
||||||
|
Color bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConsoleLogger(consoleConf *Console) *consoleLogger {
|
||||||
|
return &consoleLogger{
|
||||||
|
Encoder: consoleConf.Encoder,
|
||||||
|
Level: consoleConf.Level,
|
||||||
|
Color: consoleConf.Color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consoleLogger) Init() zapcore.Core {
|
||||||
|
encoderConfig := zap.NewProductionEncoderConfig()
|
||||||
|
encoderConfig.TimeKey = "time"
|
||||||
|
encoderConfig.EncodeTime = func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||||
|
encoder.AppendString(time.Format("2006-01-02 15:04:05.000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := zapcore.AddSync(os.Stdout)
|
||||||
|
var encoder zapcore.Encoder
|
||||||
|
switch c.Encoder {
|
||||||
|
case "json":
|
||||||
|
encoder = customencoder.NewJsonEncoder(encoderConfig)
|
||||||
|
case "console":
|
||||||
|
if c.Color {
|
||||||
|
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||||
|
}
|
||||||
|
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
level, _ := zapcore.ParseLevel(strings.ToLower(c.Level))
|
||||||
|
|
||||||
|
return zapcore.NewCore(encoder, writer, level)
|
||||||
|
}
|
40
default_logger.yaml
Normal file
40
default_logger.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# 通用配置
|
||||||
|
logger:
|
||||||
|
# 编码方式 json 或者 console
|
||||||
|
encoder: json
|
||||||
|
# 日志级别
|
||||||
|
level: info
|
||||||
|
|
||||||
|
# 文件形式
|
||||||
|
file:
|
||||||
|
# 是否开启
|
||||||
|
enable: false
|
||||||
|
# 文件名称,不填写则默认生成,生成的格式为 app-[host-name]-日期.log,例如 app-ubuntu-20250530.log
|
||||||
|
name:
|
||||||
|
# 单文件最大大小 (M)
|
||||||
|
max_size: 10
|
||||||
|
# 单文件保存最长时间 (天)
|
||||||
|
max_age: 7
|
||||||
|
# 单文件最大备份数量
|
||||||
|
max_backups: 5
|
||||||
|
# 备份时间戳
|
||||||
|
localtime: true
|
||||||
|
# 是否启用备份压缩
|
||||||
|
compress: true
|
||||||
|
|
||||||
|
# 控制台
|
||||||
|
console:
|
||||||
|
# 是否启用
|
||||||
|
enable: true
|
||||||
|
# 开启颜色
|
||||||
|
color: false
|
||||||
|
|
||||||
|
# Loki推送
|
||||||
|
loki:
|
||||||
|
enable: false
|
||||||
|
host: http://localhost
|
||||||
|
port: 3100
|
||||||
|
source: logger_source
|
||||||
|
service: logger_service
|
||||||
|
job: logger_job
|
||||||
|
environment: logger_environment
|
101
encoder/json_encoder.go
Normal file
101
encoder/json_encoder.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package encoder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.uber.org/zap/buffer"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type jsonEncoder struct {
|
||||||
|
zapcore.Encoder
|
||||||
|
pool buffer.Pool
|
||||||
|
zapcore.EncoderConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJsonEncoder(enderConfig zapcore.EncoderConfig) *jsonEncoder {
|
||||||
|
return &jsonEncoder{
|
||||||
|
Encoder: zapcore.NewJSONEncoder(enderConfig),
|
||||||
|
pool: buffer.NewPool(),
|
||||||
|
EncoderConfig: enderConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone 服用自带encoder的clone方法
|
||||||
|
func (e *jsonEncoder) Clone() zapcore.Encoder {
|
||||||
|
clone := e.Encoder.Clone()
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeEntry 重写EncodeEntry方法
|
||||||
|
func (e *jsonEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||||
|
buf, err := e.Encoder.EncodeEntry(entry, fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Stack != "" && e.StacktraceKey != "" {
|
||||||
|
return e.formatStacktrace(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatStacktrace 自定义stacktrace格式
|
||||||
|
func (e *jsonEncoder) formatStacktrace(originalBuf *buffer.Buffer) (*buffer.Buffer, error) {
|
||||||
|
var jsonData struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
Caller string `json:"caller,omitempty"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
StackTrace any `json:"stacktrace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(originalBuf.Bytes(), &jsonData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存在就处理,反之不处理
|
||||||
|
if jsonData.StackTrace != nil && e.StacktraceKey != "" {
|
||||||
|
formattedStack := e.customStackFormat(jsonData.StackTrace.(string))
|
||||||
|
jsonData.StackTrace = formattedStack
|
||||||
|
}
|
||||||
|
|
||||||
|
newBuf := e.pool.Get()
|
||||||
|
encoder := json.NewEncoder(newBuf)
|
||||||
|
encoder.SetEscapeHTML(true)
|
||||||
|
if err := encoder.Encode(jsonData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBuf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// customStackFormat 自定义stacktrace格式
|
||||||
|
func (e *jsonEncoder) customStackFormat(stack string) []map[string]string {
|
||||||
|
lines := strings.Split(strings.TrimSpace(stack), "\n")
|
||||||
|
var filteredLines []map[string]string
|
||||||
|
|
||||||
|
// 是否为偶数行,如果不是添加空行
|
||||||
|
if len(lines)%2 != 0 {
|
||||||
|
lines = append(lines, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每两行一组:奇数行作为key,偶数行作为value
|
||||||
|
for i := 0; i < len(lines); i += 2 {
|
||||||
|
key := lines[i]
|
||||||
|
value := ""
|
||||||
|
if i+1 < len(lines) {
|
||||||
|
value = lines[i+1]
|
||||||
|
}
|
||||||
|
// 取出换行符和制表符
|
||||||
|
key = strings.Replace(key, "\n", "", -1)
|
||||||
|
value = strings.Replace(value, "\t", "", -1)
|
||||||
|
filteredLines = append(filteredLines, map[string]string{
|
||||||
|
key: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredLines
|
||||||
|
}
|
72
file.go
Normal file
72
file.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package zap_logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
|
customencoder "gitee.ltd/lxh/logger/v2/encoder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileLogger struct {
|
||||||
|
Filename string
|
||||||
|
Encoder string
|
||||||
|
Level string
|
||||||
|
MaxSize int
|
||||||
|
MaxAge int
|
||||||
|
MaxBackups int
|
||||||
|
LocalTime bool
|
||||||
|
Compress bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileLogger(fileConf *File) *fileLogger {
|
||||||
|
if fileConf.Filename == "" {
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
fileConf.Filename = fmt.Sprintf("app-%s-%s.log", hostname, time.Now().Format("20060102"))
|
||||||
|
}
|
||||||
|
return &fileLogger{
|
||||||
|
Filename: fileConf.Filename,
|
||||||
|
Encoder: fileConf.Encoder,
|
||||||
|
Level: fileConf.Level,
|
||||||
|
MaxSize: fileConf.MaxSize,
|
||||||
|
MaxAge: fileConf.MaxAge,
|
||||||
|
MaxBackups: fileConf.MaxBackups,
|
||||||
|
LocalTime: fileConf.LocalTime,
|
||||||
|
Compress: fileConf.Compress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileLogger) Init() zapcore.Core {
|
||||||
|
lumberLogger := &lumberjack.Logger{
|
||||||
|
Filename: f.Filename,
|
||||||
|
MaxSize: f.MaxSize,
|
||||||
|
MaxAge: f.MaxAge,
|
||||||
|
MaxBackups: f.MaxBackups,
|
||||||
|
LocalTime: f.LocalTime,
|
||||||
|
Compress: f.Compress,
|
||||||
|
}
|
||||||
|
|
||||||
|
encoderConfig := zap.NewProductionEncoderConfig()
|
||||||
|
encoderConfig.TimeKey = "time"
|
||||||
|
encoderConfig.EncodeTime = func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||||
|
encoder.AppendString(time.Format("2006-01-02 15:04:05.000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := zapcore.AddSync(lumberLogger)
|
||||||
|
var encoder zapcore.Encoder
|
||||||
|
switch f.Encoder {
|
||||||
|
case "json":
|
||||||
|
encoder = customencoder.NewJsonEncoder(encoderConfig)
|
||||||
|
case "console":
|
||||||
|
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
level, _ := zapcore.ParseLevel(strings.ToLower(f.Level))
|
||||||
|
|
||||||
|
return zapcore.NewCore(encoder, writer, level)
|
||||||
|
}
|
53
go.mod
Normal file
53
go.mod
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
module gitee.ltd/lxh/logger/v2
|
||||||
|
|
||||||
|
go 1.25
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/grafana/loki-client-go v0.0.0-20240913122146-e119d400c3a5
|
||||||
|
github.com/prometheus/common v0.34.0
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dennwc/varint v1.0.0 // indirect
|
||||||
|
github.com/go-kit/kit v0.10.0 // indirect
|
||||||
|
github.com/go-kit/log v0.2.0 // indirect
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/grafana/loki/pkg/push v0.0.0-20240912152814-63e84b476a9a // indirect
|
||||||
|
github.com/grafana/regexp v0.0.0-20220304095617-2e8d9baf4ac2 // indirect
|
||||||
|
github.com/jpillora/backoff v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
|
github.com/prometheus/prometheus v0.35.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.2 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/goleak v1.3.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||||
|
golang.org/x/net v0.38.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
|
golang.org/x/text v0.23.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
|
google.golang.org/grpc v1.56.3 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
86
log/log.go
Normal file
86
log/log.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Info(msg string) {
|
||||||
|
zap.S().Info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(msg string) {
|
||||||
|
zap.S().Warn(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(msg string) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(msg string) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Fatal(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Panic(msg string) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Panic(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(msg string) {
|
||||||
|
zap.S().Debug(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infof(format string, args ...any) {
|
||||||
|
zap.S().Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warnf(format string, args ...any) {
|
||||||
|
zap.S().Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, args ...any) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatalf(format string, args ...any) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Panicf(format string, args ...any) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(format string, args ...any) {
|
||||||
|
zap.S().Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infow(msg string, keysAndValues ...any) {
|
||||||
|
zap.S().Infow(msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warnw(msg string, keysAndValues ...any) {
|
||||||
|
zap.S().Warnw(msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorw(msg string, keysAndValues ...any) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Errorw(msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatalw(msg string, keysAndValues ...any) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Fatalw(msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Panicw(msg string, keysAndValues ...any) {
|
||||||
|
defer zap.S().Sync()
|
||||||
|
zap.S().Panicw(msg, keysAndValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugw(msg string, keysAndValues ...any) {
|
||||||
|
zap.S().Debugw(msg, keysAndValues...)
|
||||||
|
}
|
79
logger.go
Normal file
79
logger.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package zap_logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed default_logger.yaml
|
||||||
|
var defaultConfigByte []byte
|
||||||
|
|
||||||
|
type IZapLogger interface {
|
||||||
|
Init() zapcore.Core
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZapLogger struct{}
|
||||||
|
|
||||||
|
func NewZapLogger(filePath string, opts ...Option) error {
|
||||||
|
var fileBytes []byte
|
||||||
|
var err error
|
||||||
|
// 读取默认配置
|
||||||
|
if filePath == "" {
|
||||||
|
fileBytes = defaultConfigByte
|
||||||
|
} else {
|
||||||
|
fileBytes, err = os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("read logger file error: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
err = yaml.Unmarshal(fileBytes, &config)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("unmarshal logger file error: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用最新设置
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cores []zapcore.Core
|
||||||
|
if config.File.Enable {
|
||||||
|
config.File.Encoder = config.Logger.Encoder
|
||||||
|
config.File.Level = config.Logger.Level
|
||||||
|
cores = append(cores, newFileLogger(config.File).Init())
|
||||||
|
}
|
||||||
|
if config.Console.Enable {
|
||||||
|
config.Console.Encoder = config.Logger.Encoder
|
||||||
|
config.Console.Level = config.Logger.Level
|
||||||
|
cores = append(cores, newConsoleLogger(config.Console).Init())
|
||||||
|
}
|
||||||
|
if config.Loki.Enable {
|
||||||
|
config.Loki.Encoder = config.Logger.Encoder
|
||||||
|
config.Loki.Level = config.Logger.Level
|
||||||
|
cores = append(cores, newLokiLogger(config.Loki).Init())
|
||||||
|
}
|
||||||
|
// 如果一个都没开启这默认开启一个console,info级别
|
||||||
|
if len(cores) <= 0 {
|
||||||
|
config.Console.Encoder = "console"
|
||||||
|
config.Logger.Level = "info"
|
||||||
|
config.Console.Level = "info"
|
||||||
|
cores = append(cores, newConsoleLogger(config.Console).Init())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := zap.New(
|
||||||
|
zapcore.NewTee(cores...), // 开启的日志核心
|
||||||
|
zap.AddCaller(), // 启用调用者信息
|
||||||
|
zap.AddCallerSkip(1), // 调用者信息跳过
|
||||||
|
zap.AddStacktrace(zap.ErrorLevel), // 开启panic日志错误堆栈收集
|
||||||
|
)
|
||||||
|
zap.ReplaceGlobals(logger)
|
||||||
|
return nil
|
||||||
|
}
|
20
logger_test.go
Normal file
20
logger_test.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package zap_logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitee.ltd/lxh/logger/v2/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewZapLogger(t *testing.T) {
|
||||||
|
err := NewZapLogger("", WithEncoder(JsonEncoder))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("hahahahah")
|
||||||
|
log.Error("123321")
|
||||||
|
log.Panic("this is panic")
|
||||||
|
|
||||||
|
}
|
70
loki.go
Normal file
70
loki.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package zap_logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
customencoder "gitee.ltd/lxh/logger/v2/encoder"
|
||||||
|
"gitee.ltd/lxh/logger/v2/write"
|
||||||
|
"github.com/grafana/loki-client-go/loki"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LokiLogger struct {
|
||||||
|
Encoder string `json:"encoder"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Source string `yaml:"source"`
|
||||||
|
Service string `yaml:"service"`
|
||||||
|
Job string `yaml:"job"`
|
||||||
|
Environment string `yaml:"environment"`
|
||||||
|
|
||||||
|
lokiClient *loki.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLokiLogger(lokiConf *Loki) *LokiLogger {
|
||||||
|
lokiConfig := &LokiLogger{
|
||||||
|
Enable: lokiConf.Enable,
|
||||||
|
Encoder: lokiConf.Encoder,
|
||||||
|
Level: lokiConf.Level,
|
||||||
|
Host: lokiConf.Host,
|
||||||
|
Port: lokiConf.Port,
|
||||||
|
Source: lokiConf.Source,
|
||||||
|
Service: lokiConf.Service,
|
||||||
|
Job: lokiConf.Job,
|
||||||
|
Environment: lokiConf.Environment,
|
||||||
|
}
|
||||||
|
|
||||||
|
lc, err := loki.NewWithDefault(fmt.Sprintf("%v:%v/loki/api/v1/push", lokiConfig.Host, lokiConfig.Port))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lokiConfig.lokiClient = lc
|
||||||
|
return lokiConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LokiLogger) Init() zapcore.Core {
|
||||||
|
encoderConfig := zap.NewProductionEncoderConfig()
|
||||||
|
encoderConfig.TimeKey = "time"
|
||||||
|
encoderConfig.EncodeTime = func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||||
|
encoder.AppendString(time.Format("2006-01-02 15:04:05.000"))
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := zapcore.AddSync(write.NewLokiWriter(l.lokiClient, l.Encoder, l.Job, l.Source, l.Environment))
|
||||||
|
var encoder zapcore.Encoder
|
||||||
|
switch l.Encoder {
|
||||||
|
case "json":
|
||||||
|
encoder = customencoder.NewJsonEncoder(encoderConfig)
|
||||||
|
case "console":
|
||||||
|
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
level, _ := zapcore.ParseLevel(strings.ToLower(l.Level))
|
||||||
|
|
||||||
|
return zapcore.NewCore(encoder, writer, level)
|
||||||
|
}
|
118
option.go
Normal file
118
option.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package zap_logger
|
||||||
|
|
||||||
|
type Encoder string
|
||||||
|
|
||||||
|
const (
|
||||||
|
JsonEncoder Encoder = "json"
|
||||||
|
ConsoleEncoder Encoder = "console"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(conf *Config)
|
||||||
|
|
||||||
|
func WithEncoder(encoder Encoder) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Logger.Encoder = string(encoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLevel(level string) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Logger.Level = level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithEnableFile(enable bool) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.File.Enable = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFilename(filename string) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.File.Filename = filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFileMaxSize(maxSize int) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.File.MaxSize = maxSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFileMaxAge(maxAge int) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.File.MaxAge = maxAge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFileMaxBackups(maxBackups int) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.File.MaxBackups = maxBackups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFileLocaltime(localtime bool) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.File.LocalTime = localtime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFileCompress(compress bool) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.File.Compress = compress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithConsoleEnable(enable bool) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Console.Enable = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithConsoleEnableColor(color bool) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Console.Color = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLokiEnable(enable bool) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Loki.Enable = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLokiHost(host string) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Loki.Host = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLokiPort(port int) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Loki.Port = port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLokiSource(source string) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Loki.Source = source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLokiService(service string) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Loki.Service = service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLokiJob(job string) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Loki.Job = job
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLokiEnvironment(environment string) Option {
|
||||||
|
return func(conf *Config) {
|
||||||
|
conf.Loki.Environment = environment
|
||||||
|
}
|
||||||
|
}
|
141
write/loki.go
Normal file
141
write/loki.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package write
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/loki-client-go/loki"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LokiWriter struct {
|
||||||
|
lokiClient *loki.Client
|
||||||
|
encoder string
|
||||||
|
job string
|
||||||
|
source string
|
||||||
|
environment string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLokiWriter(lokiClient *loki.Client, encoder, job, source, environment string) *LokiWriter {
|
||||||
|
return &LokiWriter{
|
||||||
|
lokiClient: lokiClient,
|
||||||
|
encoder: encoder,
|
||||||
|
job: job,
|
||||||
|
source: source,
|
||||||
|
environment: environment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logInfo struct {
|
||||||
|
Level string `json:"level"` // 日志级别
|
||||||
|
Ts string `json:"time"` // 格式化后的时间(在zap那边配置的)
|
||||||
|
Caller string `json:"caller,omitempty"` // 日志输出的文件名和行号
|
||||||
|
Msg string `json:"msg"` // 日志内容
|
||||||
|
Stacktrace any `json:"stacktrace,omitempty"` // 错误的堆栈信息
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw *LokiWriter) Write(p []byte) (int, error) {
|
||||||
|
var li logInfo
|
||||||
|
switch lw.encoder {
|
||||||
|
case "json":
|
||||||
|
err := json.Unmarshal(p, &li)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
case "console":
|
||||||
|
li = *lw.parse(string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
label := model.LabelSet{"job": model.LabelValue(lw.job)}
|
||||||
|
label["source"] = model.LabelValue(lw.source)
|
||||||
|
label["level"] = model.LabelValue(strings.ToUpper(li.Level))
|
||||||
|
label["caller"] = model.LabelValue(li.Caller)
|
||||||
|
label["environment"] = model.LabelValue(lw.environment)
|
||||||
|
|
||||||
|
t, e := time.ParseInLocation("2006-01-02 15:04:05.000", li.Ts, time.Local)
|
||||||
|
if e != nil {
|
||||||
|
t = time.Now().Local()
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
switch lw.encoder {
|
||||||
|
case "json":
|
||||||
|
jm, err := json.Marshal(li)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = string(jm)
|
||||||
|
case "console":
|
||||||
|
builder := strings.Builder{}
|
||||||
|
builder.WriteString(li.Level)
|
||||||
|
builder.WriteString(" ")
|
||||||
|
if li.Caller != "" {
|
||||||
|
builder.WriteString(li.Caller)
|
||||||
|
builder.WriteString(" ")
|
||||||
|
}
|
||||||
|
builder.WriteString(li.Msg)
|
||||||
|
builder.WriteString(" ")
|
||||||
|
if li.Stacktrace != nil {
|
||||||
|
builder.WriteString(li.Stacktrace.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := lw.lokiClient.Handle(label, t, msg); err != nil {
|
||||||
|
fmt.Printf("日志推送到Loki失败: %v\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lw *LokiWriter) parse(logText string) *logInfo {
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(logText))
|
||||||
|
|
||||||
|
if !scanner.Scan() {
|
||||||
|
return nil // 空日志
|
||||||
|
}
|
||||||
|
|
||||||
|
firstLine := strings.TrimSpace(scanner.Text())
|
||||||
|
parts := strings.Split(firstLine, "\t")
|
||||||
|
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return nil // 格式错误
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := &logInfo{
|
||||||
|
Ts: parts[0],
|
||||||
|
Level: strings.ToUpper(parts[1]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断字段结构
|
||||||
|
switch len(parts) {
|
||||||
|
case 3:
|
||||||
|
entry.Msg = parts[2]
|
||||||
|
case 4:
|
||||||
|
entry.Caller = parts[2]
|
||||||
|
entry.Msg = parts[3]
|
||||||
|
default:
|
||||||
|
entry.Caller = parts[2]
|
||||||
|
entry.Msg = strings.Join(parts[3:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集堆栈信息
|
||||||
|
var stackLines []string
|
||||||
|
for scanner.Scan() {
|
||||||
|
if scanner.Text() != "" {
|
||||||
|
stackLines = append(stackLines, scanner.Text())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(stackLines) > 0 {
|
||||||
|
entry.Stacktrace = strings.Join(stackLines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
Reference in New Issue
Block a user