Skip to content

Commit 8318901

Browse files
authored
Update logger implementation
- Added WithLogConfig bootstrap option to create the logger after the metrics and be able to register the metrics log-level counter. - Added logutil utility function to return the log level name. - Moved logutil hook logic to a separate file.
1 parent 90cc8ea commit 8318901

File tree

13 files changed

+179
-43
lines changed

13 files changed

+179
-43
lines changed

examples/service/cmd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var (
2323
var exitFn = os.Exit //nolint:gochecknoglobals
2424

2525
func main() {
26+
// set default logger
2627
logattr := []logutil.Attr{
2728
slog.String("program", cli.AppName),
2829
slog.String("version", programVersion),

examples/service/internal/cli/cli.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"github.com/tecnickcom/gogen/pkg/bootstrap"
1414
"github.com/tecnickcom/gogen/pkg/config"
1515
"github.com/tecnickcom/gogen/pkg/httputil/jsendx"
16-
"github.com/tecnickcom/gogen/pkg/logsrv"
1716
"github.com/tecnickcom/gogen/pkg/logutil"
1817
)
1918

@@ -34,6 +33,7 @@ func New(version, release string, bootstrapFn bootstrapFunc) (*cobra.Command, er
3433
}
3534
)
3635

36+
// command-line arguments
3737
rootCmd.Flags().StringVarP(&argConfigDir, "configDir", "c", "", "Configuration directory to be added on top of the search list")
3838
rootCmd.Flags().StringVarP(&argLogFormat, "logFormat", "f", "", "Logging format: CONSOLE, JSON")
3939
rootCmd.Flags().StringVarP(&argLogLevel, "logLevel", "o", "", "Log level: EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG")
@@ -47,6 +47,8 @@ func New(version, release string, bootstrapFn bootstrapFunc) (*cobra.Command, er
4747
return fmt.Errorf("failed loading config: %w", err)
4848
}
4949

50+
// Configure logger
51+
5052
if argLogFormat != "" {
5153
cfg.Log.Format = argLogFormat
5254
}
@@ -65,7 +67,6 @@ func New(version, release string, bootstrapFn bootstrapFunc) (*cobra.Command, er
6567
return fmt.Errorf("log config error: %w", err)
6668
}
6769

68-
// Configure logger
6970
logattr := []logutil.Attr{
7071
slog.String("program", AppName),
7172
slog.String("version", version),
@@ -78,19 +79,15 @@ func New(version, release string, bootstrapFn bootstrapFunc) (*cobra.Command, er
7879
logutil.WithLevel(logLevel),
7980
logutil.WithCommonAttr(logattr...),
8081
)
81-
// if err != nil {
82-
// return fmt.Errorf("failed configuring logger: %w", err)
83-
// }
84-
85-
l := logsrv.NewLogger(logcfg)
8682

8783
appInfo := &jsendx.AppInfo{
8884
ProgramName: AppName,
8985
ProgramVersion: version,
9086
ProgramRelease: release,
9187
}
9288

93-
// Confifure metrics
89+
// Initialize metrics
90+
9491
mtr := metrics.New()
9592

9693
// Wait group used for graceful shutdown of all dependants (e.g.: servers).
@@ -102,7 +99,7 @@ func New(version, release string, bootstrapFn bootstrapFunc) (*cobra.Command, er
10299
// Boostrap application
103100
return bootstrapFn(
104101
bind(cfg, appInfo, mtr, wg, sc),
105-
bootstrap.WithLogger(l),
102+
bootstrap.WithLogConfig(logcfg),
106103
bootstrap.WithCreateMetricsClientFunc(mtr.CreateMetricsClientFunc),
107104
bootstrap.WithShutdownTimeout(time.Duration(cfg.ShutdownTimeout)*time.Second),
108105
bootstrap.WithShutdownWaitGroup(wg),

pkg/bootstrap/bootstrap.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"sync"
1919
"syscall"
2020
"time"
21+
22+
"github.com/tecnickcom/gogen/pkg/logutil"
2123
)
2224

2325
// Bootstrap is the function in charge of configuring the core components
@@ -43,6 +45,13 @@ func Bootstrap(bindFn BindFunc, opts ...Option) error {
4345
return fmt.Errorf("error creating application metric: %w", err)
4446
}
4547

48+
if cfg.logConfig != nil {
49+
// metric hook to count logs by level
50+
cfg.logConfig.HookFn = func(level logutil.LogLevel, _ string) {
51+
m.IncLogLevelCounter(logutil.LevelName(level))
52+
}
53+
}
54+
4655
l := cfg.createLoggerFunc()
4756

4857
l.Debug("binding application components")

pkg/bootstrap/bootstrap_test.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010
"time"
1111

12+
"github.com/tecnickcom/gogen/pkg/logutil"
1213
"github.com/tecnickcom/gogen/pkg/metrics"
1314
"github.com/tecnickcom/gogen/pkg/metrics/prometheus"
1415
)
@@ -69,6 +70,7 @@ func TestBootstrap(t *testing.T) {
6970
{
7071
name: "should succeed and exit with SIGTERM",
7172
opts: []Option{
73+
WithLogConfig(logutil.DefaultConfig()),
7274
WithShutdownTimeout(1 * time.Millisecond),
7375
WithShutdownWaitGroup(shutdownWG),
7476
WithShutdownSignalChan(shutdownSG),
@@ -106,13 +108,6 @@ func TestBootstrap(t *testing.T) {
106108
}
107109
opts = append(opts, tt.opts...)
108110

109-
if tt.createLoggerFunc != nil {
110-
opts = append(opts, WithCreateLoggerFunc(tt.createLoggerFunc))
111-
} else {
112-
fn := slog.Default
113-
opts = append(opts, WithCreateLoggerFunc(fn))
114-
}
115-
116111
if tt.createMetricsClientFunc != nil {
117112
opts = append(opts, WithCreateMetricsClientFunc(tt.createMetricsClientFunc))
118113
} else {

pkg/bootstrap/config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"sync"
88
"time"
99

10+
"github.com/tecnickcom/gogen/pkg/logsrv"
11+
"github.com/tecnickcom/gogen/pkg/logutil"
1012
"github.com/tecnickcom/gogen/pkg/metrics"
1113
)
1214

@@ -24,6 +26,9 @@ type config struct {
2426
// context is the application context.
2527
context context.Context //nolint:containedctx
2628

29+
// logConfig stores the logger configuration
30+
logConfig *logutil.Config
31+
2732
// createLoggerFunc is the function used to create a new logger.
2833
createLoggerFunc CreateLoggerFunc
2934

@@ -44,6 +49,7 @@ type config struct {
4449
func defaultConfig() *config {
4550
return &config{
4651
context: context.Background(),
52+
logConfig: nil,
4753
createLoggerFunc: defaultCreateLogger,
4854
createMetricsClientFunc: defaultCreateMetricsClientFunc,
4955
shutdownTimeout: 30 * time.Second,
@@ -62,6 +68,11 @@ func defaultCreateMetricsClientFunc() (metrics.Client, error) {
6268
return &metrics.Default{}, nil
6369
}
6470

71+
// newLogger returs a nes slog logger with the logConfig settings.
72+
func (c *config) newLogger() *slog.Logger {
73+
return logsrv.NewLogger(c.logConfig)
74+
}
75+
6576
// validate the configuration.
6677
func (c *config) validate() error {
6778
if c.context == nil {

pkg/bootstrap/config_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func Test_defaultConfig(t *testing.T) {
1212
cfg := defaultConfig()
1313
require.NotNil(t, cfg)
1414
require.NotNil(t, cfg.context)
15+
require.Nil(t, cfg.logConfig)
1516
require.NotNil(t, cfg.createLoggerFunc)
1617
require.NotNil(t, cfg.createMetricsClientFunc)
1718
}

pkg/bootstrap/options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"log/slog"
66
"sync"
77
"time"
8+
9+
"github.com/tecnickcom/gogen/pkg/logutil"
810
)
911

1012
// Option is a type alias for a function that configures the application logger.
@@ -17,7 +19,17 @@ func WithContext(ctx context.Context) Option {
1719
}
1820
}
1921

22+
// WithLogConfig sets the log configuration options.
23+
// This should be used in alternative to WithLogger and WithCreateLoggerFunc.
24+
func WithLogConfig(c *logutil.Config) Option {
25+
return func(cfg *config) {
26+
cfg.logConfig = c
27+
cfg.createLoggerFunc = cfg.newLogger
28+
}
29+
}
30+
2031
// WithLogger overrides the default application logger.
32+
// This should be used in alternative to WithLogConfig and WithCreateLoggerFunc.
2133
func WithLogger(l *slog.Logger) Option {
2234
return func(cfg *config) {
2335
cfg.createLoggerFunc = func() *slog.Logger {
@@ -27,6 +39,7 @@ func WithLogger(l *slog.Logger) Option {
2739
}
2840

2941
// WithCreateLoggerFunc overrides the root logger creation function.
42+
// This should be used in alternative to WithLogConfig and WithLogger.
3043
func WithCreateLoggerFunc(fn CreateLoggerFunc) Option {
3144
return func(cfg *config) {
3245
cfg.createLoggerFunc = fn

pkg/bootstrap/options_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"github.com/stretchr/testify/require"
12+
"github.com/tecnickcom/gogen/pkg/logutil"
1213
"github.com/tecnickcom/gogen/pkg/metrics"
1314
)
1415

@@ -24,6 +25,18 @@ func TestWithContext(t *testing.T) {
2425
require.Equal(t, v, cfg.context)
2526
}
2627

28+
func TestWithLogConfig(t *testing.T) {
29+
t.Parallel()
30+
31+
cfg := &config{}
32+
33+
lc := logutil.DefaultConfig()
34+
35+
WithLogConfig(lc)(cfg)
36+
require.Equal(t, lc, cfg.logConfig)
37+
require.NotNil(t, cfg.createLoggerFunc)
38+
}
39+
2740
func TestWithLogger(t *testing.T) {
2841
t.Parallel()
2942

pkg/logutil/config.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package logutil
22

33
import (
4-
"context"
54
"io"
65
"log/slog"
76
"os"
@@ -10,12 +9,6 @@ import (
109
// Attr is a type alias for slog.Attr.
1110
type Attr = slog.Attr
1211

13-
// HookFunc is an adaptor to allow the use of an ordinary function as a Hook.
14-
// The argument is the log level.
15-
16-
// HookFunc is used to intercept the log message before passing it to the underlying handler.
17-
type HookFunc func(level LogLevel, message string)
18-
1912
// Config holds common logger parameters.
2013
type Config struct {
2114
Out io.Writer
@@ -83,26 +76,11 @@ func (c *Config) SlogHandler() slog.Handler {
8376
h = slog.NewJSONHandler(os.Stderr, nil)
8477
}
8578

79+
h = h.WithAttrs(c.CommonAttr)
80+
8681
if c.HookFn != nil {
87-
h = &SlogHookHandler{
88-
Handler: h,
89-
hookFn: c.HookFn,
90-
}
82+
h = NewSlogHookHandler(h, c.HookFn)
9183
}
9284

93-
return h.WithAttrs(c.CommonAttr)
94-
}
95-
96-
// SlogHookHandler is a slog.Handler that wraps another handler to add custom logic.
97-
type SlogHookHandler struct {
98-
slog.Handler
99-
100-
hookFn HookFunc
101-
}
102-
103-
// Handle intercepts the log record, modifies the message, and then passes
104-
// it to the underlying handler.
105-
func (h SlogHookHandler) Handle(ctx context.Context, record slog.Record) error {
106-
h.hookFn(record.Level, record.Message)
107-
return h.Handler.Handle(ctx, record) //nolint:wrapcheck
85+
return h
10886
}

pkg/logutil/hook.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package logutil
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
)
7+
8+
// HookFunc is used to intercept the log message before passing it to the underlying handler.
9+
type HookFunc func(level LogLevel, message string)
10+
11+
// SlogHookHandler is a slog.Handler that wraps another handler to add custom logic.
12+
type SlogHookHandler struct {
13+
slog.Handler
14+
15+
hookFn HookFunc
16+
}
17+
18+
// NewSlogHookHandler adds a hook function to the slog Handler.
19+
func NewSlogHookHandler(h slog.Handler, f HookFunc) *SlogHookHandler {
20+
return &SlogHookHandler{
21+
Handler: h,
22+
hookFn: f,
23+
}
24+
}
25+
26+
// Handle intercepts the log record, modifies the message, and then passes
27+
// it to the underlying handler.
28+
func (h SlogHookHandler) Handle(ctx context.Context, record slog.Record) error {
29+
h.hookFn(record.Level, record.Message)
30+
return h.Handler.Handle(ctx, record) //nolint:wrapcheck
31+
}

0 commit comments

Comments
 (0)