Skip to content
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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

Conversation

4gust
Copy link
Collaborator

@4gust 4gust commented Nov 19, 2024

Praposal for logging support for msal-go

Copy link
Member

@bgavrilMS bgavrilMS left a 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.

@4gust 4gust changed the title Added MD file Praposal for logging support for msal-go Nov 25, 2024

// Logger struct for Go versions <= 1.20.
type Logger struct {
LogCallback CallbackFunc
Copy link
Member

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
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) {
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)

// Logger struct for Go 1.21+ with full `slog` logging support.
type Logger struct {
Copy link
Member

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 {
Copy link
Collaborator

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(),
Copy link
Collaborator

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

Copy link
Collaborator

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?

Copy link
Collaborator

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")
Copy link
Collaborator

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)`
Copy link
Collaborator

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?

Copy link
Collaborator

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
Copy link
Collaborator

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:

  1. make it impossible for this function to receive an error i.e., make it impossible for logger.New to return an error
  2. separate logger construction from this function i.e., have users call logger.New themselves, check its error, pass the logger to WithLogger

Copy link
Member

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 ?

Copy link
Collaborator

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

Copy link
Member

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

Copy link
Collaborator

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

Copy link
Collaborator

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)

Copy link
Collaborator

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?

Copy link

sonarqubecloud bot commented Jan 8, 2025

@@ -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))
Copy link
Collaborator

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,
Copy link
Collaborator

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{}))
Copy link
Collaborator

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 {
Copy link
Collaborator

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:

Suggested change
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{
Copy link
Collaborator

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants