UNPKG

moleculer

Version:

Fast & powerful microservices framework for Node.JS

229 lines (197 loc) 5.02 kB
/* * moleculer * Copyright (c) 2023 MoleculerJS (https://github.com/moleculerjs/moleculer) * MIT Licensed */ "use strict"; const BaseReporter = require("./base"); const _ = require("lodash"); const dgram = require("dgram"); const METRIC = require("../constants"); /** * Import types * * @typedef {import("../registry")} MetricRegistry * @typedef {import("./statsd").StatsDReporterOptions} StatsDReporterOptions * @typedef {import("./statsd")} StatsDReporterClass * @typedef {import("../types/base").BaseMetricPOJO} BaseMetricPOJO * @typedef {import("../types/base")} BaseMetric */ /** * UDP (StatsD) reporter for Moleculer. * * @class DatadogReporter * @extends {BaseReporter} * @implements {StatsDReporterClass} */ class StatsDReporter extends BaseReporter { /** * Constructor of StatsDReporters * * @param {StatsDReporterOptions} opts * @memberof StatsDReporter */ constructor(opts) { super(opts); /** @type {StatsDReporterOptions} */ this.opts = _.defaultsDeep(this.opts, { host: "localhost", port: 8125, types: null, maxPayloadSize: 1300 }); } /** * Initialize reporter. * * @param {MetricRegistry} registry * @memberof StatsDReporter */ init(registry) { super.init(registry); this.flush(); } /** * Flush metric data * * @memberof StatsDReporter */ flush() { const series = this.generateStatsDSeries(); if (series.length === 0) return; this.sendChunks(series); } /** * Create & send chunks. * * @param {Array<Object>} series */ sendChunks(series) { let len = 0; const chunks = []; while (series.length > 0 && (!this.opts.maxPayloadSize || len < this.opts.maxPayloadSize)) { const item = series.shift(); chunks.push(item); len += item.length; } if (chunks.length > 0) { this.send(Buffer.from(chunks.join("\n"))); } if (series.length > 0) { setTimeout(() => this.sendChunks(series), 100); } } /** * Send concatenated data to StatsD server via UDP * * @param {Buffer} buf */ send(buf) { //this.logger.info("Buffer\n" + buf.toString()); const sock = dgram.createSocket("udp4"); sock.send(buf, 0, buf.length, this.opts.port, this.opts.host, (err, bytes) => { if (err) { this.logger.warn( "Unable to send metrics to StatsD server. Error:" + err.message, err ); } else { this.logger.debug("Metrics are uploaded to StatsD. Sent bytes:", bytes); } sock.close(); }); } /** * Generate metric data. * * @returns {Array<Object>} * @memberof StatsDReporter */ generateStatsDSeries() { const series = []; const list = this.registry.list({ types: this.opts.types, includes: this.opts.includes, excludes: this.opts.excludes }); list.forEach(metric => { metric.values.forEach(item => { const line = this.generateStatDLine(metric, item); if (line) series.push(line); }); }); return series; } generateStatDLine(metric, item, lastValue) { const metricName = this.formatMetricName(metric.name); switch (metric.type) { case METRIC.TYPE_COUNTER: { let line = `${metricName}:${item.value}|c`; if (metric.labelNames.length > 0) line += "|#" + this.labelsToTags(item.labels); return line; } case METRIC.TYPE_GAUGE: { let line = `${metricName}:${item.value}|g`; if (metric.labelNames.length > 0) line += "|#" + this.labelsToTags(item.labels); return line; } case METRIC.TYPE_INFO: { let line = `${metricName}:${ typeof item.value == "number" ? item.value : '"' + item.value + '"' }|s`; if (metric.labelNames.length > 0) line += "|#" + this.labelsToTags(item.labels); return line; } case METRIC.TYPE_HISTOGRAM: { if (lastValue != null) { let line = `${metricName}:${lastValue}|ms`; if (metric.labelNames.length > 0) line += "|#" + this.labelsToTags(item.labels); return line; } } } } /** * Some metric has been changed. * * @param {BaseMetric} metric * @param {any} value * @param {Object} labels * * @memberof BaseReporter */ metricChanged(metric, value, labels) { if (!this.matchMetricName(metric.name)) return; const line = this.generateStatDLine(metric, metric.get(labels), value); if (line) { this.send(Buffer.from(line)); } } /** * Escape label value characters. * @param {String} str * @returns {String} * @memberof DatadogReporter */ escapeLabelValue(str) { if (typeof str == "string") return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); return str; } /** * Convert labels to StatsD label string * * @param {Object} itemLabels * @returns {String} * * @memberof StatsDReporter */ labelsToTags(itemLabels) { const labels = Object.assign({}, this.defaultLabels || {}, itemLabels || {}); const keys = Object.keys(labels); if (keys.length === 0) return ""; return keys .map(key => `${this.formatLabelName(key)}:${this.escapeLabelValue(labels[key])}`) .join(","); } } module.exports = StatsDReporter;