use ts-util package
This commit is contained in:
@@ -21,7 +21,6 @@
|
|||||||
"lru-cache": "^11.1.0",
|
"lru-cache": "^11.1.0",
|
||||||
"pg": "^8.12.0",
|
"pg": "^8.12.0",
|
||||||
"pino": "^9.2.0",
|
"pino": "^9.2.0",
|
||||||
"prom-client": "^15.1.3",
|
|
||||||
"uint8arrays": "^5.1.0"
|
"uint8arrays": "^5.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Vendored
+18
-26
@@ -8,11 +8,10 @@ import {ExpiredTokenError} from '@atproto/api/dist/client/types/com/atproto/serv
|
|||||||
import {MINUTE} from '@atproto/common'
|
import {MINUTE} from '@atproto/common'
|
||||||
import {LRUCache} from 'lru-cache'
|
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 Database from '../db/index.js'
|
||||||
import {type SafelinkRule} from '../db/schema.js'
|
import {type SafelinkRule} from '../db/schema.js'
|
||||||
import {redirectLogger} from '../logger.js'
|
import {redirectLogger} from '../logger.js'
|
||||||
import {type Metrics} from '../metrics.js'
|
|
||||||
|
|
||||||
const SAFELINK_MIN_FETCH_INTERVAL = 1_000
|
const SAFELINK_MIN_FETCH_INTERVAL = 1_000
|
||||||
const SAFELINK_MAX_FETCH_INTERVAL = 10_000
|
const SAFELINK_MAX_FETCH_INTERVAL = 10_000
|
||||||
@@ -22,22 +21,12 @@ export class SafelinkClient {
|
|||||||
private domainCache: LRUCache<string, SafelinkRule | 'ok'>
|
private domainCache: LRUCache<string, SafelinkRule | 'ok'>
|
||||||
private urlCache: LRUCache<string, SafelinkRule | 'ok'>
|
private urlCache: LRUCache<string, SafelinkRule | 'ok'>
|
||||||
|
|
||||||
private db: Database
|
private ctx: AppContext
|
||||||
private metrics: Metrics
|
|
||||||
|
|
||||||
private ozoneAgent: OzoneAgent
|
private ozoneAgent: OzoneAgent
|
||||||
|
|
||||||
private cursor?: string
|
private cursor?: string
|
||||||
|
|
||||||
constructor({
|
constructor(ctx: AppContext) {
|
||||||
cfg,
|
|
||||||
db,
|
|
||||||
metrics,
|
|
||||||
}: {
|
|
||||||
cfg: ServiceConfig
|
|
||||||
db: Database
|
|
||||||
metrics: Metrics
|
|
||||||
}) {
|
|
||||||
this.domainCache = new LRUCache<string, SafelinkRule | 'ok'>({
|
this.domainCache = new LRUCache<string, SafelinkRule | 'ok'>({
|
||||||
max: 10000,
|
max: 10000,
|
||||||
})
|
})
|
||||||
@@ -46,13 +35,12 @@ export class SafelinkClient {
|
|||||||
max: 25000,
|
max: 25000,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.db = db
|
this.ctx = ctx
|
||||||
this.metrics = metrics
|
|
||||||
|
|
||||||
this.ozoneAgent = new OzoneAgent(
|
this.ozoneAgent = new OzoneAgent(
|
||||||
cfg.safelinkPdsUrl!,
|
this.ctx.cfg.service.safelinkPdsUrl!,
|
||||||
cfg.safelinkAgentIdentifier!,
|
this.ctx.cfg.service.safelinkAgentIdentifier!,
|
||||||
cfg.safelinkAgentPass!,
|
this.ctx.cfg.service.safelinkAgentPass!,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +50,12 @@ export class SafelinkClient {
|
|||||||
const end = process.hrtime.bigint()
|
const end = process.hrtime.bigint()
|
||||||
const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3
|
const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3
|
||||||
|
|
||||||
this.metrics.safeLinkLookups.labels(status, cached ? 'yes' : 'no').inc()
|
this.ctx.metrics
|
||||||
this.metrics.safeLinkLookupDuration
|
.getCounter('safeLinkLookups')
|
||||||
|
.labels(status, cached ? 'yes' : 'no')
|
||||||
|
.inc()
|
||||||
|
this.ctx.metrics
|
||||||
|
.getHistogram('safeLinkLookupDuration')
|
||||||
.labels(status, cached ? 'yes' : 'no')
|
.labels(status, cached ? 'yes' : 'no')
|
||||||
.observe(respTimeMs)
|
.observe(respTimeMs)
|
||||||
}
|
}
|
||||||
@@ -100,7 +92,7 @@ export class SafelinkClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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)
|
this.urlCache.set(url, maybeUrlRule)
|
||||||
|
|
||||||
addMetrics('ok', false)
|
addMetrics('ok', false)
|
||||||
@@ -110,7 +102,7 @@ export class SafelinkClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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)
|
this.domainCache.set(domain, maybeDomainRule)
|
||||||
|
|
||||||
addMetrics('ok', false)
|
addMetrics('ok', false)
|
||||||
@@ -249,7 +241,7 @@ export class SafelinkClient {
|
|||||||
redirectLogger.info('received no new safelink events from ozone')
|
redirectLogger.info('received no new safelink events from ozone')
|
||||||
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
|
setTimeout(() => this.runFetchEvents(), SAFELINK_MAX_FETCH_INTERVAL)
|
||||||
} else {
|
} else {
|
||||||
await this.db.transaction(async db => {
|
await this.ctx.db.transaction(async db => {
|
||||||
for (const rule of res.data.events) {
|
for (const rule of res.data.events) {
|
||||||
switch (rule.eventType) {
|
switch (rule.eventType) {
|
||||||
case 'removeRule':
|
case 'removeRule':
|
||||||
@@ -277,7 +269,7 @@ export class SafelinkClient {
|
|||||||
|
|
||||||
private async getCursor() {
|
private async getCursor() {
|
||||||
if (this.cursor === '') {
|
if (this.cursor === '') {
|
||||||
const res = await this.db.db
|
const res = await this.ctx.db.db
|
||||||
.selectFrom('safelink_cursor')
|
.selectFrom('safelink_cursor')
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where('id', '=', 1)
|
.where('id', '=', 1)
|
||||||
@@ -293,7 +285,7 @@ export class SafelinkClient {
|
|||||||
private async setCursor(cursor: string) {
|
private async setCursor(cursor: string) {
|
||||||
const updatedAt = new Date()
|
const updatedAt = new Date()
|
||||||
try {
|
try {
|
||||||
await this.db.db
|
await this.ctx.db.db
|
||||||
.insertInto('safelink_cursor')
|
.insertInto('safelink_cursor')
|
||||||
.values({
|
.values({
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|||||||
+53
-7
@@ -1,28 +1,74 @@
|
|||||||
|
import {type MetricConfig, Metrics} from '@haileyok/ts-util'
|
||||||
|
|
||||||
import {SafelinkClient} from './cache/safelinkClient.js'
|
import {SafelinkClient} from './cache/safelinkClient.js'
|
||||||
import {type Config} from './config.js'
|
import {type Config} from './config.js'
|
||||||
import Database from './db/index.js'
|
import Database from './db/index.js'
|
||||||
import {Metrics} from './metrics.js'
|
|
||||||
|
|
||||||
export type AppContextOptions = {
|
export type AppContextOptions = {
|
||||||
cfg: Config
|
cfg: Config
|
||||||
db: Database
|
db: Database
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BlinkMetricNames =
|
||||||
|
| 'requestDuration'
|
||||||
|
| 'redirects'
|
||||||
|
| 'shortLinkRequests'
|
||||||
|
| 'safeLinkLookups'
|
||||||
|
| 'safeLinkLookupDuration'
|
||||||
|
|
||||||
|
type BlinkMetricConfig = Record<BlinkMetricNames, MetricConfig>
|
||||||
|
|
||||||
export class AppContext {
|
export class AppContext {
|
||||||
cfg: Config
|
cfg: Config
|
||||||
db: Database
|
db: Database
|
||||||
metrics: Metrics = new Metrics()
|
metrics: Metrics<BlinkMetricConfig>
|
||||||
safelinkClient: SafelinkClient
|
safelinkClient: SafelinkClient
|
||||||
abortController = new AbortController()
|
abortController = new AbortController()
|
||||||
|
|
||||||
constructor(private opts: AppContextOptions) {
|
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.cfg = this.opts.cfg
|
||||||
this.db = this.opts.db
|
this.db = this.opts.db
|
||||||
this.safelinkClient = new SafelinkClient({
|
this.safelinkClient = new SafelinkClient(this)
|
||||||
cfg: this.opts.cfg.service,
|
|
||||||
db: this.opts.db,
|
|
||||||
metrics: this.metrics,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromConfig(cfg: Config, overrides?: Partial<AppContextOptions>) {
|
static async fromConfig(cfg: Config, overrides?: Partial<AppContextOptions>) {
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export class LinkService {
|
|||||||
const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3
|
const respTimeMs = Number(end - start) / 1_000_000 // ns to ms :3
|
||||||
|
|
||||||
if (req.route) {
|
if (req.route) {
|
||||||
ctx.metrics.requestDuration
|
ctx.metrics
|
||||||
|
.getHistogram('requestDuration')
|
||||||
.labels(req.route.path, req.method, res.statusCode.toString())
|
.labels(req.route.path, req.method, res.statusCode.toString())
|
||||||
.observe(respTimeMs)
|
.observe(respTimeMs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ export default function (ctx: AppContext, app: Express) {
|
|||||||
bodyParser.json(),
|
bodyParser.json(),
|
||||||
handler(async (req, res) => {
|
handler(async (req, res) => {
|
||||||
const addMetrics = (statusCode: number) => {
|
const addMetrics = (statusCode: number) => {
|
||||||
ctx.metrics.shortLinkRequests
|
ctx.metrics
|
||||||
|
.getCounter('shortLinkRequests')
|
||||||
.labels('POST', statusCode.toString())
|
.labels('POST', statusCode.toString())
|
||||||
.inc()
|
.inc()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export default function (ctx: AppContext, app: Express) {
|
|||||||
'/redirect',
|
'/redirect',
|
||||||
handler(async (req, res) => {
|
handler(async (req, res) => {
|
||||||
const addMetrics = (ruleStr: string, statusCode: number) => {
|
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
|
let link = req.query.u
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ export default function (ctx: AppContext, app: Express) {
|
|||||||
'/:linkId',
|
'/:linkId',
|
||||||
handler(async (req, res) => {
|
handler(async (req, res) => {
|
||||||
const addMetrics = (statusCode: number) => {
|
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
|
const linkId = req.params.linkId
|
||||||
|
|||||||
Reference in New Issue
Block a user