-
Notifications
You must be signed in to change notification settings - Fork 0
feat:Split the loggerfx into a separate internal package before split… #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good convention, but it should stay a convention. I'm ok with this being in |
||
| 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 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what value this provides, since |
||
| 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() | ||
| } | ||
| 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 | ||
| }) | ||
| } | ||
| } |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good pattern, but not something we want to import into every application. This is the kind of code that belongs in each application that uses these libraries.
The main reason for this is you cannot know a priori how an application is going to organize its modules. An application might put each
http.Serverin its own module, for example.