Skip to content

Commit

Permalink
feat:Split the loggerfx into a separate internal package before split…
Browse files Browse the repository at this point in the history
…ting it to an external library.
  • Loading branch information
schmidtw committed Dec 11, 2024
1 parent fc67457 commit e444077
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 57 deletions.
56 changes: 56 additions & 0 deletions internal/loggerfx/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0

/*
Package loggerfx provides a function that can be used to create a logger module
for an fx application. The logger module is based on the sallust, goschtalt,
and zap libraries and provides a simple, tested way to include a standard logger.
Example Usage:
package main
import (
"github.com/goschtalt/goschtalt"
"github.com/xmidt-org/sallust"
"github.com/xmidt-org/skeleton/internal/loggerfx"
"go.uber.org/fx"
"go.uber.org/zap"
)
func main() {
cfg := struct {
Logging sallust.Config
}{
Logging: sallust.Config{
Level: "info",
OutputPaths: []string{},
ErrorOutputPaths: []string{},
},
}
config, err := goschtalt.New(
goschtalt.ConfigIs("two_words"),
goschtalt.AddValue("built-in", goschtalt.Root, cfg, goschtalt.AsDefault()),
)
if err != nil {
panic(err)
}
app := fx.New(
fx.Provide(
func() *goschtalt.Config {
return config
}),
loggerfx.Module(),
fx.Invoke(func(logger *zap.Logger) {
logger.Info("Hello, world!")
}),
loggerfx.SyncOnShutdown(),
)
app.Run()
}
*/
package loggerfx
122 changes: 122 additions & 0 deletions internal/loggerfx/loggerfx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-FileCopyrightText: 2023 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0

package loggerfx

import (
"errors"
"fmt"

"github.com/goschtalt/goschtalt"
"github.com/xmidt-org/sallust"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

const (
deaultConfigPath = "logging"
)

var (
ErrInvalidConfigPath = errors.New("configuration structure path like 'logging' or 'foo.logging' is required")
)

// Module is function that builds the loggerfx module based on the inputs. If
// the configPath is not provided then the default path is used.
func Module(configPath ...string) fx.Option {
configPath = append(configPath, deaultConfigPath)

var path string
for _, cp := range configPath {
if cp != "" {
path = cp
break
}
}

// Why not use sallust.WithLogger()? It is because we want to provide the
// developer configuration based on the sallust configuration, not the zap
// options. This makes the configuration consistent between the two modes.
return fx.Options(
// Provide the logger configuration based on the input path.
fx.Provide(
goschtalt.UnmarshalFunc[sallust.Config](path),
provideLogger,
),

// Inform fx that we are providing a logger for it.
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: log}
}),
)
}

// DefaultConfig is a helper function that creates a default sallust configuration
// for the logger based on the application name.
func DefaultConfig(appName string) sallust.Config {
return sallust.Config{
// Use the default zap logger configuration for most of the settings.
OutputPaths: []string{
fmt.Sprintf("/var/log/%s/%s.log", appName, appName),
},
ErrorOutputPaths: []string{
fmt.Sprintf("/var/log/%s/%s.log", appName, appName),
},
Rotation: &sallust.Rotation{
MaxSize: 50,
MaxBackups: 10,
MaxAge: 2,
},
}
}

// SyncOnShutdown is a helper function that returns an fx option that will
// sync the logger on shutdown.
//
// Make sure to include this option last in the fx.Options list, so that the
// logger is the last component to be shutdown.
func SyncOnShutdown() fx.Option {
return sallust.SyncOnShutdown()
}

// LoggerIn describes the dependencies used to bootstrap a zap logger within
// an fx application.
type LoggerIn struct {
fx.In

// Config is the sallust configuration for the logger. This component is optional,
// and if not supplied a default zap logger will be created.
Config sallust.Config `optional:"true"`

// DevMode is a flag that indicates if the logger should be in debug mode.
DevMode bool `name:"loggerfx.dev_mode" optional:"true"`
}

