feat(monitoring): add /api/metrics endpoint for Prometheus scraping
This commit is contained in:
68
memento-note/app/api/metrics/route.ts
Normal file
68
memento-note/app/api/metrics/route.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import { prisma } from '@/lib/prisma'
|
||||||
|
import { redis } from '@/lib/redis'
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const lines: string[] = []
|
||||||
|
|
||||||
|
const metric = (name: string, help: string, type: string, value: number | string, labels = '') => {
|
||||||
|
lines.push(`# HELP ${name} ${help}`)
|
||||||
|
lines.push(`# TYPE ${name} ${type}`)
|
||||||
|
lines.push(labels ? `${name}{${labels}} ${value}` : `${name} ${value}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uptime
|
||||||
|
metric('memento_uptime_seconds', 'Application uptime in seconds', 'gauge', process.uptime().toFixed(2))
|
||||||
|
|
||||||
|
// Database
|
||||||
|
try {
|
||||||
|
const dbStart = Date.now()
|
||||||
|
const [noteCount, notebookCount, userCount] = await Promise.all([
|
||||||
|
prisma.note.count(),
|
||||||
|
prisma.notebook.count(),
|
||||||
|
prisma.user.count(),
|
||||||
|
])
|
||||||
|
const dbLatency = Date.now() - dbStart
|
||||||
|
metric('memento_db_up', 'Database connectivity (1=up, 0=down)', 'gauge', 1)
|
||||||
|
metric('memento_db_latency_ms', 'Database query latency in milliseconds', 'gauge', dbLatency)
|
||||||
|
metric('memento_notes_total', 'Total number of notes', 'gauge', noteCount)
|
||||||
|
metric('memento_notebooks_total', 'Total number of notebooks', 'gauge', notebookCount)
|
||||||
|
metric('memento_users_total', 'Total number of users', 'gauge', userCount)
|
||||||
|
} catch {
|
||||||
|
metric('memento_db_up', 'Database connectivity (1=up, 0=down)', 'gauge', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
try {
|
||||||
|
const redisStart = Date.now()
|
||||||
|
await redis.ping()
|
||||||
|
const dbSize = await redis.dbsize()
|
||||||
|
const info = await redis.info('memory')
|
||||||
|
const redisLatency = Date.now() - redisStart
|
||||||
|
const memMatch = info.match(/used_memory:(\d+)/)
|
||||||
|
const usedMemoryBytes = memMatch ? parseInt(memMatch[1]) : 0
|
||||||
|
metric('memento_redis_up', 'Redis connectivity (1=up, 0=down)', 'gauge', 1)
|
||||||
|
metric('memento_redis_latency_ms', 'Redis ping latency in milliseconds', 'gauge', redisLatency)
|
||||||
|
metric('memento_redis_keys_total', 'Total number of Redis keys', 'gauge', dbSize)
|
||||||
|
metric('memento_redis_memory_bytes', 'Redis used memory in bytes', 'gauge', usedMemoryBytes)
|
||||||
|
} catch {
|
||||||
|
metric('memento_redis_up', 'Redis connectivity (1=up, 0=down)', 'gauge', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node.js process memory
|
||||||
|
const mem = process.memoryUsage()
|
||||||
|
metric('memento_process_heap_used_bytes', 'Node.js heap used in bytes', 'gauge', mem.heapUsed)
|
||||||
|
metric('memento_process_heap_total_bytes', 'Node.js heap total in bytes', 'gauge', mem.heapTotal)
|
||||||
|
metric('memento_process_rss_bytes', 'Node.js RSS memory in bytes', 'gauge', mem.rss)
|
||||||
|
|
||||||
|
const body = lines.join('\n') + '\n'
|
||||||
|
|
||||||
|
return new NextResponse(body, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user