Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Logger wraps zap.SugaredLogger for structured logging.
|
||||
// Corresponds to Chatwoot's Rails Logger but with structured output.
|
||||
var globalLogger *zap.SugaredLogger
|
||||
|
||||
// Config for logger initialization
|
||||
type Config struct {
|
||||
Level string // debug, info, warn, error
|
||||
Format string // json, console
|
||||
Output string // stdout, stderr, or file path
|
||||
ErrorOutput string // stderr or file path
|
||||
}
|
||||
|
||||
// Init initializes the global logger based on config.
|
||||
func Init(cfg Config) error {
|
||||
level, err := parseLevel(cfg.Level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.TimeKey = "timestamp"
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
|
||||
var encoder zapcore.Encoder
|
||||
if cfg.Format == "console" {
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
||||
} else {
|
||||
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
outputWriter, err := getOutput(cfg.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
errorWriter, err := getOutput(cfg.ErrorOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
core := zapcore.NewCore(
|
||||
encoder,
|
||||
zapcore.NewMultiWriteSyncer(zapcore.AddSync(outputWriter)),
|
||||
level,
|
||||
)
|
||||
|
||||
// Also send error-level logs to error output
|
||||
errorCore := zapcore.NewCore(
|
||||
encoder,
|
||||
zapcore.AddSync(errorWriter),
|
||||
zapcore.ErrorLevel,
|
||||
)
|
||||
|
||||
combinedCore := zapcore.NewTee(core, errorCore)
|
||||
|
||||
zapLogger := zap.New(combinedCore, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
globalLogger = zapLogger.Sugar()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// L returns the global sugared logger
|
||||
func L() *zap.SugaredLogger {
|
||||
if globalLogger == nil {
|
||||
// Fallback: if Init wasn't called, use a default logger
|
||||
zapLogger, _ := zap.NewProduction()
|
||||
globalLogger = zapLogger.Sugar()
|
||||
}
|
||||
return globalLogger
|
||||
}
|
||||
|
||||
// Sync flushes any buffered log entries. Should be called before program exit.
|
||||
func Sync() {
|
||||
if globalLogger != nil {
|
||||
_ = globalLogger.Sync()
|
||||
}
|
||||
}
|
||||
|
||||
func parseLevel(levelStr string) (zapcore.Level, error) {
|
||||
switch levelStr {
|
||||
case "debug":
|
||||
return zapcore.DebugLevel, nil
|
||||
case "info":
|
||||
return zapcore.InfoLevel, nil
|
||||
case "warn":
|
||||
return zapcore.WarnLevel, nil
|
||||
case "error":
|
||||
return zapcore.ErrorLevel, nil
|
||||
default:
|
||||
return zapcore.InfoLevel, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getOutput(output string) (*os.File, error) {
|
||||
switch output {
|
||||
case "stdout":
|
||||
return os.Stdout, nil
|
||||
case "stderr":
|
||||
return os.Stderr, nil
|
||||
default:
|
||||
return os.OpenFile(output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user