refactor: better project structure (#35)
This project is starting to grow, and I've seen flaws in the initial project structure design I think I'll restructure the entire project directory for better scalability and also cleaner
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Level int
|
||||
|
||||
const (
|
||||
LevelDebug Level = iota
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
logFile *os.File
|
||||
currentLvl = LevelInfo
|
||||
)
|
||||
|
||||
// Init initializes the logger writing to logFilePath
|
||||
func Init(logFilePath string, debug bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if logFile != nil {
|
||||
_ = logFile.Close()
|
||||
logFile = nil
|
||||
}
|
||||
|
||||
// get log level from env
|
||||
lvlEnv := strings.ToLower(os.Getenv("IRIS_LOG_LEVEL"))
|
||||
switch lvlEnv {
|
||||
case "debug":
|
||||
currentLvl = LevelDebug
|
||||
case "info":
|
||||
currentLvl = LevelInfo
|
||||
case "warn":
|
||||
currentLvl = LevelWarn
|
||||
case "error":
|
||||
currentLvl = LevelError
|
||||
default:
|
||||
if debug {
|
||||
currentLvl = LevelDebug
|
||||
} else {
|
||||
currentLvl = LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
// rotate if log file is larger than 5MB
|
||||
if info, err := os.Stat(logFilePath); err == nil && info.Size() > 5*1024*1024 {
|
||||
_ = os.Rename(logFilePath, logFilePath+".old")
|
||||
}
|
||||
|
||||
_ = os.MkdirAll(filepath.Dir(logFilePath), 0755)
|
||||
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err == nil {
|
||||
logFile = f
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the underlying log file
|
||||
func Close() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if logFile != nil {
|
||||
_ = logFile.Close()
|
||||
logFile = nil
|
||||
}
|
||||
}
|
||||
|
||||
func logmsg(lvl Level, lvlStr string, format string, a ...any) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if logFile == nil || lvl < currentLvl {
|
||||
return
|
||||
}
|
||||
|
||||
// get caller information to append file:line
|
||||
caller := "unknown:0"
|
||||
if _, file, line, ok := runtime.Caller(2); ok {
|
||||
caller = fmt.Sprintf("%s:%d", filepath.Base(file), line)
|
||||
}
|
||||
|
||||
tStr := time.Now().Format("2006-01-02T15:04:05.000Z07:00")
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
_, _ = fmt.Fprintf(logFile, "%s [%s] [%s] %s\n", tStr, lvlStr, caller, msg)
|
||||
}
|
||||
|
||||
// Debugf writes a debug log message
|
||||
func Debugf(format string, a ...any) {
|
||||
logmsg(LevelDebug, "DEBUG", format, a...)
|
||||
}
|
||||
|
||||
// Infof writes an info log message
|
||||
func Infof(format string, a ...any) {
|
||||
logmsg(LevelInfo, "INFO", format, a...)
|
||||
}
|
||||
|
||||
// Warnf writes a warning log message
|
||||
func Warnf(format string, a ...any) {
|
||||
logmsg(LevelWarn, "WARN", format, a...)
|
||||
}
|
||||
|
||||
// Errorf writes an error log message
|
||||
func Errorf(format string, a ...any) {
|
||||
logmsg(LevelError, "ERROR", format, a...)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "iris-log-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
logFilePath := filepath.Join(tempDir, "test.log")
|
||||
|
||||
// test 1: default init sets level to info
|
||||
Init(logFilePath, false)
|
||||
Debugf("this debug msg should not be logged")
|
||||
Infof("this info msg should be logged")
|
||||
Close()
|
||||
|
||||
data, err := os.ReadFile(logFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read log file: %v", err)
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
if strings.Contains(content, "this debug msg should not be logged") {
|
||||
t.Errorf("expected debug log to be skipped, got: %s", content)
|
||||
}
|
||||
if !strings.Contains(content, "this info msg should be logged") {
|
||||
t.Errorf("expected info log to be recorded, got: %s", content)
|
||||
}
|
||||
if !strings.Contains(content, "[INFO]") {
|
||||
t.Errorf("expected log to contain INFO tag, got: %s", content)
|
||||
}
|
||||
if !strings.Contains(content, "logger_test.go:") {
|
||||
t.Errorf("expected log to contain caller trace info, got: %s", content)
|
||||
}
|
||||
|
||||
// test 2: override init with debug = true
|
||||
_ = os.Remove(logFilePath)
|
||||
Init(logFilePath, true)
|
||||
Debugf("this debug msg should now be logged")
|
||||
Close()
|
||||
|
||||
data, err = os.ReadFile(logFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read log file: %v", err)
|
||||
}
|
||||
|
||||
content = string(data)
|
||||
if !strings.Contains(content, "this debug msg should now be logged") {
|
||||
t.Errorf("expected debug log to be recorded, got: %s", content)
|
||||
}
|
||||
if !strings.Contains(content, "[DEBUG]") {
|
||||
t.Errorf("expected log to contain DEBUG tag, got: %s", content)
|
||||
}
|
||||
|
||||
// test 3: test log rotation to .old
|
||||
// create a large file
|
||||
largeData := make([]byte, 6*1024*1024)
|
||||
err = os.WriteFile(logFilePath, largeData, 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write large file: %v", err)
|
||||
}
|
||||
|
||||
Init(logFilePath, false)
|
||||
Infof("new log after rotation")
|
||||
Close()
|
||||
|
||||
// check if old file exists and is rotated
|
||||
oldPath := logFilePath + ".old"
|
||||
if _, err = os.Stat(oldPath); os.IsNotExist(err) {
|
||||
t.Errorf("expected rotated log file to exist at %s", oldPath)
|
||||
}
|
||||
|
||||
data, err = os.ReadFile(logFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read log file: %v", err)
|
||||
}
|
||||
content = string(data)
|
||||
if !strings.Contains(content, "new log after rotation") {
|
||||
t.Errorf("expected new log to exist in the fresh log file, got: %s", content)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user