CatchError function of response interceptor execute multiple times - Angular 6 - node.js

I created a interceptor for my angular project to intercept all the requests and responses, but the function that validates errors in the responses is executed 7 times.
I realized that when I use the throwError of rjxs it performs the function many times, if I use the of rxjs it executes only one, but fails to execute functions that validate errors in subscribes.
constructor(private injector: Injector, public errorHandler: ApplicationErrorHandler) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const sessaoService = this.injector.get(SessaoService);
if (sessaoService.isLoogedIn()) {
const token = localStorage.getItem('token');
const tokenSplit = token.split(' ');
request = request.clone(
{ setHeaders: { 'Authorization': `${tokenSplit[1]}` } }
);
}
return next.handle(request).pipe(
catchError((err: HttpErrorResponse) => {
console.log('Execute function');
let data = {};
data = {
error: err,
status: err.status,
method: request.method
};
this.errorHandler.handleError(data);
return throwError(err);
})
);
}
I expect that catchError function execute only one time, but it is running 7 times for each request.
My Angular version is: 6.1.3;
My Rxjs version is: 6.4.0;
Sorry for my bad english...

I found the problem, I have a shared module in my application and inside it I had added HTTP_INTERCEPTORS as provider using my class with theintercept function.

Related

Google Cloud Function failed with timeout

I have a Pub/Sub triggered cloud function that calls an API end-point and logs the message. But I am not seeing all log messages being logged in console except everything right before calling API.
Once the API is called I am logging the response, and exception messages in case of any error.
It is logging: Function execution took 120015 ms. Finished with status: timeout Earlier the default timeout was set to 60 sec, later I increased it to 120 sec. Still the problem persist.
I am not understanding the issue here since it is working locally without any issues.
Here I have custom module to log messages to Winston and GCP console (it doesn't have any issue and working fine).
Code calling the API module:
const console = require('./logging-utils');
const portal_api = require('./api-utils');
exports.triggerPortalNotifier = async (event, context) => {
try {
/*
.....
*/
console.metadata.cloudFunction = cf_name;
console.metadata.requestId = requestId;
console.metadata.organizationId = organizationId;
console.metadata.instanceId = instanceId;
console.logMessage(`Event received with payload: some message`);
var payload = {
//payload to API
}
var response = await portal_api.notifyPortal(payload);
console.logMessage(`Response received from portal API is: ${JSON.stringify(response.data)}`);
}
else {
throw new Error(`Invalid message received: ${_message}`);
}
}
catch (error) {
console.logMessage(`Portal API failed with exception: ${error}`);
throw new Error(`${error.message}`);
}
}
Code that make API request (using axios module)
require('dotenv').config();
const axios = require('./axios-instance');
const console = require('./logging-utils');
const nextgen_api = {
notifyPortal: async (payload) => {
try {
const config = {
headers: {
'Authorization': process.env.PORTAL_AUTHORIZATION_TOKEN,
'Content-Type': "application/json",
'Accept': "application/plain"
}
}
console.logMessage(`Input paylod for API end-point: ${process.env.PORTAL_API} => ${JSON.stringify(payload)}`)
const response = await axios.post(process.env.PORTAL_API, JSON.parse(JSON.stringify(payload)), config);
console.logMessage(`Response from API: ${JSON.stringify(response.data)}`);
return response;
}
catch (err) {
if (err.response && err.response.status !== 200) {
console.logMessage(`API call failed with status code: ${err.response.status} `);
throw new Error(`API call failed with status code: ${err.response.status} `);
}
else {
console.logMessage(`API call failed with ${err.stack}`);
throw new Error(`API call failed with status code: ${err.stack} `);
}
}
}
}
module.exports = my_api;
Message Response from API: ${JSON.stringify(response.data)} is not being logged.
Any help here is appreciated.

NestJs include TypeORM repository in middleware

