Merged in chore/upgrade-sentry (pull request #3191)

feat: upgrade sentry and use metrics

* feat: upgrade sentry and use metrics

* remove ununsed deps

* rename span

* .


Approved-by: Linus Flood
This commit is contained in:
Joakim Jäderberg
2025-11-20 13:24:53 +00:00
parent 5eaaea527f
commit b1d7fbad88
14 changed files with 510 additions and 470 deletions

View File

@@ -72,8 +72,7 @@
"./utils/zod/*": "./utils/zod/*.ts"
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@sentry/nextjs": "^10.11.0",
"@sentry/nextjs": "^10.26.0",
"@t3-oss/env-nextjs": "^0.13.4",
"deepmerge": "^4.3.1",
"flat": "^6.0.1",

View File

@@ -1,7 +1,4 @@
// Central place for telemetry
// TODO: Replace all of this with proper tracers and events
import { type Attributes, metrics } from "@opentelemetry/api"
import * as Sentry from "@sentry/nextjs"
import deepmerge from "deepmerge"
import { flatten } from "flat"
@@ -20,7 +17,6 @@ import type { ZodError } from "zod"
*
* @example
* ```typescript
* import { sanitize } from '@/server/telemetry';
*
* const input = {
* key1: "Example",
@@ -43,7 +39,7 @@ import type { ZodError } from "zod"
* // }
* ```
*/
export function sanitize(data: any): Attributes {
export function sanitize(data: any): Record<string, string | number | boolean> {
if (!data) return {}
if (typeof data === "string") {
return { value: data }
@@ -68,14 +64,8 @@ export function sanitize(data: any): Attributes {
* See the codebase for reference usage.
*/
export function createCounter(meterName: string, counterName: string) {
const meter = metrics.getMeter(meterName)
const fullName = `${meterName}.${counterName}`
const counter = meter.createCounter(fullName)
const success = meter.createCounter(`${fullName}-success`)
const fail = meter.createCounter(`${fullName}-fail`)
return {
/**
* Initializes the counter event handlers with a set of base attributes.
@@ -91,12 +81,8 @@ export function createCounter(meterName: string, counterName: string) {
*
* @param attrs - Additional attributes specific to this 'start' event. Defaults to an empty object.
*/
start(attrs: object = {}) {
const mergedAttrs = deepmerge.all<object>([baseAttrs, attrs])
const finalAttrs = sanitize(mergedAttrs)
counter.add(1, finalAttrs)
logger.debug(`[${fullName}] start`, mergedAttrs)
start(attrs: object | undefined = undefined) {
logger.debug(`[${fullName}] start`, attrs)
},
/**
@@ -107,8 +93,9 @@ export function createCounter(meterName: string, counterName: string) {
success(attrs: object = {}) {
const mergedAttrs = deepmerge.all<object>([baseAttrs, attrs])
const finalAttrs = sanitize(mergedAttrs)
success.add(1, finalAttrs)
Sentry.metrics.count(fullName, 1, {
attributes: { ...finalAttrs, status: "success" },
})
logger.debug(`[${fullName}] success`, mergedAttrs)
},
@@ -132,7 +119,9 @@ export function createCounter(meterName: string, counterName: string) {
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
Sentry.metrics.count(fullName, 1, {
attributes: { ...finalAttrs, status: "error" },
})
logger.error(`[${fullName}] dataError: ${errorMsg}`, mergedAttrs)
},
@@ -154,7 +143,9 @@ export function createCounter(meterName: string, counterName: string) {
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
Sentry.metrics.count(fullName, 1, {
attributes: { ...finalAttrs, status: "error" },
})
logger.error(`[${fullName}] noDataError:`, mergedAttrs)
},
@@ -174,7 +165,9 @@ export function createCounter(meterName: string, counterName: string) {
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
Sentry.metrics.count(fullName, 1, {
attributes: { ...finalAttrs, status: "error" },
})
logger.error(`[${fullName}] validationError`, mergedAttrs)
},
@@ -198,11 +191,14 @@ export function createCounter(meterName: string, counterName: string) {
"error.status": res.status,
"error.statusText": res.statusText,
"error.text": text,
url: res.url,
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
Sentry.metrics.count(fullName, 1, {
attributes: { ...finalAttrs, status: "error" },
})
logger.error(
`[${fullName}] httpError ${res.status}, ${res.statusText}:`,
mergedAttrs
@@ -235,7 +231,9 @@ export function createCounter(meterName: string, counterName: string) {
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
Sentry.metrics.count(fullName, 1, {
attributes: { ...finalAttrs, status: "error" },
})
logger.error(`[${fullName}] fail message: ${msg}`, mergedAttrs)
},
}

View File

@@ -1,4 +1,4 @@
import { trace, type Tracer } from "@opentelemetry/api"
import * as Sentry from "@sentry/nextjs"
import { getCacheClient } from "../dataCache"
import { env } from "../env/server"
@@ -11,24 +11,18 @@ interface ServiceTokenResponse {
expires_in: number
}
export async function getServiceToken() {
const tracer = trace.getTracer("getServiceToken")
return await tracer.startActiveSpan("getServiceToken", async () => {
export async function getServiceToken(): Promise<ServiceTokenResponse> {
return Sentry.startSpan({ name: "getServiceToken" }, async () => {
const scopes = env.CURITY_CLIENT_SERVICE_SCOPES
const cacheKey = getServiceTokenCacheKey(scopes)
const cacheClient = await getCacheClient()
const token = await getOrSetServiceTokenFromCache(cacheKey, scopes, tracer)
const token = await getOrSetServiceTokenFromCache(cacheKey, scopes)
if (token.expiresAt < Date.now()) {
await cacheClient.deleteKey(cacheKey)
const newToken = await getOrSetServiceTokenFromCache(
cacheKey,
scopes,
tracer
)
const newToken = await getOrSetServiceTokenFromCache(cacheKey, scopes)
return newToken.jwt
}
@@ -38,16 +32,14 @@ export async function getServiceToken() {
async function getOrSetServiceTokenFromCache(
cacheKey: string,
scopes: string[],
tracer: Tracer
scopes: string[]
) {
const cacheClient = await getCacheClient()
const token = await cacheClient.cacheOrGet(
cacheKey,
async () => {
return await tracer.startActiveSpan("fetch new token", async () => {
const newToken = await getJwt(scopes)
return newToken
return Sentry.startSpan({ name: "fetch new serviceToken" }, async () => {
return await getJwt(scopes)
})
},
"1h"
@@ -56,19 +48,10 @@ async function getOrSetServiceTokenFromCache(
}
async function getJwt(scopes: string[]) {
const getJwtCounter = createCounter("tokenManager", "getJwt")
const metricsGetJwt = getJwtCounter.init({
scopes,
})
metricsGetJwt.start()
const jwt = await fetchServiceToken(scopes)
const expiresAt = Date.now() + jwt.expires_in * 1000
metricsGetJwt.success()
return { expiresAt, jwt }
}