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

GODRIVER-2585 Add log messages to SDAM #1305

Merged
merged 18 commits into from
Jul 19, 2023

Conversation

prestonvasquez
Copy link
Collaborator

GODRIVER-2585

Summary

Define log messages for SDAM events.

Background & Motivation

The SDAM logging and monitoring specification defines a set of behaviors in the driver for providing runtime information about server discovery and monitoring (SDAM) in log messages, as well as in events that users can consume programmatically, either directly or by integrating with third-party APM libraries.

@prestonvasquez prestonvasquez marked this pull request as ready for review June 23, 2023 22:22
Comment on lines +84 to +87
for _, ignoreMessage := range clm.IgnoreMessages {
if err := verifyLogMatch(ctx, ignoreMessage, msg); err == nil {
return true
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a really unfortunate pattern, but I don't think there is a "set" solution that can be implemented here since the "logMessage" struct contains a slice of bytes. From the comparison spec:

Slice, map, and function types are not comparable.

Even though the time complexity is linear, the overall size of the IgnoreMessages should be very minimal.

@@ -199,3 +211,62 @@ func SerializeConnection(conn Connection, extraKeysAndValues ...interface{}) []i

return keysAndValues
}

// Server contains data that all server messages MAY contain.
type Server struct {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Even though both "Server" and "Topology" fall under the "ComponentTopology" component, it feels more natural to separate them into two separate message structs since they will ultimately contain non-intersecting fields.

expectedLogMessageCount int,
hasOperationalFailPoint bool,
) context.Context {
ctx = context.WithValue(ctx, operationalFailPointKey, hasOperationalFailPoint)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Before this ticket we supported reducing heartbeatFrequencyMS at the file-level client, but not the operational-level client. Since the operation does not maintain access to the "TestCase" object, it feels natural to extend the test context to include whether or not the set of operations contains a fail point operation.

t.Helper()

message, err := newLogMessage(level, msg, args...)
require.Nil(t, err, "failed to create test log message: %v", err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use require.NoError instead.

Suggested change
require.Nil(t, err, "failed to create test log message: %v", err)
require.NoError(t, err, "failed to create test log message")

func TestClientLogMessages(t *testing.T) {
t.Parallel()

t.Run("ignore", func(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Optional: The existing pattern of using one test function for testing a type with a bunch of subtests per function causes some problems:

  • Makes running more granular tests more difficult because popular IDE integrations don't allow running individual subtests.
  • Makes debugging more granular tests more difficult because popular IDE integrations don't allow debugging individual subtests.
  • Increases the probability of unexpected interaction between subtests because they share a scope.

Consider naming this test function something like TestClientLogMessagesIgnore and removing the "ignore" subtest.

Comment on lines 84 to 85
assert.Equal(t, test.want, got,
"clientLogMessages.ignore() = %v, want: %v", got, test.want)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Optional: Printing the values in the message is redundant because assert.Equal will already print them on an assertion failure. Consider removing the redundant values.

Suggested change
assert.Equal(t, test.want, got,
"clientLogMessages.ignore() = %v, want: %v", got, test.want)
assert.Equal(t, test.want, got, "unexpected clientLogMessages.ignore() result")

keysAndValues.Add(KeyServerConnectionID, connID)
}

port, err := strconv.ParseInt(srv.ServerPort, 0, 32)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Automatically determining the number base (i.e. base 0) may lead to unexpected results when parsing some port numberes (see examples here). It's safer to explicitly specify the expected base.

Suggested change
port, err := strconv.ParseInt(srv.ServerPort, 0, 32)
port, err := strconv.ParseInt(srv.ServerPort, 10, 32)

Alternatively, consider using the simpler strconv.Atoi unless you need to specify the base or bit size.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This makes sense, note that the specifications defined the port to be an integer (e.g. CMAP). The two cases you note that would cause unexpected behavior ("0123", "0999") would result in invalid ports regardless of the base

type Server struct {
DriverConnectionID uint64 // Driver's ID for the connection
TopologyID primitive.ObjectID // Driver's unique ID for this topology
Message string // Message associated with the topology
Copy link
Collaborator

Choose a reason for hiding this comment

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

Including the message here and passing it via the msg field in the LogSink.Info function seems like it will repeat the message twice in logs. Is that intended?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes it will. go-logr/logr's LogSink.Info() method requires a message string:

The log message consists of a constant message attached to the log line. This should generally be a simple description of what's occurring, and should never be a format string. Variable information can then be attached using named values.

And the DBX logging specifications include the messages in the structured data, e.g. here.

The duplication is unfortunate, but I'm not sure there is an obvious way around it. A logrus example:

DEBU[0000] Command started                               command="{\"insert\": \"test\",\"ordere..." commandName=insert databaseName=test driverConnectionId=1 message="Command started" operationId=0 requestId=11 serverConnectionId=2014 serverHost=localhost serverPort=27018

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added a comment to the "Print" method acknowledging this.

Copy link
Collaborator

@matthewdale matthewdale Jul 11, 2023

Choose a reason for hiding this comment

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

I think we have some latitude to interpret the logging specification as requiring passing the message, but not necessarily requiring it to be passed in the key/value pairs. As long as the "default" structured logger output contains the expected structure and a user can implement the expected logger (e.g. by using a structured logger that puts the log message in the "message" field), that should satisfy the specification requirements.

TLDR: The duplication is fine, but I'd argue it's unnecessary even given the spec requirements.

@prestonvasquez prestonvasquez requested a review from a team as a code owner July 18, 2023 23:32
@prestonvasquez prestonvasquez merged commit acf1235 into mongodb:master Jul 19, 2023
18 of 20 checks passed
@prestonvasquez prestonvasquez deleted the GODRIVER-2585 branch July 19, 2023 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants