Skip to content

Commit 038249a

Browse files
GODRIVER-2586 Add log messages to CMAP spec (#1165)
Co-authored-by: Matt Dale <[email protected]>
1 parent a0bd0e5 commit 038249a

21 files changed

+2134
-287
lines changed

event/monitoring.go

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ type PoolEvent struct {
105105
// ServiceID is only set if the Type is PoolCleared and the server is deployed behind a load balancer. This field
106106
// can be used to distinguish between individual servers in a load balanced deployment.
107107
ServiceID *primitive.ObjectID `json:"serviceId"`
108+
Error error `json:"error"`
108109
}
109110

110111
// PoolMonitor is a function that allows the user to gain access to events occurring in the pool

internal/logger/component.go

+99-17
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,60 @@ import (
1414
)
1515

1616
const (
17-
CommandFailed = "Command failed"
18-
CommandStarted = "Command started"
19-
CommandSucceeded = "Command succeeded"
17+
CommandFailed = "Command failed"
18+
CommandStarted = "Command started"
19+
CommandSucceeded = "Command succeeded"
20+
ConnectionPoolCreated = "Connection pool created"
21+
ConnectionPoolReady = "Connection pool ready"
22+
ConnectionPoolCleared = "Connection pool cleared"
23+
ConnectionPoolClosed = "Connection pool closed"
24+
ConnectionCreated = "Connection created"
25+
ConnectionReady = "Connection ready"
26+
ConnectionClosed = "Connection closed"
27+
ConnectionCheckoutStarted = "Connection checkout started"
28+
ConnectionCheckoutFailed = "Connection checkout failed"
29+
ConnectionCheckedOut = "Connection checked out"
30+
ConnectionCheckedIn = "Connection checked in"
31+
)
32+
33+
const (
34+
KeyCommand = "command"
35+
KeyCommandName = "commandName"
36+
KeyDatabaseName = "databaseName"
37+
KeyDriverConnectionID = "driverConnectionId"
38+
KeyDurationMS = "durationMS"
39+
KeyError = "error"
40+
KeyFailure = "failure"
41+
KeyMaxConnecting = "maxConnecting"
42+
KeyMaxIdleTimeMS = "maxIdleTimeMS"
43+
KeyMaxPoolSize = "maxPoolSize"
44+
KeyMessage = "message"
45+
KeyMinPoolSize = "minPoolSize"
46+
KeyOperationID = "operationId"
47+
KeyReason = "reason"
48+
KeyReply = "reply"
49+
KeyRequestID = "requestId"
50+
KeyServerConnectionID = "serverConnectionId"
51+
KeyServerHost = "serverHost"
52+
KeyServerPort = "serverPort"
53+
KeyServiceID = "serviceId"
54+
KeyTimestamp = "timestamp"
55+
)
56+
57+
type KeyValues []interface{}
58+
59+
func (kvs *KeyValues) Add(key string, value interface{}) {
60+
*kvs = append(*kvs, key, value)
61+
}
62+
63+
const (
64+
ReasonConnClosedStale = "Connection became stale because the pool was cleared"
65+
ReasonConnClosedIdle = "Connection has been available but unused for longer than the configured max idle time"
66+
ReasonConnClosedError = "An error occurred while using the connection"
67+
ReasonConnClosedPoolClosed = "Connection pool was closed"
68+
ReasonConnCheckoutFailedTimout = "Wait queue timeout elapsed without a connection becoming available"
69+
ReasonConnCheckoutFailedError = "An error occurred while trying to establish a new connection"
70+
ReasonConnCheckoutFailedPoolClosed = "Connection pool was closed"
2071
)
2172

2273
// Component is an enumeration representing the "components" which can be
@@ -87,31 +138,62 @@ type Command struct {
87138
// structured logging.
88139
func SerializeCommand(cmd Command, extraKeysAndValues ...interface{}) []interface{} {
89140
// Initialize the boilerplate keys and values.
90-
keysAndValues := append([]interface{}{
91-
"commandName", cmd.Name,
92-
"driverConnectionId", cmd.DriverConnectionID,
93-
"message", cmd.Message,
94-
"operationId", cmd.OperationID,
95-
"requestId", cmd.RequestID,
96-
"serverHost", cmd.ServerHost,
97-
}, extraKeysAndValues...)
141+
keysAndValues := KeyValues{
142+
KeyCommandName, cmd.Name,
143+
KeyDriverConnectionID, cmd.DriverConnectionID,
144+
KeyMessage, cmd.Message,
145+
KeyOperationID, cmd.OperationID,
146+
KeyRequestID, cmd.RequestID,
147+
KeyServerHost, cmd.ServerHost,
148+
}
149+
150+
// Add the extra keys and values.
151+
for i := 0; i < len(extraKeysAndValues); i += 2 {
152+
keysAndValues.Add(extraKeysAndValues[i].(string), extraKeysAndValues[i+1])
153+
}
98154

99-
// Add the optional keys and values.
100155
port, err := strconv.ParseInt(cmd.ServerPort, 0, 32)
101156
if err == nil {
102-
keysAndValues = append(keysAndValues, "serverPort", port)
157+
keysAndValues.Add(KeyServerPort, port)
103158
}
104159

105160
// Add the "serverConnectionId" if it is not nil.
106161
if cmd.ServerConnectionID != nil {
107-
keysAndValues = append(keysAndValues,
108-
"serverConnectionId", *cmd.ServerConnectionID)
162+
keysAndValues.Add(KeyServerConnectionID, *cmd.ServerConnectionID)
109163
}
110164

111165
// Add the "serviceId" if it is not nil.
112166
if cmd.ServiceID != nil {
113-
keysAndValues = append(keysAndValues,
114-
"serviceId", cmd.ServiceID.Hex())
167+
keysAndValues.Add(KeyServiceID, cmd.ServiceID.Hex())
168+
}
169+
170+
return keysAndValues
171+
}
172+
173+
// Connection contains data that all connection log messages MUST contain.
174+
type Connection struct {
175+
Message string // Message associated with the connection
176+
ServerHost string // Hostname or IP address for the server
177+
ServerPort string // Port for the server
178+
}
179+
180+
// SerializeConnection serializes a ConnectionMessage into a slice of keys
181+
// and values that can be passed to a logger.
182+
func SerializeConnection(conn Connection, extraKeysAndValues ...interface{}) []interface{} {
183+
// Initialize the boilerplate keys and values.
184+
keysAndValues := KeyValues{
185+
KeyMessage, conn.Message,
186+
KeyServerHost, conn.ServerHost,
187+
}
188+
189+
// Add the optional keys and values.
190+
for i := 0; i < len(extraKeysAndValues); i += 2 {
191+
keysAndValues.Add(extraKeysAndValues[i].(string), extraKeysAndValues[i+1])
192+
}
193+
194+
port, err := strconv.ParseInt(conn.ServerPort, 0, 32)
195+
if err == nil {
196+
keysAndValues.Add(KeyServerPort, port)
115197
}
116198

117199
return keysAndValues

internal/logger/io_sink.go

+27-66
Original file line numberDiff line numberDiff line change
@@ -7,91 +7,52 @@
77
package logger
88

99
import (
10+
"encoding/json"
1011
"io"
11-
"log"
12+
"sync"
13+
"time"
1214
)
1315

14-
// IOSink writes to an io.Writer using the standard library logging solution and
15-
// is the default sink for the logger, with the default IO being os.Stderr.
16+
// IOSink writes a JSON-encoded message to the io.Writer.
1617
type IOSink struct {
17-
log *log.Logger
18+
enc *json.Encoder
19+
20+
// encMu protects the encoder from concurrent writes. While the logger
21+
// itself does not concurrently write to the sink, the sink may be used
22+
// concurrently within the driver.
23+
encMu sync.Mutex
1824
}
1925

20-
// Compile-time check to ensure osSink implements the LogSink interface.
26+
// Compile-time check to ensure IOSink implements the LogSink interface.
2127
var _ LogSink = &IOSink{}
2228

23-
// NewIOSink will create a new IOSink that writes to the provided io.Writer.
29+
// NewIOSink will create an IOSink object that writes JSON messages to the
30+
// provided io.Writer.
2431
func NewIOSink(out io.Writer) *IOSink {
2532
return &IOSink{
26-
log: log.New(out, "", log.LstdFlags),
33+
enc: json.NewEncoder(out),
2734
}
2835
}
2936

30-
func logCommandMessageStarted(log *log.Logger, kvMap map[string]interface{}) {
31-
format := "Command %q started on database %q using a connection with " +
32-
"server-generated ID %d to %s:%d. The requestID is %d and " +
33-
"the operation ID is %d. Command: %s"
34-
35-
log.Printf(format,
36-
kvMap["commandName"],
37-
kvMap["databaseName"],
38-
kvMap["serverConnectionId"],
39-
kvMap["serverHost"],
40-
kvMap["serverPort"],
41-
kvMap["requestId"],
42-
kvMap["operationId"],
43-
kvMap["command"])
44-
45-
}
46-
47-
func logCommandMessageSucceeded(log *log.Logger, kvMap map[string]interface{}) {
48-
format := "Command %q succeeded in %d ms using server-generated ID " +
49-
"%d to %s:%d. The requestID is %d and the operation ID is " +
50-
"%d. Command reply: %s"
37+
// Info will write a JSON-encoded message to the io.Writer.
38+
func (sink *IOSink) Info(_ int, msg string, keysAndValues ...interface{}) {
39+
kvMap := make(map[string]interface{}, len(keysAndValues)/2+2)
5140

52-
log.Printf(format,
53-
kvMap["commandName"],
54-
kvMap["duration"],
55-
kvMap["serverConnectionId"],
56-
kvMap["serverHost"],
57-
kvMap["serverPort"],
58-
kvMap["requestId"],
59-
kvMap["operationId"],
60-
kvMap["reply"])
61-
}
41+
kvMap[KeyTimestamp] = time.Now().UnixNano()
42+
kvMap[KeyMessage] = msg
6243

63-
func logCommandMessageFailed(log *log.Logger, kvMap map[string]interface{}) {
64-
format := "Command %q failed in %d ms using a connection with " +
65-
"server-generated ID %d to %s:%d. The requestID is %d and " +
66-
"the operation ID is %d. Error: %s"
67-
68-
log.Printf(format,
69-
kvMap["commandName"],
70-
kvMap["duration"],
71-
kvMap["serverConnectionID"],
72-
kvMap["serverHost"],
73-
kvMap["serverPort"],
74-
kvMap["requestId"],
75-
kvMap["operationId"],
76-
kvMap["failure"])
77-
}
78-
79-
func (osSink *IOSink) Info(_ int, msg string, keysAndValues ...interface{}) {
80-
kvMap := make(map[string]interface{})
8144
for i := 0; i < len(keysAndValues); i += 2 {
8245
kvMap[keysAndValues[i].(string)] = keysAndValues[i+1]
8346
}
8447

85-
switch msg {
86-
case CommandStarted:
87-
logCommandMessageStarted(osSink.log, kvMap)
88-
case CommandSucceeded:
89-
logCommandMessageSucceeded(osSink.log, kvMap)
90-
case CommandFailed:
91-
logCommandMessageFailed(osSink.log, kvMap)
92-
}
48+
sink.encMu.Lock()
49+
defer sink.encMu.Unlock()
50+
51+
_ = sink.enc.Encode(kvMap)
9352
}
9453

95-
func (osSink *IOSink) Error(err error, msg string, kv ...interface{}) {
96-
osSink.Info(0, msg, kv...)
54+
// Error will write a JSON-encoded error message tot he io.Writer.
55+
func (sink *IOSink) Error(err error, msg string, kv ...interface{}) {
56+
kv = append(kv, KeyError, err.Error())
57+
sink.Info(0, msg, kv...)
9758
}

internal/logger/logger_test.go

+77-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
package logger
88

99
import (
10+
"bytes"
11+
"encoding/json"
12+
"fmt"
1013
"os"
1114
"reflect"
15+
"sync"
1216
"testing"
1317
)
1418

@@ -33,12 +37,83 @@ func BenchmarkLogger(b *testing.B) {
3337
b.Fatal(err)
3438
}
3539

36-
for i := 0; i < b.N; i++ {
37-
logger.Print(LevelInfo, ComponentCommand, "foo", "bar", "baz")
40+
b.RunParallel(func(pb *testing.PB) {
41+
for pb.Next() {
42+
logger.Print(LevelInfo, ComponentCommand, "foo", "bar", "baz")
43+
}
44+
})
45+
})
46+
}
47+
48+
func mockKeyValues(length int) (KeyValues, map[string]interface{}) {
49+
keysAndValues := KeyValues{}
50+
m := map[string]interface{}{}
51+
52+
for i := 0; i < length; i++ {
53+
keyName := fmt.Sprintf("key%d", i)
54+
valueName := fmt.Sprintf("value%d", i)
55+
56+
keysAndValues.Add(keyName, valueName)
57+
m[keyName] = valueName
58+
}
59+
60+
return keysAndValues, m
61+
}
62+
63+
func BenchmarkIOSinkInfo(b *testing.B) {
64+
keysAndValues, _ := mockKeyValues(10)
65+
66+
b.ReportAllocs()
67+
b.ResetTimer()
68+
69+
sink := NewIOSink(bytes.NewBuffer(nil))
70+
71+
b.RunParallel(func(pb *testing.PB) {
72+
for pb.Next() {
73+
sink.Info(0, "foo", keysAndValues...)
3874
}
3975
})
4076
}
4177

78+
func TestIOSinkInfo(t *testing.T) {
79+
t.Parallel()
80+
81+
const threshold = 1000
82+
83+
mockKeyValues, kvmap := mockKeyValues(10)
84+
85+
buf := new(bytes.Buffer)
86+
sink := NewIOSink(buf)
87+
88+
wg := sync.WaitGroup{}
89+
wg.Add(threshold)
90+
91+
for i := 0; i < threshold; i++ {
92+
go func() {
93+
defer wg.Done()
94+
95+
sink.Info(0, "foo", mockKeyValues...)
96+
}()
97+
}
98+
99+
wg.Wait()
100+
101+
dec := json.NewDecoder(buf)
102+
for dec.More() {
103+
var m map[string]interface{}
104+
if err := dec.Decode(&m); err != nil {
105+
t.Fatalf("error unmarshaling JSON: %v", err)
106+
}
107+
108+
delete(m, KeyTimestamp)
109+
delete(m, KeyMessage)
110+
111+
if !reflect.DeepEqual(m, kvmap) {
112+
t.Fatalf("expected %v, got %v", kvmap, m)
113+
}
114+
}
115+
}
116+
42117
func TestSelectMaxDocumentLength(t *testing.T) {
43118
t.Parallel()
44119

0 commit comments

Comments
 (0)