Skip to content

Commit d6a9e48

Browse files
feat(NODE-4687): Add logging to server selection
1 parent 9126b3e commit d6a9e48

File tree

14 files changed

+1773
-0
lines changed

14 files changed

+1773
-0
lines changed

src/constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ export const TOPOLOGY_OPENING = 'topologyOpening' as const;
2424
export const TOPOLOGY_CLOSED = 'topologyClosed' as const;
2525
export const TOPOLOGY_DESCRIPTION_CHANGED = 'topologyDescriptionChanged' as const;
2626
/** @internal */
27+
export const SERVER_SELECTION_STARTED = 'serverSelectionStarted' as const;
28+
/** @internal */
29+
export const SERVER_SELECTION_FAILED = 'serverSelectionFailed' as const;
30+
/** @internal */
31+
export const SERVER_SELECTION_SUCCEEDED = 'serverSelectionSucceeded' as const;
32+
/** @internal */
33+
export const WAITING_FOR_SUITABLE_SERVER = 'waitingForSuitableServer' as const;
34+
/** @internal */
2735
export const CONNECTION_POOL_CREATED = 'connectionPoolCreated' as const;
2836
/** @internal */
2937
export const CONNECTION_POOL_CLOSED = 'connectionPoolClosed' as const;

src/sdam/server_selection_events.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import {
2+
SERVER_SELECTION_FAILED,
3+
SERVER_SELECTION_STARTED,
4+
SERVER_SELECTION_SUCCEEDED,
5+
WAITING_FOR_SUITABLE_SERVER
6+
} from '../constants';
7+
import { type ReadPreference } from '../read_preference';
8+
import { type ServerSelector } from './server_selection';
9+
import type { TopologyDescription } from './topology_description';
10+
11+
/**
12+
* The base export class for all monitoring events published from server selection
13+
* @public
14+
* @category Event
15+
*/
16+
export abstract class ServerSelectionEvent {
17+
/** String representation of the selector being used to select the server.
18+
* Defaults to 'custom selector' for application-provided custom selector case.
19+
*/
20+
selector: string;
21+
/** The name of the operation for which a server is being selected. */
22+
operation: string;
23+
/** String representation of the current topology description. */
24+
topologyDescription: string;
25+
26+
/** @internal */
27+
abstract name:
28+
| typeof SERVER_SELECTION_STARTED
29+
| typeof SERVER_SELECTION_SUCCEEDED
30+
| typeof SERVER_SELECTION_FAILED
31+
| typeof WAITING_FOR_SUITABLE_SERVER;
32+
33+
/** @internal */
34+
constructor(
35+
selector: string | ReadPreference | ServerSelector,
36+
operation: string | undefined,
37+
topologyDescription: TopologyDescription
38+
) {
39+
this.selector =
40+
typeof selector === 'string'
41+
? selector
42+
: typeof selector === 'function'
43+
? 'custom selector'
44+
: JSON.stringify(selector.toJSON(), null, 2);
45+
this.operation = operation ?? 'custom operation';
46+
this.topologyDescription = topologyDescription.toString();
47+
}
48+
}
49+
50+
/**
51+
* An event published when server selection starts
52+
* @public
53+
* @category Event
54+
*/
55+
export class ServerSelectionStartedEvent extends ServerSelectionEvent {
56+
/** @internal */
57+
name = SERVER_SELECTION_STARTED;
58+
message = 'Server selection started';
59+
60+
/** @internal */
61+
constructor(
62+
selector: string | ReadPreference | ServerSelector,
63+
operation: string | undefined,
64+
topologyDescription: TopologyDescription
65+
) {
66+
super(selector, operation, topologyDescription);
67+
}
68+
}
69+
70+
/**
71+
* An event published when a server selection fails
72+
* @public
73+
* @category Event
74+
*/
75+
export class ServerSelectionFailedEvent extends ServerSelectionEvent {
76+
/** @internal */
77+
name = SERVER_SELECTION_FAILED;
78+
message = 'Server selection failed';
79+
/** Representation of the error the driver will throw regarding server selection failing. */
80+
failure: string;
81+
82+
/** @internal */
83+
constructor(
84+
selector: string | ReadPreference | ServerSelector,
85+
operation: string | undefined,
86+
topologyDescription: TopologyDescription,
87+
errMsg: string
88+
) {
89+
super(selector, operation, topologyDescription);
90+
this.failure = errMsg;
91+
}
92+
}
93+
94+
/**
95+
* An event published when server selection succeeds
96+
* @public
97+
* @category Event
98+
*/
99+
export class ServerSelectionSuccessEvent extends ServerSelectionEvent {
100+
/** @internal */
101+
name = SERVER_SELECTION_SUCCEEDED;
102+
message = 'Server selection succeeded';
103+
/** The hostname, IP address, or Unix domain socket path for the selected server.*/
104+
serverHost: string;
105+
/** The port for the selected server. Optional; not present for Unix domain sockets.
106+
* When the user does not specify a port and the default (27017) is used
107+
* */
108+
serverPort: number;
109+
110+
/** @internal */
111+
constructor(
112+
selector: string | ReadPreference | ServerSelector,
113+
operation: string | undefined,
114+
topologyDescription: TopologyDescription,
115+
serverHost: string,
116+
serverPort: number
117+
) {
118+
super(selector, operation, topologyDescription);
119+
this.serverHost = serverHost;
120+
this.serverPort = serverPort;
121+
}
122+
}
123+
124+
/**
125+
* An event published when server selection is waiting for a suitable server to become available
126+
* @public
127+
* @category Event
128+
*/
129+
export class WaitingForSuitableServerEvent extends ServerSelectionEvent {
130+
/** @internal */
131+
name = WAITING_FOR_SUITABLE_SERVER;
132+
message = 'Waiting for suitable server to become available';
133+
/** The remaining time left until server selection will time out. */
134+
remainingTimeMS?: number;
135+
136+
/** @internal */
137+
constructor(
138+
selector: string | ReadPreference | ServerSelector,
139+
operation: string | undefined,
140+
topologyDescription: TopologyDescription,
141+
remainingTimeMS: number | undefined
142+
) {
143+
super(selector, operation, topologyDescription);
144+
if (remainingTimeMS) {
145+
this.remainingTimeMS = remainingTimeMS;
146+
}
147+
}
148+
}

