feat: complete contract-driven business development and MySQL integration

This commit is contained in:
2026-09-24 21:56:04 +08:00
parent f9afb8f8dd
commit f5478cdc76
108 changed files with 6557 additions and 550 deletions

67
logx/adapters.go Normal file
View File

@@ -0,0 +1,67 @@
package logx
import (
"context"
"errors"
"log"
"log/slog"
"strings"
)
// Validate checks configuration without opening files or creating resources.
func (c Config) Validate() error {
c = normalizeConfig(c)
if _, err := parseLevel(c.Level); err != nil {
return errors.New("logx: invalid level")
}
if c.Format != "json" && c.Format != "text" {
return errors.New("logx: format must be json or text")
}
switch c.Output {
case "stdout", "stderr", "discard", "none":
default:
return errors.New("logx: invalid output")
}
return c.Rotation.validate(c)
}
type loggerContextKey struct{}
// WithLogger borrows a logger for a call tree; it does not alter the global
// default and does not transfer ownership. nil preserves the existing context.
func WithLogger(ctx context.Context, logger *Logger) context.Context {
ctx = normalizeContext(ctx)
if logger == nil {
return ctx
}
return context.WithValue(ctx, loggerContextKey{}, logger)
}
// FromContext returns a call-scoped logger, falling back to package logging.
func FromContext(ctx context.Context) *Logger {
if ctx != nil {
if l, ok := ctx.Value(loggerContextKey{}).(*Logger); ok && l != nil {
return l
}
}
return defaultLogger.Load()
}
// Standard adapts this instance to the standard logger (for http.Server etc).
// It borrows the writer and never closes it. Each Print call becomes one event.
func (l *Logger) Standard(level slog.Level) *log.Logger {
if l == nil {
l = defaultLogger.Load()
}
return log.New(standardWriter{logger: l, level: level}, "", 0)
}
type standardWriter struct {
logger *Logger
level slog.Level
}
func (w standardWriter) Write(p []byte) (int, error) {
w.logger.logger.Log(context.Background(), w.level, strings.TrimRight(string(p), "\r\n"))
return len(p), nil
}

33
logx/adapters_test.go Normal file
View File

@@ -0,0 +1,33 @@
package logx
import (
"bytes"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
)
func TestValidateBeforeOpeningWriter(t *testing.T) {
p := filepath.Join(t.TempDir(), "must-not-exist.log")
for _, cfg := range []Config{{File: p, Format: "typo"}, {File: p, Level: "typo"}, {File: p, Output: "typo"}} {
if cfg.Validate() == nil {
t.Fatal("invalid configuration accepted")
}
if _, err := New(cfg); err == nil {
t.Fatal("invalid logger created")
}
if _, err := os.Stat(p); !os.IsNotExist(err) {
t.Fatal("validation touched filesystem", err)
}
}
}
func TestStandardLoggerUsesBorrowedInstance(t *testing.T) {
var out bytes.Buffer
l, _ := New(Config{Writer: &out})
l.Standard(slog.LevelWarn).Print("driver diagnostic")
if !strings.Contains(out.String(), `"level":"WARN"`) || !strings.Contains(out.String(), "driver diagnostic") {
t.Fatal(out.String())
}
}

View File

@@ -73,7 +73,7 @@ func init() {
// call Close when the process is shutting down.
func New(cfg Config) (*Logger, error) {
cfg = normalizeConfig(cfg)
if err := cfg.Rotation.validate(cfg); err != nil {
if err := cfg.Validate(); err != nil {
return nil, err
}
level, err := parseLevel(cfg.Level)
@@ -225,7 +225,7 @@ func (l *Logger) ErrorStackContext(ctx context.Context, msg string, args ...any)
}
func ErrorStackContext(ctx context.Context, msg string, args ...any) {
defaultLogger.Load().ErrorStackContext(ctx, msg, args...)
FromContext(ctx).ErrorStackContext(ctx, msg, args...)
}
func (l *Logger) Debug(msg string, args ...any) { l.DebugContext(context.Background(), msg, args...) }
@@ -234,19 +234,19 @@ func (l *Logger) Warn(msg string, args ...any) { l.WarnContext(context.Backgrou
func (l *Logger) Error(msg string, args ...any) { l.ErrorContext(context.Background(), msg, args...) }
func DebugContext(ctx context.Context, msg string, args ...any) {
defaultLogger.Load().DebugContext(ctx, msg, args...)
FromContext(ctx).DebugContext(ctx, msg, args...)
}
func InfoContext(ctx context.Context, msg string, args ...any) {
defaultLogger.Load().InfoContext(ctx, msg, args...)
FromContext(ctx).InfoContext(ctx, msg, args...)
}
func WarnContext(ctx context.Context, msg string, args ...any) {
defaultLogger.Load().WarnContext(ctx, msg, args...)
FromContext(ctx).WarnContext(ctx, msg, args...)
}
func ErrorContext(ctx context.Context, msg string, args ...any) {
defaultLogger.Load().ErrorContext(ctx, msg, args...)
FromContext(ctx).ErrorContext(ctx, msg, args...)
}
func Debug(msg string, args ...any) { DebugContext(context.Background(), msg, args...) }