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:
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{},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user