chore: Add initial project files and dependencies
This commit is contained in:
commit
107dde65bf
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# ---> Go
|
||||
# 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/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# log files
|
||||
*.log
|
||||
# zip files
|
||||
*.zip
|
17
LICENSE
Normal file
17
LICENSE
Normal file
@ -0,0 +1,17 @@
|
||||
Permission is hereby granted, without written agreement and without
|
||||
license or royalty fees, to use, copy, modify, and distribute this
|
||||
software and its documentation for any purpose, provided that the
|
||||
above copyright notice and the following two paragraphs appear in
|
||||
all copies of this software.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
|
||||
THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# LogX
|
||||
|
||||
logx 是一个基于 `log/slog` 的日志库,提供了控制台输出、文件输出、日志级别控制等功能。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
go get -u github.com/yeqown/logx
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
```go
|
57
console_handler.go
Normal file
57
console_handler.go
Normal file
@ -0,0 +1,57 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
type ConsoleHandler struct {
|
||||
slog.Handler
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func NewConsoleHandler(level slog.Level) *ConsoleHandler {
|
||||
return &ConsoleHandler{
|
||||
Handler: slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: level,
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
if a.Key == slog.LevelKey {
|
||||
level := a.Value.Any().(slog.Level)
|
||||
switch level {
|
||||
case slog.LevelDebug:
|
||||
a.Value = slog.StringValue(color.BlueString(level.String()))
|
||||
case slog.LevelInfo:
|
||||
a.Value = slog.StringValue(color.GreenString(level.String()))
|
||||
case slog.LevelWarn:
|
||||
a.Value = slog.StringValue(color.YellowString(level.String()))
|
||||
case slog.LevelError:
|
||||
a.Value = slog.StringValue(color.RedString(level.String()))
|
||||
}
|
||||
}
|
||||
return a
|
||||
},
|
||||
}),
|
||||
w: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ConsoleHandler) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ConsoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &ConsoleHandler{
|
||||
Handler: h.Handler.WithAttrs(attrs),
|
||||
w: h.w,
|
||||
}
|
||||
}
|
||||
|
||||
func WithConsoleHandler(level slog.Level) Option {
|
||||
return func(l *Logger) {
|
||||
h := NewConsoleHandler(level)
|
||||
l.handlers = append(l.handlers, h)
|
||||
}
|
||||
}
|
91
example/example.go
Normal file
91
example/example.go
Normal file
@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"gitea.cccvno1.com/chenchi/logx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建一个新的日志记录器
|
||||
logger := logx.New(
|
||||
logx.WithConsoleHandler(slog.LevelDebug),
|
||||
logx.WithFileHandler("app.log", 10, 5, 30, true, slog.LevelInfo),
|
||||
logx.WithContextExtractor(customContextExtractor),
|
||||
)
|
||||
|
||||
// 设置全局日志记录器
|
||||
logx.SetGlobalLogger(logger)
|
||||
|
||||
// 使用全局日志记录器
|
||||
logx.Info("Application started")
|
||||
|
||||
// 创建一个带有跟踪ID的上下文
|
||||
ctx := context.WithValue(context.Background(), "trace_id", "abc123")
|
||||
|
||||
// 使用带有上下文的日志记录器
|
||||
contextLogger := logx.WithContext(ctx)
|
||||
contextLogger.Debug("This is a debug message with context")
|
||||
contextLogger.Info("This is an info message with context")
|
||||
contextLogger.Warn("This is a warning message with context")
|
||||
contextLogger.Error("This is an error message with context")
|
||||
|
||||
// 使用不同的日志级别
|
||||
logx.Debug("This is a debug message")
|
||||
logx.Info("This is an info message")
|
||||
logx.Warn("This is a warning message")
|
||||
logx.Error("This is an error message")
|
||||
|
||||
// 使用结构化日志
|
||||
logx.Info("User logged in",
|
||||
slog.String("username", "john_doe"),
|
||||
slog.Int("user_id", 12345),
|
||||
slog.Time("login_time", time.Now()),
|
||||
)
|
||||
|
||||
// 模拟一个错误情况
|
||||
if err := simulateError(); err != nil {
|
||||
logx.Error("An error occurred", slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
// 关闭日志记录器
|
||||
if err := logger.Close(); err != nil {
|
||||
logx.Error("Failed to close logger", slog.String("error", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
func customContextExtractor(ctx context.Context) []slog.Attr {
|
||||
return []slog.Attr{
|
||||
slog.String("trace_id", getTraceID(ctx)),
|
||||
slog.String("user_id", getUserID(ctx)),
|
||||
}
|
||||
}
|
||||
|
||||
func getTraceID(ctx context.Context) string {
|
||||
if traceID, ok := ctx.Value("trace_id").(string); ok {
|
||||
return traceID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getUserID(ctx context.Context) string {
|
||||
if userID, ok := ctx.Value("user_id").(string); ok {
|
||||
return userID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func simulateError() error {
|
||||
// 模拟一个错误
|
||||
return &customError{message: "Something went wrong"}
|
||||
}
|
||||
|
||||
type customError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *customError) Error() string {
|
||||
return e.message
|
||||
}
|
40
file_handler.go
Normal file
40
file_handler.go
Normal file
@ -0,0 +1,40 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
type FileHandler struct {
|
||||
slog.JSONHandler
|
||||
logger *lumberjack.Logger
|
||||
}
|
||||
|
||||
func WithFileHandler(path string, maxSize int, maxBackups int, maxAge int, compress bool, level slog.Level) Option {
|
||||
return func(l *Logger) {
|
||||
logger := &lumberjack.Logger{
|
||||
Filename: path,
|
||||
MaxSize: maxSize,
|
||||
MaxBackups: maxBackups,
|
||||
MaxAge: maxAge,
|
||||
Compress: compress,
|
||||
}
|
||||
h := &FileHandler{
|
||||
JSONHandler: *slog.NewJSONHandler(logger, &slog.HandlerOptions{Level: level}),
|
||||
logger: logger,
|
||||
}
|
||||
l.handlers = append(l.handlers, h)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *FileHandler) Close() error {
|
||||
return h.logger.Close()
|
||||
}
|
||||
|
||||
func (h *FileHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &FileHandler{
|
||||
JSONHandler: *h.JSONHandler.WithAttrs(attrs).(*slog.JSONHandler),
|
||||
logger: h.logger,
|
||||
}
|
||||
}
|
54
global.go
Normal file
54
global.go
Normal file
@ -0,0 +1,54 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
globalLogger *Logger
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func SetGlobalLogger(l *Logger) {
|
||||
once.Do(func() {
|
||||
globalLogger = l
|
||||
})
|
||||
}
|
||||
|
||||
func WithContext(ctx context.Context) *Logger {
|
||||
if globalLogger != nil {
|
||||
return globalLogger.WithContext(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Debug(msg string, args ...any) {
|
||||
if globalLogger != nil {
|
||||
globalLogger.Debug(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func Info(msg string, args ...any) {
|
||||
if globalLogger != nil {
|
||||
globalLogger.Info(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func Warn(msg string, args ...any) {
|
||||
if globalLogger != nil {
|
||||
globalLogger.Warn(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func Error(msg string, args ...any) {
|
||||
if globalLogger != nil {
|
||||
globalLogger.Error(msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func Fatal(msg string, args ...any) {
|
||||
if globalLogger != nil {
|
||||
globalLogger.Fatal(msg, args...)
|
||||
}
|
||||
}
|
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
||||
module gitea.cccvno1.com/chenchi/logx
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.17.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
)
|
13
go.sum
Normal file
13
go.sum
Normal file
@ -0,0 +1,13 @@
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
115
logger.go
Normal file
115
logger.go
Normal file
@ -0,0 +1,115 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
handlers []Handler
|
||||
ctxExtractor ContextExtractor
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
slog.Handler
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Option func(*Logger)
|
||||
|
||||
type ContextExtractor func(context.Context) []slog.Attr
|
||||
|
||||
func New(opts ...Option) *Logger {
|
||||
l := &Logger{
|
||||
ctxExtractor: defaultContextExtractor,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(l)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Logger) WithContext(ctx context.Context) *Logger {
|
||||
newLogger := &Logger{
|
||||
handlers: make([]Handler, len(l.handlers)),
|
||||
ctxExtractor: l.ctxExtractor,
|
||||
}
|
||||
attrs := l.ctxExtractor(ctx)
|
||||
for i, h := range l.handlers {
|
||||
newHandler := h.WithAttrs(attrs)
|
||||
if handlerWithClose, ok := newHandler.(Handler); ok {
|
||||
newLogger.handlers[i] = handlerWithClose
|
||||
} else {
|
||||
newLogger.handlers[i] = &handlerWrapper{newHandler}
|
||||
}
|
||||
}
|
||||
return newLogger
|
||||
}
|
||||
|
||||
type handlerWrapper struct {
|
||||
slog.Handler
|
||||
}
|
||||
|
||||
func (h *handlerWrapper) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(msg string, args ...any) {
|
||||
l.log(slog.LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(msg string, args ...any) {
|
||||
l.log(slog.LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(msg string, args ...any) {
|
||||
l.log(slog.LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(msg string, args ...any) {
|
||||
l.log(slog.LevelError, msg, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(msg string, args ...any) {
|
||||
l.log(slog.LevelError, msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) log(level slog.Level, msg string, args ...any) {
|
||||
for _, h := range l.handlers {
|
||||
if !h.Enabled(context.Background(), level) {
|
||||
continue
|
||||
}
|
||||
r := slog.NewRecord(time.Now(), level, msg, 0)
|
||||
r.Add(args...)
|
||||
_ = h.Handle(context.Background(), r)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Close() error {
|
||||
for _, h := range l.handlers {
|
||||
if err := h.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithContextExtractor(extractor ContextExtractor) Option {
|
||||
return func(l *Logger) {
|
||||
l.ctxExtractor = extractor
|
||||
}
|
||||
}
|
||||
|
||||
func defaultContextExtractor(ctx context.Context) []slog.Attr {
|
||||
return []slog.Attr{
|
||||
slog.String("trace_id", getTraceID(ctx)),
|
||||
}
|
||||
}
|
||||
|
||||
func getTraceID(ctx context.Context) string {
|
||||
traceID := ctx.Value("trace_id")
|
||||
return traceID.(string)
|
||||
}
|
Loading…
Reference in New Issue
Block a user