Reputation: 41
I'm using jsforce library for communicating with Salesforce. This is a backend service (one of the microservices) - used by other services.
I want to log every call to Salesforce, and as far as I was searching and investigating, such an ability does not exist in the library.
After investigating, I've found one way to achieve this. I'm not sure if this is the right approach (I didn't find another one), And this approach also makes problems...
The most common way that I'm using this library is first, creating a connection with:
conn = new jsforce.Connection
and afterwards for example, conn.sobject(Account).retrieve(...)
, ect.
So I wrapped the connection creation with SalesforceClient
object, that will wrap sobject
with a new SobjectWrapper
object that utilizes javascript Proxy:
class SobjectWrapper {
constructor(conn, sob, correlationId) {
this.conn = conn;
this.sob = sob;
this.correlationId = correlationId;
}
logAndExecute(method, methodName, args) {
const logInfo = {
operation: `${this.sob} - ${methodName}`,
data: {
...args[0],
correlation_id: this.correlationId
},
};
logger.info('Request to SF!', logInfo);
console.log('options: ', Object.getPrototypeOf(this.conn.sobject(this.sob).find(args[0])));
// if (methodName == 'find') {
// return this.conn.sobject(this.sob).find(args[0]);
// }
return method
.apply(this.conn.sobject(this.sob), args)
.then((response) => {
return response;
})
.catch((error) => {
logger.error('Error from SF!', {
error
});
throw error; // Propagate the error after logging
});
}
setCorrelationId(correlationId) {
this.correlationId = correlationId;
}
// Proxy all functions to add logging
createProxy() {
return new Proxy(this.conn.sobject(this.sob), {
get: (target, prop) => {
if (typeof target[prop] === 'function') {
console.log('------------------'); // TODO: remove
return (...args) => this.logAndExecute(target[prop], prop, args);
}
return target[prop];
},
});
}
}
class SalesforceClient {
constructor() {
this.conn = new jsforce.Connection({
oauth2: {
loginUrl: SF_LOGIN_URL,
serverUrl: SF_SERVER_URL,
clientId: SF_APP_ID,
clientSecret: SF_APP_SECRET,
},
});
this.conn.login(SF_USERNAME, SF_PASSWORD, function handleConnections(err) {
if (err) {
throw new Error(`Salesforce login faild - ${err.message}`, {});
}
});
this.correlationId = null;
}
setCorrelationId(correlationId) {
this.correlationId = correlationId;
}
sobject(sob) {
return new SobjectWrapper(this.conn, sob, this.correlationId).createProxy();
}
}
The idea is to call the normal library's function, but just before that to log everything I need.
The problem with this approach is logAndExecute
function.
When I have a call like: conn.sobject(Account).find({...}).execute(...)
(chains more functions)
It seems like the object looses its context, and I'm getting an error: conn.sobject(...).find(...).execute is not a function
.
When I'm printing the prototype of this.conn.sobject(this.sob).find(args[0])
I can clearly see many other functions that are chained to find({...})
, and I would like to still have those in the response's context.
And, Indeed, if I'm doing something like
if (methodName == 'find') { return this.conn.sobject(this.sob).find(args[0]); }
I don't want to call to each function separately, because there are many functions under this.conn.sobject(this.sob)
, and it will make me do a long if-else process.
It's not so bad, but it's probably not ideal ether.
Is it possible to somehow bind this context to the result?
Do you think there is another approach to tackle this issue?
Would greatly appreciate to hear your opinions! thanks.
Upvotes: 0
Views: 147
Reputation: 41
Seems my approach was good, only I had tiny a thing missing in my understanding of jsforce
library.
Apparently method.apply(this.conn.sobject(this.sob), args)
doesn't return a promise, but an object that happens to have a method then
.
So doing this inside logAndExecute
solved the issue with this approach:
let result;
try {
result = method.apply(this.conn.sobject(this.sob), args);
} catch (err) {
logger.error('Error from SF!', { ...logInfo, error: err });
throw err;
}
return result;
Upvotes: 0