chore: Add initial project files and dependencies

This commit is contained in:
Chen Chi 2024-09-01 00:03:12 +08:00
commit 107dde65bf
10 changed files with 441 additions and 0 deletions

27
.gitignore vendored Normal file
View 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
View 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
View File

@ -0,0 +1,13 @@
# LogX
logx 是一个基于 `log/slog` 的日志库,提供了控制台输出、文件输出、日志级别控制等功能。
## 安装
```bash
go get -u github.com/yeqown/logx
```
## 使用
```go

57
console_handler.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}