Add metrics

This commit is contained in:
2025-09-15 00:13:57 +02:00
parent be5b042380
commit 33c7e2335f
8 changed files with 108 additions and 3 deletions
+3 -2
View File
@@ -8,12 +8,12 @@ WORKDIR /src
COPY --link package.json ./package.json
COPY --link bun.lock ./bun.lock
COPY --link patches ./patches
COPY --link prisma ./prisma
COPY --link shared ./shared
RUN bun add prisma@6.16.1
RUN bunx --bun prisma generate
RUN bunx --bun prisma@6.16.1 generate
FROM oven/bun:${BUN_VERSION} AS base
@@ -25,6 +25,7 @@ WORKDIR /src
FROM base AS build
COPY --link package.json bun.lock ./
COPY --link patches ./patches
RUN bun install --production
COPY --from=prisma /src/shared/generated shared/generated
+26
View File
@@ -4,6 +4,7 @@
"": {
"name": "nuxt-app",
"dependencies": {
"@artmizu/nuxt-prometheus": "2.6.0",
"@nuxt/eslint": "1.9.0",
"@nuxtjs/i18n": "^10.0.3",
"@nuxtjs/tailwindcss": "^6.14.0",
@@ -40,6 +41,9 @@
},
},
},
"patchedDependencies": {
"@artmizu/nuxt-prometheus@2.6.0": "patches/@artmizu%2Fnuxt-prometheus@2.6.0.patch",
},
"packages": {
"@adonisjs/hash": ["@adonisjs/hash@9.1.1", "", { "dependencies": { "@phc/format": "^1.0.0", "@poppinss/utils": "^6.9.3" }, "peerDependencies": { "argon2": "^0.31.2 || ^0.41.0 || ^0.43.0", "bcrypt": "^5.1.1 || ^6.0.0" }, "optionalPeers": ["argon2", "bcrypt"] }, "sha512-ZkRguwjAp4skKvKDdRAfdJ2oqQ0N7p9l3sioyXO1E8o0WcsyDgEpsTQtuVNoIdMiw4sn4gJlmL3nyF4BcK1ZDQ=="],
@@ -51,6 +55,8 @@
"@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@14.2.0", "", { "dependencies": { "js-yaml": "^4.1.0" }, "peerDependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-NaGMMWwppbByagq+LwQMq6PMXHFWVu6kSwwx+eJfYTJ5zdpOvb9TIk6ZWxEEeXMUvGdVOZq3JalYsjsTZDvtkA=="],
"@artmizu/nuxt-prometheus": ["@artmizu/nuxt-prometheus@2.6.0", "", { "dependencies": { "@mswjs/interceptors": "^0.36.10", "consola": "^3.2.3", "defu": "^6.1.2", "h3": "^1.7.1", "prom-client": "^14.2.0" } }, "sha512-ufS377w+fuhj1cay/2ZwOYdqxhLzyc3F97+SHMMn8TVl/rrtODN2X4VQtMbP967Hz8+qVUyxPMiCMU71zKMFbg=="],
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
"@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="],
@@ -265,6 +271,8 @@
"@miyaneee/rollup-plugin-json5": ["@miyaneee/rollup-plugin-json5@1.2.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "json5": "^2.2.3" }, "peerDependencies": { "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" } }, "sha512-JjTIaXZp9WzhUHpElrqPnl1AzBi/rvRs065F71+aTmlqvTMVkdbjZ8vfFl4nRlgJy+TPBw69ZK4pwFdmOAt4aA=="],
"@mswjs/interceptors": ["@mswjs/interceptors@0.36.10", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-GXrJgakgJW3DWKueebkvtYgGKkxA7s0u5B0P5syJM5rvQUnrpLPigvci8Hukl7yEM+sU06l+er2Fgvx/gmiRgg=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
"@netlify/binary-info": ["@netlify/binary-info@1.0.0", "", {}, "sha512-4wMPu9iN3/HL97QblBsBay3E1etIciR84izI3U+4iALY+JHCrI+a2jO0qbAZ/nxKoegypYEaiiqWXylm+/zfrw=="],
@@ -317,6 +325,12 @@
"@nuxtjs/tailwindcss": ["@nuxtjs/tailwindcss@6.14.0", "", { "dependencies": { "@nuxt/kit": "^3.16.0", "autoprefixer": "^10.4.20", "c12": "^3.0.2", "consola": "^3.4.0", "defu": "^6.1.4", "h3": "^1.15.1", "klona": "^2.0.6", "ohash": "^2.0.11", "pathe": "^2.0.3", "pkg-types": "^2.1.0", "postcss": "^8.5.3", "postcss-nesting": "^13.0.1", "tailwind-config-viewer": "^2.0.4", "tailwindcss": "~3.4.17", "ufo": "^1.5.4", "unctx": "^2.4.1" } }, "sha512-30RyDK++LrUVRgc2A85MktGWIZoRQgeQKjE4CjjD64OXNozyl+4ScHnnYgqVToMM6Ch2ZG2W4wV2J0EN6F0zkQ=="],
"@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="],
"@open-draft/logger": ["@open-draft/logger@0.3.0", "", { "dependencies": { "is-node-process": "^1.2.0", "outvariant": "^1.4.0" } }, "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ=="],
"@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
"@oxc-minify/binding-android-arm64": ["@oxc-minify/binding-android-arm64@0.78.0", "", { "os": "android", "cpu": "arm64" }, "sha512-tza8rCLefHlwksl+uVgdwHtmGKotOYSnJ5PPOdgATgOc+JqQPDbZqf68aTUU8y1VnrOR2bi7iYDFiY6RbkpKQA=="],
"@oxc-minify/binding-darwin-arm64": ["@oxc-minify/binding-darwin-arm64@0.78.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dCSJnY7b4GwCmoWNksou3fNYXlhxibJvktFLvoq5eMmMTLoowrwcZT+WG2cIJV+RF33AREDac4QgZTDKI4LLmw=="],
@@ -779,6 +793,8 @@
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
"bintrees": ["bintrees@1.0.2", "", {}, "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="],
"birpc": ["birpc@2.5.0", "", {}, "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ=="],
"block-stream2": ["block-stream2@2.1.0", "", { "dependencies": { "readable-stream": "^3.4.0" } }, "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg=="],
@@ -1331,6 +1347,8 @@
"is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
"is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-path-inside": ["is-path-inside@4.0.0", "", {}, "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA=="],
@@ -1603,6 +1621,8 @@
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="],
"oxc-minify": ["oxc-minify@0.78.0", "", { "optionalDependencies": { "@oxc-minify/binding-android-arm64": "0.78.0", "@oxc-minify/binding-darwin-arm64": "0.78.0", "@oxc-minify/binding-darwin-x64": "0.78.0", "@oxc-minify/binding-freebsd-x64": "0.78.0", "@oxc-minify/binding-linux-arm-gnueabihf": "0.78.0", "@oxc-minify/binding-linux-arm-musleabihf": "0.78.0", "@oxc-minify/binding-linux-arm64-gnu": "0.78.0", "@oxc-minify/binding-linux-arm64-musl": "0.78.0", "@oxc-minify/binding-linux-riscv64-gnu": "0.78.0", "@oxc-minify/binding-linux-s390x-gnu": "0.78.0", "@oxc-minify/binding-linux-x64-gnu": "0.78.0", "@oxc-minify/binding-linux-x64-musl": "0.78.0", "@oxc-minify/binding-wasm32-wasi": "0.78.0", "@oxc-minify/binding-win32-arm64-msvc": "0.78.0", "@oxc-minify/binding-win32-x64-msvc": "0.78.0" } }, "sha512-QmoYJBPvzm+uqagkUaCRmyQL5LAzUYnz0r1JtMa6gi2sGyc5elDR8oE0F/1G1NjM3K3Kefwn4sdDFTZeRWc8sA=="],
"oxc-parser": ["oxc-parser@0.72.3", "", { "dependencies": { "@oxc-project/types": "^0.72.3" }, "optionalDependencies": { "@oxc-parser/binding-darwin-arm64": "0.72.3", "@oxc-parser/binding-darwin-x64": "0.72.3", "@oxc-parser/binding-freebsd-x64": "0.72.3", "@oxc-parser/binding-linux-arm-gnueabihf": "0.72.3", "@oxc-parser/binding-linux-arm-musleabihf": "0.72.3", "@oxc-parser/binding-linux-arm64-gnu": "0.72.3", "@oxc-parser/binding-linux-arm64-musl": "0.72.3", "@oxc-parser/binding-linux-riscv64-gnu": "0.72.3", "@oxc-parser/binding-linux-s390x-gnu": "0.72.3", "@oxc-parser/binding-linux-x64-gnu": "0.72.3", "@oxc-parser/binding-linux-x64-musl": "0.72.3", "@oxc-parser/binding-wasm32-wasi": "0.72.3", "@oxc-parser/binding-win32-arm64-msvc": "0.72.3", "@oxc-parser/binding-win32-x64-msvc": "0.72.3" } }, "sha512-JYQeJKDcUTTZ/uTdJ+fZBGFjAjkLD1h0p3Tf44ZYXRcoMk+57d81paNPFAAwzrzzqhZmkGvKKXDxwyhJXYZlpg=="],
@@ -1797,6 +1817,8 @@
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"prom-client": ["prom-client@14.2.0", "", { "dependencies": { "tdigest": "^0.1.1" } }, "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"protocols": ["protocols@2.0.2", "", {}, "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ=="],
@@ -1971,6 +1993,8 @@
"streamx": ["streamx@2.22.1", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA=="],
"strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="],
"strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
@@ -2019,6 +2043,8 @@
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
"tdigest": ["tdigest@0.1.2", "", { "dependencies": { "bintrees": "1.0.2" } }, "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA=="],
"terser": ["terser@5.43.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg=="],
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
+6 -1
View File
@@ -14,8 +14,13 @@ export default defineNuxtConfig({
'@vuestic/nuxt',
'@pinia/nuxt',
'@vueuse/nuxt',
'@nuxt/eslint'
'@nuxt/eslint',
'@artmizu/nuxt-prometheus'
],
prometheus: {
prometheusPath: '/api/metrics',
verbose: false
},
runtimeConfig: {
public: {
s3Bucket: 'pichunt',
+4
View File
@@ -18,6 +18,7 @@
"dev:down": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml down"
},
"dependencies": {
"@artmizu/nuxt-prometheus": "2.6.0",
"@nuxt/eslint": "1.9.0",
"@nuxtjs/i18n": "^10.0.3",
"@nuxtjs/tailwindcss": "^6.14.0",
@@ -51,5 +52,8 @@
"prettier-plugin-tailwindcss": "^0.6.14",
"prisma": "^6.16.1",
"vue-tsc": "^3.0.6"
},
"patchedDependencies": {
"@artmizu/nuxt-prometheus@2.6.0": "patches/@artmizu%2Fnuxt-prometheus@2.6.0.patch"
}
}
@@ -0,0 +1,21 @@
diff --git a/dist/runtime/registry.js b/dist/runtime/registry.js
index b06d20fce7900f947ad55f5b1b3f8e25cf9dbe84..b0cf94339671ed3042d6fc3f2c30d2d37723402a 100644
--- a/dist/runtime/registry.js
+++ b/dist/runtime/registry.js
@@ -1,4 +1,4 @@
-import { Gauge, Summary, collectDefaultMetrics, register } from "prom-client";
+import { Gauge, Summary, register } from "prom-client";
export const metrics = {
/** If `true`, metrics was initialized with `initMetrics` */
isInitialized: false,
@@ -14,10 +14,6 @@ export const metrics = {
export const initMetrics = (p) => {
if (metrics.isInitialized)
return;
- collectDefaultMetrics({
- prefix: p.prefix,
- register
- });
const prefix = p.prefix ?? "";
metrics.renderTime = new Gauge({
name: `${prefix}page_render_time`,
+4
View File
@@ -1,6 +1,7 @@
import { verify } from 'argon2';
import { useValidatedBody, z } from 'h3-zod';
import prisma from '~~/lib/prisma';
import { logins } from '~~/server/metrics';
export default defineEventHandler(async (event) => {
const { email, password } = await useValidatedBody(
@@ -25,10 +26,12 @@ export default defineEventHandler(async (event) => {
});
if (!user || user.password.length <= 0) {
console.warn(`Failed login attempted: Unknown email: ${email}`);
logins.inc({ success: 'false' });
throw createError({ status: 404, statusText: 'Not found!' });
}
if (!(await verify(user.password, password))) {
console.warn(`Failed login attempted: Wrong password: ${email}`);
logins.inc({ success: 'false' });
throw createError({ status: 404, statusText: 'Not found!' });
}
@@ -41,6 +44,7 @@ export default defineEventHandler(async (event) => {
},
loggedInAt: new Date()
});
logins.inc({ success: 'true' });
return { success: true, user: { id: user.id } };
});
+19
View File
@@ -3,6 +3,11 @@ import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import prisma from '~~/lib/prisma';
import { minioClient, s3Bucket, s3Host } from '~~/lib/s3';
import {
answerChanged,
answerFinalized,
imageUploaded
} from '~~/server/metrics';
export default defineEventHandler(async (event) => {
const { questId } = await useValidatedParams(
@@ -21,6 +26,7 @@ export default defineEventHandler(async (event) => {
},
select: {
id: true,
huntId: true,
hunt: {
select: {
teams: {
@@ -125,6 +131,9 @@ export default defineEventHandler(async (event) => {
undefined;
if (data.image) {
imageUploaded.inc({
hunt: quest.huntId
});
const uuid = randomUUID();
await minioClient.putObject(
s3Bucket,
@@ -215,5 +224,15 @@ export default defineEventHandler(async (event) => {
}
}
answerChanged.inc({
hunt: quest.huntId
});
if (data.final) {
answerFinalized.inc({
hunt: quest.huntId
});
}
return { ok: true };
});
+25
View File
@@ -0,0 +1,25 @@
import { Counter } from 'prom-client';
export const imageUploaded = new Counter({
name: 'image_uploaded_total',
help: 'Images uploaded',
labelNames: ['hunt']
});
export const answerFinalized = new Counter({
name: 'answer_finalized_total',
help: 'Answer got finalized',
labelNames: ['hunt']
});
export const answerChanged = new Counter({
name: 'answer_changed_total',
help: 'Answer got uploaded or changed',
labelNames: ['hunt']
});
export const logins = new Counter({
name: 'login_total',
help: 'Logins',
labelNames: ['success']
});