Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/lokstra/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import (
"slices"

"github.com/primadi/lokstra"
"github.com/primadi/lokstra/common/logger"
"github.com/primadi/lokstra/common/utils"
"github.com/primadi/lokstra/core/annotation"
"github.com/primadi/lokstra/core/deploy"
)

const version = "1.0.2"

func main() {
deploy.SetLogLevel(deploy.LogLevelInfo)
logger.SetLogLevel(logger.LogLevelInfo)

// for debugging purpose
if lokstra.DetectRunMode() != lokstra.RunModeProd {
Expand Down
6 changes: 3 additions & 3 deletions cmd/lokstra/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func migrationCmd() {
migrationFlags := flag.NewFlagSet("migration", flag.ExitOnError)
configFileFlag := migrationFlags.String("config", "config.yaml", "Lokstra config file")
migDirFlag := migrationFlags.String("dir", "migrations", "Migrations directory")
dbFlag := migrationFlags.String("db", "global-db", "Database pool name")
dbFlag := migrationFlags.String("db", "db_main", "Database pool name")
stepsFlag := migrationFlags.Int("steps", 1, "Number of migrations to rollback")

// Handle create command separately (doesn't need DB connection)
Expand Down Expand Up @@ -134,9 +134,9 @@ func executeMigration(subCmd, configFile, migrationDir, dbPoolName string, steps
return fmt.Errorf("database pool '%s' not found in registry", dbPoolName)
}

pool, ok := poolAny.(serviceapi.DbPoolWithSchema)
pool, ok := poolAny.(serviceapi.DbPool)
if !ok {
return fmt.Errorf("database pool '%s' does not implement DbPoolWithSchema interface", dbPoolName)
return fmt.Errorf("database pool '%s' does not implement DbPool interface", dbPoolName)
}

// Create migration runner
Expand Down
78 changes: 78 additions & 0 deletions common/logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package logger

import (
"os"
"strings"
)

// LogLevel represents the logging level
type LogLevel int

const (
LogLevelSilent LogLevel = iota
LogLevelError
LogLevelWarn
LogLevelInfo
LogLevelDebug
LogLevelFromEnvi
)

var (
activeBackend LoggerBackend = NewSlogBackend() // default slog
)

// LoggerBackend is the interface for logging backends.
// This allows replacing slog with zap, zerolog, etc in the future.
type LoggerBackend interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
Panic(msg string, args ...any)
SetLogLevel(level LogLevel)
GetLogLevel() LogLevel
}

// SetBackend replaces the active logger backend
func SetBackend(backend LoggerBackend) {
activeBackend = backend
}

// SetLogLevel sets the global log level
func SetLogLevel(level LogLevel) {
if level == LogLevelFromEnvi {
SetLogLevelFromEnv()
return
}
activeBackend.SetLogLevel(level)
}

// GetLogLevel returns the current log level
func GetLogLevel() LogLevel {
return activeBackend.GetLogLevel()
}

// SetLogLevelFromEnv sets log level from env var: LOKSTRA_LOG_LEVEL
func SetLogLevelFromEnv() {
envLevel := strings.ToLower(os.Getenv("LOKSTRA_LOG_LEVEL"))
switch envLevel {
case "silent":
SetLogLevel(LogLevelSilent)
case "error":
SetLogLevel(LogLevelError)
case "warn", "warning":
SetLogLevel(LogLevelWarn)
case "info":
SetLogLevel(LogLevelInfo)
case "debug":
SetLogLevel(LogLevelDebug)
}
}

// Public wrapper functions (unchanged API)
func LogDebug(format string, args ...any) { activeBackend.Debug(format, args...) }
func LogInfo(format string, args ...any) { activeBackend.Info(format, args...) }
func LogWarn(format string, args ...any) { activeBackend.Warn(format, args...) }
func LogWarning(format string, args ...any) { activeBackend.Warn(format, args...) }
func LogError(format string, args ...any) { activeBackend.Error(format, args...) }
func LogPanic(format string, args ...any) { activeBackend.Panic(format, args...) }
54 changes: 54 additions & 0 deletions common/logger/readable_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package logger

import (
"context"
"fmt"
"log/slog"
"os"
"strings"
"time"
)

type ReadableHandler struct {
Level LogLevel
Out *os.File
}

func (h *ReadableHandler) Enabled(_ context.Context, level slog.Level) bool {
switch h.Level {
case LogLevelSilent:
return false
case LogLevelError:
return level >= slog.LevelError
case LogLevelWarn:
return level >= slog.LevelWarn
case LogLevelInfo:
return level >= slog.LevelInfo
case LogLevelDebug:
return level >= slog.LevelDebug
}
return true
}

func (h *ReadableHandler) Handle(_ context.Context, r slog.Record) error {
timestamp := time.Now().Format("2006/01/02 15:04:05")

level := "[" + strings.ToUpper(r.Level.String()) + "]"

// message
line := fmt.Sprintf("%s %s %s", timestamp, level, r.Message)

// attributes → optional, currently ignored for simplicity
// You may add key=value printing here if needed.

_, err := fmt.Fprintln(h.Out, line)
return err
}

func (h *ReadableHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return h
}

func (h *ReadableHandler) WithGroup(name string) slog.Handler {
return h
}
73 changes: 73 additions & 0 deletions common/logger/slog_backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package logger

import (
"fmt"
"log/slog"
"os"
)

type SlogBackend struct {
logger *slog.Logger
level LogLevel
}

func NewSlogBackend() *SlogBackend {
b := &SlogBackend{level: LogLevelInfo}
b.rebuildLogger()
return b
}

func (b *SlogBackend) SetLogLevel(level LogLevel) {
// If user passes LogLevelFromEnvi directly → load env level
if level == LogLevelFromEnvi {
// Call global logic to resolve env variable
SetLogLevelFromEnv()
return
}

b.level = level
b.rebuildLogger()
}

func (b *SlogBackend) GetLogLevel() LogLevel {
return b.level
}

func (b *SlogBackend) Debug(format string, args ...any) {
if b.level >= LogLevelDebug {
b.logger.Debug(fmt.Sprintf(format, args...))
}
}

func (b *SlogBackend) Info(format string, args ...any) {
if b.level >= LogLevelInfo {
b.logger.Info(fmt.Sprintf(format, args...))
}
}

func (b *SlogBackend) Warn(format string, args ...any) {
if b.level >= LogLevelWarn {
b.logger.Warn(fmt.Sprintf(format, args...))
}
}

func (b *SlogBackend) Error(format string, args ...any) {
if b.level >= LogLevelError {
b.logger.Error(fmt.Sprintf(format, args...))
}
}

func (b *SlogBackend) Panic(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
b.logger.Error(msg)
panic(msg)
}

func (b *SlogBackend) rebuildLogger() {
handler := &ReadableHandler{
Level: b.level,
Out: os.Stdout,
}

b.logger = slog.New(handler)
}
11 changes: 6 additions & 5 deletions common/utils/maps.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package utils

import (
"fmt"
"maps"
"time"

"github.com/primadi/lokstra/common/logger"
)

func GetValueFromMap[T any](settings map[string]any, key string, defaultValue T) T {
Expand Down Expand Up @@ -31,7 +32,7 @@ func GetDurationFromMap(settings map[string]any, key string, defaultValue any) t
if d, err := time.ParseDuration(v); err == nil {
return d
} else {
fmt.Printf("Invalid duration string for key %q: %v\n", key, err)
logger.LogInfo("Invalid duration string for key %q: %v\n", key, err)
}
case float64: // jika YAML sudah diparse jadi angka (ms, s, dst)
return time.Duration(v) * time.Second
Expand All @@ -42,7 +43,7 @@ func GetDurationFromMap(settings map[string]any, key string, defaultValue any) t
case time.Duration:
return v
default:
fmt.Printf("Unsupported duration type for key %q: %T\n", key, val)
logger.LogInfo("Unsupported duration type for key %q: %T\n", key, val)
}
}

Expand All @@ -51,7 +52,7 @@ func GetDurationFromMap(settings map[string]any, key string, defaultValue any) t
if d, err := time.ParseDuration(v); err == nil {
return d
} else {
fmt.Printf("Invalid duration string for key %q: %v\n", key, err)
logger.LogInfo("Invalid duration string for key %q: %v\n", key, err)
}
case float64: // jika YAML sudah diparse jadi angka (ms, s, dst)
return time.Duration(v) * time.Second
Expand All @@ -62,7 +63,7 @@ func GetDurationFromMap(settings map[string]any, key string, defaultValue any) t
case time.Duration:
return v
default:
fmt.Printf("Unsupported default duration type for key %q: %T\n",
logger.LogInfo("Unsupported default duration type for key %q: %T\n",
key, defaultValue)
}

Expand Down
5 changes: 3 additions & 2 deletions core/annotation/complex_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"sync"
"time"

"github.com/primadi/lokstra/common/logger"
"github.com/primadi/lokstra/common/utils"
"github.com/primadi/lokstra/core/annotation/internal"
)
Expand Down Expand Up @@ -131,7 +132,7 @@ func ProcessComplexAnnotations(rootPath []string, maxWorkers int,
if len(packagesWithServices) > 0 && len(rootPath) > 0 {
if err := generateImportFile(rootPath[0], packagesWithServices); err != nil {
// Log warning but don't fail the whole operation
fmt.Printf("⚠️ Warning: Failed to generate import file: %v\n", err)
logger.LogWarn("⚠️ Warning: Failed to generate import file: %v\n", err)
}
}

Expand Down Expand Up @@ -793,6 +794,6 @@ func generateImportFile(startPath string, packages []string) error {
return fmt.Errorf("failed to write %s: %w", importFilePath, err)
}

fmt.Printf("✅ Generated: %s\n", importFilePath)
logger.LogInfo("✅ Generated: %s\n", importFilePath)
return nil
}
4 changes: 2 additions & 2 deletions core/annotation/examples/annotation_parsing/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package main

import (
"fmt"
"log"
"strings"

"github.com/primadi/lokstra/common/logger"
"github.com/primadi/lokstra/core/annotation"
)

Expand All @@ -16,7 +16,7 @@ func main() {

annotations, err := annotation.ParseFileAnnotations(filePath)
if err != nil {
log.Fatalf("Error parsing file: %v", err)
logger.LogPanic("Error parsing file: %v", err)
}

fmt.Printf("\nFound %d annotations:\n\n", len(annotations))
Expand Down
Loading