-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:Split the loggerfx into a separate internal package before split…
…ting it to an external library.
- Loading branch information
Showing
5 changed files
with
358 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) | ||
} | ||
} |
Oops, something went wrong.