From 4985e46ea49f9c43b3d218994b21b02bea5ce9b1 Mon Sep 17 00:00:00 2001 From: JandereDev Date: Thu, 28 Apr 2022 07:31:02 +0200 Subject: [PATCH] add prometheus metrics to bridge --- .env.example | 4 ++++ bridge/package.json | 1 + bridge/src/discord/events.ts | 5 +++++ bridge/src/index.ts | 1 + bridge/src/metrics.ts | 32 ++++++++++++++++++++++++++++++++ bridge/src/revolt/events.ts | 7 +++++++ bridge/yarn.lock | 19 +++++++++++++++++++ docker-compose.yml.example | 1 + 8 files changed, 70 insertions(+) create mode 100644 bridge/src/metrics.ts diff --git a/.env.example b/.env.example index c325b81..c0e2f61 100644 --- a/.env.example +++ b/.env.example @@ -51,6 +51,10 @@ LOG_WEBHOOK= # BasicAuth or a different form of authentication. BOT_METRICS_PORT= +# Same as above, but for the bridge service. +# Make sure the ports don't overlap! +BRIDGE_METRICS_PORT= + # Optional: Set this to a channel ID if you # want Prometheus metrics to return `msg_ping`. # The bot will regularly send a message in that diff --git a/bridge/package.json b/bridge/package.json index 77db5c9..99aa2a6 100644 --- a/bridge/package.json +++ b/bridge/package.json @@ -22,6 +22,7 @@ "form-data": "^4.0.0", "log75": "^2.2.0", "monk": "^7.3.4", + "prom-client": "^14.0.1", "revolt-api": "^0.5.3-rc.8", "smart-replace": "^1.0.2", "ulid": "^2.3.0" diff --git a/bridge/src/discord/events.ts b/bridge/src/discord/events.ts index 2aef16b..20760e3 100644 --- a/bridge/src/discord/events.ts +++ b/bridge/src/discord/events.ts @@ -9,6 +9,7 @@ import FormData from 'form-data'; import { discordFetchUser, revoltFetchMessage } from "../util"; import { TextChannel } from "discord.js"; import { smartReplace } from "smart-replace"; +import { metrics } from "../metrics"; const MAX_BRIDGED_FILE_SIZE = 8_000_000; // 8 MB const RE_MENTION_USER = /<@!*[0-9]+>/g; @@ -31,6 +32,7 @@ client.on('messageDelete', async message => { if (!targetMsg) return logger.debug(`Discord: Could not fetch message from Revolt`); await targetMsg.delete(); + metrics.messages.inc({ source: 'discord', type: 'delete' }); } catch(e) { console.error(e); } @@ -57,6 +59,7 @@ client.on('messageUpdate', async (oldMsg, newMsg) => { if (!targetMsg) return logger.debug(`Discord: Could not fetch message from Revolt`); await targetMsg.edit({ content: newMsg.content ? await renderMessageBody(newMsg.content) : undefined }); + metrics.messages.inc({ source: 'discord', type: 'edit' }); } catch(e) { console.error(e); } @@ -182,6 +185,8 @@ client.on('messageCreate', async message => { $set: { "revolt.messageId": res.data._id }, } ); + + metrics.messages.inc({ source: 'discord', type: 'create' }); }) .catch(async e => { console.error(`Failed to send message`, e.response.data); diff --git a/bridge/src/index.ts b/bridge/src/index.ts index 4634126..7edd7f8 100644 --- a/bridge/src/index.ts +++ b/bridge/src/index.ts @@ -24,6 +24,7 @@ for (const v of [ 'REVOLT_TOKEN', 'DISCORD_TOKEN', 'DB_STRING' ]) { } (async () => { + import('./metrics'); const [ revolt, discord ] = await Promise.allSettled([ loginRevolt(), loginDiscord(), diff --git a/bridge/src/metrics.ts b/bridge/src/metrics.ts new file mode 100644 index 0000000..4d7adc0 --- /dev/null +++ b/bridge/src/metrics.ts @@ -0,0 +1,32 @@ +import prom from 'prom-client'; +import http from 'http'; +import { logger } from '.'; + +const PORT = Number(process.env.BRIDGE_METRICS_PORT); + +prom.collectDefaultMetrics({ prefix: 'automod_bridge_' }); + +const metrics = { + messages: new prom.Counter({ name: 'messages', help: 'Bridged message events', labelNames: [ 'source', 'type' ] }), +} + +if (!isNaN(PORT)) { + logger.info(`Enabling Prometheus metrics on :${PORT}`); + + const server = new http.Server(); + + server.on('request', async (req, res) => { + if (req.url == '/metrics') { + res.write(await prom.register.metrics()); + res.end(); + } else { + res.statusCode = 404; + res.write('404 not found'); + res.end(); + } + }); + + server.listen(PORT, () => logger.done(`Prometheus metrics ready`)); +} + +export { metrics } diff --git a/bridge/src/revolt/events.ts b/bridge/src/revolt/events.ts index 0e2f57c..817b51e 100644 --- a/bridge/src/revolt/events.ts +++ b/bridge/src/revolt/events.ts @@ -6,6 +6,7 @@ import GenericEmbed from "../types/GenericEmbed"; import { SendableEmbed } from "revolt-api"; import { clipText, discordFetchMessage, revoltFetchUser } from "../util"; import { smartReplace } from "smart-replace"; +import { metrics } from "../metrics"; const RE_MENTION_USER = /<@[0-9A-HJ-KM-NP-TV-Z]{26}>/g; const RE_MENTION_CHANNEL = /<#[0-9A-HJ-KM-NP-TV-Z]{26}>/g; @@ -31,8 +32,10 @@ client.on('message/delete', async id => { const client = new WebhookClient({ id: bridgeCfg.discordWebhook.id, token: bridgeCfg.discordWebhook.token }); await client.deleteMessage(bridgedMsg.discord.messageId); client.destroy(); + metrics.messages.inc({ source: 'revolt', type: 'delete' }); } else if (targetMsg.deletable) { targetMsg.delete(); + metrics.messages.inc({ source: 'revolt', type: 'delete' }); } else logger.debug(`Revolt: Unable to delete Discord message`); } catch(e) { console.error(e); @@ -61,6 +64,8 @@ client.on('message/update', async message => { const client = new WebhookClient({ id: bridgeCfg.discordWebhook.id, token: bridgeCfg.discordWebhook.token }); await client.editMessage(targetMsg, { content: await renderMessageBody(message.content), allowedMentions: { parse: [ ] } }); client.destroy(); + + metrics.messages.inc({ source: 'revolt', type: 'edit' }); } catch(e) { console.error(e) } }); @@ -203,6 +208,8 @@ client.on('message', async message => { "discord.messageId": res.id } }); + + metrics.messages.inc({ source: 'revolt', type: 'create' }); }) .catch(async e => { console.error('Failed to execute webhook:', e?.response?.data ?? e); diff --git a/bridge/yarn.lock b/bridge/yarn.lock index 49f4520..857076e 100644 --- a/bridge/yarn.lock +++ b/bridge/yarn.lock @@ -158,6 +158,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bintrees@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" + integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= + bl@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" @@ -464,6 +469,13 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prom-client@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-14.0.1.tgz#bdd9583e02ec95429677c0e013712d42ef1f86a8" + integrity sha512-HxTArb6fkOntQHoRGvv4qd/BkorjliiuO2uSWC2KC17MUTKYttWdDoXX/vxOhQdkoECEM9BBH0pj2l8G8kev6w== + dependencies: + tdigest "^0.1.1" + readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -532,6 +544,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +tdigest@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021" + integrity sha1-Ljyyw56kSeVdHmzZEReszKRYgCE= + dependencies: + bintrees "1.0.1" + tiny-glob@^0.2.9: version "0.2.9" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" diff --git a/docker-compose.yml.example b/docker-compose.yml.example index 039ca7f..55516c8 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -52,6 +52,7 @@ services: - DISCORD_TOKEN=${BOT_TOKEN_DISCORD} - DB_STRING=mongodb://mogus:${DB_PASS}@mongo:27017/admin - NODE_ENV=production + - BRIDGE_METRICS_PORT restart: unless-stopped depends_on: - mongo