I have the following middleware to log all http requests:
#Injectable()
export class RequestMiddleware implements NestMiddleware {
constructor(
#InjectRepository(Request) private requestsRepository: Repository<Request>,
) {}
private readonly logger = new Logger('HTTP');
use(request: Request, response: Response, next: NextFunction) {
response.on('finish', () => {
const { method, originalUrl } = request;
const { statusCode, statusMessage } = response;
const message = `${method} ${originalUrl} ${statusCode} ${statusMessage}`;
return this.logger.log(message);
});
next();
}
}
My goal is to log all requests to a database. I am using TypeORM so I would like to inject the Request repository and log each request that way. When I do this I receive the following error:
Error: Nest can't resolve dependencies of the class RequestMiddleware
The issue is that this middleware isn't part of a module, it's just a single typescript file so how do I import the TypeORM repo module into a normal typescript file so I can use it?
In the module where RequestMiddleware is defined and used, TypeormModule.forFeature([Request]) needs to be added to the imports array

Proper way of formatting logs to sentry

I am using NestJS to build the rest APIs and trying to implement logging using Sentry but did not get the right way of formatting logs before sending them to sentry.
I already have a filter that catches every kind of log (HTTP as well as application errors). And I'm trying to implement sentry globally without having it to inject into each controller and service.
error-filter.ts
import {
HttpExceptionResponse,
CustomeHttpExceptionResponse,
} from './modals/http-exception-response.interface';
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '#nestjs/common';
import * as fs from 'fs';
import { Request } from 'express';
import { captureException } from '#sentry/node';
#Catch()
export class HttpErrorFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
let status: HttpStatus;
let errorMessage: string;
if (exception instanceof HttpException) {
status = exception.getStatus();
const errorResponse = exception.getResponse();
errorMessage =
(errorResponse as HttpExceptionResponse).error ||
exception.message ||
"Sorry, can't process this request at the moment!";
} else {
status = HttpStatus.INTERNAL_SERVER_ERROR;
errorMessage = 'Internal server error!';
}
const errorResponse = this.getErrorResponse(status, errorMessage, request);
const errorLog = this.getErrorLog(errorResponse, request, exception);
// this.writeErrorLogToFile(errorLog);
this.sendLogsToSentry(errorLog);
response.status(status).json(errorResponse);
}
private getErrorResponse = (
status: HttpStatus,
errorMessage: string,
request: Request,
): CustomeHttpExceptionResponse => ({
statusCode: status,
error: errorMessage,
path: request.url,
method: request.method,
timeStamp: new Date(),
message: errorMessage,
});
private getErrorLog = (
errorResponse: CustomeHttpExceptionResponse,
request: Request,
exception: unknown,
): string => {
const { statusCode, error } = errorResponse;
const { method, url } = request;
const errorLog = ` Response code: ${statusCode} - Method: ${method} - URL: ${url}\n
${JSON.stringify(errorResponse)}\n
User: ${JSON.stringify(request.user ?? 'User not signed in')}\n
Details: ${
exception instanceof HttpException ? exception.stack : error
}\n\n`;
return errorLog;
};
private writeErrorLogToFile = (errorLog: string): void => {
fs.appendFile('error.log', errorLog, 'utf8', (err) => {
if (err) throw err;
});
};
private sendLogsToSentry = async (errorLog: any) => {
captureException(errorLog);
};
}
main.ts
....
// Sentry setup for logging
const configService = app.get(ConfigService);
const sentryClientKey = configService.get('SENTRY_CLIENT_KEY');
Sentry.init({
dsn: sentryClientKey,
tracesSampleRate: 1.0,
});
These are the only two files I tried for setting up the sentry.
.....
With this, the errors are being successfully captured but not in the way they have to.
I can't get the full information like Stacktrace for non-HTTP errors, no user info, and many other missing pieces. I know that I've constructed the log as a string and sent it to Sentry but I tried sending the whole exception object too, and that event didn't work.
I tried looking into the documentation but couldn't figure it out. So how the logs should be formatted to get the most out of it in sentry? I am searching for a solution with less code change. But if it isn't possible without injecting Sentry into the service and controller, will do that. Please guide me on how.

There is a way to make Axios return the data as default response?