// Create the logger and configure it based on if the program is in
// debug mode or normal mode.
func provideLogger(in LoggerIn) (*zap.Logger, error) {
if in.DevMode {
in.Config.Level = "DEBUG"
in.Config.Development = true
in.Config.Encoding = "console"
in.Config.EncoderConfig = sallust.EncoderConfig{
TimeKey: "T",
LevelKey: "L",
NameKey: "N",
CallerKey: "C",
FunctionKey: zapcore.OmitKey,
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: "capitalColor",
EncodeTime: "RFC3339",
EncodeDuration: "string",
EncodeCaller: "short",
}
in.Config.OutputPaths = []string{"stderr"}
in.Config.ErrorOutputPaths = []string{"stderr"}
}
return in.Config.Build()
}
162 changes: 162 additions & 0 deletions internal/loggerfx/loggerfx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-FileCopyrightText: 2023 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0

package loggerfx

import (
"bytes"
"io"
"os"
"testing"

"github.com/goschtalt/goschtalt"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xmidt-org/sallust"
"go.uber.org/fx"
"go.uber.org/fx/fxtest"
"go.uber.org/zap"
)

/*
func TestModule(t *testing.T) {
app := fxtest.New(
t,
Module(),
fx.Invoke(func(logger *zap.Logger) {
assert.NotNil(t, logger)
}),
)
defer app.RequireStart().RequireStop()
}
*/

func TestDefaultConfig(t *testing.T) {
appName := "testapp"
config := DefaultConfig(appName)

expectedConfig := sallust.Config{
OutputPaths: []string{
"/var/log/testapp/testapp.log",
},
ErrorOutputPaths: []string{
"/var/log/testapp/testapp.log",
},
Rotation: &sallust.Rotation{
MaxSize: 50,
MaxBackups: 10,
MaxAge: 2,
},
}

assert.Equal(t, expectedConfig, config)
}

func TestLoggerFX_EndToEnd(t *testing.T) {
// Create a temporary configuration file
loggerConfig := struct {
Logger sallust.Config
}{
Logger: sallust.Config{
Level: "info",
OutputPaths: []string{},
ErrorOutputPaths: []string{},
},
}
loggingConfig := struct {
Logging sallust.Config
}{
Logging: sallust.Config{
Level: "info",
OutputPaths: []string{},
ErrorOutputPaths: []string{},
},
}

tests := []struct {
name string
input any
label string
dev bool
err bool
}{
{
name: "empty path, no error",
input: loggingConfig,
}, {
name: "specific path, no error",
input: loggerConfig,
label: "logger",
}, {
name: "empty path, no error, dev mode",
input: loggingConfig,
dev: true,
}, {
name: "specific path, no error, dev mode",
input: loggerConfig,
label: "logger",
dev: true,
}, {
name: "specific path, error",
input: loggerConfig,
label: "invalid",
err: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// Initialize goschtalt with the configuration file
config, err := goschtalt.New(
goschtalt.ConfigIs("two_words"),
goschtalt.AddValue("built-in", goschtalt.Root, test.input, goschtalt.AsDefault()),
)
require.NoError(t, err)

opt := Module()
if test.label != "" {
opt = Module(test.label)
}

// Capture stderr
oldStderr := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w

var stderr bytes.Buffer
done := make(chan struct{})
go func() {
io.Copy(&stderr, r)
close(done)
}()

opts := []fx.Option{
fx.Supply(config),
fx.Supply(fx.Annotate(test.dev, fx.ResultTags(`name:"loggerfx.dev_mode"`))),
opt,
fx.Invoke(func(logger *zap.Logger) {
assert.NotNil(t, logger)
logger.Info("End-to-end test log message")
}),
SyncOnShutdown(),
}

if test.err {
app := fx.New(opts...)
require.NotNil(t, app)
assert.Error(t, app.Err())
} else {
app := fxtest.New(t, opts...)
require.NotNil(t, app)
assert.NoError(t, app.Err())
app.RequireStart().RequireStop()
}

// Close the writer and restore stderr
w.Close()
os.Stderr = oldStderr
<-done
})
}
}
37 changes: 0 additions & 37 deletions logger.go

This file was deleted.

Loading

0 comments on commit e444077

Please sign in to comment.