src/sdam/topology.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ import type { ServerMonitoringMode } from './monitor';
6969
import { Server, type ServerEvents, type ServerOptions } from './server';
7070
import { compareTopologyVersion, ServerDescription } from './server_description';
7171
import { readPreferenceServerSelector, type ServerSelector } from './server_selection';
72+
import {
73+
ServerSelectionFailedEvent,
74+
ServerSelectionStartedEvent,
75+
ServerSelectionSuccessEvent,
76+
WaitingForSuitableServerEvent
77+
} from './server_selection_events';
7278
import { SrvPoller, type SrvPollingEvent } from './srv_polling';
7379
import { TopologyDescription } from './topology_description';
7480

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as path from 'path';
2+
3+
import { loadSpecTests } from '../../spec';
4+
import { runUnifiedSuite } from '../../tools/unified-spec-runner/runner';
5+
6+
describe.only('Server Selection Tests - Unified', function () {
7+
runUnifiedSuite(loadSpecTests(path.join('server-selection', 'logging')));
8+
});
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
{
2+
"description": "server-selection-logging",
3+
"schemaVersion": "1.13",
4+
"runOnRequirements": [
5+
{
6+
"topologies": [
7+
"load-balanced"
8+
]
9+
}
10+
],
11+
"createEntities": [
12+
{
13+
"client": {
14+
"id": "client",
15+
"uriOptions": {
16+
"heartbeatFrequencyMS": 500
17+
},
18+
"observeLogMessages": {
19+
"serverSelection": "debug"
20+
},
21+
"observeEvents": [
22+
"serverDescriptionChangedEvent"
23+
]
24+
}
25+
},
26+
{
27+
"database": {
28+
"id": "database",
29+
"client": "client",
30+
"databaseName": "logging-tests"
31+
}
32+
},
33+
{
34+
"collection": {
35+
"id": "collection",
36+
"database": "database",
37+
"collectionName": "server-selection"
38+
}
39+
}
40+
],
41+
"tests": [
42+
{
43+
"description": "A successful operation - load balanced cluster",
44+
"operations": [
45+
{
46+
"name": "waitForEvent",
47+
"object": "testRunner",
48+
"arguments": {
49+
"client": "client",
50+
"event": {
51+
"serverDescriptionChangedEvent": {
52+
"newDescription": {
53+
"type": "LoadBalancer"
54+
}
55+
}
56+
},
57+
"count": 1
58+
}
59+
},
60+
{
61+
"name": "insertOne",
62+
"object": "collection",
63+
"arguments": {
64+
"document": {
65+
"x": 1
66+
}
67+
}
68+
}
69+
],
70+
"expectLogMessages": [
71+
{
72+
"client": "client",
73+
"messages": [
74+
{
75+
"level": "debug",
76+
"component": "serverSelection",
77+
"data": {
78+
"message": "Server selection started",
79+
"selector": {
80+
"$$exists": true
81+
},
82+
"operation": "insert",
83+
"topologyDescription": {
84+
"$$exists": true
85+
}
86+
}
87+
},
88+
{
89+
"level": "debug",
90+
"component": "serverSelection",
91+
"data": {
92+
"message": "Server selection succeeded",
93+
"selector": {
94+
"$$exists": true
95+
},
96+
"operation": "insert",
97+
"topologyDescription": {
98+
"$$exists": true
99+
}
100+
}
101+
}
102+
]
103+
}
104+
]
105+
}
106+
]
107+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
description: "server-selection-logging"
2+
3+
schemaVersion: "1.13"
4+
5+
runOnRequirements:
6+
- topologies:
7+
- load-balanced
8+
9+
createEntities:
10+
- client:
11+
id: &client client
12+
uriOptions:
13+
heartbeatFrequencyMS: 500
14+
observeLogMessages:
15+
serverSelection: debug
16+
observeEvents:
17+
- serverDescriptionChangedEvent
18+
- database:
19+
id: &database database
20+
client: *client
21+
databaseName: &databaseName logging-tests
22+
- collection:
23+
id: &collection collection
24+
database: *database
25+
collectionName: &collectionName server-selection
26+
27+
tests:
28+
- description: "A successful operation - load balanced cluster"
29+
operations:
30+
# ensure we've discovered the entire topology before starting.
31+
- name: waitForEvent
32+
object: testRunner
33+
arguments:
34+
client: *client
35+
event:
36+
serverDescriptionChangedEvent:
37+
newDescription:
38+
type: LoadBalancer
39+
count: 1
40+
- name: insertOne
41+
object: *collection
42+
arguments:
43+
document: { x : 1 }
44+
expectLogMessages:
45+
- client: *client
46+
messages:
47+
- level: debug
48+
component: serverSelection
49+
data:
50+
message: "Server selection started"
51+
selector: { $$exists: true }
52+
operation: insert
53+
topologyDescription: { $$exists: true }
54+
- level: debug
55+
component: serverSelection
56+
data:
57+
message: "Server selection succeeded"
58+
selector: { $$exists: true }
59+
operation: insert
60+
topologyDescription: { $$exists: true }

0 commit comments

Comments
 (0)