When we use Axios we always have to get the data from response. Like this:
const response = await Axios.get('/url')
const data = response.data
There is a way to make Axios return the data already? Like this:
const data = await Axios.get('/url')
We never used anything besides the data from the response.
You can use ES6 Destructing like this:
const { data } = await Axios.get('/url');
So you won't have write another line of code.
add a response interceptors
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response.data; // do like this
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
what i normally do is create a js file called interceptors.js
import axios from 'axios';
export function registerInterceptors() {
axios.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response.data;
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
}
);
}
in ./src/index.js
import { registerInterceptors } from './path/to/interceptors';
registerInterceptors();//this will register the interceptors.
For a best practice don't use axios every where, just in case in the future if you want to migrate to a different http provider then you have to change everywhere it uses.
create a wrapper around axios and use that wrapper in your app
for ex:
create a js file called http.js
const execute = ({url, method, params, data}) => {
return axios({
url,
method,//GET or POST
data,
params,
});
}
const get = (url, params) => {
return execute({
url, method: 'GET', params
})
}
const post = (url, data) => {
return execute({
url, method: 'POST', data
})
}
export default {
get,
post,
};
and use it like
import http from './http';
....
http.get('url', {a:1, b:2})
so now you can customize all over the app, even changing the http provider is so simple.

How can I change the result status in Axios with an adapter?

The why
We're using the axios-retry library, which uses this code internally:
axios.interceptors.response.use(null, error => {
Since it only specifies the error callback, the Axios documentation says:
Any status codes that falls outside the range of 2xx cause this function to trigger
Unfortunately we're calling a non-RESTful API that can return 200 with an error code in the body, and we need to retry that.
We've tried adding an Axios interceptor before axios-retry does and changing the result status in this case; that did not trigger the subsequent interceptor error callback though.
What did work was specifying a custom adapter. However this is not well-documented and our code does not handle every case.
The code
const axios = require('axios');
const httpAdapter = require('axios/lib/adapters/http');
const settle = require('axios/lib/core/settle');
const axiosRetry = require('axios-retry');
const myAdapter = async function(config) {
return new Promise((resolve, reject) => {
// Delegate to default http adapter
return httpAdapter(config).then(result => {
// We would have more logic here in the production code
if (result.status === 200) result.status = 500;
settle(resolve, reject, result);
return result;
});
});
}
const axios2 = axios.create({
adapter: myAdapter
});
function isErr(error) {
console.log('retry checking response', error.response.status);
return !error.response || (error.response.status === 500);
}
axiosRetry(axios2, {
retries: 3,
retryCondition: isErr
});
// httpstat.us can return various status codes for testing
axios2.get('http://httpstat.us/200')
.then(result => {
console.log('Result:', result.data);
})
.catch(e => console.error('Service returned', e.message));
This works in the error case, printing:
retry checking response 500
retry checking response 500
retry checking response 500
retry checking response 500
Service returned Request failed with status code 500
It works in the success case too (change the URL to http://httpstat.us/201):
Result: { code: 201, description: 'Created' }
The issue
Changing the URL to http://httpstat.us/404, though, results in:
(node:19759) UnhandledPromiseRejectionWarning: Error: Request failed with status code 404
at createError (.../node_modules/axios/lib/core/createError.js:16:15)
at settle (.../node_modules/axios/lib/core/settle.js:18:12)
A catch on the httpAdapter call will catch that error, but how do we pass that down the chain?
What is the correct way to implement an Axios adapter?
If there is a better way to handle this (short of forking the axios-retry library), that would be an acceptable answer.
Update
A coworker figured out that doing .catch(e => reject(e)) (or just .catch(reject)) on the httpAdapter call appears to handle the issue. However we'd still like to have a canonical example of implementing an Axios adapter that wraps the default http adapter.
Here's what worked (in node):
const httpAdapter = require('axios/lib/adapters/http');
const settle = require('axios/lib/core/settle');
const customAdapter = config =>
new Promise((resolve, reject) => {
httpAdapter(config).then(response => {
if (response.status === 200)
// && response.data contains particular error
{
// log if desired
response.status = 503;
}
settle(resolve, reject, response);
}).catch(reject);
});
// Then do axios.create() and pass { adapter: customAdapter }
// Now set up axios-retry and its retryCondition will be checked
Workaround with interceptor and custom error
const axios = require("axios").default;
const axiosRetry = require("axios-retry").default;
axios.interceptors.response.use(async (response) => {
if (response.status == 200) {
const err = new Error("I want to retry");
err.config = response.config; // axios-retry using this
throw err;
}
return response;
});
axiosRetry(axios, {
retries: 1,
retryCondition: (error) => {
console.log("retryCondition");
return false;
},
});
axios
.get("https://example.com/")
.catch((err) => console.log(err.message)); // gonna be here anyway as we'll fail due to interceptor logic

Resources