Skip to content

Commit 3bf15bc

Browse files
SNOW-1633278 connection layer (#911)
1 parent 5175843 commit 3bf15bc

File tree

10 files changed

+365
-42
lines changed

10 files changed

+365
-42
lines changed

lib/connection/connection.js

Lines changed: 164 additions & 19 deletions
Large diffs are not rendered by default.

lib/connection/connection_context.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const Util = require('../util');
66
const Errors = require('../errors');
77
const SfService = require('../services/sf');
88
const LargeResultSetService = require('../services/large_result_set');
9+
const Logger = require('../logger');
910

1011
/**
1112
* Creates a new ConnectionContext.
@@ -18,26 +19,31 @@ const LargeResultSetService = require('../services/large_result_set');
1819
*/
1920
function ConnectionContext(connectionConfig, httpClient, config) {
2021
// validate input
22+
Logger.getInstance().trace('Creating ConnectionContext object.');
2123
Errors.assertInternal(Util.isObject(connectionConfig));
2224
Errors.assertInternal(Util.isObject(httpClient));
2325

2426
// if a config object was specified, verify
2527
// that it has all the information we need
2628
let sfServiceConfig;
2729
if (Util.exists(config)) {
30+
Logger.getInstance().trace('ConnectionContext - validating received config.');
31+
2832
Errors.assertInternal(Util.isObject(config));
2933
Errors.assertInternal(Util.isObject(config.services));
3034
Errors.assertInternal(Util.isObject(config.services.sf));
3135

3236
sfServiceConfig = config.services.sf;
3337
}
38+
Logger.getInstance().debug('ConnectionContext - received data was validated.');
3439

3540
// create a map that contains all the services we'll be using
3641
const services =
3742
{
3843
sf: new SfService(connectionConfig, httpClient, sfServiceConfig),
3944
largeResultSet: new LargeResultSetService(connectionConfig, httpClient)
4045
};
46+
Logger.getInstance().debug('ConnectionContext - services were instantiated.');
4147

4248
/**
4349
* Returns the ConnectionConfig for use by the connection.

lib/core.js

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ function Core(options) {
3232

3333
// set the logger instance
3434
Logger.setInstance(new (options.loggerClass)());
35+
Logger.getInstance().trace('Logger was initialized.');
3536

3637
// if a connection class is specified, it must be an object or function
3738
let connectionClass = options.connectionClass;
3839
if (Util.exists(connectionClass)) {
3940
Errors.assertInternal(
4041
Util.isObject(connectionClass) || Util.isFunction(connectionClass));
42+
Logger.getInstance().debug('Connection class provided in driver core options will be used.');
4143
} else {
4244
// fall back to Connection
4345
connectionClass = Connection;
46+
Logger.getInstance().debug('Connection class was not overridden. Default connection class will be used.');
4447
}
4548

4649
const qaMode = options.qaMode;
@@ -64,27 +67,38 @@ function Core(options) {
6467
// Alternatively, if the connectionOptions includes token information then we will use that
6568
// instead of the username/password
6669

70+
Logger.getInstance().info('Creating new connection object');
71+
6772
if (connectionOptions == null) {
73+
Logger.getInstance().info('Connection options were not specified. Loading connection configuration.');
6874
try {
6975
connectionOptions = loadConnectionConfiguration();
7076
} catch ( error ) {
71-
Logger.getInstance().debug(`Problem during reading connection configuration from file: ${error.message}`);
77+
Logger.getInstance().error('Unable to load the connection configuration. Error: %s', error.message);
7278
Errors.checkArgumentExists(Util.exists(connectionOptions),
7379
ErrorCodes.ERR_CONN_CREATE_MISSING_OPTIONS);
7480
}
7581
}
7682

7783
const validateCredentials = !config && (connectionOptions && !connectionOptions.sessionToken);
84+
7885
const connectionConfig =
7986
new ConnectionConfig(connectionOptions, validateCredentials, qaMode, clientInfo);
87+
Logger.getInstance().debug('Connection configuration object created');
8088

8189
// if an http client was specified in the options passed to the module, use
8290
// it, otherwise create a new HttpClient
8391
const httpClient = options.httpClient ||
8492
new options.httpClientClass(connectionConfig);
93+
Logger.getInstance().debug('HttpClient setup finished');
94+
8595

86-
return new connectionClass(
87-
new ConnectionContext(connectionConfig, httpClient, config));
96+
const connection = new connectionClass(
97+
new ConnectionContext(connectionConfig, httpClient, config)
98+
);
99+
100+
Logger.getInstance().info('Connection[id: %s] - connection object created successfully.', connection.getId());
101+
return connection;
88102
};
89103

90104
const instance =
@@ -124,13 +138,17 @@ function Core(options) {
124138
*/
125139
deserializeConnection: function (options, serializedConnection) {
126140
// check for missing serializedConfig
141+
Logger.getInstance().trace('Deserializing connection');
142+
127143
Errors.checkArgumentExists(Util.exists(serializedConnection),
128144
ErrorCodes.ERR_CONN_DESERIALIZE_MISSING_CONFIG);
129145

130146
// check for invalid serializedConfig
131147
Errors.checkArgumentValid(Util.isString(serializedConnection),
132148
ErrorCodes.ERR_CONN_DESERIALIZE_INVALID_CONFIG_TYPE);
133149

150+
Logger.getInstance().debug('Deserializing connection from string object');
151+
134152
// try to json-parse serializedConfig
135153
let config;
136154
try {
@@ -140,6 +158,7 @@ function Core(options) {
140158
Errors.checkArgumentValid(Util.isObject(config),
141159
ErrorCodes.ERR_CONN_DESERIALIZE_INVALID_CONFIG_FORM);
142160
}
161+
Logger.getInstance().debug('Connection deserialized successfully');
143162

144163
return createConnection(options, config);
145164
},
@@ -152,6 +171,7 @@ function Core(options) {
152171
* @returns {String} a serialized version of the connection.
153172
*/
154173
serializeConnection: function (connection) {
174+
Logger.getInstance().trace('Connection[id: %s] - serializing connection.', connection.getId());
155175
return connection ? connection.serialize() : connection;
156176
},
157177

@@ -161,11 +181,13 @@ function Core(options) {
161181
* @param {Object} options
162182
*/
163183
configure: function (options) {
184+
Logger.getInstance().debug('Configuring Snowflake core module.');
164185
const logLevel = extractLogLevel(options);
165186
const logFilePath = options.logFilePath;
166187
const additionalLogToConsole = options.additionalLogToConsole;
188+
167189
if (logLevel != null || logFilePath) {
168-
Logger.getInstance().debug(`Configuring logger with level: ${logLevel}, filePath: ${logFilePath}, additionalLogToConsole: ${additionalLogToConsole}`);
190+
Logger.getInstance().info('Configuring logger with level: %s, filePath: %s, additionalLogToConsole: %s', logLevel, logFilePath, additionalLogToConsole);
169191
Logger.getInstance().configure(
170192
{
171193
level: logLevel,
@@ -181,6 +203,7 @@ function Core(options) {
181203
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_INSECURE_CONNECT);
182204

183205
GlobalConfig.setInsecureConnect(insecureConnect);
206+
Logger.getInstance().debug('Setting insecureConnect to value from core options: %s', insecureConnect);
184207
}
185208

186209
const ocspFailOpen = options.ocspFailOpen;
@@ -189,6 +212,7 @@ function Core(options) {
189212
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_OCSP_MODE);
190213

191214
GlobalConfig.setOcspFailOpen(ocspFailOpen);
215+
Logger.getInstance().debug('Setting ocspFailOpen to value from core options: %s ', ocspFailOpen);
192216
}
193217

194218
const jsonColumnVariantParser = options.jsonColumnVariantParser;
@@ -197,6 +221,7 @@ function Core(options) {
197221
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_JSON_PARSER);
198222

199223
GlobalConfig.setJsonColumnVariantParser(jsonColumnVariantParser);
224+
Logger.getInstance().debug('Setting JSON Column Variant Parser to value from core options');
200225
}
201226

202227
const xmlColumnVariantParser = options.xmlColumnVariantParser;
@@ -206,8 +231,10 @@ function Core(options) {
206231
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_XML_PARSER);
207232

208233
GlobalConfig.setXmlColumnVariantParser(xmlColumnVariantParser);
234+
Logger.getInstance().debug('Setting XML Column Variant Parser to value from core options');
209235
} else if (Util.exists(xmlParserConfig)) {
210236
GlobalConfig.createXmlColumnVariantParserWithParameters(xmlParserConfig);
237+
Logger.getInstance().debug('Creating XML Column Variant Parser with parameters from core options');
211238
}
212239

213240
const keepAlive = options.keepAlive;
@@ -216,6 +243,7 @@ function Core(options) {
216243
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_KEEP_ALIVE);
217244

218245
GlobalConfig.setKeepAlive(keepAlive);
246+
Logger.getInstance().debug('Setting keepAlive to value from core options: %s', keepAlive);
219247
}
220248

221249
const useEnvProxy = options.useEnvProxy;
@@ -232,7 +260,8 @@ function Core(options) {
232260
ErrorCodes.ERR_GLOBAL_CONFIGURE_INVALID_CUSTOM_CREDENTIAL_MANAGER);
233261

234262
GlobalConfig.setCustomCredentialManager(customCredentialManager);
235-
}
263+
Logger.getInstance().debug('Setting customCredentialManager to value from core options %s', customCredentialManager);
264+
}
236265
}
237266
};
238267

