inceptum
Version:
hipages take on the foundational library for enterprise-grade apps written in NodeJS
200 lines • 7.92 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RabbitmqConsumer = void 0;
const prom_client_1 = require("prom-client");
const NewrelicUtil_1 = require("../newrelic/NewrelicUtil");
const LogManager_1 = require("../log/LogManager");
const RabbitmqClient_1 = require("./RabbitmqClient");
const RabbitmqConsumerHandler_1 = require("./RabbitmqConsumerHandler");
const RabbitmqConsumerHandlerError_1 = require("./RabbitmqConsumerHandlerError");
const logger = LogManager_1.LogManager.getLogger(__filename);
const newrelic = NewrelicUtil_1.NewrelicUtil.getNewrelicIfAvailable();
class NewrelichandlerWrapper extends RabbitmqConsumerHandler_1.RabbitmqConsumerHandler {
constructor(baseHandler) {
super();
this.baseHandler = baseHandler;
}
async handle(message) {
await newrelic.startBackgroundTransaction(this.baseHandler.constructor.name, 'RabbitMQConsumer', async () => {
const transaction = newrelic.getTransaction();
try {
await this.baseHandler.handle(message);
}
finally {
transaction.end();
}
});
}
}
const rabbitmqConsumeTime = new prom_client_1.Histogram({
name: 'rabbitmq_consume_time',
help: 'Time required to consumer a messages from RabbitMQ',
labelNames: ['name'],
buckets: [0.003, 0.01, 0.05, 0.1, 0.3, 0.5, 1, 2, 5]
});
const rabbitmqConsumeErrorCounter = new prom_client_1.Counter({
name: 'rabbitmq_consume_error_counter',
help: 'Number of errors encountered when consuming messages',
labelNames: ['name'],
});
const rabbitmqConsumeDLQCounter = new prom_client_1.Counter({
name: 'rabbitmq_consume_to_dlq_counter',
help: 'Number of messages sent to the DLQ',
labelNames: ['name'],
});
const rabbitmqConsumeDelayedCounter = new prom_client_1.Counter({
name: 'rabbitmq_consume_delayed_counter',
help: 'Number of messages sent to the delay queue',
labelNames: ['name'],
});
class RabbitmqConsumer extends RabbitmqClient_1.RabbitmqClient {
constructor(clientConfig, name, consumerConfig, handler) {
super(clientConfig, name);
this.messageHandler = newrelic ? new NewrelichandlerWrapper(handler) : handler;
this.consumerConfig = Object.assign({}, consumerConfig);
this.consumerConfig.options = this.consumerConfig.options || {};
this.logger = logger;
this.consumeDurationHistogram = rabbitmqConsumeTime.labels(name);
this.consumeFailures = rabbitmqConsumeErrorCounter.labels(name);
this.consumeFailuresDLQ = rabbitmqConsumeDLQCounter.labels(name);
this.consumeFailuresDelayed = rabbitmqConsumeDelayedCounter.labels(name);
}
async init(addHandler = true) {
try {
await super.init(addHandler);
await this.subscribe(this.consumerConfig.appQueueName, this.consumerConfig);
}
catch (e) {
const c = Object.assign({}, this.consumerConfig);
this.logger.error(e, `failed to subscribe with config - ${JSON.stringify(c)}`);
NewrelicUtil_1.NewrelicUtil.noticeError(e, { config: c });
throw e;
}
}
/**
* Subscribe to a queue
*/
async subscribe(queueName, consumerConfig) {
if (consumerConfig.prefetch && consumerConfig.prefetch > 0) {
await this.channel.prefetch(consumerConfig.prefetch);
}
const rc = await this.channel.consume(queueName, (message) => {
this.handleMessage(message);
}, consumerConfig.options);
this.consumerTag = rc.consumerTag;
return rc;
}
async handleMessage(message) {
const timer = this.consumeDurationHistogram.startTimer();
try {
await this.messageHandler.handle(message);
this.channel.ack(message);
}
catch (e) {
this.logger.error(e, 'failed to handle message');
this.consumeFailures.inc();
NewrelicUtil_1.NewrelicUtil.noticeError(e, message);
if (!message) {
await this.cancelConsumerAndResubscribe();
return;
}
if (message.properties.headers === undefined) {
message.properties.headers = this.defaultHeader();
}
const retriesCount = ++message.properties.headers.retriesCount;
if (e instanceof RabbitmqConsumerHandlerError_1.RabbitmqConsumerHandlerUnrecoverableError || !this.allowRetry(retriesCount)) {
// add to dlq
this.consumeFailuresDLQ.inc();
try {
this.sendMessageToDlq(message);
this.channel.ack(message);
}
catch (err) {
this.logger.error(err, 'failed to send message to dlq');
NewrelicUtil_1.NewrelicUtil.noticeError(err, message);
this.channel.nack(message);
}
}
else {
this.consumeFailuresDelayed.inc();
try {
this.sendMessageToDelayedQueue(message, retriesCount, e);
this.channel.ack(message);
}
catch (error) {
// put message back to rabbitmq
this.logger.error(error, 'failed to send message to delayed queue');
NewrelicUtil_1.NewrelicUtil.noticeError(error, message);
try {
this.channel.nack(message);
}
catch (e) {
this.logger.error(error, 'failed to nack message');
NewrelicUtil_1.NewrelicUtil.noticeError(error, message);
}
}
}
}
finally {
timer();
}
}
sendMessageToDlq(message) {
this.channel.sendToQueue(this.consumerConfig.dlqName, message.content);
this.logger.info(this.stringyifyMessageContent(message), 'sent message to dlq');
}
sendMessageToDelayedQueue(message, retriesCount, e) {
const ct = this.stringyifyMessageContent(message);
// depending on retries config, retry
const ttl = this.getTtl(retriesCount);
const options = {
expiration: ttl,
headers: message.properties.headers,
};
this.channel.sendToQueue(this.consumerConfig.delayQueueName, message.content, options);
const data = {
queueName: this.consumerConfig.delayQueueName,
messageContent: ct,
options,
};
this.logger.info(data, `sent message to delayed queue`);
}
stringyifyMessageContent(message) {
return message.content.toString();
}
/**
*
* @param retriesCount number
* @reutrn number in milliseconds
*/
getTtl(retriesCount = 1) {
if (this.allowRetry(retriesCount)) {
return Math.pow(retriesCount, this.consumerConfig.retryDelayFactor)
* this.consumerConfig.retryDelayInMinute * 60 * 1000;
}
return 0;
}
allowRetry(retriesCount) {
return retriesCount && this.consumerConfig.maxRetries >= retriesCount;
}
async close() {
await super.close();
}
async cancelConsumerAndResubscribe() {
this.logger.error('Invalid message');
try {
await this.channel.cancel(this.consumerTag);
}
catch (e) {
this.logger.error(e, 'failed to cancel consumer');
}
try {
await this.closeAllAndScheduleReconnection();
}
catch (e) {
this.logger.error(e, 'failed to resubscribe');
}
}
}
exports.RabbitmqConsumer = RabbitmqConsumer;
//# sourceMappingURL=RabbitmqConsumer.js.map