Skip to content

Commit

Permalink
feat(logging): integrate slog with fx for structured logging
Browse files Browse the repository at this point in the history
- Replace bliss.GetFxLogger with logfx.GetFxLogger
- Add logfx module for structured logging
- Implement RegisterLogger and GetFxLogger functions
- Update LoadConfig and RegisterConfig for unified config management
- Remove RegisterHttpConfig, integrate HttpConfig provision into LoadConfig
- Enhance configfx loader to support boolean type conversion
- Replace logger.Info references with slogger
  • Loading branch information
eser committed Aug 18, 2024
1 parent 07cb006 commit 3ec9ec9
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 17 deletions.
3 changes: 2 additions & 1 deletion pkg/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package app

import (
"github.com/eser/go-service/pkg/bliss"
"github.com/eser/go-service/pkg/bliss/logfx"
"go.uber.org/fx"
)

func Run() {
app := fx.New(
// fx.WithLogger(bliss.GetFxLogger),
fx.WithLogger(logfx.GetFxLogger),
bliss.Module,
Module,
)
Expand Down
18 changes: 7 additions & 11 deletions pkg/app/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/http"

"github.com/eser/go-service/pkg/bliss"
"github.com/eser/go-service/pkg/bliss/configfx"
"github.com/eser/go-service/pkg/bliss/httpfx"
"github.com/eser/go-service/pkg/bliss/httpfx/middlewares"
Expand All @@ -18,22 +19,21 @@ var Module = fx.Module( //nolint:gochecknoglobals
RegisterRoutes,
),
fx.Provide(
LoadConfig,
RegisterHttpConfig,
bliss.LoadConfig[AppConfig](LoadConfig),
),
healthcheck.Module,
openapi.Module,
)

