Skip to content

Commit

Permalink
GODRIVER-2572 Add log messages to Server selection spec (#1325)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Dale <9760375+matthewdale@users.noreply.github.com>
  • Loading branch information
prestonvasquez and matthewdale authored Aug 17, 2023
1 parent 6c7e124 commit 4bd1c27
Show file tree
Hide file tree
Showing 48 changed files with 2,489 additions and 113 deletions.
31 changes: 31 additions & 0 deletions internal/driverutil/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (C) MongoDB, Inc. 2023-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package driverutil

// Operation Names should be sourced from the command reference documentation:
// https://www.mongodb.com/docs/manual/reference/command/
const (
AbortTransactionOp = "abortTransaction" // AbortTransactionOp is the name for aborting a transaction
AggregateOp = "aggregate" // AggregateOp is the name for aggregating
CommitTransactionOp = "commitTransaction" // CommitTransactionOp is the name for committing a transaction
CountOp = "count" // CountOp is the name for counting
CreateOp = "create" // CreateOp is the name for creating
CreateIndexesOp = "createIndexes" // CreateIndexesOp is the name for creating indexes
DeleteOp = "delete" // DeleteOp is the name for deleting
DistinctOp = "distinct" // DistinctOp is the name for distinct
DropOp = "drop" // DropOp is the name for dropping
DropDatabaseOp = "dropDatabase" // DropDatabaseOp is the name for dropping a database
DropIndexesOp = "dropIndexes" // DropIndexesOp is the name for dropping indexes
EndSessionsOp = "endSessions" // EndSessionsOp is the name for ending sessions
FindAndModifyOp = "findAndModify" // FindAndModifyOp is the name for finding and modifying
FindOp = "find" // FindOp is the name for finding
InsertOp = "insert" // InsertOp is the name for inserting
ListCollectionsOp = "listCollections" // ListCollectionsOp is the name for listing collections
ListIndexesOp = "listIndexes" // ListIndexesOp is the name for listing indexes
ListDatabasesOp = "listDatabases" // ListDatabasesOp is the name for listing databases
UpdateOp = "update" // UpdateOp is the name for updating
)
40 changes: 40 additions & 0 deletions internal/logger/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const (
ConnectionCheckoutFailed = "Connection checkout failed"
ConnectionCheckedOut = "Connection checked out"
ConnectionCheckedIn = "Connection checked in"
ServerSelectionFailed = "Server selection failed"
ServerSelectionStarted = "Server selection started"
ServerSelectionSucceeded = "Server selection succeeded"
ServerSelectionWaiting = "Waiting for suitable server to become available"
TopologyClosed = "Stopped topology monitoring"
TopologyDescriptionChanged = "Topology description changed"
TopologyOpening = "Starting topology monitoring"
Expand All @@ -53,21 +57,27 @@ const (
KeyMessage = "message"
KeyMinPoolSize = "minPoolSize"
KeyNewDescription = "newDescription"
KeyOperation = "operation"
KeyOperationID = "operationId"
KeyPreviousDescription = "previousDescription"
KeyRemainingTimeMS = "remainingTimeMS"
KeyReason = "reason"
KeyReply = "reply"
KeyRequestID = "requestId"
KeySelector = "selector"
KeyServerConnectionID = "serverConnectionId"
KeyServerHost = "serverHost"
KeyServerPort = "serverPort"
KeyServiceID = "serviceId"
KeyTimestamp = "timestamp"
KeyTopologyDescription = "topologyDescription"
KeyTopologyID = "topologyId"
)

// KeyValues is a list of key-value pairs.
type KeyValues []interface{}

// Add adds a key-value pair to an instance of a KeyValues list.
func (kvs *KeyValues) Add(key string, value interface{}) {
*kvs = append(*kvs, key, value)
}
Expand Down Expand Up @@ -252,6 +262,36 @@ func SerializeServer(srv Server, extraKV ...interface{}) KeyValues {
return keysAndValues
}

// ServerSelection contains data that all server selection messages MUST
// contain.
type ServerSelection struct {
Selector string
OperationID *int32
Operation string
TopologyDescription string
}

// SerializeServerSelection serializes a Topology message into a slice of keys
// and values that can be passed to a logger.
func SerializeServerSelection(srvSelection ServerSelection, extraKV ...interface{}) KeyValues {
keysAndValues := KeyValues{
KeySelector, srvSelection.Selector,
KeyOperation, srvSelection.Operation,
KeyTopologyDescription, srvSelection.TopologyDescription,
}

if srvSelection.OperationID != nil {
keysAndValues.Add(KeyOperationID, *srvSelection.OperationID)
}

// Add the optional keys and values.
for i := 0; i < len(extraKV); i += 2 {
keysAndValues.Add(extraKV[i].(string), extraKV[i+1])
}

return keysAndValues
}

// Topology contains data that all topology messages MAY contain.
type Topology struct {
ID primitive.ObjectID // Driver's unique ID for this topology
Expand Down
48 changes: 48 additions & 0 deletions internal/logger/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (C) MongoDB, Inc. 2023-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package logger

import "context"

// contextKey is a custom type used to prevent key collisions when using the
// context package.
type contextKey string

const (
contextKeyOperation contextKey = "operation"
contextKeyOperationID contextKey = "operationID"
)

// WithOperationName adds the operation name to the context.
func WithOperationName(ctx context.Context, operation string) context.Context {
return context.WithValue(ctx, contextKeyOperation, operation)
}

// WithOperationID adds the operation ID to the context.
func WithOperationID(ctx context.Context, operationID int32) context.Context {
return context.WithValue(ctx, contextKeyOperationID, operationID)
}

// OperationName returns the operation name from the context.
func OperationName(ctx context.Context) (string, bool) {
operationName := ctx.Value(contextKeyOperation)
if operationName == nil {
return "", false
}

return operationName.(string), true
}

// OperationID returns the operation ID from the context.
func OperationID(ctx context.Context) (int32, bool) {
operationID := ctx.Value(contextKeyOperationID)
if operationID == nil {
return 0, false
}

return operationID.(int32), true
}
187 changes: 187 additions & 0 deletions internal/logger/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (C) MongoDB, Inc. 2023-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package logger_test

import (
"context"
"testing"

"go.mongodb.org/mongo-driver/internal/assert"
"go.mongodb.org/mongo-driver/internal/logger"
)

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

tests := []struct {
name string
ctx context.Context
opName string
ok bool
}{
{
name: "simple",
ctx: context.Background(),
opName: "foo",
ok: true,
},
}

for _, tt := range tests {
tt := tt // Capture the range variable.

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ctx := logger.WithOperationName(tt.ctx, tt.opName)

opName, ok := logger.OperationName(ctx)
assert.Equal(t, tt.ok, ok)

if ok {
assert.Equal(t, tt.opName, opName)
}
})
}
}

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

