diff --git a/bskylink/package.json b/bskylink/package.json index 0d9b8444d3..c57cb3cd89 100644 --- a/bskylink/package.json +++ b/bskylink/package.json @@ -21,7 +21,6 @@ "lru-cache": "^11.1.0", "pg": "^8.12.0", "pino": "^9.2.0", - "prom-client": "^15.1.3", "uint8arrays": "^5.1.0" }, "devDependencies": { diff --git a/bskylink/src/cache/safelinkClient.ts b/bskylink/src/cache/safelinkClient.ts index a3baa13d77..ed0e30bbe3 100644 --- a/bskylink/src/cache/safelinkClient.ts +++ b/bskylink/src/cache/safelinkClient.ts @@ -8,11 +8,10 @@ import {ExpiredTokenError} from '@atproto/api/dist/client/types/com/atproto/serv import {MINUTE} from '@atproto/common' import {LRUCache} from 'lru-cache' -import {type ServiceConfig} from '../config.js' +import {type AppContext} from '../context.js' import type Database from '../db/index.js' import {type SafelinkRule} from '../db/schema.js' import {redirectLogger} from '../logger.js' -import {type Metrics} from '../metrics.js' const SAFELINK_MIN_FETCH_INTERVAL = 1_000 const SAFELINK_MAX_FETCH_INTERVAL = 10_000 @@ -22,22 +21,12 @@ export class SafelinkClient { private domainCache: LRUCache private urlCache: LRUCache - private db: Database - private metrics: Metrics + private ctx: AppContext private ozoneAgent: OzoneAgent - private cursor?: string - constructor({ - cfg, - db, - metrics, - }: { - cfg: ServiceConfig - db: Database - metrics: Metrics - }) { + constructor(ctx: AppContext) { this.domainCache = new LRUCache({ max: 10000, }) @@ -46,13 +35,12 @@ export class SafelinkClient { max: 25000, }) - this.db = db - this.metrics = metrics + this.ctx = ctx this.ozoneAgent = new OzoneAgent( - cfg.safelinkPdsUrl!, - cfg.safelinkAgentIdentifier!, - cfg.safelinkAgentPass!, + this.ctx.cfg.service.safelinkPdsUrl!, + this.ctx.cfg.service.safelinkAgentIdentifier!, + this.ctx.cfg.service.safelinkAgentPass!, ) } @@ -62,8 +50,12 @@ export class SafelinkClient { const end = process.hrtime.bigint() const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3 - this.metrics.safeLinkLookups.labels(status, cached ? 'yes' : 'no').inc() - this.metrics.safeLinkLookupDuration + this.ctx.metrics + .getCounter('safeLinkLookups') + .labels(status, cached ? 'yes' : 'no') + .inc() + this.ctx.metrics + .getHistogram('safeLinkLookupDuration') .labels(status, cached ? 'yes' : 'no') .observe(respTimeMs) } @@ -100,7 +92,7 @@ export class SafelinkClient { } try { - const maybeUrlRule = await this.getRule(this.db, url, 'url') + const maybeUrlRule = await this.getRule(this.ctx.db, url, 'url') this.urlCache.set(url, maybeUrlRule) addMetrics('ok', false) @@ -110,7 +102,7 @@ export class SafelinkClient { } try { - const maybeDomainRule = await this.getRule(this.db, domain, 'domain') + const maybeDomainRule = await this.getRule(this.ctx.db, domain, 'domain') this.domainCache.set(domain, maybeDomainRule) addMetrics('ok', false) @@ -249,7 +241,7 @@ export class SafelinkClient { redirectLogger.info('received no new safelink events from ozone') setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL) } else { - await this.db.transaction(async db => { + await this.ctx.db.transaction(async db => { for (const rule of res.data.events) { switch (rule.eventType) { case 'removeRule': @@ -277,7 +269,7 @@ export class SafelinkClient { private async getCursor() { if (this.cursor === '') { - const res = await this.db.db + const res = await this.ctx.db.db .selectFrom('safelink_cursor') .selectAll() .where('id', '=', 1) @@ -293,7 +285,7 @@ export class SafelinkClient { private async setCursor(cursor: string) { const updatedAt = new Date() try { - await this.db.db + await this.ctx.db.db .insertInto('safelink_cursor') .values({ id: 1, diff --git a/bskylink/src/context.ts b/bskylink/src/context.ts index adc7fb1e10..a057647e65 100644 --- a/bskylink/src/context.ts +++ b/bskylink/src/context.ts @@ -1,28 +1,74 @@ +import {type MetricConfig, Metrics} from '@haileyok/ts-util' + import {SafelinkClient} from './cache/safelinkClient.js' import {type Config} from './config.js' import Database from './db/index.js' -import {Metrics} from './metrics.js' export type AppContextOptions = { cfg: Config db: Database } +type BlinkMetricNames = + | 'requestDuration' + | 'redirects' + | 'shortLinkRequests' + | 'safeLinkLookups' + | 'safeLinkLookupDuration' + +type BlinkMetricConfig = Record + export class AppContext { cfg: Config db: Database - metrics: Metrics = new Metrics() + metrics: Metrics safelinkClient: SafelinkClient abortController = new AbortController() constructor(private opts: AppContextOptions) { + const metricsConfig: BlinkMetricConfig = { + requestDuration: { + type: 'histogram', + name: 'request_duration_millis', + help: 'Request duration in millis', + labelNames: ['path', 'method', 'code'], + buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + }, + redirects: { + type: 'counter', + name: 'redirects', + help: 'Number of link redirects handled', + labelNames: ['safelink_rule', 'code'], + }, + shortLinkRequests: { + type: 'counter', + name: 'shortlink_requests', + help: 'Number of shortlink requests handled', + labelNames: ['method', 'code'], + }, + safeLinkLookups: { + type: 'counter', + name: 'safelink_lookups', + help: 'Number of safelink lookups handled', + labelNames: ['status', 'cached'], + }, + safeLinkLookupDuration: { + type: 'histogram', + name: 'safelink_lookup_duration_millis', + help: 'Request duration in millis', + labelNames: ['status', 'cached'], + buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + }, + } + + this.metrics = new Metrics(metricsConfig, { + prefix: 'blink_', + collectDefaultMetrics: true, + }) + this.cfg = this.opts.cfg this.db = this.opts.db - this.safelinkClient = new SafelinkClient({ - cfg: this.opts.cfg.service, - db: this.opts.db, - metrics: this.metrics, - }) + this.safelinkClient = new SafelinkClient(this) } static async fromConfig(cfg: Config, overrides?: Partial) { diff --git a/bskylink/src/index.ts b/bskylink/src/index.ts index 51462fec9e..b9a58e1709 100644 --- a/bskylink/src/index.ts +++ b/bskylink/src/index.ts @@ -44,7 +44,8 @@ export class LinkService { const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3 if (req.route) { - ctx.metrics.requestDuration + ctx.metrics + .getHistogram('requestDuration') .labels(req.route.path, req.method, res.statusCode.toString()) .observe(respTimeMs) } diff --git a/bskylink/src/routes/createShortLink.ts b/bskylink/src/routes/createShortLink.ts index 2eeb5960e5..ad8037f7ba 100644 --- a/bskylink/src/routes/createShortLink.ts +++ b/bskylink/src/routes/createShortLink.ts @@ -14,7 +14,8 @@ export default function (ctx: AppContext, app: Express) { bodyParser.json(), handler(async (req, res) => { const addMetrics = (statusCode: number) => { - ctx.metrics.shortLinkRequests + ctx.metrics + .getCounter('shortLinkRequests') .labels('POST', statusCode.toString()) .inc() } diff --git a/bskylink/src/routes/redirect.ts b/bskylink/src/routes/redirect.ts index 43de79d530..a32aea7614 100644 --- a/bskylink/src/routes/redirect.ts +++ b/bskylink/src/routes/redirect.ts @@ -20,7 +20,10 @@ export default function (ctx: AppContext, app: Express) { '/redirect', handler(async (req, res) => { const addMetrics = (ruleStr: string, statusCode: number) => { - ctx.metrics.redirects.labels(ruleStr, statusCode.toString()).inc() + ctx.metrics + .getCounter('redirects') + .labels(ruleStr, statusCode.toString()) + .inc() } let link = req.query.u diff --git a/bskylink/src/routes/shortLink.ts b/bskylink/src/routes/shortLink.ts index c46d1e2c67..08ffe40bba 100644 --- a/bskylink/src/routes/shortLink.ts +++ b/bskylink/src/routes/shortLink.ts @@ -11,7 +11,10 @@ export default function (ctx: AppContext, app: Express) { '/:linkId', handler(async (req, res) => { const addMetrics = (statusCode: number) => { - ctx.metrics.shortLinkRequests.labels('GET', statusCode.toString()).inc() + ctx.metrics + .getCounter('shortLinkRequests') + .labels('GET', statusCode.toString()) + .inc() } const linkId = req.params.linkId