-
Notifications
You must be signed in to change notification settings - Fork 89
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
Praposal for logging support for msal-go #535
base: main
Are you sure you want to change the base?
Conversation
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 discussed that we don't need the callback option for 1.18 for now.
apps/design/logPraposal.md
Outdated
|
||
// Logger struct for Go versions <= 1.20. | ||
type Logger struct { | ||
LogCallback CallbackFunc |
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 discussed that for golang 1.18 it's ok to not have any logging.
….18, and just focusing on the logger working on 1.21 and above * adds tests * refactors logger documentation * adds sample logger app
apps/logger/logger.go
Outdated
if logger, ok := input.(*slog.Logger); ok { | ||
return &Logger{logging: logger}, nil | ||
// New creates a new logger instance | ||
func New(slogLogger *slog.Logger) (*Logger, 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.
I am not sure about this. The moment we put slog in the public API surface, Azure SDK will not be able to use MSAL SDK.
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.
You need to hide this API in MSAL 1.18 and lower and only expose it in MSAL 1.2x
Internally, MSAL 1.18 should still continue to work and the internal logging statement will do nothing.
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.
See with https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/main/apps/design/release.md for testing with azure sdk
apps/logger/logger.go
Outdated
) | ||
|
||
// Logger struct for Go 1.21+ with full `slog` logging support. | ||
type Logger 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.
Do we need this object?
) | ||
|
||
// WithLogger allows for a custom logger to be set. | ||
func WithLogger(l interface{}) Option { |
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.
interface{}
effectively disables compiler type checking here, making this function difficult to use and turning a class of build errors into runtime errors--not great. This option only works if l
is a *slog.Logger
; should that be the parameter's type?
|
||
// Log the entry with the message and fields | ||
a.logging.Log( | ||
context.Background(), |
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 must be the Context of the encompassing operation i.e., the caller must provide 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.
Who's the audience for this?
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.
Was made for testing purposes but can remove
return &logger{logging: loggerInterface}, nil | ||
} | ||
|
||
return nil, fmt.Errorf("invalid input for Go 1.21+; expected *slog.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.
then shouldn't the parameter be of type *slog.Logger
?
## Key Pointers | ||
|
||
1. **For Go <= 1.20**: | ||
- User should pass *nil* to `logger.New(nil)` |
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.
Users can't call logger.New
because it's in an internal
package. Is this doc or the implementation incorrect?
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 doc is the old proposal that was updated for the newer implementation, but the new implementation has since changed so will have to update this document once we are happy with the implementation
logInstance, err := logger.New(l) | ||
if err != nil { | ||
fmt.Println("Error creating logger with slog:", err) | ||
return |
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 won't do. The caller has every reason to believe the call succeeded and won't find out something went wrong until they notice the lack of logging and/or this line in stdout. Returning the error is the right thing to do but impossible here. So, this needs a redesign. Off the top of my head, I suppose the alternatives are:
- make it impossible for this function to receive an error i.e., make it impossible for
logger.New
to return an error - separate logger construction from this function i.e., have users call
logger.New
themselves, check its error, pass the logger toWithLogger
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.
Option 2 is not great for developer experience though. The app developer would be exposed to an unnecessary abstraction.
Option 1 looks great. If not possible, would Option 3 be feasible - return the error in the ConfidentialClientApplication / ManagedIdentityApplication builder ?
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 have updated the code for something like Option 1, so a default logger gets created in the case there is type assertion failures or nil provided
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 see any updates in the 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.
I just pushed now, was changing some test stuff
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 question I'm really circling around here is, do you imagine ever supporting logging via something other than slog? Answering this points the way forward. If you want to support only slog, you can simply have WithLogger(logger slog.Logger)
, you don't need an abstraction layer like LoggerInterface
, and you get my option 1 for free because this WithLogger
can't produce an error. If you want to maintain the possibility of easy non-slog logging, then you need to expose LoggerInterface
, have WithLogger
take one of those, and decide what to do when someone calls WithLogger(nil)
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 think the main issue or maybe just a misunderstanding on how best to handle it, is for go versions < 1.21
The LoggerInterface i think makes it simple to handle having something that supports 1.21 and also not causing an issue with < 1.21.
If we remove the loggerInterface would it not cause the code to become a bit more messy and having to use build tags directly inside the confidential.go class, and having clientOptions defined in both places?
Quality Gate passedIssues Measures |
@@ -345,6 +349,7 @@ func New(authority, clientID string, cred Credential, options ...Option) (Client | |||
} | |||
base.AuthParams.IsConfidentialClient = true | |||
|
|||
opts.logger.Log(context.Background(), logger.Info, "Created confidential client", logger.Field("clientID", clientID)) |
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 wouldn't log this because it has no new information for the application: the caller knows that it called this function with this argument, and the error return communicates success/failure. If you still want to log this, you must set a timeout on the Context, and I suggest changing the level to debug
opts := clientOptions{ | ||
authority: authority, | ||
// if the caller specified a token provider, it will handle all details of authentication, using Client only as a token cache | ||
disableInstanceDiscovery: cred.tokenProvider != nil, | ||
httpClient: shared.DefaultClient, | ||
azureRegion: autoEnabledRegion, | ||
logger: nil, |
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.
zero value of a pointer is nil
// If nil is provided a default logger instance is created. | ||
func New(slogLogger *slog.Logger) *Logger { | ||
if slogLogger == nil { | ||
defaultLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{})) |
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.
Why not slog.Default()?
Error = slog.LevelError | ||
) | ||
|
||
type Logger 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.
You don't need to wrap slog types, you can simply export them with the same names. For example:
type Logger struct { | |
type Logger = slog.Logger |
Then if you make this package slog
, all your calling code can use the slog API directly on all versions of Go MSAL supports
|
||
// Check the output | ||
output := buf.String() | ||
expectedMessages := []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.
Shouldn't this test the structure as well i.e., the attributes added to each message?
Praposal for logging support for msal-go