tests := []struct {
name string
ctx context.Context
opName interface{}
ok bool
}{
{
name: "nil",
ctx: context.Background(),
opName: nil,
ok: false,
},
{
name: "string type",
ctx: context.Background(),
opName: "foo",
ok: true,
},
{
name: "non-string type",
ctx: context.Background(),
opName: int32(1),
ok: false,
},
}

for _, tt := range tests {
tt := tt // Capture the range variable.

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ctx := context.Background()

if opNameStr, ok := tt.opName.(string); ok {
ctx = logger.WithOperationName(tt.ctx, opNameStr)
}

opName, ok := logger.OperationName(ctx)
assert.Equal(t, tt.ok, ok)

if ok {
assert.Equal(t, tt.opName, opName)
}
})
}
}

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

tests := []struct {
name string
ctx context.Context
opID int32
ok bool
}{
{
name: "non-zero",
ctx: context.Background(),
opID: 1,
ok: true,
},
}

for _, tt := range tests {
tt := tt // Capture the range variable.

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ctx := logger.WithOperationID(tt.ctx, tt.opID)

opID, ok := logger.OperationID(ctx)
assert.Equal(t, tt.ok, ok)

if ok {
assert.Equal(t, tt.opID, opID)
}
})
}
}

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

tests := []struct {
name string
ctx context.Context
opID interface{}
ok bool
}{
{
name: "nil",
ctx: context.Background(),
opID: nil,
ok: false,
},
{
name: "i32 type",
ctx: context.Background(),
opID: int32(1),
ok: true,
},
{
name: "non-i32 type",
ctx: context.Background(),
opID: "foo",
ok: false,
},
}

for _, tt := range tests {
tt := tt // Capture the range variable.

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ctx := context.Background()

if opIDI32, ok := tt.opID.(int32); ok {
ctx = logger.WithOperationID(tt.ctx, opIDI32)
}

opName, ok := logger.OperationID(ctx)
assert.Equal(t, tt.ok, ok)

if ok {
assert.Equal(t, tt.opID, opName)
}
})
}
}
2 changes: 2 additions & 0 deletions internal/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

// Package logger provides the internal logging solution for the MongoDB Go
// Driver.
package logger

import (
Expand Down
Loading

0 comments on commit 4bd1c27

Please sign in to comment.