@@ -275,15 +304,17 @@ function Core(options) {
275304
* @returns {Object}
276305
*/
277306
this.create = function () {
307+
Logger.getInstance().debug('Creating new connection from factory.');
278308
const connection = new createConnection(connectionOptions);
279309

280310
return new Promise((resolve, reject) => {
281311
connection.connect(
282312
function (err, conn) {
283313
if (err) {
284-
Logger.getInstance().error('Unable to connect: ' + err.message);
314+
Logger.getInstance().error('Connection[id: %s] - Unable to connect. Error: %s', conn.getId(), err.message);
285315
reject(new Error(err.message));
286316
} else {
317+
Logger.getInstance().debug('Connection[id: %s] - connected successfully. Callback called.', conn.getId());
287318
resolve(conn);
288319
}
289320
}
@@ -299,10 +330,13 @@ function Core(options) {
299330
* @returns {Object}
300331
*/
301332
this.destroy = function (connection) {
333+
Logger.getInstance().debug('Destroying connection instance.');
302334
return new Promise((resolve) => {
303-
connection.destroy(function (err) {
335+
connection.destroy(function (err, conn) {
304336
if (err) {
305-
Logger.getInstance().error('Unable to disconnect: ' + err.message);
337+
Logger.getInstance().error('Connection[id: %s] - disconnecting failed with error: %s', conn.getId(), err.message);
338+
} else {
339+
Logger.getInstance().debug('Connection[id: %s] - connection disconnected successfully. Callback called.', conn.getId());
306340
}
307341
resolve();
308342
});
@@ -317,6 +351,7 @@ function Core(options) {
317351
* @returns {Boolean}
318352
*/
319353
this.validate = async function (connection) {
354+
Logger.getInstance().debug('Connection[id: %s] - validating connection instance', connection.getId());
320355
return await connection.isValidAsync();
321356
};
322357
}
@@ -330,19 +365,25 @@ function Core(options) {
330365
* @returns {Object}
331366
*/
332367
const createPool = function createPool(connectionOptions, poolOptions) {
368+
Logger.getInstance().info('Creating connection pool with provided options');
369+
333370
const connectionPool = GenericPool.createPool(
334371
new ConnectionFactory(connectionOptions),
335372
poolOptions
336373
);
374+
Logger.getInstance().debug('Base for connection pool created');
337375

338376
// avoid infinite loop if factory creation fails
339377
connectionPool.on('factoryCreateError', function (err) {
378+
Logger.getInstance().error('Connection pool factory creation failed: %s', err.message);
340379
const clientResourceRequest = connectionPool._waitingClientsQueue.dequeue();
341380
if (clientResourceRequest) {
342381
clientResourceRequest.reject(err);
343382
}
344383
});
345384

385+
Logger.getInstance().info('Connection pool object created successfully');
386+
346387
return connectionPool;
347388
};
348389

lib/logger/execution_timer.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const { performance } = require('perf_hooks');
2+
const Logger = require('../logger');
3+
const Util = require('../util');
4+
5+
function ExecutionTimer() {
6+
let startTime = null;
7+
let endTime = null;
8+
9+
// Private function to log and check if the timer was started
10+
function wasStarted() {
11+
return Util.exists(startTime);
12+
13+
}
14+
15+
this.start = function () {
16+
startTime = performance.now();
17+
endTime = null; // Reset endTime if the timer is reused
18+
return this;
19+
};
20+
21+
22+
this.stop = function () {
23+
if (!wasStarted()) {
24+
// Returning this to allow chaining even after invalid call.
25+
// startTime can be used to check, if any start point was ever recorded.
26+
Logger.getInstance().debug('Tried to stop timer, that was not started');
27+
return this;
28+
}
29+
endTime = performance.now();
30+
return this;
31+
};
32+
33+
// Get the duration in milliseconds
34+
this.getDuration = function () {
35+
if (!wasStarted()) {
36+
return;
37+
}
38+
if (endTime === null) {
39+
endTime = performance.now();
40+
}
41+
return endTime - startTime;
42+
};
43+
}
44+
45+
module.exports = ExecutionTimer;

lib/logger/logging_utils.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const Util = require('../util');
2+
3+
exports.describePresence = function (valueToDescribe) {
4+
const VAR_PROVIDED_TEXT = 'provided';
5+
const VAR_NOT_PROVIDED_TEXT = 'not provided';
6+
7+
return Util.isNotEmptyAsString(valueToDescribe) ? VAR_PROVIDED_TEXT : VAR_NOT_PROVIDED_TEXT;
8+
};

lib/secret_detector.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ function SecretDetector(customPatterns, mock) {
7171
'`' +
7272
String.raw`\{\|\}~]{8,})`,
7373
'gi');
74+
const PASSCODE_PATTERN = new RegExp(String.raw`(passcode|otp|pin|otac)\s*([:=])\s*([0-9]{4,6})`, 'gi');
75+
7476

7577
function maskAwsKeys(text) {
7678
return text.replace(AWS_KEY_PATTERN, String.raw`$1$2****`);
@@ -100,6 +102,11 @@ function SecretDetector(customPatterns, mock) {
100102
return text.replace(PASSWORD_PATTERN, String.raw`$1$2****`);
101103
}
102104

105+
function maskPasscode(text) {
106+
return text.replace(PASSCODE_PATTERN, String.raw`$1$2****`);
107+
}
108+
109+
103110
/**
104111
* Masks any secrets.
105112
*
@@ -128,13 +135,15 @@ function SecretDetector(customPatterns, mock) {
128135
}
129136

130137
maskedtxt =
131-
maskConnectionToken(
132-
maskPassword(
133-
maskPrivateKeyData(
134-
maskPrivateKey(
135-
maskAwsToken(
136-
maskSasToken(
137-
maskAwsKeys(text)
138+
maskPasscode(
139+
maskConnectionToken(
140+
maskPassword(
141+
maskPrivateKeyData(
142+
maskPrivateKey(
143+
maskAwsToken(
144+
maskSasToken(
145+
maskAwsKeys(text)
146+
)
138147
)
139148
)
140149
)

lib/util.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,4 +892,11 @@ exports.getNoProxyEnv = function () {
892892
return noProxy.split(',').join('|');
893893
}
894894
return undefined;
895+
};
896+
897+
exports.isNotEmptyAsString = function (variable) {
898+
if (typeof variable === 'string') {
899+
return variable;
900+
}
901+
return exports.exists(variable);
895902
};

test/configureLogger.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const snowflake = require('../lib/snowflake');
55
/**
66
* @param logLevel one of OFF | ERROR | WARN | INFO | DEBUG | TRACE
77
*/
8-
exports.configureLogger = (logLevel = 'ERROR') => {
8+
exports.configureLogger = (logLevel = 'INFO') => {
99
Logger.getInstance().closeTransports();
1010
Logger.setInstance(new NodeLogger({ filePath: 'STDOUT' }));
1111
snowflake.configure({ logLevel });

0 commit comments

Comments
 (0)