-
Notifications
You must be signed in to change notification settings - Fork 845
Logging: Add interface with default glog implementation #4085
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: master
Are you sure you want to change the base?
Conversation
|
I think we want to add something about the compile time usage of this feature to the README. Mention the default, and how to compile the alternate logger in. |
|
@bsardo mentioned that a similar thing is done with the time package - will take a look |
SyntaxNode
left a comment
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.
@zhongshixi This is very similar to an idea you wanted to implement. Could you please review this PR instead and share your views?
.semgrep/adapter/package-import.go
Outdated
| "github.com/mitchellh/copystructure" | ||
| // ruleid: package-import-check | ||
| "github.com/golang/glog" | ||
| "github.com/prebid/prebid-server/v3/di" |
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 code tests the semgrep rule. Please review the rule to check if any changes to the semgrep definition is required,
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.
the semgrep rule seems to be checking that either of glog or copystructure packages is not used in the adapters, so I reverted this change to the package-import.go.
di/di.go
Outdated
| @@ -0,0 +1,8 @@ | |||
| package di | |||
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.
I prefer if this is called the "logger" package, which is more intuitive. I don't know if we'd want to put all global state items in one package and currently there is only one. For simplicity, I'd like to see the interfaces and providers packages collapsed up to this level under the same "logger" package.
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.
renamed
di/interfaces/log.go
Outdated
| @@ -0,0 +1,18 @@ | |||
| package interfaces | |||
|
|
|||
| type ILogger interface { | |||
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.
Nitpick: The Java / C# naming convention does not apply to Go. It is most appropriate to name this "Logger".
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.
renamed
di/providers/log/default.go
Outdated
| "github.com/prebid/prebid-server/v3/di/interfaces" | ||
| ) | ||
|
|
||
| type GlogWrapper struct { |
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.
Nitpick: GlogLogger would be more direct name.
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.
renamed
di/providers/log/alternative.go
Outdated
| @@ -0,0 +1,74 @@ | |||
| //go:build custom_logger | |||
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 project does not currently use build directives to choose features. Instead, we offer a configuration system.
I like the idea of having a global log variable to override. I've seen the same in other Go projects, and it provides support for #3961. I propose to keep this ability, while also providing a config option to switch to slog from glog.
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 build tag is actually not required, it's just one potential mechanism that could be used to swap a dependency in compile time, so can probably removed. Configuration would require shipping both implementations and then have them swapped in runtime depending on the configuration, while compile time would only ship a single dependency. Just to be clear, by configuration - do you mean a host-level config file pbs.yaml / pbs.json right?
|
@justadreamer we'll try to revisit this in a few weeks. Sorry for the delay. |
|
This was discussed at the last engineering subcommittee meeting. The suggestion was to move from a compile time config to a runtime config. |
…tructure of the logger: encapsulate the logger object, call only external functions
# Conflicts: # modules/fiftyonedegrees/devicedetection/device_info_extractor.go
hhhjort
left a comment
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.
LGTM
linux019
left a comment
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.
Consider to use build flags to include only one logging lib instead of bringing up more libs to the project codebase
logger/alternative.go
Outdated
| } | ||
|
|
||
| func (logger *SlogWrapper) Info(args ...any) { | ||
| msg := fmt.Sprint(args...) |
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.
The issue with this logger that glog or fmt.Sprintf use sync.Pool with buffer reuse but this logger always allocates a string to store message and pass it to the underlying function. At least this package should use sync.Pool to store formatted msg to bypass GC
buf := new(bytes.Buffer) // get it from sync.Pool
msg := fmt.Fprintf(buf, args...) //it takes io.Writer
slog.Info(buf.String()) // better to pass io.Reader instead of the string
// put buf back to pool
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 file will be removed as @bsardo mentioned on the call. build tags were used originally and were discarded by the committee in favor of runtime configuration.
config/config.go
Outdated
| return nil, err | ||
| } | ||
|
|
||
| logger.New(c.Logger.Type, c.Logger.Depth) |
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.
We should validate Type is a valid string value
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.
Type validation added
README.md
Outdated
| This can be done compile-time in the `logger` package. | ||
| It contains `Logger` interface definition and `default` and `alternative` implementation. | ||
| The `default` logger implementation based on `github.com/golang/glog` package. | ||
| The `alternative` logger implementation based on `log/slog` package. |
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.
Delete line since we are proposing removing the alternative logger implementation for now.
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.
Done
config/config.go
Outdated
| type Logger struct { | ||
| // the type of logger: default or alternative | ||
| Type string `mapstructure:"type"` | ||
| Depth *int `mapstructure:"depth"` |
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.
I'm curious why this is a pointer? I imagine you want to detect presence for some reason. Is it because you want to know whether to apply some default depth if depth is not specified? What should the default be? Do you think it will dependent on the implementation?
I see you're detecting presence via a nil check and have declared a default depth of 1 in logger.go.
Should we verify that depth is > 0 or >= 0? What happens if depth is some large number? Perhaps validation is needed for the upper bound as well?
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.
Should we verify that depth is > 0 or >= 0? What happens if depth is some large number? Perhaps validation is needed for the upper bound as well?
Fun fact: glog has no depth validation within itself:
https://github.com/golang/glog/blob/master/glog.go#L432
Re-check source code will show the same.
// If Depth is present, this function calls log from a different depth in the call stack.
// This enables a callee to emit logs that use the callsite information of its caller
// or any other callers in the stack. When depth == 0, the original callee's line
// information is emitted. When depth > 0, depth frames are skipped in the call stack
// and the final frame is treated like the original callee to Info.
I've tested with different values.
What I've got:
- -1000
glog.ErrorDepth(-1000, "Hello World")
Log:
extern.go:304] Hello World
- 0
glog.ErrorDepth(0, "Hello World")
Log:
main.go:8] Hello World
- 1000
glog.ErrorDepth(0, "Hello World")
Log:
???:1] Hello World
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.
I don't know in what range to validate the value, because glog doesn't have such validation, and the current implementation doesn't have any alternatives for it now.
logger/default.go
Outdated
| glog.FatalDepthf(logger.depth, format, args...) | ||
| } | ||
|
|
||
| func ProvideDefaultLogger(depth int) Logger { |
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.
Nitpick: maybe call this NewDefaultLogger to follow patterns throughout the code base?
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.
Done
logger/interface.go
Outdated
| Warning(args ...any) | ||
| Warningf(format string, args ...any) | ||
| Warningln(args ...any) |
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.
Do we need Warningln? The default implementation is the same as Warning.
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.
Removed
logger/alternative.go
Outdated
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.
Let's delete this file. We can add an slog implementation in the future. For now, simply defining the logger interface and the glog implementation suffices.
In the future we will expand the interface to include functions that are more suitable for structured logging .
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.
Removed
# Conflicts: # stored_requests/backends/http_fetcher/fetcher.go # stored_requests/config/config.go
…with thread-safe operations; introduced extensive unit tests for the new logger structure.
|
Changes to the logger for PHASE 1:
All other methods (logger calls with context) were removed.
|
Comprehensive Review: Logger Abstraction Phase 1I've reviewed PR #4085 with golang-pro, code-reviewer, and refactoring-specialist perspectives. Here's the synthesis: 🎯 Executive SummaryThis phase 1 logger abstraction is well-structured and follows sound incremental refactoring principles. Most importantly: Your preference to keep "f" suffixes (Errorf, Infof, Warningf) is CORRECT and essential for the eventual slog migration. ✅ Why the "f" Suffix Decision is RightPhase 1 (current): logger.Infof("formatted %s", value) // Printf-style formatted logging
logger.Errorf("error: %v", err) // Printf-style formatted loggingPhase 2 (future with structured logging): // Formatted logging - legacy compatibility
logger.Infof("formatted %s", value)
// Structured logging - new slog style
logger.Info("message", "key", value, "error", err)Why this naming is essential:
All major Go logging libraries use this pattern:
✅ Strengths
📋 Key Findings on Existing CommentsDepth Parameter DiscussionThe depth parameter discussion is valid. Current implementation is appropriate - glog handles excessive depth gracefully (shows Type ValidationGood work by @postindustria-code adding type validation. Consider making it more type-safe with constants in future iterations. Alternative Logger RemovalCorrect decision to remove Warningln RemovalGood call removing 📝 Minor Recommendations
🎯 Final VerdictStrong approval. The "f" suffix decision is architecturally correct. This PR demonstrates excellent refactoring discipline:
All three specialized review perspectives (golang-pro, code-reviewer, refactoring-specialist) concur: keep the "f" suffixes. Great work on a well-executed phase 1! This sets up an excellent foundation for the eventual slog migration. |
|
FYI - the previous comment was from 🤖 Claude - I had prompted with a preference to stay with Errorf for the glog since all other major logging solutions (including glog, the thing we're replacing) keep the "f" for "format" and have some other alternative - Error (without any suffix) in slog or Errorw (in zap) - I don't know if I like the I Believe that for formatted loggers and specifically this PR, we should stick with the |
| type Logger interface { | ||
| // Debug level logging | ||
| Debug(msg string, args ...any) | ||
|
|
||
| // Info level logging | ||
| Info(msg string, args ...any) | ||
|
|
||
| // Warn level logging | ||
| Warn(msg string, args ...any) | ||
|
|
||
| // Error level logging | ||
| Error(msg string, args ...any) | ||
|
|
||
| // Fatal level logging | ||
| Fatal(msg string, args ...any) | ||
| } |
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.
| type Logger interface { | |
| // Debug level logging | |
| Debug(msg string, args ...any) | |
| // Info level logging | |
| Info(msg string, args ...any) | |
| // Warn level logging | |
| Warn(msg string, args ...any) | |
| // Error level logging | |
| Error(msg string, args ...any) | |
| // Fatal level logging | |
| Fatal(msg string, args ...any) | |
| } | |
| type Logger interface { | |
| // Debug level logging | |
| Debugf(msg string, args ...any) | |
| // Info level logging | |
| Infof(msg string, args ...any) | |
| // Warn level logging | |
| Warnf(msg string, args ...any) | |
| // Error level logging | |
| Errorf(msg string, args ...any) | |
| // Fatal level logging | |
| Fatalf(msg string, args ...any) | |
| } |
logger/logger.go
Outdated
| // Debug level logging | ||
| func Debug(msg string, args ...any) { | ||
| logger.Debug(msg, args...) | ||
| } | ||
|
|
||
| // Info level logging | ||
| func Info(msg string, args ...any) { | ||
| logger.Info(msg, args...) | ||
| } | ||
|
|
||
| // Warn level logging | ||
| func Warn(msg string, args ...any) { | ||
| logger.Warn(msg, args...) | ||
| } | ||
|
|
||
| // Error level logging | ||
| func Error(msg string, args ...any) { | ||
| logger.Error(msg, args...) | ||
| } | ||
|
|
||
| // Fatal level logging and terminates the program execution. | ||
| func Fatal(msg string, args ...any) { | ||
| logger.Fatal(msg, args...) | ||
| } |
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.
| // Debug level logging | |
| func Debug(msg string, args ...any) { | |
| logger.Debug(msg, args...) | |
| } | |
| // Info level logging | |
| func Info(msg string, args ...any) { | |
| logger.Info(msg, args...) | |
| } | |
| // Warn level logging | |
| func Warn(msg string, args ...any) { | |
| logger.Warn(msg, args...) | |
| } | |
| // Error level logging | |
| func Error(msg string, args ...any) { | |
| logger.Error(msg, args...) | |
| } | |
| // Fatal level logging and terminates the program execution. | |
| func Fatal(msg string, args ...any) { | |
| logger.Fatal(msg, args...) | |
| } | |
| // Debugf level logging | |
| func Debugf(msg string, args ...any) { | |
| logger.Debug(msg, args...) | |
| } | |
| // Infof level logging | |
| func Infof(msg string, args ...any) { | |
| logger.Info(msg, args...) | |
| } | |
| // Warnf level logging | |
| func Warnf(msg string, args ...any) { | |
| logger.Warn(msg, args...) | |
| } | |
| // Errorf level logging | |
| func Errorf(msg string, args ...any) { | |
| logger.Error(msg, args...) | |
| } | |
| // Fatalf level logging and terminates the program execution. | |
| func Fatalf(msg string, args ...any) { | |
| logger.Fatal(msg, args...) | |
| } |
scr-oath
left a comment
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.
So close - just change the names to add the f like glog and log.Printf have to differentiate formatted logs from the followup work to add structured logs. call sites should only change from glog to our logger module.
| req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(payload)) | ||
| if err != nil { | ||
| glog.Error(err) | ||
| logger.Error(err.Error()) |
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.
| logger.Error(err.Error()) | |
| logger.Error("%v", err) |
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.
done
| endpoint, err := url.Parse(baseUrl) | ||
| if err != nil { | ||
| glog.Error(err) | ||
| logger.Error(err.Error()) |
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.
| logger.Error(err.Error()) | |
| logger.Error("%v", err) |
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.
Done
config/structlog.go
Outdated
| logInternal.Error("LogStruct called on type %s, whuch is not a struct!", v.Type().String()) | ||
| logger("LogStruct called on type %s, whuch is not a struct!", v.Type().String()) | ||
| os.Exit(1) |
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.
Let's add a Fatalf instead of calling os.Exit(1) here.
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.
Done
…ated the interface implementation; Updated logger calls with a new signature
bsardo
left a comment
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 looking good, just a couple of quick comments.
config/structlog.go
Outdated
| func logStructWithLogger(v reflect.Value, prefix string, logger logMsg) { | ||
| if v.Kind() != reflect.Struct { | ||
| glog.Fatalf("LogStruct called on type %s, whuch is not a struct!", v.Type().String()) | ||
| logger("LogStruct called on type %s, whuch is not a struct!", v.Type().String()) |
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.
Is this line needed? Did you determine this was just missing so you decided to add it?
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.
I replaced the glog call with our internal logger
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.
Yes but I see two logger calls in sequence now
logger("LogStruct called on type %s, whuch is not a struct!", v.Type().String())
logInternal.Fatalf("LogStruct called on type %s, whuch is not a struct!", v.Type().String())
I think we can delete logger("LogStruct called on type %s, whuch is not a struct!", v.Type().String()) and just have the fatal call.
config/structlog.go
Outdated
| func logMapWithLogger(v reflect.Value, prefix string, logger logMsg) { | ||
| if v.Kind() != reflect.Map { | ||
| glog.Fatalf("LogMap called on type %s, whuch is not a map!", v.Type().String()) | ||
| logger("LogMap called on type %s, whuch is not a map!", v.Type().String()) |
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.
Is this line needed? Did you determine this was just missing so you decided to add it?
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.
I replaced the glog call with our internal logger
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.
Same comment as above.
main.go
Outdated
|
|
||
| func main() { | ||
| flag.Parse() // required for glog flags and testing package flags | ||
| flag.Parse() // required for di.Log flags and testing package flags |
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.
What is di? Is this left over from a previous iteration of this PR?
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.
Yes, you are right, this is a leftover from the previous implementation and is no longer relevant. Removed.
| }, "Debug with args should not panic") | ||
| } | ||
|
|
||
| func TestGlogLogger_Info(t *testing.T) { |
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.
Can we add a test for Fatal?
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.
that can be tricky - but I've seen/written similar in other libraries that exit with a setting for whether fatal uses os.Exit or panic. If set to panic for testing, that can be easily caught and verified.
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.
Fatal test added
| assert.True(t, ok, "Default logger should be *GlogLogger") | ||
| } | ||
|
|
||
| func TestDebug(t *testing.T) { |
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.
Can we add a fatal test?
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.
Fatal test added
|
|
||
| func NewGlogLogger() Logger { | ||
| return &GlogLogger{ | ||
| depth: 1, |
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.
question (not-blocking), should there be a way to set the depth? Would that be an interface on the logger or in the New method itself… Would we want to consider the Functional Options Pattern or a single config object that could be passed that knows to choose default values for any "zero" (uninitialized) fields?
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.
Judging by the implementation phases, in the current phase 1
PHASE 1:
interface with just the nonstructured logging functions (call it Error, Warning, Info, etc)
swap out all calls to glog library with the nonstructured logging function calls from the glog concrete implementation
we are not implementing the logger settings at this stage.
Judging by the phase 2 description
PHASE 2 (should span multiple smaller PRs):
host config option to choose logger type (just glog or slog, glog is the default)
expand interface to include structured logging functions (ErrorS, WarningS, InfoS etc)
add slog concrete implementation that implements the interface structured logging functions
add shim so nonstructured calls when slog is enabled are converted to structured logging and vice versa
slog conversion to text just sprintf msg
glog conversion to structured (TBD)
going forward, authors can either call the glog or the slog function
the settings will be implemented during the implementation of phase 2
in fact, in phase 2 we can decide which parameters we can specify and which methods will be added for them.
But you are absolutely right, this method will be needed in the future for full configuration of the logger.
| }, "Debug with args should not panic") | ||
| } | ||
|
|
||
| func TestGlogLogger_Info(t *testing.T) { |
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.
that can be tricky - but I've seen/written similar in other libraries that exit with a setting for whether fatal uses os.Exit or panic. If set to panic for testing, that can be easily caught and verified.
logger/logger_test.go
Outdated
| <-done | ||
| } | ||
|
|
||
| assert.Equal(t, 10, len(mock.infoCalls), "Should have 10 info calls from concurrent execution") |
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.
Is this test flaky or is something wrong? The test is failing in the the validate github action workflow.
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.
I deleted this test because it runs correctly locally.
|
The action also crashes due to problems with the publishonly.yml file. |
| } | ||
|
|
||
| func main() { | ||
| flag.Parse() // required for glog flags and testing package flags |
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 still needed so other flags may be passed in at startup.
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.
Reverted the changes back
…ithLogger, replacing them with Fatalf for stricter error handling
This PR introduces a
loggershim and was meant to accompany a more global proposal: #4084. With this different loggers can be swapped in compile-time, however leaving the possibility to also swap a global instance in runtime (during tests f.e.)The global object is instantiated in logger/logger.go, the interface is defined in
logger/interface.go, while implementations of loggers are inlogger/default.goandlogger/alternative.go.