diff --git a/apps/redis-api/ci/bicep/app/main.bicep b/apps/redis-api/ci/bicep/app/main.bicep index 89d98874b..19ad3107d 100644 --- a/apps/redis-api/ci/bicep/app/main.bicep +++ b/apps/redis-api/ci/bicep/app/main.bicep @@ -2,6 +2,7 @@ import { Environment, EnvironmentVar } from '../types.bicep' targetScope = 'subscription' +param version string param environment Environment param containerImageTag string param redisConnection string @@ -45,6 +46,7 @@ module containerApp 'containerApp.bicep' = { { name: 'SENTRY_DSN', value: sentryDSN } { name: 'SENTRY_ENABLED', value: sentryEnabled } { name: 'SENTRY_TRACE_SAMPLE_RATE', value: sentryTraceSampleRate } + { name: 'VERSION', value: version } { name: 'timestamp', value: timestamp } ] diff --git a/apps/redis-api/ci/bicep/main.bicep b/apps/redis-api/ci/bicep/main.bicep index f9b7e3a1d..0afbd1bfb 100644 --- a/apps/redis-api/ci/bicep/main.bicep +++ b/apps/redis-api/ci/bicep/main.bicep @@ -4,6 +4,7 @@ targetScope = 'subscription' param environment Environment param containerImageTag string = 'latest' +param version string param primaryApiKey string param secondaryApiKey string @@ -51,5 +52,6 @@ module containerApp 'app/main.bicep' = { sentryDSN: sentryDSN sentryEnabled: sentryEnabled sentryTraceSampleRate: sentryTraceSampleRate + version: version } } diff --git a/apps/redis-api/src/env.ts b/apps/redis-api/src/env.ts index 48bc8de6d..18d32ed61 100644 --- a/apps/redis-api/src/env.ts +++ b/apps/redis-api/src/env.ts @@ -47,7 +47,7 @@ export const env = createEnv({ }, createFinalSchema: (shape) => { return z.object(shape).transform((env, ctx) => { - if (!env.SENTRY_ENABLED || !env.SENTRY_DSN) { + if (env.SENTRY_ENABLED && !env.SENTRY_DSN) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: diff --git a/apps/redis-api/src/routes/api/cache.ts b/apps/redis-api/src/routes/api/cache.ts index f32a445de..6b83bcdc4 100644 --- a/apps/redis-api/src/routes/api/cache.ts +++ b/apps/redis-api/src/routes/api/cache.ts @@ -16,7 +16,7 @@ export const cacheRoutes = new Elysia({ prefix: "/cache" }) "/", async ({ query: { key }, status }) => { key = validateKey(key); - cacheRouteLogger.info("GET /cache", key); + cacheRouteLogger.info(`GET /cache ${key}`); const value = await redis.get(key); if (!value) { @@ -48,7 +48,7 @@ export const cacheRoutes = new Elysia({ prefix: "/cache" }) "/", async ({ query: { key }, body, status, set }) => { key = validateKey(key); - cacheRouteLogger.info("PUT /cache", key); + cacheRouteLogger.info(`PUT /cache ${key}`); if (!body.ttl || body.ttl < 0) { return status("Bad Request", "ttl is required"); @@ -56,35 +56,45 @@ export const cacheRoutes = new Elysia({ prefix: "/cache" }) await redis.set(key, JSON.stringify(body.data), "EX", body.ttl); - return status(204, void 0); + set.status = 204; + return undefined; }, { body: t.Object({ data: t.Any(), ttl: t.Number() }), query: QUERY_TYPE, - response: { 204: t.Void(), 400: t.String() }, + response: { 204: t.Undefined(), 400: t.String() }, } ) .delete( "/", - async ({ query: { key, fuzzy }, status }) => { + async ({ query: { key, fuzzy }, set }) => { key = validateKey(key); - cacheRouteLogger.info("DELETE /cache", key, { fuzzy }); + cacheRouteLogger.info( + `DELETE /cache ${key} ${fuzzy ? "fuzzy" : ""}` + ); if (fuzzy) { await deleteWithPattern(`*${key}*`); } else { - await redis.del(key); - cacheRouteLogger.info("Deleted key: ", key); + const deletedKeys = await redis.del(key); + if (deletedKeys === 0) { + cacheRouteLogger.info( + `Key '${key}' not found, nothing deleted` + ); + } else { + cacheRouteLogger.info(`Deleted key '${key}'`); + } } - return status(204, void 0); + set.status = 204; + return undefined; }, { query: t.Object({ ...QUERY_TYPE.properties, ...t.Object({ fuzzy: t.Optional(t.Boolean()) }).properties, }), - response: { 204: t.Void(), 400: t.String() }, + response: { 204: t.Undefined(), 400: t.String() }, } ); @@ -97,9 +107,6 @@ function validateKey(key: string) { ); } - if (parsedKey.includes("*")) { - throw new ModelValidationError("Key cannot contain wildcards"); - } if (parsedKey.includes("*")) { throw new ModelValidationError("Key cannot contain wildcards"); } @@ -128,7 +135,13 @@ async function deleteWithPattern(pattern: string) { } while (cursor !== "0"); if (keys.length > 0) { - await redis.del(...keys); + const deleteCount = await redis.del(...keys); + keys.map((key, idx) => { + cacheRouteLogger.info( + `Deleted key ${idx + 1}/${deleteCount}: ${key}` + ); + }); + + cacheRouteLogger.info(`Deleted number of keys: ${deleteCount}`); } - cacheRouteLogger.info("Deleted number of keys: ", keys.length); } diff --git a/apps/redis-api/src/routes/health.ts b/apps/redis-api/src/routes/health.ts index 667890784..240eb94cb 100644 --- a/apps/redis-api/src/routes/health.ts +++ b/apps/redis-api/src/routes/health.ts @@ -1,33 +1,55 @@ import Elysia, { t } from "elysia"; import { redis } from "@/services/redis"; -import { baseLogger } from "@/utils/logger"; +import { baseLogger, loggerModule } from "@/utils/logger"; +import { env } from "@/env"; + +const healthLogger = baseLogger.child({ + module: "health", +}); export const healthRoutes = new Elysia().get( "/health", - async ({ set, status }) => { + async ({ status }) => { const perf = performance.now(); + let healthy = true; try { await redis.ping(); } catch (e) { - baseLogger.error("Redis connection error:", e); - console.log("Redis connection error:", e); + healthLogger.error("Redis connection error:", e); - return status(503, { healthy: false }); + healthy = false; } const duration = performance.now() - perf; - baseLogger.info(`Service healthy: ${duration.toFixed(2)} ms`); + const durationString = `${duration.toFixed(2)} ms`; - return { healthy: true }; + if (!healthy) { + healthLogger.error("Health check failed"); + return status(503, { + healthy, + version: env.VERSION, + duration: durationString, + }); + } + + return { + healthy, + version: env.VERSION, + duration: durationString, + }; }, { response: { 200: t.Object({ healthy: t.Boolean(), + version: t.String(), + duration: t.String(), }), 503: t.Object({ healthy: t.Boolean(), + version: t.String(), + duration: t.String(), }), }, }