func LoadConfig(conf configfx.ConfigLoader) (*AppConfig, error) {
func LoadConfig(cl configfx.ConfigLoader) (*AppConfig, error) {
appConfig := &AppConfig{} //nolint:exhaustruct

err := conf.Load(
err := cl.Load(
appConfig,

conf.FromJsonFile("config.json"),
conf.FromEnvFile(".env"),
conf.FromSystemEnv(),
cl.FromJsonFile("config.json"),
cl.FromEnvFile(".env"),
cl.FromSystemEnv(),
)
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
Expand All @@ -42,10 +42,6 @@ func LoadConfig(conf configfx.ConfigLoader) (*AppConfig, error) {
return appConfig, nil
}

func RegisterHttpConfig(appConfig *AppConfig) *httpfx.Config {
return &appConfig.Http
}

func RegisterRoutes(routes httpfx.Router, appConfig *AppConfig) {
routes.Use(middlewares.ErrorHandlerMiddleware())
routes.Use(middlewares.ResponseTimeMiddleware())
Expand Down
50 changes: 50 additions & 0 deletions pkg/bliss/config.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package bliss

import (
"reflect"

"github.com/eser/go-service/pkg/bliss/configfx"
"github.com/eser/go-service/pkg/bliss/httpfx"
"github.com/eser/go-service/pkg/bliss/logfx"
"go.uber.org/fx"
)

type BaseConfig struct {
Env string `conf:"ENV" default:"development"`

Log logfx.Config `conf:"LOG"`
Http httpfx.Config `conf:"HTTP"`

// AppName string `conf:"APP_NAME" default:"go-service"`
Expand All @@ -17,3 +23,47 @@ type BaseConfig struct {
// CorsStrictHeaders bool `conf:"CORS_STRICT_HEADERS"`
// DataConnstr string `conf:"DATA_CONNSTR"`
}

type RegisterConfigResult[T any] struct {
fx.Out

Config *T
BaseConfig *BaseConfig
LogConfig *logfx.Config
HttpConfig *httpfx.Config
}

func LoadConfig[T any](
fnc func(cl configfx.ConfigLoader) (*T, error),
) func(configfx.ConfigLoader) (RegisterConfigResult[T], error) {
return func(cl configfx.ConfigLoader) (RegisterConfigResult[T], error) {
config, err := fnc(cl)
if err != nil {
return RegisterConfigResult[T]{}, err
}

return RegisterConfig(config), nil
}
}

func RegisterConfig[T any](config *T) RegisterConfigResult[T] {
rConfig := reflect.ValueOf(config).Elem()
rBaseConfig := rConfig.FieldByName("BaseConfig")

if !rBaseConfig.IsValid() {
panic("config must have BaseConfig field")
}

baseConfig, ok := rBaseConfig.Interface().(BaseConfig)

if !ok {
panic("BaseConfig field must be of type BaseConfig")
}

return RegisterConfigResult[T]{ //nolint:exhaustruct
Config: config,
BaseConfig: &baseConfig,
LogConfig: &baseConfig.Log,
HttpConfig: &baseConfig.Http,
}
}
4 changes: 3 additions & 1 deletion pkg/bliss/configfx/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package configfx
import (
"errors"
"reflect"
"strconv"
"time"
)

Expand Down Expand Up @@ -214,7 +215,8 @@ func reflectSetField(field reflect.Value, fieldType reflect.Type, value string)
case reflect.TypeFor[float64]():
finalValue = reflect.ValueOf(value)
case reflect.TypeFor[bool]():
finalValue = reflect.ValueOf(value)
boolValue, _ := strconv.ParseBool(value)
finalValue = reflect.ValueOf(boolValue)
case reflect.TypeFor[time.Duration]():
durationValue, _ := time.ParseDuration(value)
finalValue = reflect.ValueOf(durationValue)
Expand Down
9 changes: 5 additions & 4 deletions pkg/bliss/httpfx/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"net"
"net/http"
"os"
Expand Down Expand Up @@ -58,10 +59,10 @@ func New(config *Config) (Result, error) {
}

// , conf *config.Config, logger *log.Logger.
func RegisterHooks(lc fx.Lifecycle, hs *HttpService) {
func RegisterHooks(lc fx.Lifecycle, hs *HttpService, logger *slog.Logger) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
// logger.Info("HttpService is starting...", log.String("addr", hs.Config.Addr))
logger.Info("HttpService is starting...", slog.String("addr", hs.Config.Addr))

// serverErr := make(chan error, 1)

Expand Down Expand Up @@ -92,7 +93,7 @@ func RegisterHooks(lc fx.Lifecycle, hs *HttpService) {
return nil
},
OnStop: func(ctx context.Context) error {
// logger.Info("HttpService is stopping...")
logger.Info("HttpService is stopping...")

shutdownCtx, cancel := context.WithTimeout(ctx, hs.Config.GracefulShutdownTimeout)
defer cancel()
Expand All @@ -103,7 +104,7 @@ func RegisterHooks(lc fx.Lifecycle, hs *HttpService) {
}

<-shutdownCtx.Done()
// logger.Info("HttpService has stopped...")
logger.Info("HttpService has stopped...")

return nil
},
Expand Down
6 changes: 6 additions & 0 deletions pkg/bliss/logfx/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package logfx

type Config struct {
Level string `conf:"LEVEL" default:"INFO"`
AddSource bool `conf:"ADD_SOURCE" default:"false"`
}
147 changes: 147 additions & 0 deletions pkg/bliss/logfx/fx-adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package logfx

import (
"log/slog"

"go.uber.org/fx/fxevent"
)

type (
FxLogger struct {
*slog.Logger
}
)

func GetFxLogger(logger *slog.Logger) fxevent.Logger { //nolint:ireturn
return FxLogger{logger}
}

func (l FxLogger) LogEvent(event fxevent.Event) { //nolint:cyclop
switch e := event.(type) { //nolint:varnamelen
case *fxevent.OnStartExecuting:
l.logOnStartExecuting(e)
case *fxevent.OnStartExecuted:
l.logOnStartExecuted(e)
case *fxevent.OnStopExecuting:
l.logOnStopExecuting(e)
case *fxevent.OnStopExecuted:
l.logOnStopExecuted(e)
case *fxevent.Supplied:
l.logSupplied(e)
case *fxevent.Provided:
l.logProvided(e)
case *fxevent.Decorated:
l.logDecorated(e)
case *fxevent.Invoking:
l.logInvoking(e)
case *fxevent.Started:
l.logStarted(e)
case *fxevent.LoggerInitialized:
l.logLoggerInitialized(e)
}
}

func (l *FxLogger) logOnStartExecuting(e *fxevent.OnStartExecuting) {
l.Logger.Debug(
"OnStart hook executing: ",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
)
}

func (l *FxLogger) logOnStartExecuted(e *fxevent.OnStartExecuted) { //nolint:varnamelen
if e.Err != nil {
l.Logger.Debug(
"OnStart hook failed: ",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slog.Any("error", e.Err),
)

return
}

l.Logger.Debug(
"OnStart hook executed: ",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slog.String("runtime", e.Runtime.String()),
)
}

func (l *FxLogger) logOnStopExecuting(e *fxevent.OnStopExecuting) {
l.Logger.Debug(
"OnStop hook executing: ",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
)
}

func (l *FxLogger) logOnStopExecuted(e *fxevent.OnStopExecuted) { //nolint:varnamelen
if e.Err != nil {
l.Logger.Debug(
"OnStop hook failed: ",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slog.Any("error", e.Err),
)

return
}

l.Logger.Debug(
"OnStop hook executed: ",
slog.String("callee", e.FunctionName),
slog.String("caller", e.CallerName),
slog.String("runtime", e.Runtime.String()),
)
}

func (l *FxLogger) logSupplied(e *fxevent.Supplied) {
l.Logger.Debug(
"supplied: ",
slog.String("type", e.TypeName),
slog.Any("error", e.Err),
)
}

func (l *FxLogger) logProvided(e *fxevent.Provided) {
for _, rtype := range e.OutputTypeNames {
l.Logger.Debug(
"provided: ",
slog.String("constructor", e.ConstructorName),
slog.String("type", rtype),
)
}
}

func (l *FxLogger) logDecorated(e *fxevent.Decorated) {
for _, rtype := range e.OutputTypeNames {
l.Logger.Debug("decorated: ",
slog.String("decorator", e.DecoratorName),
slog.String("type", rtype),
)
}
}

func (l *FxLogger) logInvoking(e *fxevent.Invoking) {
l.Logger.Debug(
"invoking: ",
slog.String("function", e.FunctionName),
)
}

func (l *FxLogger) logStarted(e *fxevent.Started) {
if e.Err == nil {
l.Logger.Debug("started")
}
}

func (l *FxLogger) logLoggerInitialized(e *fxevent.LoggerInitialized) {
if e.Err == nil {
l.Logger.Debug(
"initialized: custom fxevent.Logger",
slog.String("constructor", e.ConstructorName),
)
}
}
37 changes: 37 additions & 0 deletions pkg/bliss/logfx/mod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package logfx

import (
"log/slog"
"os"

"go.uber.org/fx"
)

//nolint:gochecknoglobals
var Module = fx.Module(
"log",
fx.Provide(
RegisterLogger,
),
)

func RegisterLogger(config *Config) (*slog.Logger, error) {
var level slog.Level

err := level.UnmarshalText([]byte(config.Level))
if err != nil {
return nil, err
}

handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: level,
ReplaceAttr: replaceAttr,
AddSource: config.AddSource,
})

logger := slog.New(handler)

slog.SetDefault(logger)

return logger, nil
}
Loading

0 comments on commit 3ec9ec9

Please sign in to comment.