Skip to content

Commit

Permalink
Logging refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
mantzas committed Oct 20, 2024
1 parent 3d3926e commit a0f6478
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 73 deletions.
2 changes: 1 addition & 1 deletion observability/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestSetup(t *testing.T) {
t.Setenv("OTEL_EXPORTER_OTLP_INSECURE", "true")
ctx := context.Background()

got, err := Setup(ctx, "test", "1.2.3")
got, err := Setup(ctx, Config{})
require.NoError(t, err)

require.NoError(t, got.Shutdown(ctx))
Expand Down
34 changes: 34 additions & 0 deletions observability/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,44 @@ package log
import (
"context"
"log/slog"
"os"
)

// Config represents the configuration for setting up the logger.
type Config struct {
Attributes []slog.Attr
IsJSON bool
Level string
}

type ctxKey struct{}

func Setup(cfg *Config) {
ho := &slog.HandlerOptions{
AddSource: true,
Level: level(cfg.Level),
}

var hnd slog.Handler

if cfg.IsJSON {
hnd = slog.NewJSONHandler(os.Stderr, ho)
} else {
hnd = slog.NewTextHandler(os.Stderr, ho)
}

Check warning on line 31 in observability/log/log.go

View check run for this annotation

Codecov / codecov/patch

observability/log/log.go#L19-L31

Added lines #L19 - L31 were not covered by tests

slog.New(hnd.WithAttrs(cfg.Attributes))

Check warning on line 33 in observability/log/log.go

View check run for this annotation

Codecov / codecov/patch

observability/log/log.go#L33

Added line #L33 was not covered by tests
}

func level(lvl string) slog.Level {
lv := slog.LevelVar{}
if err := lv.UnmarshalText([]byte(lvl)); err != nil {
return slog.LevelInfo
}

Check warning on line 40 in observability/log/log.go

View check run for this annotation

Codecov / codecov/patch

observability/log/log.go#L36-L40

Added lines #L36 - L40 were not covered by tests

return lv.Level()

Check warning on line 42 in observability/log/log.go

View check run for this annotation

Codecov / codecov/patch

observability/log/log.go#L42

Added line #L42 was not covered by tests
}

// FromContext returns the logger, if it exists in the context, or nil.
func FromContext(ctx context.Context) *slog.Logger {
if l, ok := ctx.Value(ctxKey{}).(*slog.Logger); ok {
Expand Down
16 changes: 13 additions & 3 deletions observability/observability.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log/slog"

"github.com/beatlabs/patron/observability/log"

Check failure on line 9 in observability/observability.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

ST1019: package "github.com/beatlabs/patron/observability/log" is being imported more than once (stylecheck)
patronlog "github.com/beatlabs/patron/observability/log"

Check failure on line 10 in observability/observability.go

View workflow job for this annotation

GitHub Actions / Lint and fmt check

ST1019(related information): other import of "github.com/beatlabs/patron/observability/log" (stylecheck)
patronmetric "github.com/beatlabs/patron/observability/metric"
patrontrace "github.com/beatlabs/patron/observability/trace"
"go.opentelemetry.io/otel"
Expand Down Expand Up @@ -49,11 +50,20 @@ func StatusAttribute(err error) attribute.KeyValue {
return SucceededAttribute
}

// Config represents the configuration for setting up traces, metrics and logs.
type Config struct {
Name string
Version string
LogConfig log.Config
}

// Setup initializes OpenTelemetry's traces and metrics.
// It creates a resource with the given name and version, sets up the metric and trace providers,
// and returns a Provider containing the initialized providers.
func Setup(ctx context.Context, name, version string) (*Provider, error) {
res, err := createResource(name, version)
func Setup(ctx context.Context, cfg Config) (*Provider, error) {
patronlog.Setup(&cfg.LogConfig)

res, err := createResource(cfg.Name, cfg.Version)
if err != nil {
return nil, err
}
Expand All @@ -64,7 +74,7 @@ func Setup(ctx context.Context, name, version string) (*Provider, error) {
if err != nil {
return nil, err
}
traceProvider, err := patrontrace.SetupGRPC(ctx, name, res)
traceProvider, err := patrontrace.SetupGRPC(ctx, cfg.Name, res)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func WithLogFields(attrs ...slog.Attr) OptionFunc {
continue
}

svc.logConfig.attrs = append(svc.logConfig.attrs, attr)
svc.observabilityCfg.LogConfig.Attributes = append(svc.observabilityCfg.LogConfig.Attributes, attr)
}

return nil
Expand All @@ -44,7 +44,7 @@ func WithLogFields(attrs ...slog.Attr) OptionFunc {
// WithJSONLogger to use Go's slog package.
func WithJSONLogger() OptionFunc {
return func(svc *Service) error {
svc.logConfig.json = true
svc.observabilityCfg.LogConfig.IsJSON = true
return nil
}
}
25 changes: 16 additions & 9 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,46 @@ import (
"log/slog"
"testing"

"github.com/beatlabs/patron/observability"
"github.com/beatlabs/patron/observability/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLogFields(t *testing.T) {
defaultAttrs := defaultLogAttrs("test", "1.0")
attrs := []slog.Attr{slog.String("key", "value")}
attrs1 := defaultLogAttrs("name1", "version1")
attrs1 := []slog.Attr{slog.String("name1", "version1")}

expectedSuccess := observability.Config{LogConfig: log.Config{
Attributes: attrs,
}}
expectedNoOverwrite := observability.Config{LogConfig: log.Config{
Attributes: []slog.Attr{slog.String("name1", "version2")},
}}

type args struct {
fields []slog.Attr
}
tests := map[string]struct {
args args
want logConfig
want observability.Config
expectedErr string
}{
"empty attributes": {args: args{fields: nil}, expectedErr: "attributes are empty"},
"success": {args: args{fields: attrs}, want: logConfig{attrs: append(defaultAttrs, attrs...)}},
"no overwrite": {args: args{fields: attrs1}, want: logConfig{attrs: defaultAttrs}},
"success": {args: args{fields: attrs}, want: expectedSuccess},
"no overwrite": {args: args{fields: attrs1}, want: expectedNoOverwrite},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
svc := &Service{
logConfig: logConfig{
attrs: defaultAttrs,
},
observabilityCfg: observability.Config{},
}

err := WithLogFields(tt.args.fields...)(svc)

if tt.expectedErr == "" {
require.NoError(t, err)
assert.Equal(t, tt.want, svc.logConfig)
assert.Equal(t, tt.want, svc.observabilityCfg)
} else {
require.EqualError(t, err, tt.expectedErr)
}
Expand Down
57 changes: 18 additions & 39 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type Service struct {
version string
termSig chan os.Signal
sighupHandler func()
logConfig logConfig
observabilityCfg observability.Config
observabilityProvider *observability.Provider
}

Expand All @@ -46,7 +46,10 @@ func New(name, version string, options ...OptionFunc) (*Service, error) {

var err error
ctx := context.Background()
observabilityProvider, err := observability.Setup(ctx, name, version)

cfg := observabilityConfig(name, version)

observabilityProvider, err := observability.Setup(ctx, cfg)
if err != nil {
return nil, err
}
Expand All @@ -58,10 +61,7 @@ func New(name, version string, options ...OptionFunc) (*Service, error) {
sighupHandler: func() {
slog.Debug("sighup received: nothing setup")
},
logConfig: logConfig{
attrs: defaultLogAttrs(name, version),
json: false,
},
observabilityCfg: cfg,
observabilityProvider: observabilityProvider,
}

Expand All @@ -78,7 +78,6 @@ func New(name, version string, options ...OptionFunc) (*Service, error) {
return nil, errors.Join(optionErrors...)
}

setupLogging(s.logConfig)
s.setupOSSignal()

return s, nil
Expand Down Expand Up @@ -149,51 +148,31 @@ func (s *Service) waitTermination(chErr <-chan error) error {
}
}

type logConfig struct {
attrs []slog.Attr
json bool
}

func getLogLevel() slog.Level {
func observabilityConfig(name, version string) observability.Config {
var lvl string
lvl, ok := os.LookupEnv("PATRON_LOG_LEVEL")
if !ok {
return slog.LevelInfo
}

lv := slog.LevelVar{}
if err := lv.UnmarshalText([]byte(lvl)); err != nil {
return slog.LevelInfo
lvl = "info"
}

return lv.Level()
}

func defaultLogAttrs(name, version string) []slog.Attr {
hostname, err := os.Hostname()
if err != nil {
hostname = host
}

return []slog.Attr{
attrs := []slog.Attr{
slog.String(srv, name),
slog.String(ver, version),
slog.String(host, hostname),
}
}

func setupLogging(lc logConfig) {
ho := &slog.HandlerOptions{
AddSource: true,
Level: getLogLevel(),
}

var hnd slog.Handler

if lc.json {
hnd = slog.NewJSONHandler(os.Stderr, ho)
} else {
hnd = slog.NewTextHandler(os.Stderr, ho)
return observability.Config{
Name: name,
Version: version,
LogConfig: log.Config{
Attributes: attrs,
IsJSON: false,
Level: lvl,
},
}

slog.New(hnd.WithAttrs(lc.attrs))
}
19 changes: 0 additions & 19 deletions service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,3 @@ func TestNew(t *testing.T) {
})
}
}

func Test_getLogLevel(t *testing.T) {
tests := map[string]struct {
lvl string
want slog.Level
}{
"debug": {lvl: "debug", want: slog.LevelDebug},
"info": {lvl: "info", want: slog.LevelInfo},
"warn": {lvl: "warn", want: slog.LevelWarn},
"error": {lvl: "error", want: slog.LevelError},
"invalid level": {lvl: "invalid", want: slog.LevelInfo},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
t.Setenv("PATRON_LOG_LEVEL", tt.lvl)
assert.Equal(t, tt.want, getLogLevel())
})
}
}

0 comments on commit a0f6478

Please sign in to comment.