feat: add logging and collector implementations
- Introduced `hertzx` package with `NewHertz` function for server initialization. - Implemented `logx` package with various log collectors: Console, Loki, Elasticsearch, and Prometheus. - Added `Logger` struct to manage logging levels and collectors. - Created environment variable loading functionality in `osx` package to support configuration. - Enhanced logging capabilities with structured log entries and asynchronous collection.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.env*
|
||||
14
cmd/db/main.go
Normal file
14
cmd/db/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.liteyuki.org/LiteyukiStudio/folium/dbx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 初始化数据库连接
|
||||
db := dbx.GetDB(nil, "test")
|
||||
|
||||
fmt.Println(db)
|
||||
}
|
||||
13
cmd/log/main.go
Normal file
13
cmd/log/main.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.liteyuki.org/LiteyukiStudio/folium/logx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 初始化日志
|
||||
logx.Debug("This is a debug log")
|
||||
logx.Info("This is an info log")
|
||||
logx.Warn("This is a warning log")
|
||||
logx.Error("This is an error log")
|
||||
}
|
||||
88
dbx/init.go
Normal file
88
dbx/init.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"git.liteyuki.org/LiteyukiStudio/folium/osx"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DBDriverType string
|
||||
|
||||
const (
|
||||
Postgres DBDriverType = "postgres"
|
||||
MySQL DBDriverType = "mysql"
|
||||
SQLite DBDriverType = "sqlite"
|
||||
)
|
||||
|
||||
// DBConfig 持有数据库连接配置,可以部分填写,未填写的字段会从环境变量读取默认值
|
||||
type DBConfig struct {
|
||||
Driver DBDriverType
|
||||
Host string
|
||||
Port string
|
||||
User string
|
||||
Password string
|
||||
Sslmode string
|
||||
}
|
||||
|
||||
// NewDBConfigFromEnv 从环境变量构造默认配置
|
||||
func NewDBConfigFromEnv() *DBConfig {
|
||||
return &DBConfig{
|
||||
Driver: DBDriverType(osx.GetEnv("DB_DRIVER", string(Postgres))),
|
||||
Host: osx.GetEnv("DB_HOST", "localhost"),
|
||||
Port: osx.GetEnv("DB_PORT", "5432"),
|
||||
User: osx.GetEnv("DB_USER", "user"),
|
||||
Password: osx.GetEnv("DB_PASSWORD", "password"),
|
||||
Sslmode: osx.GetEnv("DB_SSLMODE", "disable"),
|
||||
}
|
||||
}
|
||||
|
||||
// FillDefaultsFromEnv 对于为空的字段,用环境变量或默认值补全
|
||||
func (c *DBConfig) FillDefaultsFromEnv() {
|
||||
if c == nil {
|
||||
c = &DBConfig{}
|
||||
}
|
||||
if c.Driver == "" {
|
||||
c.Driver = DBDriverType(osx.GetEnv("DB_DRIVER", string(Postgres)))
|
||||
}
|
||||
if c.Host == "" {
|
||||
c.Host = osx.GetEnv("DB_HOST", "localhost")
|
||||
}
|
||||
if c.Port == "" {
|
||||
c.Port = osx.GetEnv("DB_PORT", "5432")
|
||||
}
|
||||
if c.User == "" {
|
||||
c.User = osx.GetEnv("DB_USER", "user")
|
||||
}
|
||||
if c.Password == "" {
|
||||
c.Password = osx.GetEnv("DB_PASSWORD", "password")
|
||||
}
|
||||
if c.Sslmode == "" {
|
||||
c.Sslmode = osx.GetEnv("DB_SSLMODE", "disable")
|
||||
}
|
||||
}
|
||||
|
||||
// GetDB 使用给定配置(或 DefaultDBConfig if nil)并返回对应的 *gorm.DB
|
||||
// 如果 dbName 为空则使用配置中的 Name 字段(仍会从环境变量补全)
|
||||
func GetDB(cfg *DBConfig, dbName string) *gorm.DB {
|
||||
if cfg == nil {
|
||||
cfg = NewDBConfigFromEnv()
|
||||
}
|
||||
cfg.FillDefaultsFromEnv()
|
||||
|
||||
if dbName == "" {
|
||||
dbName = osx.GetEnv("DB_NAME", "database")
|
||||
if dbName == "" {
|
||||
panic("database name is not specified")
|
||||
}
|
||||
}
|
||||
|
||||
switch cfg.Driver {
|
||||
case Postgres:
|
||||
return GetPostgresDB(cfg, dbName)
|
||||
case MySQL:
|
||||
return GetMySQLDB(cfg, dbName)
|
||||
case SQLite:
|
||||
return GetSQLiteDB(cfg, dbName)
|
||||
default:
|
||||
panic("unsupported database driver: " + string(cfg.Driver))
|
||||
}
|
||||
}
|
||||
71
dbx/mysql.go
Normal file
71
dbx/mysql.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func GetMySQLDB(cfg *DBConfig, dbName string) *gorm.DB {
|
||||
db, err := getMySQLInstance(cfg, dbName)
|
||||
if err != nil {
|
||||
panic("failed to connect to MySQL database: " + err.Error())
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func getMySQLInstance(cfg *DBConfig, dbName string) (*gorm.DB, error) {
|
||||
targetDSN := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
cfg.User, cfg.Password, cfg.Host, cfg.Port, dbName)
|
||||
|
||||
// 先尝试直接连接目标库
|
||||
if db, err := gorm.Open(mysql.Open(targetDSN), &gorm.Config{}); err == nil {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// 以不指定数据库的 DSN 连接(用于创建数据库)
|
||||
adminDSN := fmt.Sprintf("%s:%s@tcp(%s:%s)/?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
cfg.User, cfg.Password, cfg.Host, cfg.Port)
|
||||
adminDB, err := gorm.Open(mysql.Open(adminDSN), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect admin mysql failed: %w", err)
|
||||
}
|
||||
// 关闭底层连接
|
||||
if sqlDB, e := adminDB.DB(); e == nil {
|
||||
defer sqlDB.Close()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 检查是否存在(使用 INFORMATION_SCHEMA)
|
||||
var count int64
|
||||
checkSQL := "SELECT count(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?"
|
||||
if err := adminDB.WithContext(ctx).Raw(checkSQL, dbName).Scan(&count).Error; err != nil {
|
||||
return nil, fmt.Errorf("check mysql database existence failed: %w", err)
|
||||
}
|
||||
if count == 0 {
|
||||
ident := escapeMySQLIdentifier(dbName)
|
||||
createSQL := fmt.Sprintf("CREATE DATABASE %s DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", ident)
|
||||
if err := adminDB.WithContext(ctx).Exec(createSQL).Error; err != nil {
|
||||
return nil, fmt.Errorf("create mysql database %s failed: %w", dbName, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 重试连接目标数据库
|
||||
db2, err := gorm.Open(mysql.Open(targetDSN), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect mysql target after create failed: %w", err)
|
||||
}
|
||||
return db2, nil
|
||||
}
|
||||
|
||||
// 辅助:安全转义 MySQL 标识符(用反引号并把 ` 替换为 “)
|
||||
func escapeMySQLIdentifier(s string) string {
|
||||
s = strings.ReplaceAll(s, "`", "``")
|
||||
return "`" + s + "`"
|
||||
}
|
||||
70
dbx/postgres.go
Normal file
70
dbx/postgres.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func GetPostgresDB(cfg *DBConfig, dbName string) *gorm.DB {
|
||||
db, err := getPostgresInstance(cfg, dbName)
|
||||
if err != nil {
|
||||
panic("failed to connect to Postgres database: " + err.Error())
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func getPostgresInstance(cfg *DBConfig, dbName string) (*gorm.DB, error) {
|
||||
targetDSN := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||||
cfg.Host, cfg.Port, cfg.User, cfg.Password, dbName, cfg.Sslmode)
|
||||
|
||||
// 先尝试直接连接目标库
|
||||
if db, err := gorm.Open(postgres.Open(targetDSN), &gorm.Config{}); err == nil {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// 连接 admin DB(postgres)
|
||||
adminDSN := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=postgres sslmode=%s",
|
||||
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Sslmode)
|
||||
adminDB, err := gorm.Open(postgres.Open(adminDSN), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect admin postgres failed: %w", err)
|
||||
}
|
||||
// 确保关闭底层连接
|
||||
if sqlDB, e := adminDB.DB(); e == nil {
|
||||
defer sqlDB.Close()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 检查是否存在
|
||||
var count int64
|
||||
if err := adminDB.WithContext(ctx).Raw("SELECT count(*) FROM pg_database WHERE datname = ?", dbName).Scan(&count).Error; err != nil {
|
||||
return nil, fmt.Errorf("check database existence failed: %w", err)
|
||||
}
|
||||
if count == 0 {
|
||||
ident := escapePostgresIdentifier(dbName)
|
||||
createSQL := fmt.Sprintf("CREATE DATABASE %s", ident)
|
||||
if err := adminDB.WithContext(ctx).Exec(createSQL).Error; err != nil {
|
||||
return nil, fmt.Errorf("create database %s failed: %w", dbName, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试连接目标数据库
|
||||
db2, err := gorm.Open(postgres.Open(targetDSN), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect target after create failed: %w", err)
|
||||
}
|
||||
return db2, nil
|
||||
}
|
||||
|
||||
// 辅助:安全转义 Postgres 标识符(用双引号并把 " 替换为 "")
|
||||
func escapePostgresIdentifier(s string) string {
|
||||
s = strings.ReplaceAll(s, `"`, `""`)
|
||||
return `"` + s + `"`
|
||||
}
|
||||
22
dbx/sqlite.go
Normal file
22
dbx/sqlite.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package dbx
|
||||
|
||||
import (
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func GetSQLiteDB(cfg *DBConfig, dbName string) *gorm.DB {
|
||||
db, err := getSQLiteInstance(cfg, dbName)
|
||||
if err != nil {
|
||||
panic("failed to connect to SQLite database: " + err.Error())
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func getSQLiteInstance(cfg *DBConfig, dbName string) (*gorm.DB, error) {
|
||||
// 对 sqlite, dbName 可以是文件路径或 ':memory:'
|
||||
return gorm.Open(
|
||||
sqlite.Open(dbName),
|
||||
&gorm.Config{},
|
||||
)
|
||||
}
|
||||
67
go.mod
Normal file
67
go.mod
Normal file
@@ -0,0 +1,67 @@
|
||||
module git.liteyuki.org/LiteyukiStudio/folium
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
go.uber.org/zap v1.27.0
|
||||
gorm.io/gorm v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/gopkg v0.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cloudwego/gopkg v0.1.4 // indirect
|
||||
github.com/cloudwego/netpoll v0.7.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/golang/protobuf v1.5.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nyaruka/phonenumbers v1.0.55 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cloudwego/hertz v0.10.3
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
)
|
||||
208
go.sum
Normal file
208
go.sum
Normal file
@@ -0,0 +1,208 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE=
|
||||
github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/gopkg v0.1.4 h1:EoQiCG4sTonTPHxOGE0VlQs+sQR+Hsi2uN0qqwu8O50=
|
||||
github.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI=
|
||||
github.com/cloudwego/hertz v0.10.3 h1:NFcQAjouVJsod79XPLC/PaFfHgjMTYbiErmW+vGBi8A=
|
||||
github.com/cloudwego/hertz v0.10.3/go.mod h1:W5dUFXZPZkyfjMMo3EQrMQbofuvTsctM9IxmhbkuT18=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cloudwego/netpoll v0.7.0 h1:bDrxQaNfijRI1zyGgXHQoE/nYegL0nr+ijO1Norelc4=
|
||||
github.com/cloudwego/netpoll v0.7.0/go.mod h1:PI+YrmyS7cIr0+SD4seJz3Eo3ckkXdu2ZVKBLhURLNU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg=
|
||||
github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
14
hertzx/hz.go
Normal file
14
hertzx/hz.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package hertzx
|
||||
|
||||
import (
|
||||
"git.liteyuki.org/LiteyukiStudio/folium/osx"
|
||||
"github.com/cloudwego/hertz/pkg/app/server"
|
||||
"github.com/cloudwego/hertz/pkg/common/config"
|
||||
)
|
||||
|
||||
func NewHertz(opts ...config.Option) *server.Hertz {
|
||||
opts = append([]config.Option{
|
||||
server.WithHostPorts(osx.GetEnv("HERTZ_HOST_PORT", ":8888")),
|
||||
}, opts...)
|
||||
return server.New(opts...)
|
||||
}
|
||||
187
logx/collector.go
Normal file
187
logx/collector.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.liteyuki.org/LiteyukiStudio/folium/osx"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type ConsoleCollector struct{}
|
||||
|
||||
func NewConsoleCollector() *ConsoleCollector { return &ConsoleCollector{} }
|
||||
|
||||
func (c *ConsoleCollector) Collect(ctx context.Context, e Entry) error {
|
||||
// 简单输出 JSON 行,供外部 log collector(Fluent/FluentBit)抓取
|
||||
b, _ := json.Marshal(e)
|
||||
fmt.Fprintln(os.Stdout, string(b))
|
||||
return nil
|
||||
}
|
||||
func (c *ConsoleCollector) Close() error { return nil }
|
||||
|
||||
// LokiCollector:把日志推送到 Loki push API(基本实现)
|
||||
type LokiCollector struct {
|
||||
url string
|
||||
client *http.Client
|
||||
labels string // e.g. `{job="myapp"}`
|
||||
}
|
||||
|
||||
func NewLokiCollector(collectorURL string, labels map[string]string) *LokiCollector {
|
||||
// 构建 labels 字符串
|
||||
lbl := "{"
|
||||
first := true
|
||||
for k, v := range labels {
|
||||
if !first {
|
||||
lbl += ","
|
||||
}
|
||||
lbl += fmt.Sprintf("%s=\"%s\"", k, v)
|
||||
first = false
|
||||
}
|
||||
lbl += "}"
|
||||
return &LokiCollector{
|
||||
url: collectorURL,
|
||||
client: &http.Client{Timeout: 5 * time.Second},
|
||||
labels: lbl,
|
||||
}
|
||||
}
|
||||
|
||||
type lokiStream struct {
|
||||
Stream map[string]string `json:"stream"`
|
||||
Values [][2]string `json:"values"`
|
||||
}
|
||||
type lokiPush struct {
|
||||
Streams []lokiStream `json:"streams"`
|
||||
}
|
||||
|
||||
func (c *LokiCollector) Collect(ctx context.Context, e Entry) error {
|
||||
// Loki requires timestamp in nanoseconds as string
|
||||
ts := fmt.Sprintf("%d", e.Time.UnixNano())
|
||||
lineB, _ := json.Marshal(e)
|
||||
stream := lokiStream{
|
||||
Stream: map[string]string{"level": string(e.Level)},
|
||||
Values: [][2]string{{ts, string(lineB)}},
|
||||
}
|
||||
push := lokiPush{Streams: []lokiStream{stream}}
|
||||
body, _ := json.Marshal(push)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", c.url+"/loki/api/v1/push", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("loki push status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *LokiCollector) Close() error { return nil }
|
||||
|
||||
// ESCollector:把日志写入 Elasticsearch 简单实现(使用 index API)
|
||||
type ESCollector struct {
|
||||
url string // e.g. http://es:9200
|
||||
index string // index name
|
||||
client *http.Client
|
||||
auth *basicAuth // optional
|
||||
}
|
||||
|
||||
type basicAuth struct {
|
||||
user string
|
||||
pass string
|
||||
}
|
||||
|
||||
func NewESCollector(url, index string, authUser, authPass string) *ESCollector {
|
||||
var auth *basicAuth
|
||||
if authUser != "" {
|
||||
auth = &basicAuth{user: authUser, pass: authPass}
|
||||
}
|
||||
return &ESCollector{
|
||||
url: url,
|
||||
index: index,
|
||||
client: &http.Client{Timeout: 5 * time.Second},
|
||||
auth: auth,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ESCollector) Collect(ctx context.Context, e Entry) error {
|
||||
b, _ := json.Marshal(e)
|
||||
endpoint := fmt.Sprintf("%s/%s/_doc", c.url, c.index)
|
||||
req, _ := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if c.auth != nil {
|
||||
req.SetBasicAuth(c.auth.user, c.auth.pass)
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("es index status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *ESCollector) Close() error { return nil }
|
||||
|
||||
// PromCollector:记录日志计数到 Prometheus(便于监控日志量)
|
||||
type PromCollector struct {
|
||||
counter *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func NewPromCollector(namespace string) *PromCollector {
|
||||
cv := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Name: "app_logs_total",
|
||||
Help: "Total application logs by level",
|
||||
}, []string{"level"})
|
||||
// 忽略重复注册错误(如果已存在,使用已注册的)
|
||||
_ = prometheus.Register(cv)
|
||||
return &PromCollector{
|
||||
counter: cv,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PromCollector) Collect(ctx context.Context, e Entry) error {
|
||||
p.counter.WithLabelValues(string(e.Level)).Inc()
|
||||
return nil
|
||||
}
|
||||
func (p *PromCollector) Close() error { return nil }
|
||||
|
||||
// LoadCollectorsFromEnv 根据环境变量 LOG_COLLECTORS 加载对应的 collectors
|
||||
func LoadCollectorsFromEnv() []Collector {
|
||||
collectors := osx.GetEnv("LOG_COLLECTORS", "")
|
||||
var result []Collector
|
||||
if collectors == "" {
|
||||
return result
|
||||
}
|
||||
types := bytes.Split([]byte(collectors), []byte(","))
|
||||
for _, t := range types {
|
||||
switch string(bytes.TrimSpace(t)) {
|
||||
case "console":
|
||||
result = append(result, NewConsoleCollector())
|
||||
case "loki":
|
||||
lokiURL := osx.GetEnv("LOKI_URL", "http://localhost:3100")
|
||||
labels := map[string]string{
|
||||
"job": osx.GetEnv("LOKI_JOB", "myapp"),
|
||||
}
|
||||
result = append(result, NewLokiCollector(lokiURL, labels))
|
||||
case "elasticsearch":
|
||||
esURL := osx.GetEnv("ES_URL", "http://localhost:9200")
|
||||
esIndex := osx.GetEnv("ES_INDEX", "app-logs")
|
||||
esUser := osx.GetEnv("ES_USER", "")
|
||||
esPass := osx.GetEnv("ES_PASS", "")
|
||||
result = append(result, NewESCollector(esURL, esIndex, esUser, esPass))
|
||||
case "prometheus":
|
||||
namespace := osx.GetEnv("PROM_NAMESPACE", "myapp")
|
||||
result = append(result, NewPromCollector(namespace))
|
||||
// 可扩展更多 collector 类型
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
442
logx/log.go
Normal file
442
logx/log.go
Normal file
@@ -0,0 +1,442 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.liteyuki.org/LiteyukiStudio/folium/osx"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// 支持的日志等级
|
||||
type Level string
|
||||
|
||||
const (
|
||||
LevelTrace Level = "trace"
|
||||
LevelDebug Level = "debug"
|
||||
LevelInfo Level = "info"
|
||||
LevelWarn Level = "warn"
|
||||
LevelError Level = "error"
|
||||
LevelFatal Level = "fatal"
|
||||
LevelPanic Level = "panic"
|
||||
)
|
||||
|
||||
// Entry 表示一条日志的可传输结构
|
||||
type Entry struct {
|
||||
Level Level `json:"level"`
|
||||
Time time.Time `json:"time"`
|
||||
Msg string `json:"msg"`
|
||||
Fields map[string]interface{} `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// Collector 是外部收集器接口(用户可实现)
|
||||
type Collector interface {
|
||||
Collect(ctx context.Context, e Entry) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Logger 支持本地打印并把日志发送到已注册的 collectors
|
||||
type Logger struct {
|
||||
zap *zap.Logger
|
||||
collectors []Collector
|
||||
mu sync.RWMutex
|
||||
wg sync.WaitGroup
|
||||
|
||||
minLevel Level // 低于此级别的日志将被忽略
|
||||
}
|
||||
|
||||
// 全局单例
|
||||
var (
|
||||
Default *Logger
|
||||
defaultOnce sync.Once
|
||||
)
|
||||
|
||||
// buildLoggerFromEnv 基于环境构造 Logger(不设置全局变量)
|
||||
func buildLoggerFromEnv() *Logger {
|
||||
// 输出格式
|
||||
output := strings.ToLower(osx.GetEnv("LOG_OUTPUT", "console"))
|
||||
cfg := zap.NewProductionConfig()
|
||||
if output == "json" {
|
||||
cfg.Encoding = "json"
|
||||
} else {
|
||||
cfg.Encoding = "console"
|
||||
}
|
||||
cfg.EncoderConfig.TimeKey = "ts"
|
||||
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
// 让 zap 本身也服从 LOG_LEVEL
|
||||
levelStr := strings.ToLower(osx.GetEnv("LOG_LEVEL", osx.GetEnv("LOG_MIN_LEVEL", "info")))
|
||||
var zapLevel zapcore.Level
|
||||
switch levelStr {
|
||||
case "trace", "debug":
|
||||
zapLevel = zapcore.DebugLevel
|
||||
case "info":
|
||||
zapLevel = zapcore.InfoLevel
|
||||
case "warn", "warning":
|
||||
zapLevel = zapcore.WarnLevel
|
||||
case "error":
|
||||
zapLevel = zapcore.ErrorLevel
|
||||
case "fatal":
|
||||
zapLevel = zapcore.FatalLevel
|
||||
case "panic":
|
||||
zapLevel = zapcore.PanicLevel
|
||||
default:
|
||||
zapLevel = zapcore.InfoLevel
|
||||
}
|
||||
cfg.Level = zap.NewAtomicLevelAt(zapLevel)
|
||||
|
||||
// 可通过环境关闭 stacktrace 输出
|
||||
if strings.ToLower(osx.GetEnv("LOG_DISABLE_STACKTRACE", "false")) == "true" {
|
||||
cfg.DisableStacktrace = true
|
||||
}
|
||||
|
||||
zl, _ := cfg.Build()
|
||||
|
||||
l := &Logger{
|
||||
zap: zl,
|
||||
collectors: nil,
|
||||
minLevel: LevelInfo,
|
||||
}
|
||||
|
||||
// 最低日志级别(同步 env)
|
||||
switch levelStr {
|
||||
case "trace":
|
||||
l.minLevel = LevelTrace
|
||||
case "debug":
|
||||
l.minLevel = LevelDebug
|
||||
case "info":
|
||||
l.minLevel = LevelInfo
|
||||
case "warn", "warning":
|
||||
l.minLevel = LevelWarn
|
||||
case "error":
|
||||
l.minLevel = LevelError
|
||||
case "fatal":
|
||||
l.minLevel = LevelFatal
|
||||
case "panic":
|
||||
l.minLevel = LevelPanic
|
||||
default:
|
||||
l.minLevel = LevelInfo
|
||||
}
|
||||
|
||||
// 从环境加载并注册 collectors(实现位于 collector.go)
|
||||
cols := LoadCollectorsFromEnv()
|
||||
for _, c := range cols {
|
||||
l.RegisterCollector(c)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// InitLogger 初始化全局单例(只第一次生效),返回单例
|
||||
func InitLogger() *Logger {
|
||||
defaultOnce.Do(func() {
|
||||
Default = buildLoggerFromEnv()
|
||||
})
|
||||
return Default
|
||||
}
|
||||
|
||||
// GetLogger 返回全局单例,如果尚未初始化则会自动 InitLogger
|
||||
func GetLogger() *Logger {
|
||||
return InitLogger()
|
||||
}
|
||||
|
||||
// 删除旧的 New / NewDefault,保留 New(...) 风格的局部 logger 创建函数改名为 NewLocal(可选)
|
||||
func NewLocal(zl *zap.Logger) *Logger {
|
||||
if zl == nil {
|
||||
// 使用一个轻量 zap 实例,不触碰全局单例
|
||||
cfg := zap.NewProductionConfig()
|
||||
cfg.Encoding = "console"
|
||||
cfg.EncoderConfig.TimeKey = "ts"
|
||||
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
zl2, _ := cfg.Build()
|
||||
return &Logger{
|
||||
zap: zl2,
|
||||
minLevel: LevelDebug,
|
||||
collectors: nil,
|
||||
}
|
||||
}
|
||||
return &Logger{
|
||||
zap: zl,
|
||||
minLevel: LevelDebug,
|
||||
collectors: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCollector 在运行时注册一个 Collector(线程安全)
|
||||
func (l *Logger) RegisterCollector(c Collector) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.collectors = append(l.collectors, c)
|
||||
}
|
||||
|
||||
// UnregisterCollector 注销已注册的 Collector(按指针相等匹配)
|
||||
func (l *Logger) UnregisterCollector(c Collector) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
for i, col := range l.collectors {
|
||||
if col == c {
|
||||
l.collectors = append(l.collectors[:i], l.collectors[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListCollectors 返回当前已注册的 collectors(副本,线程安全)
|
||||
func (l *Logger) ListCollectors() []Collector {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
cp := make([]Collector, len(l.collectors))
|
||||
copy(cp, l.collectors)
|
||||
return cp
|
||||
}
|
||||
|
||||
// Close 关闭所有 collectors(阻塞直到异步发送结束)
|
||||
func (l *Logger) Close() error {
|
||||
l.mu.RLock()
|
||||
collectors := append([]Collector(nil), l.collectors...)
|
||||
l.mu.RUnlock()
|
||||
|
||||
// 等待 async sending 完成
|
||||
l.wg.Wait()
|
||||
|
||||
var firstErr error
|
||||
for _, c := range collectors {
|
||||
if err := c.Close(); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
_ = l.zap.Sync()
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// SetLevel 设置最低日志级别,低于该级别的日志将被忽略
|
||||
func (l *Logger) SetLevel(level Level) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.minLevel = level
|
||||
}
|
||||
|
||||
// level 排序辅助
|
||||
var levelOrder = map[Level]int{
|
||||
LevelTrace: 0,
|
||||
LevelDebug: 1,
|
||||
LevelInfo: 2,
|
||||
LevelWarn: 3,
|
||||
LevelError: 4,
|
||||
LevelFatal: 5,
|
||||
LevelPanic: 6,
|
||||
}
|
||||
|
||||
func (l *Logger) log(ctx context.Context, level Level, msg string, fields map[string]interface{}) {
|
||||
// 级别过滤
|
||||
l.mu.RLock()
|
||||
min := l.minLevel
|
||||
l.mu.RUnlock()
|
||||
if levelOrder[level] < levelOrder[min] {
|
||||
return
|
||||
}
|
||||
|
||||
// 本地打印(保持原有行为),并尽量保持 trace/level 字段
|
||||
switch level {
|
||||
case LevelTrace:
|
||||
l.zap.Debug(msg, zap.String("level", string(LevelTrace)), zap.Any("fields", fields))
|
||||
case LevelDebug:
|
||||
l.zap.Debug(msg, zap.Any("fields", fields))
|
||||
case LevelInfo:
|
||||
l.zap.Info(msg, zap.Any("fields", fields))
|
||||
case LevelWarn:
|
||||
l.zap.Warn(msg, zap.Any("fields", fields))
|
||||
case LevelError:
|
||||
l.zap.Error(msg, zap.Any("fields", fields))
|
||||
case LevelFatal:
|
||||
// Fatal 会调用 os.Exit(1)
|
||||
l.zap.Fatal(msg, zap.Any("fields", fields))
|
||||
case LevelPanic:
|
||||
// Panic 会 panic
|
||||
l.zap.Panic(msg, zap.Any("fields", fields))
|
||||
default:
|
||||
l.zap.Info(msg, zap.Any("level", level), zap.Any("fields", fields))
|
||||
}
|
||||
|
||||
// 向 collectors 异步发送
|
||||
l.mu.RLock()
|
||||
collectors := append([]Collector(nil), l.collectors...)
|
||||
l.mu.RUnlock()
|
||||
|
||||
if len(collectors) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
e := Entry{
|
||||
Level: level,
|
||||
Time: time.Now(),
|
||||
Msg: msg,
|
||||
Fields: fields,
|
||||
}
|
||||
|
||||
for _, c := range collectors {
|
||||
c := c
|
||||
l.wg.Add(1)
|
||||
go func() {
|
||||
defer l.wg.Done()
|
||||
ctx2, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
if err := c.Collect(ctx2, e); err != nil {
|
||||
// collectors 的错误不影响主流程,记录到本地日志
|
||||
l.zap.Warn("collector collect error", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// 便捷方法(增加 Trace, Fatal, Panic)
|
||||
// 保留 Logger 的方法签名:需要 ctx
|
||||
func (l *Logger) Trace(ctx context.Context, msg string, fields map[string]interface{}) {
|
||||
l.log(ctx, LevelTrace, msg, fields)
|
||||
}
|
||||
func (l *Logger) Debug(ctx context.Context, msg string, fields map[string]interface{}) {
|
||||
l.log(ctx, LevelDebug, msg, fields)
|
||||
}
|
||||
func (l *Logger) Info(ctx context.Context, msg string, fields map[string]interface{}) {
|
||||
l.log(ctx, LevelInfo, msg, fields)
|
||||
}
|
||||
func (l *Logger) Warn(ctx context.Context, msg string, fields map[string]interface{}) {
|
||||
l.log(ctx, LevelWarn, msg, fields)
|
||||
}
|
||||
func (l *Logger) Error(ctx context.Context, msg string, fields map[string]interface{}) {
|
||||
l.log(ctx, LevelError, msg, fields)
|
||||
}
|
||||
func (l *Logger) Fatal(ctx context.Context, msg string, fields map[string]interface{}) {
|
||||
l.log(ctx, LevelFatal, msg, fields)
|
||||
}
|
||||
func (l *Logger) Panic(ctx context.Context, msg string, fields map[string]interface{}) {
|
||||
l.log(ctx, LevelPanic, msg, fields)
|
||||
}
|
||||
|
||||
// SimpleLogger 提供不带 ctx 的简洁 API(可选 fields)
|
||||
type SimpleLogger struct {
|
||||
parent *Logger
|
||||
}
|
||||
|
||||
func (s *SimpleLogger) Trace(msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
s.parent.Trace(context.Background(), msg, f)
|
||||
}
|
||||
func (s *SimpleLogger) Debug(msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
s.parent.Debug(context.Background(), msg, f)
|
||||
}
|
||||
func (s *SimpleLogger) Info(msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
s.parent.Info(context.Background(), msg, f)
|
||||
}
|
||||
func (s *SimpleLogger) Warn(msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
s.parent.Warn(context.Background(), msg, f)
|
||||
}
|
||||
func (s *SimpleLogger) Error(msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
s.parent.Error(context.Background(), msg, f)
|
||||
}
|
||||
func (s *SimpleLogger) Fatal(msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
s.parent.Fatal(context.Background(), msg, f)
|
||||
}
|
||||
func (s *SimpleLogger) Panic(msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
s.parent.Panic(context.Background(), msg, f)
|
||||
}
|
||||
|
||||
// Simple 返回一个绑定到当前 Logger 的 SimpleLogger(不创建新实例)
|
||||
func (l *Logger) Simple() *SimpleLogger {
|
||||
return &SimpleLogger{parent: l}
|
||||
}
|
||||
|
||||
// 包级简洁 API:无需 ctx,自动使用 background ctx(调用者更推荐显式使用 Ctx* 以保留 trace)
|
||||
func Trace(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Trace(msg, fields...) }
|
||||
func Debug(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Debug(msg, fields...) }
|
||||
func Info(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Info(msg, fields...) }
|
||||
func Warn(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Warn(msg, fields...) }
|
||||
func Error(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Error(msg, fields...) }
|
||||
func Fatal(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Fatal(msg, fields...) }
|
||||
func Panic(msg string, fields ...map[string]interface{}) { GetLogger().Simple().Panic(msg, fields...) }
|
||||
|
||||
// 包级带 ctx 的 API:当你有请求上下文(包含 trace)时使用
|
||||
func CtxTrace(ctx context.Context, msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
GetLogger().Trace(ctx, msg, f)
|
||||
}
|
||||
func CtxDebug(ctx context.Context, msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
GetLogger().Debug(ctx, msg, f)
|
||||
}
|
||||
func CtxInfo(ctx context.Context, msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
GetLogger().Info(ctx, msg, f)
|
||||
}
|
||||
func CtxWarn(ctx context.Context, msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
GetLogger().Warn(ctx, msg, f)
|
||||
}
|
||||
func CtxError(ctx context.Context, msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
GetLogger().Error(ctx, msg, f)
|
||||
}
|
||||
func CtxFatal(ctx context.Context, msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
GetLogger().Fatal(ctx, msg, f)
|
||||
}
|
||||
func CtxPanic(ctx context.Context, msg string, fields ...map[string]interface{}) {
|
||||
var f map[string]interface{}
|
||||
if len(fields) > 0 {
|
||||
f = fields[0]
|
||||
}
|
||||
GetLogger().Panic(ctx, msg, f)
|
||||
}
|
||||
78
osx/osx.go
Normal file
78
osx/osx.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package osx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 读取目录下的所有 .env 文件,加载环境变量
|
||||
// 生产环境由于部署在kubernetes等平台,通常会直接通过环境变量注入配置
|
||||
// 因此这里的加载主要用于开发环境
|
||||
loadDotEnvFiles()
|
||||
}
|
||||
|
||||
func GetEnv(key string, defaultValue ...string) string {
|
||||
if value, exists := os.LookupEnv(key); exists {
|
||||
return value
|
||||
}
|
||||
if len(defaultValue) > 0 {
|
||||
return defaultValue[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetEnvInt(key string, defaultValue int) int {
|
||||
if valueStr, exists := os.LookupEnv(key); exists {
|
||||
var value int
|
||||
_, err := fmt.Sscanf(valueStr, "%d", &value)
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetEnvBool 支持 "true", "1", "True", "TRUE" 返回 true
|
||||
// 支持 "false", "0", "False", "FALSE" 返回 false
|
||||
// 如果环境变量不存在或无法解析,则返回 defaultValue
|
||||
func GetEnvBool(key string, defaultValue bool) bool {
|
||||
if valueStr, exists := os.LookupEnv(key); exists {
|
||||
switch valueStr {
|
||||
case "true", "1", "True", "TRUE":
|
||||
return true
|
||||
case "false", "0", "False", "FALSE":
|
||||
return false
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func GetEnvT[T any](key string, parser func(string) (T, error), defaultValue T) T {
|
||||
if valueStr, exists := os.LookupEnv(key); exists {
|
||||
if value, err := parser(valueStr); err == nil {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func loadDotEnvFiles() {
|
||||
dotEnvFiles := []string{".env.local", ".env", ".env.development", ".env.production"}
|
||||
existedFiles := []string{}
|
||||
for _, file := range dotEnvFiles {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
existedFiles = append(existedFiles, file)
|
||||
}
|
||||
}
|
||||
if len(existedFiles) == 0 {
|
||||
return
|
||||
}
|
||||
// 按顺序加载,后面的会覆盖前面的
|
||||
err := godotenv.Load(existedFiles...)
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading .env files: %v\n", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user