Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve stack traces thrown from client functions #211

Merged
merged 9 commits into from May 29, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 31 additions & 28 deletions src/http.ts
Expand Up @@ -18,7 +18,7 @@ let lastRequestId = 0;
* @param {string} noEncoding Set to true to disable encoding, and return a Buffer. Defaults to false
* @returns {Promise<any>} Resolves to the response (body), rejected if a non-2xx status code was returned.
*/
export function doHttpRequest(baseUrl: string, method: "GET"|"POST"|"PUT"|"DELETE", endpoint: string, qs = null, body = null, headers = {}, timeout = 60000, raw = false, contentType = "application/json", noEncoding = false): Promise<any> {
export async function doHttpRequest(baseUrl: string, method: "GET"|"POST"|"PUT"|"DELETE", endpoint: string, qs = null, body = null, headers = {}, timeout = 60000, raw = false, contentType = "application/json", noEncoding = false): Promise<any> {
if (!endpoint.startsWith('/')) {
endpoint = '/' + endpoint;
}
Expand Down Expand Up @@ -62,52 +62,55 @@ export function doHttpRequest(baseUrl: string, method: "GET"|"POST"|"PUT"|"DELET
}
}

return new Promise((resolve, reject) => {
getRequestFn()(params, (err, response, resBody) => {
const {response, resBody} = await new Promise<{response: any, resBody: any}>((resolve, reject) => {
getRequestFn()(params, (err, res, rBody) => {
if (err) {
LogService.error("MatrixHttpClient", "(REQ-" + requestId + ")", err);
reject(err);
return;
}

if (typeof (resBody) === 'string') {
if (typeof (rBody) === 'string') {
try {
resBody = JSON.parse(resBody);
rBody = JSON.parse(resBody);
} catch (e) {
}
}

if (typeof (response.body) === 'string') {
if (typeof (res.body) === 'string') {
try {
response.body = JSON.parse(response.body);
res.body = JSON.parse(res.body);
} catch (e) {
}
}

const respIsBuffer = (response.body instanceof Buffer);
resolve({response: res, resBody: rBody});
});
});

// Check for errors.
const errBody = response.body || resBody;
if (errBody && 'errcode' in errBody) {
const redactedBody = respIsBuffer ? '<Buffer>' : redactObjectForLogging(errBody);
LogService.error("MatrixHttpClient (REQ-" + requestId + ")", redactedBody);
reject(new MatrixError(errBody, response.statusCode));
return;
}
const respIsBuffer = (response.body instanceof Buffer);

// Don't log the body unless we're in debug mode. They can be large.
if (LogService.level.includes(LogLevel.TRACE)) {
const redactedBody = respIsBuffer ? '<Buffer>' : redactObjectForLogging(response.body);
LogService.trace("MatrixHttpClient (REQ-" + requestId + " RESP-H" + response.statusCode + ")", redactedBody);
}
// Check for errors.
const errBody = response.body || resBody;
if (typeof (errBody) === "object" && 'errcode' in errBody) {
const redactedBody = respIsBuffer ? '<Buffer>' : redactObjectForLogging(errBody);
LogService.error("MatrixHttpClient (REQ-" + requestId + ")", redactedBody);
throw new MatrixError(errBody, response.statusCode);
return;
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
}

if (response.statusCode < 200 || response.statusCode >= 300) {
const redactedBody = respIsBuffer ? '<Buffer>' : redactObjectForLogging(response.body);
LogService.error("MatrixHttpClient (REQ-" + requestId + ")", redactedBody);
reject(response);
} else resolve(raw ? response : resBody);
});
});
// Don't log the body unless we're in debug mode. They can be large.
if (LogService.level.includes(LogLevel.TRACE)) {
const redactedBody = respIsBuffer ? '<Buffer>' : redactObjectForLogging(response.body);
LogService.trace("MatrixHttpClient (REQ-" + requestId + " RESP-H" + response.statusCode + ")", redactedBody);
}

if (response.statusCode < 200 || response.statusCode >= 300) {
const redactedBody = respIsBuffer ? '<Buffer>' : redactObjectForLogging(response.body);
LogService.error("MatrixHttpClient (REQ-" + requestId + ")", redactedBody);
throw response;
}
return (raw ? response : resBody);
}

export function redactObjectForLogging(input: any): any {
Expand Down
111 changes: 35 additions & 76 deletions src/metrics/decorators.ts
Expand Up @@ -16,40 +16,25 @@ import { IdentityClientCallContext, IntentCallContext, MatrixClientCallContext }
* @category Metrics
*/
export function timedMatrixClientFunctionCall() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
return function (_target: never, functionName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const metrics = this.metrics;

const context = metrics.assignUniqueContextId(<MatrixClientCallContext>{
functionName: propertyKey,
descriptor.value = async function (...args) {
const context = this.metrics.assignUniqueContextId(<MatrixClientCallContext>{
functionName,
client: this,
});
metrics.start(METRIC_MATRIX_CLIENT_FUNCTION_CALL, context);

let result;
let exception;

this.metrics.start(METRIC_MATRIX_CLIENT_FUNCTION_CALL, context);
try {
result = originalMethod.apply(this, args);
const result = await originalMethod.apply(this, args);
this.metrics.increment(METRIC_MATRIX_CLIENT_SUCCESSFUL_FUNCTION_CALL, context, 1);
return result;
} catch (e) {
exception = e;
result = Promise.reject(e);
}

let promise = result;
if (!(result instanceof Promise) && result !== null && result !== undefined) {
promise = Promise.resolve(result);
this.metrics.increment(METRIC_MATRIX_CLIENT_FAILED_FUNCTION_CALL, context, 1);
throw e;
} finally {
this.metrics.end(METRIC_MATRIX_CLIENT_FUNCTION_CALL, context)
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}

promise
.then(() => metrics.increment(METRIC_MATRIX_CLIENT_SUCCESSFUL_FUNCTION_CALL, context, 1))
.catch(() => metrics.increment(METRIC_MATRIX_CLIENT_FAILED_FUNCTION_CALL, context, 1))
.finally(() => metrics.end(METRIC_MATRIX_CLIENT_FUNCTION_CALL, context));

if (exception) throw exception;
return result;
}
};
};
}

Expand All @@ -58,39 +43,26 @@ export function timedMatrixClientFunctionCall() {
* @category Metrics
*/
export function timedIdentityClientFunctionCall() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
return function (_target: never, functionName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
descriptor.value = async function (...args: any[]) {
const metrics = this.metrics;

const context = metrics.assignUniqueContextId(<IdentityClientCallContext>{
functionName: propertyKey,
functionName,
client: this,
});
metrics.start(METRIC_IDENTITY_CLIENT_FUNCTION_CALL, context);

let result;
let exception;

this.metrics.start(METRIC_IDENTITY_CLIENT_FUNCTION_CALL, context);
try {
result = originalMethod.apply(this, args);
const result = await originalMethod.apply(this, args);
this.metrics.increment(METRIC_IDENTITY_CLIENT_SUCCESSFUL_FUNCTION_CALL, context, 1);
return result;
} catch (e) {
exception = e;
result = Promise.reject(e);
this.metrics.increment(METRIC_IDENTITY_CLIENT_FAILED_FUNCTION_CALL, context, 1);
throw e;
} finally {
this.metrics.end(METRIC_IDENTITY_CLIENT_FUNCTION_CALL, context)
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}

let promise = result;
if (!(result instanceof Promise) && result !== null && result !== undefined) {
promise = Promise.resolve(result);
}

promise
.then(() => metrics.increment(METRIC_IDENTITY_CLIENT_SUCCESSFUL_FUNCTION_CALL, context, 1))
.catch(() => metrics.increment(METRIC_IDENTITY_CLIENT_FAILED_FUNCTION_CALL, context, 1))
.finally(() => metrics.end(METRIC_IDENTITY_CLIENT_FUNCTION_CALL, context));

if (exception) throw exception;
return result;
}
};
}
Expand All @@ -100,40 +72,27 @@ export function timedIdentityClientFunctionCall() {
* @category Metrics
*/
export function timedIntentFunctionCall() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
return function (_target: never, functionName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
descriptor.value = async function (...args: any[]) {
const metrics = this.metrics;

const context = metrics.assignUniqueContextId(<IntentCallContext>{
functionName: propertyKey,
functionName,
client: this.client,
intent: this,
});
metrics.start(METRIC_INTENT_FUNCTION_CALL, context);

let result;
let exception;

this.metrics.start(METRIC_INTENT_FUNCTION_CALL, context);
try {
result = originalMethod.apply(this, args);
const result = await originalMethod.apply(this, args);
this.metrics.increment(METRIC_INTENT_SUCCESSFUL_FUNCTION_CALL, context, 1);
return result;
} catch (e) {
exception = e;
result = Promise.reject(e);
}

let promise = result;
if (!(result instanceof Promise) && result !== null && result !== undefined) {
promise = Promise.resolve(result);
this.metrics.increment(METRIC_INTENT_FAILED_FUNCTION_CALL, context, 1);
throw e;
} finally {
this.metrics.end(METRIC_INTENT_FUNCTION_CALL, context)
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}

promise
.then(() => metrics.increment(METRIC_INTENT_SUCCESSFUL_FUNCTION_CALL, context, 1))
.catch(() => metrics.increment(METRIC_INTENT_FAILED_FUNCTION_CALL, context, 1))
.finally(() => metrics.end(METRIC_INTENT_FUNCTION_CALL, context));

if (exception) throw exception;
return result;
}
};
}
2 changes: 1 addition & 1 deletion tsconfig.json
Expand Up @@ -4,7 +4,7 @@
"emitDecoratorMetadata": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2015",
"target": "es2020",
"noImplicitAny": false,
"sourceMap": false,
"outDir": "./lib",
Expand Down