UNPKG

@harvve/tslog-mongodb-transport

Version:

The library allows logs to be stored in the mongodb timeseries collection

145 lines (144 loc) 5.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Transporter = void 0; /** * The Transporter class * * @description It allows logs to be saved in the mongodb timeseries collection. * * @example * // short example * import { Db, MongoClient } from 'mongodb'; * import { Transporter } from '@harvve/tslog-mongodb-transport'; * import { Logger } from 'tslog'; * * async function main() { * const client = await new MongoClient('mongodb://localhost:27017').connect(); * const mongodbTransporter = new Transporter({ db: client.db('logs_db') }); * const logger = new Logger({ argumentsArrayName: 'argumentsArray', // default key name used by Transporter metaProperty: 'metadata', // default key name used by Transporter attachedTransports: [mongodbTransporter.transport.bind(mongodbTransporter)] }); * logger.info('Hello'); * } */ class Transporter { constructor(config) { this.baseConfig = { argumentsArrayName: 'argumentsArray', collectionName: 'logs', granuality: 'minutes', bucketSize: 5000, updateInterval: 1, logTTL: 2592000, onErrorCallback: () => { }, onBeforeWriteCallback: () => { }, onAfterWriteCallback: () => { }, onFinallyWriteCallback: () => { } }; this.bucket = []; this.checks = { isReady: false, isChecking: false, isWriting: false }; this.config = Object.assign(this.baseConfig, config); // if process exit - try to write all data from bucket process.once('exit', () => { this.writeData(this.bucket.splice(0)); }); } /** * Transport method * * @description transports logs to mongodb */ async transport(data) { if (!this.checks.isReady && !this.checks.isChecking) { this.checks.isChecking = true; const collectionMeta = await this.getCollectionMetadata(); if (!collectionMeta) { this.collection = await this.crateCollection(); } else { this.collection = this.config.db.collection(this.config.collectionName); } this.checks.isReady = true; this.checks.isChecking = false; } if (this.bucket.length > this.config.bucketSize && this.checks.isReady && !this.checks.isWriting) { this.writeData(this.bucket.splice(0, this.config.bucketSize)); } this.bucket.push(this.prepareData(data)); this.rollingUpdate(this); } rollingUpdate(context) { const self = context; if (self.timer) { clearTimeout(self.timer); } self.timer = setTimeout(() => { if (self.checks.isReady && !self.checks.isWriting) { self.writeData(self.bucket.splice(0)); } else { self.rollingUpdate(self); } }, self.config.updateInterval * 1000); } prepareData(data) { // Important: Use Consistent Field Order in Documents // Check: https://www.mongodb.com/docs/manual/core/timeseries/timeseries-best-practices/#use-consistent-field-order-in-documents return { ts: data.metadata.date, logLevelId: data.metadata.logLevelId, metadata: { runtime: data.metadata.runtime, runtimeVersion: data.metadata.runtimeVersion, hostname: data.metadata.hostname, logLevelName: data.metadata.logLevelName, argumentsArray: data.argumentsArray, name: data.metadata.name, parentNames: data.metadata.parentNames, path: data.metadata.path } }; } writeData(data) { var _a; if (!data.length) { return; } this.checks.isWriting = true; this.config.onBeforeWriteCallback(this.bucket.length); (_a = this.collection) === null || _a === void 0 ? void 0 : _a.insertMany(data).then((insertStats) => this.config.onAfterWriteCallback(insertStats, this.bucket.length)).catch(this.config.onErrorCallback).finally(() => { this.config.onFinallyWriteCallback(this.bucket.length); this.checks.isWriting = false; }); } async crateCollection() { try { const collection = await this.config.db.createCollection(this.config.collectionName, { timeseries: { timeField: 'ts', metaField: 'metadata', granularity: this.config.granuality }, expireAfterSeconds: this.config.logTTL }); await collection.createIndexes([ { key: { ts: -1 } }, { key: { ts: -1, 'metadata.logLevelName': 1, 'metadata.hostname': 1 } } ]); return collection; } catch (err) { this.config.onErrorCallback(err); throw Error(`Cannot create timeseries collection, reason: ${err}`); } } async getCollectionMetadata() { try { const collectionMeta = await this.config.db.listCollections({ name: this.config.collectionName }).next(); return collectionMeta; } catch (err) { this.config.onErrorCallback(err); throw Error(`Cannot check if collection exists, reason: ${err}`); } } } exports.Transporter = Transporter;