From 5323a8e46ead79f39ec021d5db519b20fba91384 Mon Sep 17 00:00:00 2001 From: Michael Zetterberg Date: Thu, 17 Apr 2025 07:16:11 +0200 Subject: [PATCH] feat: harmonize log and metrics --- apps/scandic-web/app/api/web/sitemap/route.ts | 58 +- apps/scandic-web/app/api/web/sitemap/sync.ts | 75 +- .../app/api/web/sitemap/telemetry.ts | 43 -- apps/scandic-web/app/api/web/sitemap/utils.ts | 51 +- .../HotelListingMapContent/HotelPin/index.tsx | 1 + apps/scandic-web/jest.config.ts | 9 +- apps/scandic-web/jest.setup.ts | 2 + apps/scandic-web/package.json | 9 +- .../server/routers/booking/mutation.ts | 425 ++--------- .../server/routers/booking/query.ts | 129 +--- .../routers/contentstack/accountPage/query.ts | 113 +-- .../server/routers/contentstack/base/query.ts | 499 +++--------- .../routers/contentstack/base/telemetry.ts | 112 --- .../routers/contentstack/breadcrumbs/query.ts | 100 +-- .../contentstack/collectionPage/output.ts | 2 +- .../contentstack/collectionPage/query.ts | 24 +- .../contentstack/collectionPage/utils.ts | 86 +-- .../routers/contentstack/contentPage/query.ts | 24 +- .../routers/contentstack/contentPage/utils.ts | 88 +-- .../contentstack/destinationCityPage/query.ts | 123 +-- .../destinationCityPage/telemetry.ts | 43 -- .../contentstack/destinationCityPage/utils.ts | 81 +- .../destinationCountryPage/query.ts | 103 +-- .../destinationCountryPage/telemetry.ts | 45 -- .../destinationCountryPage/utils.ts | 93 +-- .../destinationOverviewPage/query.ts | 109 +-- .../destinationOverviewPage/telemetry.ts | 23 - .../routers/contentstack/hotelPage/query.ts | 57 +- .../contentstack/hotelPage/telemetry.ts | 43 -- .../routers/contentstack/hotelPage/utils.ts | 146 +--- .../languageSwitcher/telemetry.ts | 12 - .../contentstack/languageSwitcher/utils.ts | 61 +- .../contentstack/loyaltyLevel/query.ts | 93 +-- .../routers/contentstack/loyaltyPage/query.ts | 130 +--- .../routers/contentstack/metadata/query.ts | 79 +- .../routers/contentstack/partner/query.ts | 50 +- .../routers/contentstack/reward/query.ts | 275 +++---- .../routers/contentstack/reward/utils.ts | 194 +---- .../routers/contentstack/startPage/query.ts | 107 +-- .../contentstack/startPage/telemetry.ts | 23 - .../server/routers/hotels/metrics.ts | 88 --- .../server/routers/hotels/query.ts | 481 +++--------- .../server/routers/hotels/telemetry.ts | 114 --- .../server/routers/hotels/utils.ts | 714 ++++++------------ .../routers/navigation/mypages/index.ts | 2 +- .../server/routers/partners/jobylon/query.ts | 75 +- .../routers/partners/jobylon/telemetry.ts | 10 - apps/scandic-web/server/routers/user/input.ts | 6 +- .../server/routers/user/mutation.ts | 91 +-- apps/scandic-web/server/routers/user/query.ts | 629 +++------------ apps/scandic-web/server/routers/user/utils.ts | 419 ++++++++-- .../server/telemetry/index.test.ts | 135 ++++ apps/scandic-web/server/telemetry/index.ts | 309 ++++++++ apps/scandic-web/server/tokenManager.ts | 71 +- apps/scandic-web/tsconfig.spec.json | 7 + apps/scandic-web/types/requests/packages.ts | 3 + .../types/trpc/routers/hotel/availability.ts | 3 + yarn.lock | 153 +++- 58 files changed, 2324 insertions(+), 4726 deletions(-) delete mode 100644 apps/scandic-web/app/api/web/sitemap/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/contentstack/base/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/contentstack/destinationCityPage/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/contentstack/destinationCountryPage/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/contentstack/destinationOverviewPage/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/contentstack/hotelPage/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/contentstack/languageSwitcher/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/contentstack/startPage/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/hotels/metrics.ts delete mode 100644 apps/scandic-web/server/routers/hotels/telemetry.ts delete mode 100644 apps/scandic-web/server/routers/partners/jobylon/telemetry.ts create mode 100644 apps/scandic-web/server/telemetry/index.test.ts create mode 100644 apps/scandic-web/server/telemetry/index.ts create mode 100644 apps/scandic-web/tsconfig.spec.json diff --git a/apps/scandic-web/app/api/web/sitemap/route.ts b/apps/scandic-web/app/api/web/sitemap/route.ts index fd447c728..87ca49f8d 100644 --- a/apps/scandic-web/app/api/web/sitemap/route.ts +++ b/apps/scandic-web/app/api/web/sitemap/route.ts @@ -2,6 +2,7 @@ import { type NextRequest, NextResponse } from "next/server" import { env } from "@/env/server" import { dt } from "@/lib/dt" +import { createCounter } from "@/server/telemetry" import { getEntries, @@ -13,22 +14,16 @@ import { } from "@/utils/sitemap" import { contentstackSync } from "./sync" -import { - generateSitemapCounter, - generateSitemapFailCounter, - generateSitemapSuccessCounter, - saveEntriesCounter, - saveSitemapDataCounter, - saveSyncTokenCounter, -} from "./telemetry" import { mapEntriesToSitemapData, mergeEntries } from "./utils" export const dynamic = "force-dynamic" export async function GET(request: NextRequest) { + const generateSitemapCounter = createCounter("sitemap", "generate") + const metricsGenerateSitemap = generateSitemapCounter.init() try { - generateSitemapCounter.add(1) - console.info("sitemap.generate start") + metricsGenerateSitemap.start() + const headersList = request.headers const secret = headersList.get("x-sitemap-sync-secret") @@ -44,49 +39,46 @@ export async function GET(request: NextRequest) { const responseData = await contentstackSync(syncToken) const mergedEntries = mergeEntries(currentEntries, responseData.entries) - saveEntriesCounter.add(1, { entriesCount: mergedEntries.length }) - console.info( - "sitemap.entries.save", - JSON.stringify({ entriesCount: mergedEntries.length }) - ) + const entriesSaveCounter = createCounter("sitemap", "entries.save") + const metricsEntriesSave = entriesSaveCounter.init({ + entriesCount: mergedEntries.length, + }) + + metricsEntriesSave.start() await saveEntries(mergedEntries) + metricsEntriesSave.success() const sitemapData = mapEntriesToSitemapData(mergedEntries) const lastUpdated = dt().utc().format() - saveSitemapDataCounter.add(1, { + + const saveDataCounter = createCounter("sitemap", "data.save") + const metricsDataSave = saveDataCounter.init({ sitemapEntriesCount: sitemapData.length, + lastUpdated, }) - console.info( - "sitemap.data.save", - JSON.stringify({ - sitemapEntriesCount: sitemapData.length, - lastUpdated, - }) - ) + metricsDataSave.start() await saveSitemapData(sitemapData) await saveLastUpdatedDate(lastUpdated) + metricsDataSave.success() if (syncToken !== responseData.syncToken) { - saveSyncTokenCounter.add(1, { + const syncTokenSaveCounter = createCounter("sitemap", "syncToken.save") + const metricsSyncTokenSave = syncTokenSaveCounter.init({ syncToken: responseData.syncToken, }) - console.info( - "sitemap.synctoken.save", - JSON.stringify({ syncToken: responseData.syncToken }) - ) + metricsSyncTokenSave.start() await saveSyncToken(responseData.syncToken) + metricsSyncTokenSave.success() } - generateSitemapSuccessCounter.add(1) + metricsGenerateSitemap.success() + return NextResponse.json({ message: "Sitemap data generated and stored successfully!", now: dt().utc().format(), }) } catch (error) { - const errorMessage = - error instanceof Error ? error.message : JSON.stringify(error) - generateSitemapFailCounter.add(1, { error: errorMessage }) - console.error("sitemap.generate.fail", errorMessage) + metricsGenerateSitemap.fail(error) return NextResponse.json( { diff --git a/apps/scandic-web/app/api/web/sitemap/sync.ts b/apps/scandic-web/app/api/web/sitemap/sync.ts index 80ea1ebb2..f90106282 100644 --- a/apps/scandic-web/app/api/web/sitemap/sync.ts +++ b/apps/scandic-web/app/api/web/sitemap/sync.ts @@ -1,13 +1,7 @@ import { Region, Stack } from "contentstack" import { env } from "@/env/server" - -import { - syncEntriesCounter, - syncEntriesFailCounter, - syncEntriesPaginationCounter, - syncEntriesSuccessCounter, -} from "./telemetry" +import { createCounter } from "@/server/telemetry" import type { SyncResponse } from "@/types/sitemap" @@ -24,72 +18,50 @@ export async function contentstackSync(syncToken: string | null) { const entries = [] const syncOptions = syncToken ? { sync_token: syncToken } : { init: true } - syncEntriesCounter.add(1, { + const entriesSyncCounter = createCounter("sitemap", "entries.sync") + const metricsEntriesSync = entriesSyncCounter.init({ environment, ...syncOptions, }) - console.info( - "sitemap.entries.sync start", - JSON.stringify({ - environment, - ...syncOptions, - }) - ) + + metricsEntriesSync.start() + try { let syncResponse: SyncResponse = await stack.sync(syncOptions) entries.push(...syncResponse.items) + const entriesSyncPaginationCounter = createCounter( + "sitemap", + "entries.sync.pagination" + ) + const metricsEntriesSyncPagination = entriesSyncPaginationCounter.init({ + environment, + ...syncOptions, + }) // Check if there is a pagination token, and fetch more data if needed while (syncResponse.pagination_token && !syncResponse.sync_token) { - syncEntriesPaginationCounter.add(1, { - environment, + metricsEntriesSyncPagination.start({ paginationToken: syncResponse.pagination_token, }) - console.info( - "sitemap.entries.sync.pagination start", - JSON.stringify({ - environment, - paginationToken: syncResponse.pagination_token, - }) - ) + syncResponse = await stack.sync({ pagination_token: syncResponse.pagination_token, }) entries.push(...syncResponse.items) - syncEntriesPaginationCounter.add(1, { - environment, + metricsEntriesSyncPagination.success({ paginationToken: syncResponse.pagination_token, - entriesCount: syncResponse.items.length, }) - console.info( - "sitemap.entries.sync.pagination success", - JSON.stringify({ - environment, - paginationToken: syncResponse.pagination_token, - entriesCount: syncResponse.items.length, - }) - ) } if (syncResponse.sync_token) { - syncEntriesSuccessCounter.add(1, { - environment, - ...syncOptions, + metricsEntriesSync.success({ newSyncToken: syncResponse.sync_token, entriesCount: entries.length, }) - console.info( - "sitemap.entries.sync success", - JSON.stringify({ - environment, - ...syncOptions, - newSyncToken: syncResponse.sync_token, - entriesCount: entries.length, - }) - ) + return { syncToken: syncResponse.sync_token, entries, @@ -98,14 +70,7 @@ export async function contentstackSync(syncToken: string | null) { throw new Error("No sync token received, something went wrong") } } catch (error) { - const errorMessage = - error instanceof Error ? error.message : JSON.stringify(error) - syncEntriesFailCounter.add(1, { - environment, - ...syncOptions, - error: errorMessage, - }) - console.error("sitemap.entries.sync error", errorMessage) + metricsEntriesSync.fail(error) throw new Error("Failed to sync entries") } diff --git a/apps/scandic-web/app/api/web/sitemap/telemetry.ts b/apps/scandic-web/app/api/web/sitemap/telemetry.ts deleted file mode 100644 index 23f61ee62..000000000 --- a/apps/scandic-web/app/api/web/sitemap/telemetry.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("sitemap") - -// OpenTelemetry metrics -export const generateSitemapCounter = meter.createCounter("sitemap.generate") -export const generateSitemapSuccessCounter = meter.createCounter( - "sitemap.generate-success" -) -export const generateSitemapFailCounter = meter.createCounter( - "sitemap.generate-fail" -) - -export const syncEntriesCounter = meter.createCounter("sitemap.entries.sync") -export const syncEntriesSuccessCounter = meter.createCounter( - "sitemap.entries.sync-success" -) -export const syncEntriesFailCounter = meter.createCounter( - "sitemap.entries.sync-fail" -) -export const syncEntriesPaginationCounter = meter.createCounter( - "sitemap.entries.sync.pagination" -) -export const syncEntriesPaginationSuccessCounter = meter.createCounter( - "sitemap.entries.sync.pagination-success" -) - -export const mergeEntriesCounter = meter.createCounter("sitemap.entries.merge") -export const mergeEntriesSuccessCounter = meter.createCounter( - "sitemap.entries.merge-success" -) -export const saveEntriesCounter = meter.createCounter("sitemap.entries.save") - -export const transformEntriesCounter = meter.createCounter( - "sitemap.entries.transform" -) -export const transformEntriesSuccessCounter = meter.createCounter( - "sitemap.entries.transform-success" -) -export const saveSitemapDataCounter = meter.createCounter("sitemap.data.save") -export const saveSyncTokenCounter = meter.createCounter( - "sitemap.synctoken.save" -) diff --git a/apps/scandic-web/app/api/web/sitemap/utils.ts b/apps/scandic-web/app/api/web/sitemap/utils.ts index 853058a3b..eeeb59884 100644 --- a/apps/scandic-web/app/api/web/sitemap/utils.ts +++ b/apps/scandic-web/app/api/web/sitemap/utils.ts @@ -1,32 +1,23 @@ import { Lang } from "@/constants/languages" import { env } from "@/env/server" +import { createCounter } from "@/server/telemetry" import { removeTrailingSlash } from "@/utils/url" -import { - mergeEntriesCounter, - mergeEntriesSuccessCounter, - transformEntriesCounter, - transformEntriesSuccessCounter, -} from "./telemetry" - import type { SitemapEntry, SyncItem } from "@/types/sitemap" export function mergeEntries( currentEntries: SyncItem[], newEntries: SyncItem[] ) { - mergeEntriesCounter.add(1, { + const entriesMergeCounter = createCounter("sitemap", "entries.merge") + const metricsEntriesMerge = entriesMergeCounter.init({ currentEntriesCount: currentEntries.length, newEntriesCount: newEntries.length, }) - console.info( - "sitemap.entries.merge start", - JSON.stringify({ - currentEntriesCount: currentEntries.length, - newEntriesCount: newEntries.length, - }) - ) + + metricsEntriesMerge.start() + const entries = [...currentEntries] newEntries.forEach((entry) => { const index = entries.findIndex( @@ -40,27 +31,20 @@ export function mergeEntries( } }) - mergeEntriesSuccessCounter.add(1, { + metricsEntriesMerge.success({ entriesCount: entries.length, }) - console.info( - "sitemap.entries.merge success", - JSON.stringify({ - entriesCount: entries.length, - }) - ) return entries } export function mapEntriesToSitemapData(entries: SyncItem[]) { - transformEntriesCounter.add(1, { entriesCount: entries.length }) - console.info( - "sitemap.entries.transform start", - JSON.stringify({ - entriesCount: entries.length, - }) - ) + const entriesTransformCounter = createCounter("sitemap", "entries.transform") + const metricsEntriesTransform = entriesTransformCounter.init({ + entriesCount: entries.length, + }) + + metricsEntriesTransform.start() const filteredEntries = filterEntriesToSitemapEntries(entries) @@ -69,17 +53,10 @@ export function mapEntriesToSitemapData(entries: SyncItem[]) { .map(([_, entries]) => mapEntriesToSitemapEntry(entries)) .filter((entry): entry is SitemapEntry => !!entry) - transformEntriesSuccessCounter.add(1, { + metricsEntriesTransform.success({ entriesCount: entries.length, sitemapEntriesCount: sitemapEntries.length, }) - console.info( - "sitemap.entries.transform success", - JSON.stringify({ - entriesCount: entries.length, - sitemapEntriesCount: sitemapEntries.length, - }) - ) return sitemapEntries } diff --git a/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/HotelPin/index.tsx b/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/HotelPin/index.tsx index 1b33b020f..d790ea4ba 100644 --- a/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/HotelPin/index.tsx +++ b/apps/scandic-web/components/Maps/InteractiveMap/HotelListingMapContent/HotelPin/index.tsx @@ -39,6 +39,7 @@ export default function HotelPin({ )} + {/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}

{isNotAvailable ? "—" : formatPrice(intl, hotelPrice, currency)}

diff --git a/apps/scandic-web/jest.config.ts b/apps/scandic-web/jest.config.ts index bf51303a6..06ae053e3 100644 --- a/apps/scandic-web/jest.config.ts +++ b/apps/scandic-web/jest.config.ts @@ -3,15 +3,18 @@ * https://jestjs.io/docs/configuration */ import nextJest from "next/jest.js" +import { createJsWithTsEsmPreset } from "ts-jest" import type { Config } from "jest" +const presetConfig = createJsWithTsEsmPreset() + const createJestConfig = nextJest({ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment dir: "./", }) -const config: Config = { +const config = { // All imported modules in your tests should be mocked automatically // automock: false, @@ -202,6 +205,8 @@ const config: Config = { // Whether to use watchman for file crawling // watchman: true, -} + + ...presetConfig, +} satisfies Config export default createJestConfig(config) diff --git a/apps/scandic-web/jest.setup.ts b/apps/scandic-web/jest.setup.ts index 31fe0b102..ec0e23ec3 100644 --- a/apps/scandic-web/jest.setup.ts +++ b/apps/scandic-web/jest.setup.ts @@ -1,6 +1,8 @@ import "@testing-library/jest-dom/jest-globals" import "@testing-library/jest-dom" +import { jest } from "@jest/globals" + jest.mock("next/navigation", () => ({ useRouter: jest.fn(), usePathname: jest.fn().mockReturnValue("/"), diff --git a/apps/scandic-web/package.json b/apps/scandic-web/package.json index e5a8c8b7e..475b61e7e 100644 --- a/apps/scandic-web/package.json +++ b/apps/scandic-web/package.json @@ -15,8 +15,8 @@ "test:e2e:headless": "start-server-and-test test:setup http://127.0.0.1:3000/en/sponsoring \"cypress run --e2e\"", "test:setup": "yarn build && yarn start", "preinstall": "/bin/sh -c \"export $(cat .env.local | grep -v '^#' | xargs)\"", - "test": "jest", - "test:watch": "jest --watch", + "test": "node --experimental-vm-modules $(yarn bin jest)", + "test:watch": "node --experimental-vm-modules $(yarn bin jest) --watch", "ci:build": "yarn lint && yarn test && yarn build", "clean": "rm -rf .next", "i18n:extract": "formatjs extract \"{actions,app,components,constants,hooks,i18n,lib,middlewares,server,stores,utils}/**/*.{ts,tsx}\" --format i18n/tooling/formatter.mjs --out-file i18n/tooling/extracted.json", @@ -76,6 +76,7 @@ "embla-carousel-react": "^8.5.2", "fast-deep-equal": "^3.1.3", "fetch-retry": "^6.0.0", + "flat": "^6.0.1", "framer-motion": "^11.3.28", "fuse.js": "^7.1.0", "graphql": "^16.8.1", @@ -88,6 +89,7 @@ "ioredis": "^5.5.0", "json-stable-stringify-without-jsonify": "^1.0.1", "libphonenumber-js": "^1.10.60", + "lodash-es": "^4.17.21", "nanoid": "^5.0.9", "next": "^14.2.25", "next-auth": "5.0.0-beta.19", @@ -101,7 +103,6 @@ "react-international-phone": "^4.2.6", "react-intl": "^6.6.8", "react-to-print": "^3.0.2", - "secure-json-parse": "^4.0.0", "server-only": "^0.0.1", "slugify": "^1.6.6", "sonner": "^1.7.0", @@ -124,6 +125,7 @@ "@types/adm-zip": "^0.5.7", "@types/jest": "^29.5.12", "@types/json-stable-stringify-without-jsonify": "^1.0.2", + "@types/lodash-es": "^4", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -148,6 +150,7 @@ "react-material-symbols": "^4.4.0", "schema-dts": "^1.1.2", "start-server-and-test": "^2.0.3", + "ts-jest": "^29.3.2", "ts-morph": "^25.0.1", "ts-node": "^10.9.2", "typescript": "5.4.5", diff --git a/apps/scandic-web/server/routers/booking/mutation.ts b/apps/scandic-web/server/routers/booking/mutation.ts index 2a6225f43..4b50a87e9 100644 --- a/apps/scandic-web/server/routers/booking/mutation.ts +++ b/apps/scandic-web/server/routers/booking/mutation.ts @@ -1,12 +1,8 @@ -import { metrics } from "@opentelemetry/api" -import sjson from "secure-json-parse" - import * as api from "@/lib/api" -import { getVerifiedUser } from "@/server/routers/user/query" +import { getMembershipNumber } from "@/server/routers/user/utils" +import { createCounter } from "@/server/telemetry" import { router, safeProtectedServiceProcedure } from "@/server/trpc" -import { isValidSession } from "@/utils/session" - import { addPackageInput, cancelBookingInput, @@ -18,75 +14,6 @@ import { } from "./input" import { bookingConfirmationSchema, createBookingSchema } from "./output" -import type { Session } from "next-auth" - -const meter = metrics.getMeter("trpc.bookings") -const createBookingCounter = meter.createCounter("trpc.bookings.create") -const createBookingSuccessCounter = meter.createCounter( - "trpc.bookings.create-success" -) -const createBookingFailCounter = meter.createCounter( - "trpc.bookings.create-fail" -) - -const priceChangeCounter = meter.createCounter("trpc.bookings.price-change") -const priceChangeSuccessCounter = meter.createCounter( - "trpc.bookings.price-change-success" -) -const priceChangeFailCounter = meter.createCounter( - "trpc.bookings.price-change-fail" -) -const cancelBookingCounter = meter.createCounter("trpc.bookings.cancel") -const cancelBookingSuccessCounter = meter.createCounter( - "trpc.bookings.cancel-success" -) -const cancelBookingFailCounter = meter.createCounter( - "trpc.bookings.cancel-fail" -) - -const addPackageCounter = meter.createCounter("trpc.bookings.add-package") -const addPackageSuccessCounter = meter.createCounter( - "trpc.bookings.add-package-success" -) -const addPackageFailCounter = meter.createCounter( - "trpc.bookings.add-package-fail" -) -const guaranteeBookingCounter = meter.createCounter("trpc.bookings.guarantee") -const guaranteeBookingSuccessCounter = meter.createCounter( - "trpc.bookings.guarantee-success" -) -const guaranteeBookingFailCounter = meter.createCounter( - "trpc.bookings.guarantee-fail" -) -const updateBookingCounter = meter.createCounter("trpc.bookings.update-booking") -const updateBookingSuccessCounter = meter.createCounter( - "trpc.bookings.update-booking-success" -) -const updateBookingFailCounter = meter.createCounter( - "trpc.bookings.update-booking-fail" -) - -const removePackageCounter = meter.createCounter("trpc.bookings.remove-package") -const removePackageSuccessCounter = meter.createCounter( - "trpc.bookings.remove-package-success" -) -const removePackageFailCounter = meter.createCounter( - "trpc.bookings.remove-package-fail" -) - -async function getMembershipNumber( - session: Session | null -): Promise { - if (!isValidSession(session)) return undefined - - const verifiedUser = await getVerifiedUser({ session }) - if (!verifiedUser || "error" in verifiedUser) { - return undefined - } - - return verifiedUser.data.membershipNumber -} - export const bookingMutationRouter = router({ create: safeProtectedServiceProcedure .input(createBookingInput) @@ -95,22 +22,17 @@ export const bookingMutationRouter = router({ const { language, ...inputWithoutLang } = input const { hotelId, checkInDate, checkOutDate } = inputWithoutLang - const loggingAttributes = { + const createBookingCounter = createCounter("trpc.booking", "create") + const metricsCreateBooking = createBookingCounter.init({ membershipNumber: await getMembershipNumber(ctx.session), checkInDate, checkOutDate, hotelId, language, - } + }) - createBookingCounter.add(1, loggingAttributes) + metricsCreateBooking.start() - console.info( - "api.booking.create start", - JSON.stringify({ - query: loggingAttributes, - }) - ) const headers = { Authorization: `Bearer ${accessToken}`, } @@ -125,29 +47,9 @@ export const bookingMutationRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - createBookingFailCounter.add(1, { - hotelId, - checkInDate, - checkOutDate, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - }), - }) - console.error( - "api.booking.create error", - JSON.stringify({ - query: loggingAttributes, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsCreateBooking.httpError(apiResponse) - const apiJson = sjson.safeParse(text) + const apiJson = await apiResponse.json() if ("errors" in apiJson && apiJson.errors.length) { const error = apiJson.errors[0] return { error: true, cause: error.code } as const @@ -160,45 +62,25 @@ export const bookingMutationRouter = router({ const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { - createBookingFailCounter.add(1, { - hotelId, - checkInDate, - checkOutDate, - error_type: "validation_error", - }) - - console.error( - "api.booking.create validation error", - JSON.stringify({ - query: loggingAttributes, - error: verifiedData.error, - }) - ) + metricsCreateBooking.validationError(verifiedData.error) return null } - createBookingSuccessCounter.add(1, { - hotelId, - checkInDate, - checkOutDate, - }) - - console.info( - "api.booking.create success", - JSON.stringify({ - query: loggingAttributes, - }) - ) + metricsCreateBooking.success() return verifiedData.data }), priceChange: safeProtectedServiceProcedure .input(priceChangeInput) .mutation(async function ({ ctx, input }) { - const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { confirmationNumber } = input - priceChangeCounter.add(1, { confirmationNumber }) + const priceChangeCounter = createCounter("trpc.booking", "price-change") + const metricsPriceChange = priceChangeCounter.init({ confirmationNumber }) + + metricsPriceChange.start() + + const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const headers = { Authorization: `Bearer ${accessToken}`, @@ -213,46 +95,18 @@ export const bookingMutationRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - priceChangeFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - }), - }) - console.error( - "api.booking.priceChange error", - JSON.stringify({ - query: { confirmationNumber }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - error: text, - }, - }) - ) + await metricsPriceChange.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { - priceChangeFailCounter.add(1, { - confirmationNumber, - error_type: "validation_error", - }) - console.error( - "api.booking.priceChange validation error", - JSON.stringify({ - query: { confirmationNumber }, - error: verifiedData.error, - }) - ) + metricsPriceChange.validationError(verifiedData.error) return null } - priceChangeSuccessCounter.add(1, { confirmationNumber }) + metricsPriceChange.success() return verifiedData.data }), @@ -262,6 +116,14 @@ export const bookingMutationRouter = router({ const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { confirmationNumber, language } = input + const cancelBookingCounter = createCounter("trpc.booking", "cancel") + const metricsCancelBooking = cancelBookingCounter.init({ + confirmationNumber, + language, + }) + + metricsCancelBooking.start() + const headers = { Authorization: `Bearer ${accessToken}`, } @@ -271,21 +133,6 @@ export const bookingMutationRouter = router({ reason: "WEB-CANCEL", } - const loggingAttributes = { - confirmationNumber, - language, - } - - cancelBookingCounter.add(1, loggingAttributes) - - console.info( - "api.booking.cancel start", - JSON.stringify({ - request: loggingAttributes, - headers, - }) - ) - const apiResponse = await api.remove( api.endpoints.v1.Booking.cancel(confirmationNumber), { @@ -296,25 +143,7 @@ export const bookingMutationRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - cancelBookingFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - }), - }) - console.error( - "api.booking.cancel error", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - query: loggingAttributes, - }) - ) + await metricsCancelBooking.httpError(apiResponse) return false } @@ -323,29 +152,11 @@ export const bookingMutationRouter = router({ const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { - cancelBookingFailCounter.add(1, { - confirmationNumber, - error_type: "validation_error", - }) - - console.error( - "api.booking.cancel validation error", - JSON.stringify({ - query: loggingAttributes, - error: verifiedData.error, - }) - ) + metricsCancelBooking.validationError(verifiedData.error) return null } - cancelBookingSuccessCounter.add(1, loggingAttributes) - - console.info( - "api.booking.cancel success", - JSON.stringify({ - query: loggingAttributes, - }) - ) + metricsCancelBooking.success() return verifiedData.data }), @@ -355,7 +166,10 @@ export const bookingMutationRouter = router({ const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { confirmationNumber, ...body } = input - addPackageCounter.add(1, { confirmationNumber }) + const addPackageCounter = createCounter("trpc.booking", "package.add") + const metricsAddPackage = addPackageCounter.init({ confirmationNumber }) + + metricsAddPackage.start() const headers = { Authorization: `Bearer ${accessToken}`, @@ -370,46 +184,18 @@ export const bookingMutationRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - addPackageFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - }), - }) - console.error( - "api.booking.addPackage error", - JSON.stringify({ - query: { confirmationNumber }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - error: text, - }, - }) - ) + await metricsAddPackage.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { - addPackageFailCounter.add(1, { - confirmationNumber, - error_type: "validation_error", - }) - console.error( - "api.booking.addPackage validation error", - JSON.stringify({ - query: { confirmationNumber }, - error: verifiedData.error, - }) - ) + metricsAddPackage.validationError(verifiedData.error) return null } - addPackageSuccessCounter.add(1, { confirmationNumber }) + metricsAddPackage.success() return verifiedData.data }), @@ -419,7 +205,12 @@ export const bookingMutationRouter = router({ const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { confirmationNumber, language, ...body } = input - guaranteeBookingCounter.add(1, { confirmationNumber }) + const guaranteeBookingCounter = createCounter("trpc.booking", "guarantee") + const metricsGuaranteeBooking = guaranteeBookingCounter.init({ + confirmationNumber, + }) + + metricsGuaranteeBooking.start() const headers = { Authorization: `Bearer ${accessToken}`, @@ -435,46 +226,18 @@ export const bookingMutationRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - guaranteeBookingFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - }), - }) - console.error( - "api.booking.guarantee error", - JSON.stringify({ - query: { confirmationNumber }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - error: text, - }, - }) - ) + await metricsGuaranteeBooking.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { - guaranteeBookingFailCounter.add(1, { - confirmationNumber, - error_type: "validation_error", - }) - console.error( - "api.booking.guarantee validation error", - JSON.stringify({ - query: { confirmationNumber }, - error: verifiedData.error, - }) - ) + metricsGuaranteeBooking.validationError(verifiedData.error) return null } - guaranteeBookingSuccessCounter.add(1, { confirmationNumber }) + metricsGuaranteeBooking.success() return verifiedData.data }), @@ -484,7 +247,12 @@ export const bookingMutationRouter = router({ const accessToken = ctx.session?.token.access_token || ctx.serviceToken const { confirmationNumber, ...body } = input - updateBookingCounter.add(1, { confirmationNumber }) + const updateBookingCounter = createCounter("trpc.booking", "update") + const metricsUpdateBooking = updateBookingCounter.init({ + confirmationNumber, + }) + + metricsUpdateBooking.start() const apiResponse = await api.put( api.endpoints.v1.Booking.booking(confirmationNumber), @@ -497,25 +265,7 @@ export const bookingMutationRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - updateBookingFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - }), - }) - console.error( - "api.booking.updateBooking error", - JSON.stringify({ - query: { confirmationNumber }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - error: text, - }, - }) - ) + await metricsUpdateBooking.httpError(apiResponse) return null } @@ -523,21 +273,11 @@ export const bookingMutationRouter = router({ const verifiedData = bookingConfirmationSchema.safeParse(apiJson) if (!verifiedData.success) { - updateBookingFailCounter.add(1, { - confirmationNumber, - error_type: "validation_error", - }) - console.error( - "api.booking.updateBooking validation error", - JSON.stringify({ - query: { confirmationNumber }, - error: verifiedData.error, - }) - ) + metricsUpdateBooking.validationError(verifiedData.error) return null } - updateBookingSuccessCounter.add(1, { confirmationNumber }) + metricsUpdateBooking.success() return verifiedData.data }), @@ -547,26 +287,22 @@ export const bookingMutationRouter = router({ const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken const { confirmationNumber, codes, language } = input - const headers = { - Authorization: `Bearer ${accessToken}`, - } - - const loggingAttributes = { + const removePackageCounter = createCounter( + "trpc.booking", + "package.remove" + ) + const metricsRemovePackage = removePackageCounter.init({ confirmationNumber, codes, language, + }) + + metricsRemovePackage.start() + + const headers = { + Authorization: `Bearer ${accessToken}`, } - removePackageCounter.add(1, loggingAttributes) - - console.info( - "api.booking.remove-package start", - JSON.stringify({ - request: loggingAttributes, - headers, - }) - ) - const apiResponse = await api.remove( api.endpoints.v1.Booking.packages(confirmationNumber), { @@ -576,36 +312,11 @@ export const bookingMutationRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - removePackageFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - }), - }) - console.error( - "api.booking.remove-package error", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - query: loggingAttributes, - }) - ) + await metricsRemovePackage.httpError(apiResponse) return false } - removePackageSuccessCounter.add(1, loggingAttributes) - - console.info( - "api.booking.remove-package success", - JSON.stringify({ - query: loggingAttributes, - }) - ) + metricsRemovePackage.success() return true }), diff --git a/apps/scandic-web/server/routers/booking/query.ts b/apps/scandic-web/server/routers/booking/query.ts index 392ad3f4e..e39913dc0 100644 --- a/apps/scandic-web/server/routers/booking/query.ts +++ b/apps/scandic-web/server/routers/booking/query.ts @@ -1,7 +1,6 @@ -import { metrics } from "@opentelemetry/api" - import * as api from "@/lib/api" import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { router, safeProtectedServiceProcedure, @@ -18,19 +17,6 @@ import { import { bookingConfirmationSchema, createBookingSchema } from "./output" import { getBookedHotelRoom } from "./utils" -const meter = metrics.getMeter("trpc.booking") -const getBookingCounter = meter.createCounter("trpc.booking.get") -const getBookingSuccessCounter = meter.createCounter("trpc.booking.get-success") -const getBookingFailCounter = meter.createCounter("trpc.booking.get-fail") - -const getBookingStatusCounter = meter.createCounter("trpc.booking.status") -const getBookingStatusSuccessCounter = meter.createCounter( - "trpc.booking.status-success" -) -const getBookingStatusFailCounter = meter.createCounter( - "trpc.booking.status-fail" -) - export const bookingQueryRouter = router({ get: safeProtectedServiceProcedure .input(getBookingInput) @@ -38,7 +24,10 @@ export const bookingQueryRouter = router({ ctx, input: { confirmationNumber, lang: inputLang }, }) { - getBookingCounter.add(1, { confirmationNumber }) + const getBookingCounter = createCounter("trpc.booking", "get") + const metricsGetBooking = getBookingCounter.init({ confirmationNumber }) + + metricsGetBooking.start() let lang = ctx.lang ?? inputLang @@ -54,23 +43,7 @@ export const bookingQueryRouter = router({ ) if (!apiResponse.ok) { - const responseMessage = await apiResponse.text() - getBookingFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: responseMessage, - }) - console.error( - "api.booking.confirmation error", - JSON.stringify({ - query: { confirmationNumber }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text: responseMessage, - }, - }) - ) + await metricsGetBooking.httpError(apiResponse) // If the booking is not found, return null. // This scenario is expected to happen when a logged in user trying to access a booking that doesn't belong to them. @@ -84,18 +57,7 @@ export const bookingQueryRouter = router({ const apiJson = await apiResponse.json() const booking = bookingConfirmationSchema.safeParse(apiJson) if (!booking.success) { - getBookingFailCounter.add(1, { - confirmationNumber, - error_type: "validation_error", - error: JSON.stringify(booking.error), - }) - console.error( - "api.booking.confirmation validation error", - JSON.stringify({ - query: { confirmationNumber }, - error: booking.error, - }) - ) + metricsGetBooking.validationError(booking.error) throw badRequestError() } @@ -109,34 +71,17 @@ export const bookingQueryRouter = router({ ) if (!hotelData) { - getBookingFailCounter.add(1, { - confirmationNumber, - hotelId: booking.data.hotelId, - error_type: "http_error", - error: "Couldn`t get hotel", - }) - console.error( - "api.booking.confirmation error", - JSON.stringify({ - query: { confirmationNumber, hotelId: booking.data.hotelId }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text: "Couldn`t get hotel", - }, - }) + metricsGetBooking.dataError( + `Failed to get hotel data for ${booking.data.hotelId}`, + { + hotelId: booking.data.hotelId, + } ) throw serverErrorByStatus(404) } - getBookingSuccessCounter.add(1, { confirmationNumber }) - console.info( - "api.booking.confirmation success", - JSON.stringify({ - query: { confirmationNumber }, - }) - ) + metricsGetBooking.success() return { ...hotelData, @@ -152,7 +97,13 @@ export const bookingQueryRouter = router({ input, }) { const { confirmationNumber } = input - getBookingStatusCounter.add(1, { confirmationNumber }) + + const getBookingStatusCounter = createCounter("trpc.booking", "status") + const metricsGetBookingStatus = getBookingStatusCounter.init({ + confirmationNumber, + }) + + metricsGetBookingStatus.start() const apiResponse = await api.get( api.endpoints.v1.Booking.status(confirmationNumber), @@ -164,52 +115,18 @@ export const bookingQueryRouter = router({ ) if (!apiResponse.ok) { - const responseMessage = await apiResponse.text() - getBookingStatusFailCounter.add(1, { - confirmationNumber, - error_type: "http_error", - error: responseMessage, - }) - console.error( - "api.booking.status error", - JSON.stringify({ - query: { confirmationNumber }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text: responseMessage, - }, - }) - ) - + await metricsGetBookingStatus.httpError(apiResponse) throw serverErrorByStatus(apiResponse.status, apiResponse) } const apiJson = await apiResponse.json() const verifiedData = createBookingSchema.safeParse(apiJson) if (!verifiedData.success) { - getBookingStatusFailCounter.add(1, { - confirmationNumber, - error_type: "validation_error", - error: JSON.stringify(verifiedData.error), - }) - console.error( - "api.booking.status validation error", - JSON.stringify({ - query: { confirmationNumber }, - error: verifiedData.error, - }) - ) + metricsGetBookingStatus.validationError(verifiedData.error) throw badRequestError() } - getBookingStatusSuccessCounter.add(1, { confirmationNumber }) - console.info( - "api.booking.status success", - JSON.stringify({ - query: { confirmationNumber }, - }) - ) + metricsGetBookingStatus.success() return verifiedData.data }), diff --git a/apps/scandic-web/server/routers/contentstack/accountPage/query.ts b/apps/scandic-web/server/routers/contentstack/accountPage/query.ts index 5a1538e06..98e3d56ab 100644 --- a/apps/scandic-web/server/routers/contentstack/accountPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/accountPage/query.ts @@ -1,11 +1,10 @@ -import { metrics } from "@opentelemetry/api" - import { GetAccountPage, GetAccountPageRefs, } from "@/lib/graphql/Query/AccountPage/AccountPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { @@ -26,36 +25,17 @@ import type { GetAccountPageSchema, } from "@/types/trpc/routers/contentstack/accountPage" -const meter = metrics.getMeter("trpc.accountPage") - -// OpenTelemetry metrics -const getAccountPageRefsCounter = meter.createCounter( - "trpc.contentstack.accountPage.get" -) -const getAccountPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.accountPage.get-success" -) -const getAccountPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.accountPage.get-fail" -) -const getAccountPageCounter = meter.createCounter( - "trpc.contentstack.accountPage.get" -) -const getAccountPageSuccessCounter = meter.createCounter( - "trpc.contentstack.accountPage.get-success" -) -const getAccountPageFailCounter = meter.createCounter( - "trpc.contentstack.accountPage.get-fail" -) - export const accountPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx - getAccountPageRefsCounter.add(1, { lang, uid }) - console.info( - "contentstack.accountPage.refs start", - JSON.stringify({ query: { lang, uid } }) + + const getAccountPageRefsCounter = createCounter( + "trpc.contentstack", + "accountPage.get.refs" ) + const metricsRefs = getAccountPageRefsCounter.init({ lang, uid }) + metricsRefs.start() + const refsResponse = await request( GetAccountPageRefs, { @@ -70,19 +50,7 @@ export const accountPageQueryRouter = router({ if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getAccountPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.accountPage.refs not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsRefs.noDataError() throw notFoundError } @@ -90,19 +58,7 @@ export const accountPageQueryRouter = router({ refsResponse.data ) if (!validatedAccountPageRefs.success) { - getAccountPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "validation_error", - error: JSON.stringify(validatedAccountPageRefs.error), - }) - console.error( - "contentstack.accountPage.refs validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedAccountPageRefs.error, - }) - ) + metricsRefs.validationError(validatedAccountPageRefs.error) return null } @@ -112,12 +68,16 @@ export const accountPageQueryRouter = router({ generateTagsFromSystem(lang, connections), generateTag(lang, validatedAccountPageRefs.data.account_page.system.uid), ].flat() - getAccountPageRefsSuccessCounter.add(1, { lang, uid, tags }) - getAccountPageCounter.add(1, { lang, uid }) - console.info( - "contentstack.accountPage start", - JSON.stringify({ query: { lang, uid } }) + + metricsRefs.success() + + const getAccountPageCounter = createCounter( + "trpc.contentstack", + "accountPage.get" ) + const metrics = getAccountPageCounter.init({ lang, uid }) + metrics.start() + const response = await request( GetAccountPage, { @@ -132,45 +92,18 @@ export const accountPageQueryRouter = router({ if (!response.data) { const notFoundError = notFound(response) - getAccountPageFailCounter.add(1, { - lang, - uid, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.accountPage not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metrics.noDataError() throw notFoundError } const validatedAccountPage = accountPageSchema.safeParse(response.data) if (!validatedAccountPage.success) { - getAccountPageFailCounter.add(1, { - lang, - uid, - error_type: "validation_error", - error: JSON.stringify(validatedAccountPage.error), - }) - console.error( - "contentstack.accountPage validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedAccountPage.error, - }) - ) + metrics.validationError(validatedAccountPage.error) return null } - getAccountPageSuccessCounter.add(1, { lang, uid }) - console.info( - "contentstack.accountPage success", - JSON.stringify({ query: { lang, uid } }) - ) + + metrics.success() const parsedtitle = response.data.account_page.title .replaceAll(" ", "") diff --git a/apps/scandic-web/server/routers/contentstack/base/query.ts b/apps/scandic-web/server/routers/contentstack/base/query.ts index 6aaf97c23..c40b5aa62 100644 --- a/apps/scandic-web/server/routers/contentstack/base/query.ts +++ b/apps/scandic-web/server/routers/contentstack/base/query.ts @@ -17,6 +17,7 @@ import { } from "@/lib/graphql/Query/SiteConfig.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackBaseProcedure, router } from "@/server/trpc" import { langInput } from "@/server/utils" @@ -43,37 +44,6 @@ import { validateFooterConfigSchema, validateFooterRefConfigSchema, } from "./output" -import { - getContactConfigCounter, - getContactConfigFailCounter, - getContactConfigSuccessCounter, - getCurrentFooterCounter, - getCurrentFooterFailCounter, - getCurrentFooterRefCounter, - getCurrentFooterSuccessCounter, - getCurrentHeaderCounter, - getCurrentHeaderFailCounter, - getCurrentHeaderRefCounter, - getCurrentHeaderSuccessCounter, - getFooterCounter, - getFooterFailCounter, - getFooterRefCounter, - getFooterRefFailCounter, - getFooterRefSuccessCounter, - getFooterSuccessCounter, - getHeaderCounter, - getHeaderFailCounter, - getHeaderRefsCounter, - getHeaderRefsFailCounter, - getHeaderRefsSuccessCounter, - getHeaderSuccessCounter, - getSiteConfigCounter, - getSiteConfigFailCounter, - getSiteConfigRefCounter, - getSiteConfigRefFailCounter, - getSiteConfigRefSuccessCounter, - getSiteConfigSuccessCounter, -} from "./telemetry" import { getAlertPhoneContactData, getConnections, @@ -96,11 +66,14 @@ import type { import type { Lang } from "@/constants/languages" const getContactConfig = cache(async (lang: Lang) => { - getContactConfigCounter.add(1, { lang }) - console.info( - "contentstack.contactConfig start", - JSON.stringify({ query: { lang } }) + const getContactConfigCounter = createCounter( + "trpc.contentstack", + "contactConfig.get" ) + const metricsGetContactConfig = getContactConfigCounter.init({ lang }) + + metricsGetContactConfig.start() + const response = await request( GetContactConfig, { @@ -114,46 +87,20 @@ const getContactConfig = cache(async (lang: Lang) => { if (!response.data) { const notFoundError = notFound(response) - - getContactConfigFailCounter.add(1, { - lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - - console.error( - "contentstack.config not found error", - JSON.stringify({ query: { lang }, error: { code: notFoundError.code } }) - ) - + metricsGetContactConfig.noDataError() throw notFoundError } - const validatedContactConfigConfig = validateContactConfigSchema.safeParse( - response.data - ) + const verifiedData = validateContactConfigSchema.safeParse(response.data) - if (!validatedContactConfigConfig.success) { - getContactConfigFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedContactConfigConfig.error), - }) - console.error( - "contentstack.contactConfig validation error", - JSON.stringify({ - query: { lang }, - error: validatedContactConfigConfig.error, - }) - ) + if (!verifiedData.success) { + metricsGetContactConfig.validationError(verifiedData.error) return null } - getContactConfigSuccessCounter.add(1, { lang }) - console.info( - "contentstack.contactConfig success", - JSON.stringify({ query: { lang } }) - ) - return validatedContactConfigConfig.data.all_contact_config.items[0] + + metricsGetContactConfig.success() + + return verifiedData.data.all_contact_config.items[0] }) export const baseQueryRouter = router({ @@ -162,11 +109,14 @@ export const baseQueryRouter = router({ }), header: contentstackBaseProcedure.query(async ({ ctx }) => { const { lang } = ctx - getHeaderRefsCounter.add(1, { lang }) - console.info( - "contentstack.header.refs start", - JSON.stringify({ query: { lang } }) + + const getHeaderRefsCounter = createCounter( + "trpc.contentstack", + "header.get.refs" ) + const metricsGetHeaderRefs = getHeaderRefsCounter.init({ lang }) + + metricsGetHeaderRefs.start() const responseRef = await request( GetHeaderRef, @@ -181,56 +131,25 @@ export const baseQueryRouter = router({ if (!responseRef.data) { const notFoundError = notFound(responseRef) - getHeaderRefsFailCounter.add(1, { - lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.header.refs not found error", - JSON.stringify({ - query: { - lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetHeaderRefs.noDataError() throw notFoundError } const validatedHeaderRefs = headerRefsSchema.safeParse(responseRef.data) if (!validatedHeaderRefs.success) { - getHeaderRefsFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedHeaderRefs.error), - }) - console.error( - "contentstack.header.refs validation error", - JSON.stringify({ - query: { - lang, - }, - error: validatedHeaderRefs.error, - }) - ) + metricsGetHeaderRefs.validationError(validatedHeaderRefs.error) return null } - getHeaderRefsSuccessCounter.add(1, { lang }) - console.info( - "contentstack.header.refs success", - JSON.stringify({ query: { lang } }) - ) + metricsGetHeaderRefs.success() const connections = getConnections(validatedHeaderRefs.data) - getHeaderCounter.add(1, { lang }) - console.info( - "contentstack.header start", - JSON.stringify({ query: { lang } }) - ) + const getHeaderCounter = createCounter("trpc.contentstack", "header.get") + const metricsGetHeader = getHeaderCounter.init({ lang }) + + metricsGetHeader.start() const tags = [ generateTagsFromSystem(lang, connections), @@ -245,43 +164,18 @@ export const baseQueryRouter = router({ if (!response.data) { const notFoundError = notFound(response) - getHeaderFailCounter.add(1, { - lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.header not found error", - JSON.stringify({ - query: { lang }, - error: { code: notFoundError.code }, - }) - ) + metricsGetHeader.noDataError() throw notFoundError } const validatedHeaderConfig = headerSchema.safeParse(response.data) if (!validatedHeaderConfig.success) { - getHeaderFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedHeaderConfig.error), - }) - console.error( - "contentstack.header validation error", - JSON.stringify({ - query: { lang }, - error: validatedHeaderConfig.error, - }) - ) + metricsGetHeader.validationError(validatedHeaderConfig.error) return null } - getHeaderSuccessCounter.add(1, { lang }) - console.info( - "contentstack.header success", - JSON.stringify({ query: { lang } }) - ) + + metricsGetHeader.success() return { data: validatedHeaderConfig.data.header, @@ -290,11 +184,16 @@ export const baseQueryRouter = router({ currentHeader: contentstackBaseProcedure .input(langInput) .query(async ({ input }) => { - getCurrentHeaderRefCounter.add(1, { lang: input.lang }) - console.info( - "contentstack.currentHeader.ref start", - JSON.stringify({ query: { lang: input.lang } }) + const getCurrentHeaderRefsCounter = createCounter( + "trpc.contentstack", + "currentHeader.get.refs" ) + const metricsGetCurrentHeaderRefs = getCurrentHeaderRefsCounter.init({ + lang: input.lang, + }) + + metricsGetCurrentHeaderRefs.start() + const responseRef = await request( GetCurrentHeaderRef, { @@ -305,13 +204,16 @@ export const baseQueryRouter = router({ ttl: "max", } ) - getCurrentHeaderCounter.add(1, { lang: input.lang }) - console.info( - "contentstack.currentHeader start", - JSON.stringify({ - query: { lang: input.lang }, - }) + + const getCurrentHeaderCounter = createCounter( + "trpc.contentstack", + "currentHeader.get" ) + const metricsGetCurrentHeader = getCurrentHeaderCounter.init({ + lang: input.lang, + }) + + metricsGetCurrentHeader.start() const currentHeaderUID = responseRef.data.all_current_header.items[0].system.uid @@ -327,20 +229,7 @@ export const baseQueryRouter = router({ if (!response.data) { const notFoundError = notFound(response) - getCurrentHeaderFailCounter.add(1, { - lang: input.lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.currentHeader not found error", - JSON.stringify({ - query: { - lang: input.lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetCurrentHeader.noDataError() throw notFoundError } @@ -349,40 +238,27 @@ export const baseQueryRouter = router({ ) if (!validatedHeaderConfig.success) { - getCurrentHeaderFailCounter.add(1, { - lang: input.lang, - error_type: "validation_error", - error: JSON.stringify(validatedHeaderConfig.error), - }) - console.error( - "contentstack.currentHeader validation error", - JSON.stringify({ - query: { - lang: input.lang, - }, - error: validatedHeaderConfig.error, - }) - ) + metricsGetCurrentHeader.validationError(validatedHeaderConfig.error) return null } - getCurrentHeaderSuccessCounter.add(1, { lang: input.lang }) - console.info( - "contentstack.currentHeader success", - JSON.stringify({ - query: { lang: input.lang }, - }) - ) + + metricsGetCurrentHeader.success() return validatedHeaderConfig.data }), currentFooter: contentstackBaseProcedure .input(langInput) .query(async ({ input }) => { - getCurrentFooterRefCounter.add(1, { lang: input.lang }) - console.info( - "contentstack.currentFooter.ref start", - JSON.stringify({ query: { lang: input.lang } }) + const getCurrentFooterRefsCounter = createCounter( + "trpc.contentstack", + "currentFooter.get.refs" ) + const metricsGetCurrentFooterRefs = getCurrentFooterRefsCounter.init({ + lang: input.lang, + }) + + metricsGetCurrentFooterRefs.start() + const responseRef = await request( GetCurrentFooterRef, { @@ -393,16 +269,17 @@ export const baseQueryRouter = router({ ttl: "max", } ) - // There's currently no error handling/validation for the responseRef, should it be added? - getCurrentFooterCounter.add(1, { lang: input.lang }) - console.info( - "contentstack.currentFooter start", - JSON.stringify({ - query: { - lang: input.lang, - }, - }) + + const getCurrentFooterCounter = createCounter( + "trpc.contentstack", + "currentFooter.get" ) + const metricsGetCurrentFooter = getCurrentFooterCounter.init({ + lang: input.lang, + }) + + metricsGetCurrentFooter.start() + const currentFooterUID = responseRef.data.all_current_footer.items[0].system.uid @@ -419,20 +296,7 @@ export const baseQueryRouter = router({ if (!response.data) { const notFoundError = notFound(response) - getCurrentFooterFailCounter.add(1, { - lang: input.lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.currentFooter not found error", - JSON.stringify({ - query: { - lang: input.lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetCurrentFooter.noDataError() throw notFoundError } @@ -440,34 +304,27 @@ export const baseQueryRouter = router({ validateCurrentFooterConfigSchema.safeParse(response.data) if (!validatedCurrentFooterConfig.success) { - getFooterFailCounter.add(1, { - lang: input.lang, - error_type: "validation_error", - error: JSON.stringify(validatedCurrentFooterConfig.error), - }) - console.error( - "contentstack.currentFooter validation error", - JSON.stringify({ - query: { lang: input.lang }, - error: validatedCurrentFooterConfig.error, - }) + metricsGetCurrentFooter.validationError( + validatedCurrentFooterConfig.error ) return null } - getCurrentFooterSuccessCounter.add(1, { lang: input.lang }) - console.info( - "contentstack.currentFooter success", - JSON.stringify({ query: { lang: input.lang } }) - ) + + metricsGetCurrentFooter.success() + return validatedCurrentFooterConfig.data.all_current_footer.items[0] }), footer: contentstackBaseProcedure.query(async ({ ctx }) => { const { lang } = ctx - getFooterRefCounter.add(1, { lang }) - console.info( - "contentstack.footer.ref start", - JSON.stringify({ query: { lang } }) + + const getFooterRefsCounter = createCounter( + "trpc.contentstack", + "footer.get.refs" ) + const metricsGetFooterRefs = getFooterRefsCounter.init({ lang }) + + metricsGetFooterRefs.start() + const responseRef = await request( GetFooterRef, { @@ -481,20 +338,7 @@ export const baseQueryRouter = router({ if (!responseRef.data) { const notFoundError = notFound(responseRef) - getFooterRefFailCounter.add(1, { - lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.footer.refs not found error", - JSON.stringify({ - query: { - lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetFooterRefs.noDataError() throw notFoundError } @@ -503,41 +347,20 @@ export const baseQueryRouter = router({ ) if (!validatedFooterRefs.success) { - getFooterRefFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedFooterRefs.error), - }) - console.error( - "contentstack.footer.refs validation error", - JSON.stringify({ - query: { - lang, - }, - error: validatedFooterRefs.error, - }) - ) + metricsGetFooterRefs.validationError(validatedFooterRefs.error) return null } - getFooterRefSuccessCounter.add(1, { lang }) - console.info( - "contentstack.footer.refs success", - JSON.stringify({ query: { lang } }) - ) + metricsGetFooterRefs.success() const connections = getFooterConnections(validatedFooterRefs.data) const footerUID = responseRef.data.all_footer.items[0].system.uid - getFooterCounter.add(1, { lang: lang }) - console.info( - "contentstack.footer start", - JSON.stringify({ - query: { - lang, - }, - }) - ) + const getFooterCounter = createCounter("trpc.contentstack", "footer.get") + const metricsGetFooter = getFooterCounter.init({ lang }) + + metricsGetFooter.start() + const tags = [ generateTags(lang, connections), generateTag(lang, footerUID), @@ -556,20 +379,7 @@ export const baseQueryRouter = router({ if (!response.data) { const notFoundError = notFound(response) - getFooterFailCounter.add(1, { - lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.footer not found error", - JSON.stringify({ - query: { - lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetFooter.noDataError() throw notFoundError } @@ -578,25 +388,11 @@ export const baseQueryRouter = router({ ) if (!validatedFooterConfig.success) { - getFooterFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedFooterConfig.error), - }) - console.error( - "contentstack.footer validation error", - JSON.stringify({ - query: { lang: lang }, - error: validatedFooterConfig.error, - }) - ) + metricsGetFooter.validationError(validatedFooterConfig.error) return null } - getFooterSuccessCounter.add(1, { lang }) - console.info( - "contentstack.footer success", - JSON.stringify({ query: { lang } }) - ) + + metricsGetFooter.success() return validatedFooterConfig.data }), @@ -605,11 +401,14 @@ export const baseQueryRouter = router({ .query(async ({ input, ctx }) => { const lang = input.lang ?? ctx.lang - getSiteConfigRefCounter.add(1, { lang }) - console.info( - "contentstack.siteConfig.ref start", - JSON.stringify({ query: { lang } }) + const getSiteConfigRefsCounter = createCounter( + "trpc.contentstack", + "siteConfig.get.refs" ) + const metricsGetSiteConfigRefs = getSiteConfigRefsCounter.init({ lang }) + + metricsGetSiteConfigRefs.start() + const responseRef = await request( GetSiteConfigRef, { @@ -623,20 +422,7 @@ export const baseQueryRouter = router({ if (!responseRef.data) { const notFoundError = notFound(responseRef) - getSiteConfigRefFailCounter.add(1, { - lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.siteConfig.refs not found error", - JSON.stringify({ - query: { - lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetSiteConfigRefs.noDataError() throw notFoundError } @@ -645,20 +431,7 @@ export const baseQueryRouter = router({ ) if (!validatedSiteConfigRef.success) { - getSiteConfigRefFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedSiteConfigRef.error), - }) - console.error( - "contentstack.siteConfig.refs validation error", - JSON.stringify({ - query: { - lang, - }, - error: validatedSiteConfigRef.error, - }) - ) + metricsGetSiteConfigRefs.validationError(validatedSiteConfigRef.error) return null } @@ -670,17 +443,16 @@ export const baseQueryRouter = router({ generateTag(lang, siteConfigUid), ].flat() - getSiteConfigRefSuccessCounter.add(1, { lang }) - console.info( - "contentstack.siteConfig.refs success", - JSON.stringify({ query: { lang } }) - ) + metricsGetSiteConfigRefs.success() - getSiteConfigCounter.add(1, { lang }) - console.info( - "contentstack.siteConfig start", - JSON.stringify({ query: { lang } }) + const getSiteConfigCounter = createCounter( + "trpc.contentstack", + "siteConfig.get" ) + const metricsGetSiteConfig = getSiteConfigCounter.init({ lang }) + + metricsGetSiteConfig.start() + const [siteConfigResponse, contactConfig] = await Promise.all([ request( GetSiteConfig, @@ -697,21 +469,7 @@ export const baseQueryRouter = router({ if (!siteConfigResponse.data) { const notFoundError = notFound(siteConfigResponse) - - getSiteConfigFailCounter.add(1, { - lang, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - - console.error( - "contentstack.siteConfig not found error", - JSON.stringify({ - query: { lang }, - error: { code: notFoundError.code }, - }) - ) - + metricsGetSiteConfig.noDataError() throw notFoundError } @@ -720,26 +478,11 @@ export const baseQueryRouter = router({ ) if (!validatedSiteConfig.success) { - getSiteConfigFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedSiteConfig.error), - }) - console.error( - "contentstack.siteConfig validation error", - JSON.stringify({ - query: { lang }, - error: validatedSiteConfig.error, - }) - ) + metricsGetSiteConfig.validationError(validatedSiteConfig.error) return null } - getSiteConfigSuccessCounter.add(1, { lang }) - console.info( - "contentstack.siteConfig success", - JSON.stringify({ query: { lang } }) - ) + metricsGetSiteConfig.success() const { sitewideAlert } = validatedSiteConfig.data diff --git a/apps/scandic-web/server/routers/contentstack/base/telemetry.ts b/apps/scandic-web/server/routers/contentstack/base/telemetry.ts deleted file mode 100644 index 159f69d36..000000000 --- a/apps/scandic-web/server/routers/contentstack/base/telemetry.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.contentstack.base") -// OpenTelemetry metrics: ContactConfig -export const getContactConfigCounter = meter.createCounter( - "trpc.contentstack.contactConfig.get" -) -export const getContactConfigSuccessCounter = meter.createCounter( - "trpc.contentstack.contactConfig.get-success" -) -export const getContactConfigFailCounter = meter.createCounter( - "trpc.contentstack.contactConfig.get-fail" -) -// OpenTelemetry metrics: CurrentHeader -export const getCurrentHeaderRefCounter = meter.createCounter( - "trpc.contentstack.currentHeader.ref.get" -) -export const getCurrentHeaderRefSuccessCounter = meter.createCounter( - "trpc.contentstack.currentHeader.ref.get-success" -) -export const getCurrentHeaderRefFailCounter = meter.createCounter( - "trpc.contentstack.currentHeader.ref.get-fail" -) -export const getCurrentHeaderCounter = meter.createCounter( - "trpc.contentstack.currentHeader.get" -) -export const getCurrentHeaderSuccessCounter = meter.createCounter( - "trpc.contentstack.currentHeader.get-success" -) -export const getCurrentHeaderFailCounter = meter.createCounter( - "trpc.contentstack.currentHeader.get-fail" -) - -// OpenTelemetry metrics: Header -export const getHeaderRefsCounter = meter.createCounter( - "trpc.contentstack.header.ref.get" -) -export const getHeaderRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.header.ref.get-success" -) -export const getHeaderRefsFailCounter = meter.createCounter( - "trpc.contentstack.header.ref.get-fail" -) -export const getHeaderCounter = meter.createCounter( - "trpc.contentstack.header.get" -) -export const getHeaderSuccessCounter = meter.createCounter( - "trpc.contentstack.header.get-success" -) -export const getHeaderFailCounter = meter.createCounter( - "trpc.contentstack.header.get-fail" -) - -// OpenTelemetry metrics: CurrentFooter -export const getCurrentFooterRefCounter = meter.createCounter( - "trpc.contentstack.currentFooter.ref.get" -) -export const getCurrentFooterRefSuccessCounter = meter.createCounter( - "trpc.contentstack.currentFooter.ref.get-success" -) -export const getCurrentFooterRefFailCounter = meter.createCounter( - "trpc.contentstack.currentFooter.ref.get-fail" -) -export const getCurrentFooterCounter = meter.createCounter( - "trpc.contentstack.currentFooter.get" -) -export const getCurrentFooterSuccessCounter = meter.createCounter( - "trpc.contentstack.currentFooter.get-success" -) -export const getCurrentFooterFailCounter = meter.createCounter( - "trpc.contentstack.currentFooter.get-fail" -) - -// OpenTelemetry metrics: Footer -export const getFooterRefCounter = meter.createCounter( - "trpc.contentstack.footer.ref.get" -) -export const getFooterRefSuccessCounter = meter.createCounter( - "trpc.contentstack.footer.ref.get-success" -) -export const getFooterRefFailCounter = meter.createCounter( - "trpc.contentstack.footer.ref.get-fail" -) -export const getFooterCounter = meter.createCounter( - "trpc.contentstack.footer.get" -) -export const getFooterSuccessCounter = meter.createCounter( - "trpc.contentstack.footer.get-success" -) -export const getFooterFailCounter = meter.createCounter( - "trpc.contentstack.footer.get-fail" -) - -// OpenTelemetry metrics: SiteConfig -export const getSiteConfigRefCounter = meter.createCounter( - "trpc.contentstack.SiteConfig.ref.get" -) -export const getSiteConfigRefSuccessCounter = meter.createCounter( - "trpc.contentstack.SiteConfig.ref.get-success" -) -export const getSiteConfigRefFailCounter = meter.createCounter( - "trpc.contentstack.SiteConfig.ref.get-fail" -) -export const getSiteConfigCounter = meter.createCounter( - "trpc.contentstack.SiteConfig.get" -) -export const getSiteConfigSuccessCounter = meter.createCounter( - "trpc.contentstack.SiteConfig.get-success" -) -export const getSiteConfigFailCounter = meter.createCounter( - "trpc.contentstack.SiteConfig.get-fail" -) diff --git a/apps/scandic-web/server/routers/contentstack/breadcrumbs/query.ts b/apps/scandic-web/server/routers/contentstack/breadcrumbs/query.ts index e3b57eedf..43cc2886e 100644 --- a/apps/scandic-web/server/routers/contentstack/breadcrumbs/query.ts +++ b/apps/scandic-web/server/routers/contentstack/breadcrumbs/query.ts @@ -1,4 +1,3 @@ -import { metrics } from "@opentelemetry/api" import { cache } from "react" import { @@ -35,6 +34,7 @@ import { } from "@/lib/graphql/Query/Breadcrumbs/LoyaltyPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { generateRefsResponseTag } from "@/utils/generateTag" @@ -49,28 +49,6 @@ import type { } from "@/types/trpc/routers/contentstack/breadcrumbs" import type { Lang } from "@/constants/languages" -const meter = metrics.getMeter("trpc.breadcrumbs") - -// OpenTelemetry metrics -const getBreadcrumbsRefsCounter = meter.createCounter( - "trpc.contentstack.breadcrumbs.refs.get" -) -const getBreadcrumbsRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.breadcrumbs.refs.get-success" -) -const getBreadcrumbsRefsFailCounter = meter.createCounter( - "trpc.contentstack.breadcrumbs.refs.get-fail" -) -const getBreadcrumbsCounter = meter.createCounter( - "trpc.contentstack.breadcrumbs.get" -) -const getBreadcrumbsSuccessCounter = meter.createCounter( - "trpc.contentstack.breadcrumbs.get-success" -) -const getBreadcrumbsFailCounter = meter.createCounter( - "trpc.contentstack.breadcrumbs.get-fail" -) - interface BreadcrumbsPageData { dataKey: keyof T refQuery: string @@ -81,11 +59,17 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs( { dataKey, refQuery, query }: BreadcrumbsPageData, { uid, lang }: { uid: string; lang: Lang } ) { - getBreadcrumbsRefsCounter.add(1, { lang, uid }) - console.info( - "contentstack.breadcrumbs refs get start", - JSON.stringify({ query: { lang, uid } }) + const getBreadcrumbsRefsCounter = createCounter( + "trpc.contentstack", + "breadcrumbs.get.refs" ) + const metricsGetBreadcrumbsRefs = getBreadcrumbsRefsCounter.init({ + lang, + uid, + }) + + metricsGetBreadcrumbsRefs.start() + const refsResponse = await request<{ [K in keyof T]: BreadcrumbsRefsSchema }>( refQuery, { locale: lang, uid }, @@ -100,32 +84,25 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs( ) if (!validatedRefsData.success) { - getBreadcrumbsRefsFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(validatedRefsData.error), - }) - console.error( - "contentstack.breadcrumbs refs validation error", - JSON.stringify({ - error: validatedRefsData.error, - }) - ) + metricsGetBreadcrumbsRefs.validationError(validatedRefsData.error) return [] } - getBreadcrumbsRefsSuccessCounter.add(1, { lang, uid }) - console.info( - "contentstack.breadcrumbs refs get success", - JSON.stringify({ query: { lang, uid } }) - ) + metricsGetBreadcrumbsRefs.success() const tags = getTags(validatedRefsData.data, lang) - getBreadcrumbsCounter.add(1, { lang, uid }) - console.info( - "contentstack.breadcrumbs get start", - JSON.stringify({ query: { lang, uid } }) + const getBreadcrumbsCounter = createCounter( + "trpc.contentstack", + "breadcrumbs.get" ) + const metricsGetBreadcrumbs = getBreadcrumbsCounter.init({ + lang, + uid, + }) + + metricsGetBreadcrumbs.start() + const response = await request( query, { locale: lang, uid }, @@ -137,19 +114,7 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs( if (!response.data) { const notFoundError = notFound(response) - getBreadcrumbsFailCounter.add(1, { - lang, - uid, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.breadcrumbs get not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetBreadcrumbs.noDataError() throw notFoundError } @@ -158,24 +123,11 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs( ) if (!validatedBreadcrumbs.success) { - getBreadcrumbsFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(validatedBreadcrumbs.error), - }) - console.error( - "contentstack.breadcrumbs validation error", - JSON.stringify({ - error: validatedBreadcrumbs.error, - }) - ) + metricsGetBreadcrumbs.validationError(validatedBreadcrumbs.error) return [] } - getBreadcrumbsSuccessCounter.add(1, { lang, uid }) - console.info( - "contentstack.breadcrumbs get success", - JSON.stringify({ query: { lang, uid } }) - ) + metricsGetBreadcrumbs.success() return validatedBreadcrumbs.data }) diff --git a/apps/scandic-web/server/routers/contentstack/collectionPage/output.ts b/apps/scandic-web/server/routers/contentstack/collectionPage/output.ts index 68febdd69..21202b844 100644 --- a/apps/scandic-web/server/routers/contentstack/collectionPage/output.ts +++ b/apps/scandic-web/server/routers/contentstack/collectionPage/output.ts @@ -7,8 +7,8 @@ import { cardsGridSchema, } from "../schemas/blocks/cardsGrid" import { - dynamicContentSchema as blockDynamicContentSchema, dynamicContentRefsSchema, + dynamicContentSchema as blockDynamicContentSchema, } from "../schemas/blocks/dynamicContent" import { shortcutsRefsSchema, diff --git a/apps/scandic-web/server/routers/contentstack/collectionPage/query.ts b/apps/scandic-web/server/routers/contentstack/collectionPage/query.ts index 7b5b8ea42..2dab99876 100644 --- a/apps/scandic-web/server/routers/contentstack/collectionPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/collectionPage/query.ts @@ -1,12 +1,12 @@ import { GetCollectionPage } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql" import { request } from "@/lib/graphql/request" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { collectionPageSchema } from "./output" import { fetchCollectionPageRefs, generatePageTags, - getCollectionPageCounter, validateCollectionPageRefs, } from "./utils" @@ -32,13 +32,16 @@ export const collectionPageQueryRouter = router({ } const tags = generatePageTags(collectionPageRefs, lang) - getCollectionPageCounter.add(1, { lang, uid }) - console.info( - "contentstack.collectionPage start", - JSON.stringify({ - query: { lang, uid }, - }) + const getCollectionPageCounter = createCounter( + "trpc.contentstack", + "collectionPage.get" ) + const metricsGetCollectionPage = getCollectionPageCounter.init({ + lang, + uid, + }) + + metricsGetCollectionPage.start() const response = await request( GetCollectionPage, @@ -51,13 +54,12 @@ export const collectionPageQueryRouter = router({ const collectionPage = collectionPageSchema.safeParse(response.data) if (!collectionPage.success) { - console.error( - `Failed to validate CollectionPage Data - (lang: ${lang}, uid: ${uid})` - ) - console.error(collectionPage.error?.format()) + metricsGetCollectionPage.validationError(collectionPage.error) return null } + metricsGetCollectionPage.success() + const tracking: TrackingSDKPageData = { pageId: collectionPage.data.collection_page.system.uid, domainLanguage: lang, diff --git a/apps/scandic-web/server/routers/contentstack/collectionPage/utils.ts b/apps/scandic-web/server/routers/contentstack/collectionPage/utils.ts index 6531d26b0..20a2db981 100644 --- a/apps/scandic-web/server/routers/contentstack/collectionPage/utils.ts +++ b/apps/scandic-web/server/routers/contentstack/collectionPage/utils.ts @@ -1,8 +1,7 @@ -import { metrics } from "@opentelemetry/api" - import { GetCollectionPageRefs } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { getCacheClient } from "@/services/dataCache" import { @@ -21,31 +20,17 @@ import type { } from "@/types/trpc/routers/contentstack/collectionPage" import type { Lang } from "@/constants/languages" -const meter = metrics.getMeter("trpc.collectionPage") -// OpenTelemetry metrics: CollectionPage - -export const getCollectionPageCounter = meter.createCounter( - "trpc.contentstack.collectionPage.get" -) - -const getCollectionPageRefsCounter = meter.createCounter( - "trpc.contentstack.collectionPage.get" -) -const getCollectionPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.collectionPage.get-fail" -) -const getCollectionPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.collectionPage.get-success" -) - export async function fetchCollectionPageRefs(lang: Lang, uid: string) { - getCollectionPageRefsCounter.add(1, { lang, uid }) - console.info( - "contentstack.collectionPage.refs start", - JSON.stringify({ - query: { lang, uid }, - }) + const getCollectionPageRefsCounter = createCounter( + "trpc.contentstack", + "collectionPage.get.refs" ) + const metricsGetCollectionPageRefs = getCollectionPageRefsCounter.init({ + lang, + uid, + }) + + metricsGetCollectionPageRefs.start() const cacheClient = await getCacheClient() const cacheKey = generateRefsResponseTag(lang, uid) @@ -61,24 +46,7 @@ export async function fetchCollectionPageRefs(lang: Lang, uid: string) { if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getCollectionPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "http_error", - error: JSON.stringify({ - code: notFoundError.code, - }), - }) - console.error( - "contentstack.collectionPage.refs not found error", - JSON.stringify({ - query: { - lang, - uid, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetCollectionPageRefs.noDataError() throw notFoundError } @@ -90,30 +58,22 @@ export function validateCollectionPageRefs( lang: Lang, uid: string ) { + const getCollectionPageRefsCounter = createCounter( + "trpc.contentstack", + "collectionPage.get.refs" + ) + const metricsGetCollectionPageRefs = getCollectionPageRefsCounter.init({ + lang, + uid, + }) + const validatedData = collectionPageRefsSchema.safeParse(data) if (!validatedData.success) { - getCollectionPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "validation_error", - error: JSON.stringify(validatedData.error), - }) - console.error( - "contentstack.collectionPage.refs validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedData.error, - }) - ) + metricsGetCollectionPageRefs.validationError(validatedData.error) return null } - getCollectionPageRefsSuccessCounter.add(1, { lang, uid }) - console.info( - "contentstack.collectionPage.refs success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + + metricsGetCollectionPageRefs.success() return validatedData.data } diff --git a/apps/scandic-web/server/routers/contentstack/contentPage/query.ts b/apps/scandic-web/server/routers/contentstack/contentPage/query.ts index 8da6a9468..045cdfff1 100644 --- a/apps/scandic-web/server/routers/contentstack/contentPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/contentPage/query.ts @@ -4,6 +4,7 @@ import { GetContentPageBlocksBatch1, GetContentPageBlocksBatch2, } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { contentPageSchema } from "./output" @@ -12,7 +13,6 @@ import { createPageType, fetchContentPageRefs, generatePageTags, - getContentPageCounter, } from "./utils" import type { TrackingSDKPageData } from "@/types/components/tracking" @@ -30,13 +30,16 @@ export const contentPageQueryRouter = router({ const tags = generatePageTags(contentPageRefs, lang) - getContentPageCounter.add(1, { lang, uid }) - console.info( - "contentstack.contentPage start", - JSON.stringify({ - query: { lang, uid }, - }) + const getContentPageCounter = createCounter( + "trpc.contentstack", + "contentPage.get" ) + const metricsGetContentPage = getContentPageCounter.init({ + lang, + uid, + }) + + metricsGetContentPage.start() const contentPageRequest = await batchRequest([ { @@ -69,13 +72,12 @@ export const contentPageQueryRouter = router({ const contentPage = contentPageSchema.safeParse(contentPageRequest.data) if (!contentPage.success) { - console.error( - `Failed to validate Contentpage Data - (lang: ${lang}, uid: ${uid})` - ) - console.error(contentPage.error?.format()) + metricsGetContentPage.validationError(contentPage.error) return null } + metricsGetContentPage.success() + const tracking: TrackingSDKPageData = { pageId: contentPage.data.content_page.system.uid, domainLanguage: lang, diff --git a/apps/scandic-web/server/routers/contentstack/contentPage/utils.ts b/apps/scandic-web/server/routers/contentstack/contentPage/utils.ts index e0866cab7..f50d4ce53 100644 --- a/apps/scandic-web/server/routers/contentstack/contentPage/utils.ts +++ b/apps/scandic-web/server/routers/contentstack/contentPage/utils.ts @@ -1,11 +1,10 @@ -import { metrics } from "@opentelemetry/api" - import { batchRequest } from "@/lib/graphql/batchRequest" import { GetContentPageBlocksRefs, GetContentPageRefs, } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { generateRefsResponseTag, @@ -18,37 +17,24 @@ import { contentPageRefsSchema } from "./output" import { TrackingChannelEnum } from "@/types/components/tracking" import { ContentPageEnum } from "@/types/enums/contentPage" import type { System } from "@/types/requests/system" -import type { - ContentPageRefs, - GetContentPageRefsSchema, +import { + type ContentPageRefs, + type GetContentPageRefsSchema, } from "@/types/trpc/routers/contentstack/contentPage" import type { Lang } from "@/constants/languages" -const meter = metrics.getMeter("trpc.contentPage") -// OpenTelemetry metrics: ContentPage - -export const getContentPageCounter = meter.createCounter( - "trpc.contentstack.contentPage.get" -) - -const getContentPageRefsCounter = meter.createCounter( - "trpc.contentstack.contentPage.get" -) -const getContentPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.contentPage.get-fail" -) -const getContentPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.contentPage.get-success" -) - export async function fetchContentPageRefs(lang: Lang, uid: string) { - getContentPageRefsCounter.add(1, { lang, uid }) - console.info( - "contentstack.contentPage.refs start", - JSON.stringify({ - query: { lang, uid }, - }) + const getContentPageRefsCounter = createCounter( + "trpc.contentstack", + "contentPage.get.refs" ) + const metricsGetContentPageRefs = getContentPageRefsCounter.init({ + lang, + uid, + }) + + metricsGetContentPageRefs.start() + const res = await batchRequest([ { document: GetContentPageRefs, @@ -69,50 +55,17 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) { ]) if (!res.data) { const notFoundError = notFound(res) - getContentPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "http_error", - error: JSON.stringify({ - code: notFoundError.code, - }), - }) - console.error( - "contentstack.contentPage.refs not found error", - JSON.stringify({ - query: { - lang, - uid, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetContentPageRefs.noDataError() throw notFoundError } + const validatedData = contentPageRefsSchema.safeParse(res.data) if (!validatedData.success) { - getContentPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "validation_error", - error: JSON.stringify(validatedData.error), - }) - console.error( - "contentstack.contentPage.refs validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedData.error, - }) - ) + metricsGetContentPageRefs.validationError(validatedData.error) return null } - getContentPageRefsSuccessCounter.add(1, { lang, uid }) - console.info( - "contentstack.contentPage.refs success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + + metricsGetContentPageRefs.success() return validatedData.data } @@ -142,8 +95,7 @@ export function getConnections({ content_page }: ContentPageRefs) { case ContentPageEnum.ContentStack.blocks.Content: { if (block.content.length) { - // TS has trouble infering the filtered types - // @ts-ignore + // @ts-expect-error: TS has trouble infering the filtered types connections.push(...block.content) } } diff --git a/apps/scandic-web/server/routers/contentstack/destinationCityPage/query.ts b/apps/scandic-web/server/routers/contentstack/destinationCityPage/query.ts index 4d293a0e3..39f173212 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationCityPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationCityPage/query.ts @@ -4,6 +4,7 @@ import { } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentStackUidWithServiceProcedure, router } from "@/server/trpc" import { generateRefsResponseTag } from "@/utils/generateTag" @@ -13,14 +14,6 @@ import { destinationCityPageRefsSchema, destinationCityPageSchema, } from "./output" -import { - getDestinationCityPageCounter, - getDestinationCityPageFailCounter, - getDestinationCityPageRefsCounter, - getDestinationCityPageRefsFailCounter, - getDestinationCityPageRefsSuccessCounter, - getDestinationCityPageSuccessCounter, -} from "./telemetry" import { generatePageTags } from "./utils" import { @@ -36,11 +29,14 @@ export const destinationCityPageQueryRouter = router({ get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => { const { lang, uid, serviceToken } = ctx - getDestinationCityPageRefsCounter.add(1, { lang, uid }) - console.info( - "contentstack.destinationCityPage.refs start", - JSON.stringify({ query: { lang, uid } }) + const getDestinationCityPageRefsCounter = createCounter( + "trpc.contentstack", + "destinationCityPage.get.refs" ) + const metricsGetDestinationCityPageRefs = + getDestinationCityPageRefsCounter.init({ lang, uid }) + + metricsGetDestinationCityPageRefs.start() const refsResponse = await request( GetDestinationCityPageRefs, @@ -53,19 +49,7 @@ export const destinationCityPageQueryRouter = router({ if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getDestinationCityPageRefsFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.destinationCityPage.refs not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetDestinationCityPageRefs.noDataError() throw notFoundError } @@ -73,33 +57,25 @@ export const destinationCityPageQueryRouter = router({ refsResponse.data ) if (!validatedRefsData.success) { - getDestinationCityPageRefsFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "validation_error", - error: JSON.stringify(validatedRefsData.error), - }) - console.error( - "contentstack.destinationCityPage.refs validation error", - JSON.stringify({ query: { lang, uid }, error: validatedRefsData.error }) - ) + metricsGetDestinationCityPageRefs.validationError(validatedRefsData.error) return null } - getDestinationCityPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationCityPage.refs success", - JSON.stringify({ query: { lang, uid } }) - ) + + metricsGetDestinationCityPageRefs.success() const tags = generatePageTags(validatedRefsData.data, lang) - getDestinationCityPageCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationCityPage start", - JSON.stringify({ - query: { lang, uid }, - }) + const getDestinationCityPageCounter = createCounter( + "trpc.contentstack", + "destinationCityPage.get" ) + const metricsGetDestinationCityPage = getDestinationCityPageCounter.init({ + lang, + uid, + }) + + metricsGetDestinationCityPage.start() + const response = await request( GetDestinationCityPage, { @@ -113,40 +89,17 @@ export const destinationCityPageQueryRouter = router({ ) if (!response.data) { const notFoundError = notFound(response) - getDestinationCityPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.destinationCityPage not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetDestinationCityPage.noDataError() throw notFoundError } const validatedResponse = destinationCityPageSchema.safeParse(response.data) if (!validatedResponse.success) { - getDestinationCityPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - console.error( - "contentstack.destinationCityPage validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedResponse.error, - }) - ) + metricsGetDestinationCityPage.validationError(validatedResponse.error) return null } + const destinationCityPage = validatedResponse.data.destination_city_page const cityIdentifier = destinationCityPage.destination_settings.city if (!cityIdentifier) { @@ -160,30 +113,16 @@ export const destinationCityPageQueryRouter = router({ }) if (!city) { - getDestinationCityPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: `Couldn't find city with cityIdentifier: ${cityIdentifier}`, - }) - - console.error( - "contentstack.destinationCityPage not found error", - JSON.stringify({ - query: { lang, uid }, - error: `Couldn't find city with cityIdentifier: ${cityIdentifier}`, - }) + metricsGetDestinationCityPage.dataError( + `Failed to get city data for ${cityIdentifier}`, + { + cityIdentifier, + } ) return null } - getDestinationCityPageSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationCityPage success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + metricsGetDestinationCityPage.success() const system = destinationCityPage.system const pageName = `destinations|${city.country}|${city.name}` diff --git a/apps/scandic-web/server/routers/contentstack/destinationCityPage/telemetry.ts b/apps/scandic-web/server/routers/contentstack/destinationCityPage/telemetry.ts deleted file mode 100644 index 243aa200d..000000000 --- a/apps/scandic-web/server/routers/contentstack/destinationCityPage/telemetry.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.contentstack.destinationCityPage") - -export const getDestinationCityPageRefsCounter = meter.createCounter( - "trpc.contentstack.destinationCityPage.get" -) -export const getDestinationCityPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.destinationCityPage.get-fail" -) -export const getDestinationCityPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.destinationCityPage.get-success" -) - -export const getDestinationCityPageCounter = meter.createCounter( - "trpc.contentstack.destinationCityPage.get" -) -export const getDestinationCityPageSuccessCounter = meter.createCounter( - "trpc.contentstack.destinationCityPage.get-success" -) -export const getDestinationCityPageFailCounter = meter.createCounter( - "trpc.contentstack.destinationCityPage.get-fail" -) - -export const getCityPageCountCounter = meter.createCounter( - "trpc.contentstack.cityPageCount.get" -) -export const getCityPageCountSuccessCounter = meter.createCounter( - "trpc.contentstack.cityPageCount.get-success" -) -export const getCityPageCountFailCounter = meter.createCounter( - "trpc.contentstack.cityPageCount.get-fail" -) - -export const getCityPageUrlsCounter = meter.createCounter( - "trpc.contentstack.cityPageUrls.get" -) -export const getCityPageUrlsSuccessCounter = meter.createCounter( - "trpc.contentstack.cityPageUrls.get-success" -) -export const getCityPageUrlsFailCounter = meter.createCounter( - "trpc.contentstack.cityPageUrls.get-fail" -) diff --git a/apps/scandic-web/server/routers/contentstack/destinationCityPage/utils.ts b/apps/scandic-web/server/routers/contentstack/destinationCityPage/utils.ts index bcf6f6de9..d5a54adaa 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationCityPage/utils.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationCityPage/utils.ts @@ -1,18 +1,11 @@ import { GetCityPageCount } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPageCount.graphql" import { GetCityPageUrls } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPageUrl.graphql" import { request } from "@/lib/graphql/request" +import { createCounter } from "@/server/telemetry" import { generateTag, generateTagsFromSystem } from "@/utils/generateTag" import { batchedCityPageUrlsSchema, cityPageCountSchema } from "./output" -import { - getCityPageCountCounter, - getCityPageCountFailCounter, - getCityPageCountSuccessCounter, - getCityPageUrlsCounter, - getCityPageUrlsFailCounter, - getCityPageUrlsSuccessCounter, -} from "./telemetry" import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage" import type { System } from "@/types/requests/system" @@ -76,11 +69,13 @@ export function getConnections({ } export async function getCityPageCount(lang: Lang) { - getCityPageCountCounter.add(1, { lang }) - console.info( - "contentstack.cityPageCount start", - JSON.stringify({ query: { lang } }) + const getCityPageCountCounter = createCounter( + "trpc.contentstack", + "cityPageCount.get" ) + const metricsGetCityPageCount = getCityPageCountCounter.init({ lang }) + + metricsGetCityPageCount.start() const response = await request( GetCityPageCount, @@ -92,15 +87,10 @@ export async function getCityPageCount(lang: Lang) { ttl: "max", } ) + if (!response.data) { - getCityPageCountFailCounter.add(1, { - lang, - error_type: "not_found", - error: `City pages count not found for lang: ${lang}`, - }) - console.error( - "contentstack.cityPageCount not found error", - JSON.stringify({ query: { lang } }) + metricsGetCityPageCount.dataError( + `Failed to get city pages count for ${lang}` ) return 0 } @@ -108,35 +98,24 @@ export async function getCityPageCount(lang: Lang) { const validatedResponse = cityPageCountSchema.safeParse(response.data) if (!validatedResponse.success) { - getCityPageCountFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - console.error( - "contentstack.hotelPageCount validation error", - JSON.stringify({ - query: { lang }, - error: validatedResponse.error, - }) - ) + metricsGetCityPageCount.validationError(validatedResponse.error) return 0 } - getCityPageCountSuccessCounter.add(1, { lang }) - console.info( - "contentstack.cityPageCount success", - JSON.stringify({ query: { lang } }) - ) + + metricsGetCityPageCount.success() return validatedResponse.data } export async function getCityPageUrls(lang: Lang) { - getCityPageUrlsCounter.add(1, { lang }) - console.info( - "contentstack.cityPageUrls start", - JSON.stringify({ query: { lang } }) + const getCityPageUrlsCounter = createCounter( + "trpc.contentstack", + "cityPageUrls.get" ) + const metricsGetCityPageUrls = getCityPageUrlsCounter.init({ lang }) + + metricsGetCityPageUrls.start() + const count = await getCityPageCount(lang) if (count === 0) { @@ -162,25 +141,11 @@ export async function getCityPageUrls(lang: Lang) { const validatedResponse = batchedCityPageUrlsSchema.safeParse(batchedResponse) if (!validatedResponse.success) { - getCityPageUrlsFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - console.error( - "contentstack.cityPageUrls validation error", - JSON.stringify({ - query: { lang }, - error: validatedResponse.error, - }) - ) + metricsGetCityPageUrls.validationError(validatedResponse.error) return [] } - getCityPageUrlsSuccessCounter.add(1, { lang }) - console.info( - "contentstack.cityPageUrls success", - JSON.stringify({ query: { lang } }) - ) + + metricsGetCityPageUrls.success() return validatedResponse.data } diff --git a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/query.ts b/apps/scandic-web/server/routers/contentstack/destinationCountryPage/query.ts index 3a5f58568..b97c8ba74 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationCountryPage/query.ts @@ -4,6 +4,7 @@ import { } from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentStackBaseWithServiceProcedure, contentstackExtendedProcedureUID, @@ -17,14 +18,6 @@ import { destinationCountryPageRefsSchema, destinationCountryPageSchema, } from "./output" -import { - getDestinationCountryPageCounter, - getDestinationCountryPageFailCounter, - getDestinationCountryPageRefsCounter, - getDestinationCountryPageRefsFailCounter, - getDestinationCountryPageRefsSuccessCounter, - getDestinationCountryPageSuccessCounter, -} from "./telemetry" import { generatePageTags, getCityPages } from "./utils" import { @@ -41,11 +34,14 @@ export const destinationCountryPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx - getDestinationCountryPageRefsCounter.add(1, { lang, uid }) - console.info( - "contentstack.destinationCountryPage.refs start", - JSON.stringify({ query: { lang, uid } }) + const getDestinationCountryPageRefsCounter = createCounter( + "trpc.contentstack", + "destinationCountryPage.get.refs" ) + const metricsGetDestinationCountryPageRefs = + getDestinationCountryPageRefsCounter.init({ lang, uid }) + + metricsGetDestinationCountryPageRefs.start() const refsResponse = await request( GetDestinationCountryPageRefs, @@ -58,19 +54,7 @@ export const destinationCountryPageQueryRouter = router({ if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getDestinationCountryPageRefsFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.destinationCountryPage.refs not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetDestinationCountryPageRefs.noDataError() throw notFoundError } @@ -78,33 +62,25 @@ export const destinationCountryPageQueryRouter = router({ refsResponse.data ) if (!validatedRefsData.success) { - getDestinationCountryPageRefsFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "validation_error", - error: JSON.stringify(validatedRefsData.error), - }) - console.error( - "contentstack.destinationCountryPage.refs validation error", - JSON.stringify({ query: { lang, uid }, error: validatedRefsData.error }) + metricsGetDestinationCountryPageRefs.validationError( + validatedRefsData.error ) return null } - getDestinationCountryPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationCountryPage.refs success", - JSON.stringify({ query: { lang, uid } }) - ) + + metricsGetDestinationCountryPageRefs.success() const tags = generatePageTags(validatedRefsData.data, lang) - getDestinationCountryPageCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationCountryPage start", - JSON.stringify({ - query: { lang, uid }, - }) + const getDestinationCountryPageCounter = createCounter( + "trpc.contentstack", + "destinationCountryPage.get" ) + const metricsGetDestinationCountryPage = + getDestinationCountryPageCounter.init({ lang, uid }) + + metricsGetDestinationCountryPage.start() + const response = await request( GetDestinationCountryPage, { @@ -118,19 +94,7 @@ export const destinationCountryPageQueryRouter = router({ ) if (!response.data) { const notFoundError = notFound(response) - getDestinationCountryPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.destinationCountryPage not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetDestinationCountryPage.noDataError() throw notFoundError } @@ -139,32 +103,15 @@ export const destinationCountryPageQueryRouter = router({ ) if (!validatedResponse.success) { - getDestinationCountryPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - console.error( - "contentstack.destinationCountryPage validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedResponse.error, - }) - ) + metricsGetDestinationCountryPage.validationError(validatedResponse.error) return null } + const destinationCountryPage = validatedResponse.data.destination_country_page const country = destinationCountryPage.destination_settings.country - getDestinationCountryPageSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationCountryPage success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + metricsGetDestinationCountryPage.success() const system = destinationCountryPage.system const pageName = `destinations|${country}` diff --git a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/telemetry.ts b/apps/scandic-web/server/routers/contentstack/destinationCountryPage/telemetry.ts deleted file mode 100644 index 4030043fc..000000000 --- a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/telemetry.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.contentstack.destinationCountryPage") - -export const getDestinationCountryPageRefsCounter = meter.createCounter( - "trpc.contentstack.destinationCountryPage.get" -) -export const getDestinationCountryPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.destinationCountryPage.get-fail" -) -export const getDestinationCountryPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.destinationCountryPage.get-success" -) - -export const getDestinationCountryPageCounter = meter.createCounter( - "trpc.contentstack.destinationCountryPage.get" -) -export const getDestinationCountryPageSuccessCounter = meter.createCounter( - "trpc.contentstack.destinationCountryPage.get-success" -) -export const getDestinationCountryPageFailCounter = meter.createCounter( - "trpc.contentstack.destinationCountryPage.get-fail" -) - -export const getCityListDataCounter = meter.createCounter( - "trpc.contentstack.cityListData.get" -) -export const getCityListDataSuccessCounter = meter.createCounter( - "trpc.contentstack.cityListData.get-success" -) -export const getCityListDataFailCounter = meter.createCounter( - "trpc.contentstack.cityListData.get-fail" -) - -export const getCountryPageUrlsCounter = meter.createCounter( - "trpc.contentstack.getCountryPageUrls" -) - -export const getCountryPageUrlsSuccessCounter = meter.createCounter( - "trpc.contentstack.getCountryPageUrls-success" -) - -export const getCountryPageUrlsFailCounter = meter.createCounter( - "trpc.contentstack.getCountryPageUrls-fail" -) diff --git a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/utils.ts b/apps/scandic-web/server/routers/contentstack/destinationCountryPage/utils.ts index e56c77fc9..26ff227a8 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationCountryPage/utils.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationCountryPage/utils.ts @@ -1,20 +1,13 @@ import { GetDestinationCityListData } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityListData.graphql" import { GetCountryPageUrls } from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPageUrl.graphql" import { request } from "@/lib/graphql/request" +import { createCounter } from "@/server/telemetry" import { generateTag, generateTagsFromSystem } from "@/utils/generateTag" import { getCitiesByCountry } from "../../hotels/utils" import { destinationCityListDataSchema } from "../destinationCityPage/output" import { countryPageUrlsSchema } from "./output" -import { - getCityListDataCounter, - getCityListDataFailCounter, - getCityListDataSuccessCounter, - getCountryPageUrlsCounter, - getCountryPageUrlsFailCounter, - getCountryPageUrlsSuccessCounter, -} from "./telemetry" import { ApiCountry, type Country } from "@/types/enums/country" import { DestinationCountryPageEnum } from "@/types/enums/destinationCountryPage" @@ -77,11 +70,16 @@ export async function getCityListDataByCityIdentifier( lang: Lang, cityIdentifier: string ) { - getCityListDataCounter.add(1, { lang, cityIdentifier }) - console.info( - "contentstack.cityListData start", - JSON.stringify({ query: { lang, cityIdentifier } }) + const getCityListDataCounter = createCounter( + "trpc.contentstack", + "cityListData.get" ) + const metricsGetCityListData = getCityListDataCounter.init({ + lang, + cityIdentifier, + }) + + metricsGetCityListData.start() const response = await request( GetDestinationCityListData, @@ -96,15 +94,8 @@ export async function getCityListDataByCityIdentifier( ) if (!response.data) { - getCityListDataFailCounter.add(1, { - lang, - cityIdentifier, - error_type: "not_found", - error: `Destination city page not found for cityIdentifier: ${cityIdentifier}`, - }) - console.error( - "contentstack.cityListData not found error", - JSON.stringify({ query: { lang, cityIdentifier } }) + metricsGetCityListData.dataError( + `Failed to get destination city page for cityIdentifier: ${cityIdentifier}` ) return null } @@ -114,26 +105,11 @@ export async function getCityListDataByCityIdentifier( ) if (!validatedResponse.success) { - getCityListDataFailCounter.add(1, { - lang, - cityIdentifier, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - console.error( - "contentstack.cityListData validation error", - JSON.stringify({ - query: { lang, cityIdentifier }, - error: validatedResponse.error, - }) - ) + metricsGetCityListData.validationError(validatedResponse.error) return null } - getCityListDataSuccessCounter.add(1, { lang, cityIdentifier }) - console.info( - "contentstack.cityListData success", - JSON.stringify({ query: { lang, cityIdentifier } }) - ) + + metricsGetCityListData.success() return validatedResponse.data } @@ -172,11 +148,13 @@ export async function getCityPages( } export async function getCountryPageUrls(lang: Lang) { - getCountryPageUrlsCounter.add(1, { lang }) - console.info( - "contentstack.countryPageUrls start", - JSON.stringify({ query: { lang } }) + const getCountryPageUrlsCounter = createCounter( + "trpc.contentstack", + "getCountryPageUrls" ) + const metricsGetCountryPageUrls = getCountryPageUrlsCounter.init({ lang }) + + metricsGetCountryPageUrls.start() const tag = `${lang}:country_page_urls` const response = await request( @@ -191,14 +169,8 @@ export async function getCountryPageUrls(lang: Lang) { ) if (!response.data) { - getCountryPageUrlsFailCounter.add(1, { - lang, - error_type: "not_found", - error: `Country pages not found for lang: ${lang}`, - }) - console.error( - "contentstack.countryPageUrls not found error", - JSON.stringify({ query: { lang } }) + metricsGetCountryPageUrls.dataError( + `Failed to get country pages for lang: ${lang}` ) return [] } @@ -208,26 +180,11 @@ export async function getCountryPageUrls(lang: Lang) { ) if (!validatedCountryPageUrls.success) { - getCountryPageUrlsFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedCountryPageUrls.error), - }) - console.error( - "contentstack.countryPageUrls validation error", - JSON.stringify({ - query: { lang }, - error: validatedCountryPageUrls.error, - }) - ) + metricsGetCountryPageUrls.validationError(validatedCountryPageUrls.error) return [] } - getCountryPageUrlsSuccessCounter.add(1, { lang }) - console.info( - "contentstack.countryPageUrls success", - JSON.stringify({ query: { lang } }) - ) + metricsGetCountryPageUrls.success() return validatedCountryPageUrls.data } diff --git a/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/query.ts b/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/query.ts index 32102f7a0..15633c40c 100644 --- a/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/query.ts @@ -5,6 +5,7 @@ import { } from "@/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router, @@ -31,14 +32,6 @@ import { destinationOverviewPageRefsSchema, destinationOverviewPageSchema, } from "./output" -import { - getDestinationOverviewPageCounter, - getDestinationOverviewPageFailCounter, - getDestinationOverviewPageRefsCounter, - getDestinationOverviewPageRefsFailCounter, - getDestinationOverviewPageRefsSuccessCounter, - getDestinationOverviewPageSuccessCounter, -} from "./telemetry" import type { Cities, @@ -57,13 +50,15 @@ export const destinationOverviewPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx - getDestinationOverviewPageRefsCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationOverviewPage.refs start", - JSON.stringify({ - query: { lang, uid }, - }) + const getDestinationOverviewPageRefsCounter = createCounter( + "trpc.contentstack", + "destinationOverviewPage.get.refs" ) + const metricsGetDestinationOverviewPageRefs = + getDestinationOverviewPageRefsCounter.init({ lang, uid }) + + metricsGetDestinationOverviewPageRefs.start() + const refsResponse = await request( GetDestinationOverviewPageRefs, { @@ -77,19 +72,7 @@ export const destinationOverviewPageQueryRouter = router({ ) if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getDestinationOverviewPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.destinationOverviewPage.refs not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetDestinationOverviewPageRefs.noDataError() throw notFoundError } @@ -98,37 +81,23 @@ export const destinationOverviewPageQueryRouter = router({ ) if (!validatedRefsData.success) { - getDestinationOverviewPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "validation_error", - error: JSON.stringify(validatedRefsData.error), - }) - console.error( - "contentstack.destinationOverviewPage.refs validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedRefsData.error, - }) + metricsGetDestinationOverviewPageRefs.validationError( + validatedRefsData.error ) return null } - getDestinationOverviewPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationOverviewPage.refs success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + metricsGetDestinationOverviewPageRefs.success() - getDestinationOverviewPageCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationOverviewPage start", - JSON.stringify({ - query: { lang, uid }, - }) + const getDestinationOverviewPageCounter = createCounter( + "trpc.contentstack", + "destinationOverviewPage.get" ) + const metricsGetDestinationOverviewPage = + getDestinationOverviewPageCounter.init({ lang, uid }) + + metricsGetDestinationOverviewPage.start() + const response = await request( GetDestinationOverviewPage, { @@ -142,19 +111,7 @@ export const destinationOverviewPageQueryRouter = router({ ) if (!response.data) { const notFoundError = notFound(response) - getDestinationOverviewPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.destinationOverviewPage not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetDestinationOverviewPage.noDataError() throw notFoundError } @@ -163,29 +120,13 @@ export const destinationOverviewPageQueryRouter = router({ ) if (!destinationOverviewPage.success) { - getDestinationOverviewPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "validation_error", - error: JSON.stringify(destinationOverviewPage.error), - }) - console.error( - "contentstack.destinationOverviewPage validation error", - JSON.stringify({ - query: { lang, uid }, - error: destinationOverviewPage.error, - }) + metricsGetDestinationOverviewPage.validationError( + destinationOverviewPage.error ) return null } - getDestinationOverviewPageSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.destinationOverviewPage success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + metricsGetDestinationOverviewPage.success() const system = destinationOverviewPage.data.destination_overview_page.system const tracking: TrackingSDKPageData = { diff --git a/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/telemetry.ts b/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/telemetry.ts deleted file mode 100644 index 84d93764a..000000000 --- a/apps/scandic-web/server/routers/contentstack/destinationOverviewPage/telemetry.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.contentstack.destinationOverviewPage") - -export const getDestinationOverviewPageRefsCounter = meter.createCounter( - "trpc.contentstack.destinationOverviewPage.get" -) -export const getDestinationOverviewPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.destinationOverviewPage.get-fail" -) -export const getDestinationOverviewPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.destinationOverviewPage.get-success" -) - -export const getDestinationOverviewPageCounter = meter.createCounter( - "trpc.contentstack.destinationOverviewPage.get" -) -export const getDestinationOverviewPageSuccessCounter = meter.createCounter( - "trpc.contentstack.destinationOverviewPage.get-success" -) -export const getDestinationOverviewPageFailCounter = meter.createCounter( - "trpc.contentstack.destinationOverviewPage.get-fail" -) diff --git a/apps/scandic-web/server/routers/contentstack/hotelPage/query.ts b/apps/scandic-web/server/routers/contentstack/hotelPage/query.ts index 18aec90f6..988b2ee51 100644 --- a/apps/scandic-web/server/routers/contentstack/hotelPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/hotelPage/query.ts @@ -1,29 +1,27 @@ import { GetHotelPage } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { generateTag } from "@/utils/generateTag" import { hotelPageSchema } from "./output" -import { - getHotelPageCounter, - getHotelPageFailCounter, - getHotelPageSuccessCounter, -} from "./telemetry" import type { GetHotelPageData } from "@/types/trpc/routers/contentstack/hotelPage" export const hotelPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx - getHotelPageCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.hotelPage start", - JSON.stringify({ - query: { lang, uid }, - }) + + const getHotelPageCounter = createCounter( + "trpc.contentstack", + "hotelPage.get" ) + const metricsGetHotelPage = getHotelPageCounter.init({ lang, uid }) + + metricsGetHotelPage.start() + const response = await request( GetHotelPage, { @@ -37,48 +35,19 @@ export const hotelPageQueryRouter = router({ ) if (!response.data) { const notFoundError = notFound(response) - getHotelPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.hotelPage not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetHotelPage.noDataError() throw notFoundError } const validatedHotelPage = hotelPageSchema.safeParse(response.data) if (!validatedHotelPage.success) { - getHotelPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "validation_error", - error: JSON.stringify(validatedHotelPage.error), - }) - console.error( - "contentstack.hotelPage validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedHotelPage.error, - }) - ) + metricsGetHotelPage.validationError(validatedHotelPage.error) return null } - getHotelPageSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.hotelPage success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + metricsGetHotelPage.success() + return validatedHotelPage.data.hotel_page }), }) diff --git a/apps/scandic-web/server/routers/contentstack/hotelPage/telemetry.ts b/apps/scandic-web/server/routers/contentstack/hotelPage/telemetry.ts deleted file mode 100644 index 2c094c055..000000000 --- a/apps/scandic-web/server/routers/contentstack/hotelPage/telemetry.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.contentstack.hotelPage") - -export const getHotelPageRefsCounter = meter.createCounter( - "trpc.contentstack.hotelPage.get" -) -export const getHotelPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.hotelPage.get-fail" -) -export const getHotelPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.hotelPage.get-success" -) - -export const getHotelPageCounter = meter.createCounter( - "trpc.contentstack.hotelPage.get" -) -export const getHotelPageSuccessCounter = meter.createCounter( - "trpc.contentstack.hotelPage.get-success" -) -export const getHotelPageFailCounter = meter.createCounter( - "trpc.contentstack.hotelPage.get-fail" -) - -export const getHotelPageUrlsCounter = meter.createCounter( - "trpc.contentstack.hotelPageUrls.get" -) -export const getHotelPageUrlsSuccessCounter = meter.createCounter( - "trpc.contentstack.hotelPageUrls.get-success" -) -export const getHotelPageUrlsFailCounter = meter.createCounter( - "trpc.contentstack.hotelPageUrls.get-fail" -) - -export const getHotelPageCountCounter = meter.createCounter( - "trpc.contentstack.hotelPageCount.get" -) -export const getHotelPageCountSuccessCounter = meter.createCounter( - "trpc.contentstack.hotelPageCount.get-success" -) -export const getHotelPageCountFailCounter = meter.createCounter( - "trpc.contentstack.hotelPageCount.get-fail" -) diff --git a/apps/scandic-web/server/routers/contentstack/hotelPage/utils.ts b/apps/scandic-web/server/routers/contentstack/hotelPage/utils.ts index 4013532cd..b90205408 100644 --- a/apps/scandic-web/server/routers/contentstack/hotelPage/utils.ts +++ b/apps/scandic-web/server/routers/contentstack/hotelPage/utils.ts @@ -3,6 +3,7 @@ import { GetHotelPageCount } from "@/lib/graphql/Query/HotelPage/HotelPageCount. import { GetHotelPageUrls } from "@/lib/graphql/Query/HotelPage/HotelPageUrl.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { generateRefsResponseTag, @@ -15,17 +16,6 @@ import { hotelPageCountSchema, hotelPageRefsSchema, } from "./output" -import { - getHotelPageCountCounter, - getHotelPageCountFailCounter, - getHotelPageCountSuccessCounter, - getHotelPageRefsCounter, - getHotelPageRefsFailCounter, - getHotelPageRefsSuccessCounter, - getHotelPageUrlsCounter, - getHotelPageUrlsFailCounter, - getHotelPageUrlsSuccessCounter, -} from "./telemetry" import { HotelPageEnum } from "@/types/enums/hotelPage" import type { System } from "@/types/requests/system" @@ -38,13 +28,13 @@ import type { import type { Lang } from "@/constants/languages" export async function fetchHotelPageRefs(lang: Lang, uid: string) { - getHotelPageRefsCounter.add(1, { lang, uid }) - console.info( - "contentstack.hotelPage.refs start", - JSON.stringify({ - query: { lang, uid }, - }) + const getHotelPageRefsCounter = createCounter( + "trpc.contentstack", + "hotelPage.get.refs" ) + const metricsGetHotelPageRefs = getHotelPageRefsCounter.init({ lang, uid }) + + metricsGetHotelPageRefs.start() const refsResponse = await request( GetHotelPageRefs, @@ -56,26 +46,10 @@ export async function fetchHotelPageRefs(lang: Lang, uid: string) { ) if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getHotelPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "http_error", - error: JSON.stringify({ - code: notFoundError.code, - }), - }) - console.error( - "contentstack.hotelPage.refs not found error", - JSON.stringify({ - query: { - lang, - uid, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetHotelPageRefs.noDataError() throw notFoundError } + return refsResponse.data } @@ -84,30 +58,20 @@ export function validateHotelPageRefs( lang: Lang, uid: string ) { + const getHotelPageRefsCounter = createCounter( + "trpc.contentstack", + "hotelPage.get.refs" + ) + const metricsGetHotelPageRefs = getHotelPageRefsCounter.init({ lang, uid }) + const validatedData = hotelPageRefsSchema.safeParse(data) + if (!validatedData.success) { - getHotelPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "validation_error", - error: JSON.stringify(validatedData.error), - }) - console.error( - "contentstack.hotelPage.refs validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedData.error, - }) - ) + metricsGetHotelPageRefs.validationError(validatedData.error) return null } - getHotelPageRefsSuccessCounter.add(1, { lang, uid }) - console.info( - "contentstack.hotelPage.refs success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + + metricsGetHotelPageRefs.success() return validatedData.data } @@ -144,11 +108,14 @@ export function getConnections({ hotel_page }: HotelPageRefs) { } export async function getHotelPageCount(lang: Lang) { - getHotelPageCountCounter.add(1, { lang }) - console.info( - "contentstack.hotelPageCount start", - JSON.stringify({ query: { lang } }) + const getHotelPageCountCounter = createCounter( + "trpc.contentstack", + "hotelPageCount.get" ) + const metricsGetHotelPageCount = getHotelPageCountCounter.init({ lang }) + + metricsGetHotelPageCount.start() + const response = await request( GetHotelPageCount, { @@ -161,50 +128,31 @@ export async function getHotelPageCount(lang: Lang) { ) if (!response.data) { - getHotelPageCountFailCounter.add(1, { - lang, - error_type: "not_found", - error: `Hotel pages count not found for lang: ${lang}`, - }) - console.error( - "contentstack.hotelPageCount not found error", - JSON.stringify({ query: { lang } }) - ) + metricsGetHotelPageCount.noDataError() return 0 } const validatedResponse = hotelPageCountSchema.safeParse(response.data) if (!validatedResponse.success) { - getHotelPageCountFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - console.error( - "contentstack.hotelPageCount validation error", - JSON.stringify({ - query: { lang }, - error: validatedResponse.error, - }) - ) + metricsGetHotelPageCount.validationError(validatedResponse.error) return 0 } - getHotelPageCountSuccessCounter.add(1, { lang }) - console.info( - "contentstack.hotelPageCount success", - JSON.stringify({ query: { lang } }) - ) + + metricsGetHotelPageCount.success() return validatedResponse.data } export async function getHotelPageUrls(lang: Lang) { - getHotelPageUrlsCounter.add(1, { lang }) - console.info( - "contentstack.hotelPageUrls start", - JSON.stringify({ query: { lang } }) + const getHotelPageUrlsCounter = createCounter( + "trpc.contentstack", + "hotelPageUrls.get" ) + const metricsGetHotelPageUrls = getHotelPageUrlsCounter.init({ lang }) + + metricsGetHotelPageUrls.start() + const count = await getHotelPageCount(lang) if (count === 0) { @@ -236,25 +184,11 @@ export async function getHotelPageUrls(lang: Lang) { batchedHotelPageUrlsSchema.safeParse(batchedResponse) if (!validatedResponse.success) { - getHotelPageUrlsFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - console.error( - "contentstack.hotelPageUrls validation error", - JSON.stringify({ - query: { lang }, - error: validatedResponse.error, - }) - ) + metricsGetHotelPageUrls.validationError(validatedResponse.error) return [] } - getHotelPageUrlsSuccessCounter.add(1, { lang }) - console.info( - "contentstack.hotelPageUrl success", - JSON.stringify({ query: { lang } }) - ) + + metricsGetHotelPageUrls.success() return validatedResponse.data } diff --git a/apps/scandic-web/server/routers/contentstack/languageSwitcher/telemetry.ts b/apps/scandic-web/server/routers/contentstack/languageSwitcher/telemetry.ts deleted file mode 100644 index 6d81b5edc..000000000 --- a/apps/scandic-web/server/routers/contentstack/languageSwitcher/telemetry.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.contentstack.languageSwitcher") -export const getLanguageSwitcherCounter = meter.createCounter( - "trpc.contentstack.languageSwitcher.get" -) -export const getLanguageSwitcherSuccessCounter = meter.createCounter( - "trpc.contentstack.languageSwitcher.get-success" -) -export const getLanguageSwitcherFailCounter = meter.createCounter( - "trpc.contentstack.languageSwitcher.get-fail" -) diff --git a/apps/scandic-web/server/routers/contentstack/languageSwitcher/utils.ts b/apps/scandic-web/server/routers/contentstack/languageSwitcher/utils.ts index 03e7525d6..1fe2c8d36 100644 --- a/apps/scandic-web/server/routers/contentstack/languageSwitcher/utils.ts +++ b/apps/scandic-web/server/routers/contentstack/languageSwitcher/utils.ts @@ -41,16 +41,12 @@ import { GetFiNoSvUrlsStartPage, } from "@/lib/graphql/Query/StartPage/StartPage.graphql" import { internalServerError } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { generateTag } from "@/utils/generateTag" import { removeTrailingSlash } from "@/utils/url" import { validateLanguageSwitcherData } from "./output" -import { - getLanguageSwitcherCounter, - getLanguageSwitcherFailCounter, - getLanguageSwitcherSuccessCounter, -} from "./telemetry" import { PageContentTypeEnum } from "@/types/requests/contentType" import type { @@ -65,21 +61,17 @@ export async function getUrlsOfAllLanguages( uid: string, contentType: string ) { - getLanguageSwitcherCounter.add(1, { - uid, + const getLanguageSwitcherCounter = createCounter( + "trpc.contentstack", + "languageSwitcher.get" + ) + const metricsGetLanguageSwitcher = getLanguageSwitcherCounter.init({ lang, + uid, contentType, }) - console.info( - "contentstack.languageSwitcher start", - JSON.stringify({ - query: { - uid, - lang, - contentType, - }, - }) - ) + + metricsGetLanguageSwitcher.start() const variables = { uid } const tagsDaDeEn = [ @@ -184,42 +176,13 @@ export async function getUrlsOfAllLanguages( validateLanguageSwitcherData.safeParse(urls) if (!validatedLanguageSwitcherData.success) { - getLanguageSwitcherFailCounter.add(1, { - uid, - lang, - contentType, - error_type: "validation_error", - error: JSON.stringify(validatedLanguageSwitcherData.error), - }) - console.error( - "contentstack.languageSwitcher validation error", - JSON.stringify({ - query: { - uid, - lang, - contentType, - }, - error: validatedLanguageSwitcherData.error, - }) + metricsGetLanguageSwitcher.validationError( + validatedLanguageSwitcherData.error ) return null } - getLanguageSwitcherSuccessCounter.add(1, { - uid, - lang, - contentType, - }) - console.info( - "contentstack.languageSwitcher success", - JSON.stringify({ - query: { - uid, - lang, - contentType, - }, - }) - ) + metricsGetLanguageSwitcher.success() return urls } diff --git a/apps/scandic-web/server/routers/contentstack/loyaltyLevel/query.ts b/apps/scandic-web/server/routers/contentstack/loyaltyLevel/query.ts index 248803f61..a73d6f7b3 100644 --- a/apps/scandic-web/server/routers/contentstack/loyaltyLevel/query.ts +++ b/apps/scandic-web/server/routers/contentstack/loyaltyLevel/query.ts @@ -1,4 +1,3 @@ -import { metrics } from "@opentelemetry/api" import { cache } from "react" import { @@ -11,6 +10,7 @@ import { } from "@/lib/graphql/Query/LoyaltyLevels.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackBaseProcedure, router } from "@/server/trpc" import { generateLoyaltyConfigTag } from "@/utils/generateTag" @@ -24,32 +24,14 @@ import { import type { Context } from "@/server/context" -const meter = metrics.getMeter("trpc.loyaltyLevel") -// OpenTelemetry metrics: Loyalty Level -const getAllLoyaltyLevelCounter = meter.createCounter( - "trpc.contentstack.loyaltyLevel.all" -) - -const getAllLoyaltyLevelSuccessCounter = meter.createCounter( - "trpc.contentstack.loyaltyLevel.all-success" -) -const getAllLoyaltyLevelFailCounter = meter.createCounter( - "trpc.contentstack.loyaltyLevel.all-fail" -) - -const getByLevelLoyaltyLevelCounter = meter.createCounter( - "trpc.contentstack.loyaltyLevel.byLevel" -) - -const getByLevelLoyaltyLevelSuccessCounter = meter.createCounter( - "trpc.contentstack.loyaltyLevel.byLevel-success" -) -const getByLevelLoyaltyLevelFailCounter = meter.createCounter( - "trpc.contentstack.loyaltyLevel.byLevel-fail" -) - export const getAllLoyaltyLevels = cache(async (ctx: Context) => { - getAllLoyaltyLevelCounter.add(1) + const getLoyaltyLevelAllCounter = createCounter( + "trpc.contentstack", + "loyaltyLevel.all" + ) + const metricsGetLoyaltyLevelAll = getLoyaltyLevelAllCounter.init() + + metricsGetLoyaltyLevelAll.start() // Ideally we should fetch all available tiers from API, but since they // are static, we can just use the enum values. We want to know which @@ -67,17 +49,8 @@ export const getAllLoyaltyLevels = cache(async (ctx: Context) => { ) if (!loyaltyLevelsConfigResponse.data) { - getAllLoyaltyLevelFailCounter.add(1) const notFoundError = notFound(loyaltyLevelsConfigResponse) - console.error( - "contentstack.loyaltyLevels not found error", - JSON.stringify({ - query: { - lang: ctx.lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetLoyaltyLevelAll.noDataError() throw notFoundError } @@ -85,30 +58,28 @@ export const getAllLoyaltyLevels = cache(async (ctx: Context) => { loyaltyLevelsConfigResponse.data ) if (!validatedLoyaltyLevels.success) { - getAllLoyaltyLevelFailCounter.add(1) - console.error(validatedLoyaltyLevels.error) - console.error( - "contentstack.rewards validation error", - JSON.stringify({ - query: { - lang: ctx.lang, - }, - error: validatedLoyaltyLevels.error, - }) - ) + metricsGetLoyaltyLevelAll.validationError(validatedLoyaltyLevels.error) return [] } - getAllLoyaltyLevelSuccessCounter.add(1) + metricsGetLoyaltyLevelAll.success() + return validatedLoyaltyLevels.data }) export const getLoyaltyLevel = cache( async (ctx: Context, level_id: MembershipLevel) => { - getByLevelLoyaltyLevelCounter.add(1, { - query: JSON.stringify({ lang: ctx.lang, level_id }), + const getLoyaltyLevelCounter = createCounter( + "trpc.contentstack", + "loyaltyLevel.get" + ) + const metricsGetLoyaltyLevel = getLoyaltyLevelCounter.init({ + lang: ctx.lang, + level_id, }) + metricsGetLoyaltyLevel.start() + const loyaltyLevelsConfigResponse = await request( GetLoyaltyLevel, { lang: ctx.lang, level_id }, @@ -121,15 +92,8 @@ export const getLoyaltyLevel = cache( !loyaltyLevelsConfigResponse.data || !loyaltyLevelsConfigResponse.data.all_loyalty_level.items.length ) { - getByLevelLoyaltyLevelFailCounter.add(1) const notFoundError = notFound(loyaltyLevelsConfigResponse) - console.error( - "contentstack.loyaltyLevel not found error", - JSON.stringify({ - query: { lang: ctx.lang, level_id }, - error: { code: notFoundError.code }, - }) - ) + metricsGetLoyaltyLevel.noDataError() throw notFoundError } @@ -137,19 +101,12 @@ export const getLoyaltyLevel = cache( loyaltyLevelsConfigResponse.data ) if (!validatedLoyaltyLevels.success) { - getByLevelLoyaltyLevelFailCounter.add(1) - console.error(validatedLoyaltyLevels.error) - console.error( - "contentstack.loyaltyLevel validation error", - JSON.stringify({ - query: { lang: ctx.lang, level_id }, - error: validatedLoyaltyLevels.error, - }) - ) + metricsGetLoyaltyLevel.validationError(validatedLoyaltyLevels.error) return null } - getByLevelLoyaltyLevelSuccessCounter.add(1) + metricsGetLoyaltyLevel.success() + const result: LoyaltyLevel = validatedLoyaltyLevels.data[0] return result } diff --git a/apps/scandic-web/server/routers/contentstack/loyaltyPage/query.ts b/apps/scandic-web/server/routers/contentstack/loyaltyPage/query.ts index 72c589cc8..f66424d99 100644 --- a/apps/scandic-web/server/routers/contentstack/loyaltyPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/loyaltyPage/query.ts @@ -1,11 +1,10 @@ -import { metrics } from "@opentelemetry/api" - import { GetLoyaltyPage, GetLoyaltyPageRefs, } from "@/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { @@ -26,39 +25,22 @@ import type { GetLoyaltyPageSchema, } from "@/types/trpc/routers/contentstack/loyaltyPage" -const meter = metrics.getMeter("trpc.loyaltyPage") -// OpenTelemetry metrics: LoyaltyPage -const getLoyaltyPageRefsCounter = meter.createCounter( - "trpc.contentstack.loyaltyPage.get" -) -const getLoyaltyPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.loyaltyPage.get-success" -) -const getLoyaltyPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.loyaltyPage.get-fail" -) -const getLoyaltyPageCounter = meter.createCounter( - "trpc.contentstack.loyaltyPage.get" -) -const getLoyaltyPageSuccessCounter = meter.createCounter( - "trpc.contentstack.loyaltyPage.get-success" -) -const getLoyaltyPageFailCounter = meter.createCounter( - "trpc.contentstack.loyaltyPage.get-fail" -) - export const loyaltyPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx - const metricsVariables = { lang, uid } - const variables = { locale: lang, uid } - getLoyaltyPageRefsCounter.add(1, metricsVariables) - console.info( - "contentstack.loyaltyPage.refs start", - JSON.stringify({ - query: metricsVariables, - }) + + const getLoyaltyPageRefsCounter = createCounter( + "trpc.contentstack", + "loyaltyPage.get.refs" ) + const metricsGetLoyaltyPageRefs = getLoyaltyPageRefsCounter.init({ + lang, + uid, + }) + + metricsGetLoyaltyPageRefs.start() + + const variables = { locale: lang, uid } const refsResponse = await request( GetLoyaltyPageRefs, variables, @@ -70,20 +52,7 @@ export const loyaltyPageQueryRouter = router({ if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getLoyaltyPageRefsFailCounter.add(1, { - ...metricsVariables, - error_type: "http_error", - error: JSON.stringify({ - code: notFoundError.code, - }), - }) - console.error( - "contentstack.loyaltyPage.refs not found error", - JSON.stringify({ - query: metricsVariables, - error: { code: notFoundError.code }, - }) - ) + metricsGetLoyaltyPageRefs.noDataError() throw notFoundError } @@ -91,27 +60,11 @@ export const loyaltyPageQueryRouter = router({ refsResponse.data ) if (!validatedLoyaltyPageRefs.success) { - getLoyaltyPageRefsFailCounter.add(1, { - ...metricsVariables, - error_type: "validation_error", - error: JSON.stringify(validatedLoyaltyPageRefs.error), - }) - console.error( - "contentstack.loyaltyPage.refs validation error", - JSON.stringify({ - query: metricsVariables, - error: validatedLoyaltyPageRefs.error, - }) - ) + metricsGetLoyaltyPageRefs.validationError(validatedLoyaltyPageRefs.error) return null } - getLoyaltyPageRefsSuccessCounter.add(1, metricsVariables) - console.info( - "contentstack.loyaltyPage.refs success", - JSON.stringify({ - query: metricsVariables, - }) - ) + + metricsGetLoyaltyPageRefs.success() const connections = getConnections(validatedLoyaltyPageRefs.data) @@ -119,13 +72,15 @@ export const loyaltyPageQueryRouter = router({ generateTagsFromSystem(lang, connections), generateTag(lang, validatedLoyaltyPageRefs.data.loyalty_page.system.uid), ].flat() - getLoyaltyPageCounter.add(1, metricsVariables) - console.info( - "contentstack.loyaltyPage start", - JSON.stringify({ - query: metricsVariables, - }) + + const getLoyaltyPageCounter = createCounter( + "trpc.contentstack", + "loyaltyPage.get" ) + const metricsGetLoyaltyPage = getLoyaltyPageCounter.init({ lang, uid }) + + metricsGetLoyaltyPage.start() + const response = await request( GetLoyaltyPage, variables, @@ -137,35 +92,13 @@ export const loyaltyPageQueryRouter = router({ if (!response.data) { const notFoundError = notFound(response) - getLoyaltyPageFailCounter.add(1, { - ...metricsVariables, - error_type: "http_error", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.loyaltyPage not found error", - JSON.stringify({ - query: metricsVariables, - error: { code: notFoundError.code }, - }) - ) - throw notFound(response) + metricsGetLoyaltyPage.noDataError() + throw notFoundError } const validatedLoyaltyPage = loyaltyPageSchema.safeParse(response.data) if (!validatedLoyaltyPage.success) { - getLoyaltyPageFailCounter.add(1, { - ...metricsVariables, - error_type: "validation_error", - error: JSON.stringify(validatedLoyaltyPage.error), - }) - console.error( - "contentstack.loyaltyPage validation error", - JSON.stringify({ - query: metricsVariables, - error: validatedLoyaltyPage.error, - }) - ) + metricsGetLoyaltyPage.validationError(validatedLoyaltyPage.error) return null } @@ -182,11 +115,8 @@ export const loyaltyPageQueryRouter = router({ siteSections: validatedLoyaltyPage.data.trackingProps.url, siteVersion: "new-web", } - getLoyaltyPageSuccessCounter.add(1, metricsVariables) - console.info( - "contentstack.loyaltyPage success", - JSON.stringify({ query: metricsVariables }) - ) + + metricsGetLoyaltyPage.success() // Assert LoyaltyPage type to get correct typings for RTE fields return { diff --git a/apps/scandic-web/server/routers/contentstack/metadata/query.ts b/apps/scandic-web/server/routers/contentstack/metadata/query.ts index 762846b62..a22e0faa6 100644 --- a/apps/scandic-web/server/routers/contentstack/metadata/query.ts +++ b/apps/scandic-web/server/routers/contentstack/metadata/query.ts @@ -1,4 +1,3 @@ -import { metrics } from "@opentelemetry/api" import { cache } from "react" import { GetAccountPageMetadata } from "@/lib/graphql/Query/AccountPage/Metadata.graphql" @@ -12,6 +11,7 @@ import { GetLoyaltyPageMetadata } from "@/lib/graphql/Query/LoyaltyPage/Metadata import { GetStartPageMetadata } from "@/lib/graphql/Query/StartPage/Metadata.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentStackUidWithServiceProcedure, router } from "@/server/trpc" import { generateTag } from "@/utils/generateTag" @@ -29,37 +29,15 @@ import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher" import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata" import type { Lang } from "@/constants/languages" -const meter = metrics.getMeter("trpc.metadata") - -// OpenTelemetry metrics -const fetchMetadataCounter = meter.createCounter( - "trpc.contentstack.metadata.get" -) -const fetchMetadataSuccessCounter = meter.createCounter( - "trpc.contentstack.metadata.get-success" -) -const fetchMetadataFailCounter = meter.createCounter( - "trpc.contentstack.metadata.get-fail" -) -const transformMetadataCounter = meter.createCounter( - "trpc.contentstack.metadata.transform" -) -const transformMetadataSuccessCounter = meter.createCounter( - "trpc.contentstack.metadata.transform-success" -) -const transformMetadataFailCounter = meter.createCounter( - "trpc.contentstack.metadata.transform-fail" -) - const fetchMetadata = cache(async function fetchMemoizedMetadata( query: string, { uid, lang }: { uid: string; lang: Lang } ) { - fetchMetadataCounter.add(1, { lang, uid }) - console.info( - "contentstack.metadata fetch start", - JSON.stringify({ query: { lang, uid } }) - ) + const getMetadataCounter = createCounter("trpc.contentstack", "metadata.get") + const metricsGetMetadata = getMetadataCounter.init({ lang, uid }) + + metricsGetMetadata.start() + const response = await request( query, { locale: lang, uid }, @@ -68,54 +46,35 @@ const fetchMetadata = cache(async function fetchMemoizedMetadata( ttl: "max", } ) + if (!response.data) { const notFoundError = notFound(response) - fetchMetadataFailCounter.add(1, { - lang, - uid, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.metadata fetch not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetMetadata.noDataError() throw notFoundError } - fetchMetadataSuccessCounter.add(1, { lang, uid }) - console.info( - "contentstack.metadata fetch success", - JSON.stringify({ query: { lang, uid } }) - ) + metricsGetMetadata.success() return response.data }) async function getTransformedMetadata(data: unknown) { - transformMetadataCounter.add(1) - console.info("contentstack.metadata transform start") + const transformMetadataCounter = createCounter( + "trpc.contentstack", + "metadata.transform" + ) + const metricsTransformMetadata = transformMetadataCounter.init() + + metricsTransformMetadata.start() + const validatedMetadata = await metadataSchema.safeParseAsync(data) if (!validatedMetadata.success) { - transformMetadataFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(validatedMetadata.error), - }) - console.error( - "contentstack.metadata validation error", - JSON.stringify({ - error: validatedMetadata.error, - }) - ) + metricsTransformMetadata.validationError(validatedMetadata.error) return null } - transformMetadataSuccessCounter.add(1) - console.info("contentstack.metadata transform success") + metricsTransformMetadata.success() return validatedMetadata.data } diff --git a/apps/scandic-web/server/routers/contentstack/partner/query.ts b/apps/scandic-web/server/routers/contentstack/partner/query.ts index 7e1cbcfde..c07b55504 100644 --- a/apps/scandic-web/server/routers/contentstack/partner/query.ts +++ b/apps/scandic-web/server/routers/contentstack/partner/query.ts @@ -1,9 +1,9 @@ -import { metrics } from "@opentelemetry/api" import { cache } from "react" import { GetAllSasTierComparison } from "@/lib/graphql/Query/SASTierComparison.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackBaseProcedure, router } from "@/server/trpc" import { validateSasTierComparisonSchema } from "./output" @@ -11,20 +11,16 @@ import { validateSasTierComparisonSchema } from "./output" import type { SasTierComparisonResponse } from "@/types/trpc/routers/contentstack/partner" import type { Context } from "@/server/context" -const meter = metrics.getMeter("trpc.partner") -const getSasTierComparisonCounter = meter.createCounter( - "trpc.contentstack.partner.getSasTierComparison" -) - -const getSasTierComparisonSuccessCounter = meter.createCounter( - "trpc.contentstack.partner.getSasTierComparison-success" -) -const getSasTierComparisonFailCounter = meter.createCounter( - "trpc.contentstack.partner.getSasTierComparison-fail" -) - export const getSasTierComparison = cache(async (ctx: Context) => { - getSasTierComparisonCounter.add(1) + const getSasTierComparisonCounter = createCounter( + "trpc.contentstack", + "partner.getSasTierComparison" + ) + const metricsGetSasTierComparison = getSasTierComparisonCounter.init({ + lang: ctx.lang, + }) + + metricsGetSasTierComparison.start() const tag = `${ctx.lang}:sas_tier_comparison` const sasTierComparisonConfigResponse = @@ -38,17 +34,8 @@ export const getSasTierComparison = cache(async (ctx: Context) => { ) if (!sasTierComparisonConfigResponse.data) { - getSasTierComparisonFailCounter.add(1) const notFoundError = notFound(sasTierComparisonConfigResponse) - console.error( - "contentstack.sas not found error", - JSON.stringify({ - query: { - lang: ctx.lang, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetSasTierComparison.noDataError() throw notFoundError } @@ -57,21 +44,14 @@ export const getSasTierComparison = cache(async (ctx: Context) => { ) if (!validatedSasTierComparison.success) { - getSasTierComparisonFailCounter.add(1) - console.error(validatedSasTierComparison.error) - console.error( - "contentstack.sas validation error", - JSON.stringify({ - query: { - lang: ctx.lang, - }, - error: validatedSasTierComparison.error, - }) + metricsGetSasTierComparison.validationError( + validatedSasTierComparison.error ) return null } - getSasTierComparisonSuccessCounter.add(1) + metricsGetSasTierComparison.success() + return validatedSasTierComparison.data }) diff --git a/apps/scandic-web/server/routers/contentstack/reward/query.ts b/apps/scandic-web/server/routers/contentstack/reward/query.ts index aa65ae2ba..732da5705 100644 --- a/apps/scandic-web/server/routers/contentstack/reward/query.ts +++ b/apps/scandic-web/server/routers/contentstack/reward/query.ts @@ -1,5 +1,6 @@ import * as api from "@/lib/api" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentStackBaseWithProtectedProcedure, contentStackBaseWithServiceProcedure, @@ -22,24 +23,9 @@ import { } from "./input" import { validateCategorizedRewardsSchema } from "./output" import { - getAllRewardCounter, - getAllRewardFailCounter, - getAllRewardSuccessCounter, - getByLevelRewardCounter, - getByLevelRewardFailCounter, - getByLevelRewardSuccessCounter, getCachedAllTierRewards, getCmsRewards, - getCurrentRewardCounter, - getCurrentRewardFailCounter, - getCurrentRewardSuccessCounter, - getRedeemCounter, - getRedeemFailCounter, - getRedeemSuccessCounter, getUniqueRewardIds, - getUnwrapSurpriseCounter, - getUnwrapSurpriseFailCounter, - getUnwrapSurpriseSuccessCounter, } from "./utils" import type { BaseReward, Surprise } from "@/types/components/myPages/rewards" @@ -50,7 +36,14 @@ export const rewardQueryRouter = router({ all: contentStackBaseWithServiceProcedure .input(rewardsAllInput) .query(async function ({ input, ctx }) { - getAllRewardCounter.add(1) + const getContentstackRewardAllCounter = createCounter( + "trpc.contentstack", + "reward.all" + ) + const metricsGetContentstackRewardAll = + getContentstackRewardAllCounter.init() + + metricsGetContentstackRewardAll.start() const allApiRewards = await getCachedAllTierRewards(ctx.serviceToken) @@ -75,16 +68,23 @@ export const rewardQueryRouter = router({ const levelsWithRewards = Object.entries(allApiRewards).map( ([level, rewards]) => { const combinedRewards = rewards - .filter((r) => (input.unique ? r?.rewardTierLevel === level : true)) + .filter((reward) => + input.unique ? reward.rewardTierLevel === level : true + ) .map((reward) => { const contentStackReward = contentStackRewards.find((r) => { - return r.reward_id === reward?.rewardId + return r.reward_id === reward.rewardId }) if (contentStackReward) { return contentStackReward } else { - console.error("No contentStackReward found", reward?.rewardId) + metricsGetContentstackRewardAll.dataError( + `Failed to find reward in CMS for reward ${reward.rewardId} `, + { + rewardId: reward.rewardId, + } + ) } }) .filter((reward): reward is CMSReward => Boolean(reward)) @@ -94,9 +94,9 @@ export const rewardQueryRouter = router({ ) if (!levelConfig) { - getAllRewardFailCounter.add(1) - - console.error("contentstack.loyaltyLevels level not found") + metricsGetContentstackRewardAll.dataError( + `Failed to matched loyalty level between API and CMS for level ${level}` + ) throw notFound() } const result: LevelWithRewards = { @@ -107,21 +107,31 @@ export const rewardQueryRouter = router({ } ) - getAllRewardSuccessCounter.add(1) + metricsGetContentstackRewardAll.success() + return levelsWithRewards }), byLevel: contentStackBaseWithServiceProcedure .input(rewardsByLevelInput) .query(async function ({ input, ctx }) { - getByLevelRewardCounter.add(1) const { level_id } = input + const getRewardByLevelCounter = createCounter( + "trpc.contentstack", + "reward.byLevel" + ) + const metricsGetRewardByLevel = getRewardByLevelCounter.init({ + level_id, + }) + + metricsGetRewardByLevel.start() + const allUpcomingApiRewards = await getCachedAllTierRewards( ctx.serviceToken ) if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) { - getByLevelRewardFailCounter.add(1) + metricsGetRewardByLevel.noDataError() return null } @@ -150,24 +160,36 @@ export const rewardQueryRouter = router({ const levelsWithRewards = apiRewards .map((reward) => { const contentStackReward = contentStackRewards.find((r) => { - return r.reward_id === reward?.rewardId + return r.reward_id === reward.rewardId }) if (contentStackReward) { return contentStackReward } else { - console.info("No contentStackReward found", reward?.rewardId) + metricsGetRewardByLevel.dataError( + `Failed to find reward in Contentstack with rewardId: ${reward.rewardId}`, + { + rewardId: reward.rewardId, + } + ) } }) .filter((reward): reward is CMSReward => Boolean(reward)) - getByLevelRewardSuccessCounter.add(1) + metricsGetRewardByLevel.success() + return { level: loyaltyLevelsConfig, rewards: levelsWithRewards } }), current: contentStackBaseWithProtectedProcedure .input(langInput.optional()) // lang is required for client, but not for server .query(async function ({ ctx }) { - getCurrentRewardCounter.add(1) + const getCurrentRewardCounter = createCounter( + "trpc.contentstack", + "reward.current" + ) + const metricsGetCurrentReward = getCurrentRewardCounter.init() + + metricsGetCurrentReward.start() const apiResponse = await api.get( api.endpoints.v1.Profile.Reward.reward, @@ -179,25 +201,7 @@ export const rewardQueryRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - getCurrentRewardFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.reward error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsGetCurrentReward.httpError(apiResponse) return null } @@ -207,19 +211,7 @@ export const rewardQueryRouter = router({ validateCategorizedRewardsSchema.safeParse(data) if (!validatedApiRewards.success) { - getCurrentRewardFailCounter.add(1, { - locale: ctx.lang, - error_type: "validation_error", - error: JSON.stringify(validatedApiRewards.error), - }) - console.error(validatedApiRewards.error) - console.error( - "contentstack.rewards validation error", - JSON.stringify({ - query: { locale: ctx.lang }, - error: validatedApiRewards.error, - }) - ) + metricsGetCurrentReward.validationError(validatedApiRewards.error) return null } @@ -243,14 +235,20 @@ export const rewardQueryRouter = router({ } }) - getCurrentRewardSuccessCounter.add(1) + metricsGetCurrentReward.success() return { rewards } }), surprises: contentStackBaseWithProtectedProcedure .input(langInput.optional()) // lang is required for client, but not for server .query(async ({ ctx }) => { - getCurrentRewardCounter.add(1) + const getSurprisesCounter = createCounter( + "trpc.contentstack", + "surprises" + ) + const metricsGetSurprises = getSurprisesCounter.init() + + metricsGetSurprises.start() const endpoint = api.endpoints.v1.Profile.Reward.reward @@ -262,25 +260,7 @@ export const rewardQueryRouter = router({ }) if (!apiResponse.ok) { - const text = await apiResponse.text() - getCurrentRewardFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.reward error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsGetSurprises.httpError(apiResponse) return null } @@ -289,19 +269,7 @@ export const rewardQueryRouter = router({ validateCategorizedRewardsSchema.safeParse(data) if (!validatedApiRewards.success) { - getCurrentRewardFailCounter.add(1, { - locale: ctx.lang, - error_type: "validation_error", - error: JSON.stringify(validatedApiRewards.error), - }) - console.error(validatedApiRewards.error) - console.error( - "contentstack.surprises validation error", - JSON.stringify({ - query: { locale: ctx.lang }, - error: validatedApiRewards.error, - }) - ) + metricsGetSurprises.validationError(validatedApiRewards.error) return null } @@ -316,8 +284,6 @@ export const rewardQueryRouter = router({ return null } - getCurrentRewardSuccessCounter.add(1) - const surprises: Surprise[] = cmsRewards .map((cmsReward) => { // Non-null assertion is used here because we know our reward exist @@ -336,73 +302,80 @@ export const rewardQueryRouter = router({ }) .flatMap((surprises) => (surprises ? [surprises] : [])) + metricsGetSurprises.success() + return surprises }), unwrap: protectedProcedure .input(rewardsUpdateInput) .mutation(async ({ input, ctx }) => { - getUnwrapSurpriseCounter.add(1) + const results = await Promise.allSettled( + // Execute each unwrap individually + input.map(({ rewardId, couponCode }) => { + async function handleUnwrap() { + const getUnwrapSurpriseCounter = createCounter( + "trpc.contentstack", + "reward.unwrap" + ) - const promises = input.map(({ rewardId, couponCode }) => { - return api.post(api.endpoints.v1.Profile.Reward.unwrap, { - body: { - rewardId, - couponCode, - }, - headers: { - Authorization: `Bearer ${ctx.session.token.access_token}`, - }, - }) - }) - - const responses = await Promise.all(promises) - - const errors = await Promise.all( - responses.map(async (apiResponse) => { - if (!apiResponse.ok) { - const text = await apiResponse.text() - - getUnwrapSurpriseFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), + const metricsGetUnwrapSurprise = getUnwrapSurpriseCounter.init({ + rewardId, + couponCode, }) - console.error( - "contentstack.unwrap API error", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, + metricsGetUnwrapSurprise.start() + + const apiResponse = await api.post( + api.endpoints.v1.Profile.Reward.unwrap, + { + body: { + rewardId, + couponCode, }, - query: {}, - }) + headers: { + Authorization: `Bearer ${ctx.session.token.access_token}`, + }, + } ) - return false + + if (!apiResponse.ok) { + metricsGetUnwrapSurprise.httpError(apiResponse) + return false + } + + metricsGetUnwrapSurprise.success() + + return true } - return true + + return handleUnwrap() }) ) - if (errors.filter((ok) => !ok).length > 0) { + if ( + results.some( + (result) => result.status === "rejected" || result.value === false + ) + ) { return null } - getUnwrapSurpriseSuccessCounter.add(1) - return true }), redeem: protectedProcedure .input(rewardsRedeemInput) .mutation(async ({ input, ctx }) => { - getRedeemCounter.add(1) - const { rewardId, couponCode } = input + const getRedeemCounter = createCounter( + "trpc.contentstack", + "reward.redeem" + ) + + const metricGetRedeem = getRedeemCounter.init({ rewardId, couponCode }) + + metricGetRedeem.start() + const apiResponse = await api.post( api.endpoints.v1.Profile.Reward.redeem, { @@ -417,29 +390,11 @@ export const rewardQueryRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - getRedeemFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.redeem error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + metricGetRedeem.httpError(apiResponse) return null } - getRedeemSuccessCounter.add(1) + metricGetRedeem.success() return true }), diff --git a/apps/scandic-web/server/routers/contentstack/reward/utils.ts b/apps/scandic-web/server/routers/contentstack/reward/utils.ts index 1751299e5..00494aa2b 100644 --- a/apps/scandic-web/server/routers/contentstack/reward/utils.ts +++ b/apps/scandic-web/server/routers/contentstack/reward/utils.ts @@ -1,5 +1,3 @@ -import { metrics } from "@opentelemetry/api" - import * as api from "@/lib/api" import { GetRewards as GetRewards, @@ -7,6 +5,7 @@ import { } from "@/lib/graphql/Query/RewardsWithRedeem.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { getCacheClient } from "@/services/dataCache" import { @@ -26,63 +25,6 @@ import type { } from "@/types/trpc/routers/contentstack/reward" import type { Lang } from "@/constants/languages" -const meter = metrics.getMeter("trpc.reward") -export const getAllRewardCounter = meter.createCounter( - "trpc.contentstack.reward.all" -) -export const getAllRewardFailCounter = meter.createCounter( - "trpc.contentstack.reward.all-fail" -) -export const getAllRewardSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.all-success" -) -export const getCurrentRewardCounter = meter.createCounter( - "trpc.contentstack.reward.current" -) -export const getCurrentRewardFailCounter = meter.createCounter( - "trpc.contentstack.reward.current-fail" -) -export const getCurrentRewardSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.current-success" -) -export const getByLevelRewardCounter = meter.createCounter( - "trpc.contentstack.reward.byLevel" -) -export const getByLevelRewardFailCounter = meter.createCounter( - "trpc.contentstack.reward.byLevel-fail" -) -export const getByLevelRewardSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.byLevel-success" -) -export const getUnwrapSurpriseCounter = meter.createCounter( - "trpc.contentstack.reward.unwrap" -) -export const getUnwrapSurpriseFailCounter = meter.createCounter( - "trpc.contentstack.reward.unwrap-fail" -) -export const getUnwrapSurpriseSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.unwrap-success" -) -export const getRedeemCounter = meter.createCounter( - "trpc.contentstack.reward.redeem" -) -export const getRedeemFailCounter = meter.createCounter( - "trpc.contentstack.reward.redeem-fail" -) -export const getRedeemSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.redeem-success" -) - -export const getAllCMSRewardRefsCounter = meter.createCounter( - "trpc.contentstack.reward.all" -) -export const getAllCMSRewardRefsFailCounter = meter.createCounter( - "trpc.contentstack.reward.all-fail" -) -export const getAllCMSRewardRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.reward.all-success" -) - export function getUniqueRewardIds(rewardIds: string[]) { const uniqueRewardIds = new Set(rewardIds) return Array.from(uniqueRewardIds) @@ -97,6 +39,14 @@ export async function getCachedAllTierRewards(token: string) { return await cacheClient.cacheOrGet( "getAllTierRewards", async () => { + const getApiRewardAllTiersCounter = createCounter( + "trpc.api", + "reward.allTiers" + ) + const metricsGetApiRewardAllTiers = getApiRewardAllTiersCounter.init() + + metricsGetApiRewardAllTiers.start() + const apiResponse = await api.get( api.endpoints.v1.Profile.Reward.allTiers, { @@ -107,26 +57,7 @@ export async function getCachedAllTierRewards(token: string) { ) if (!apiResponse.ok) { - const text = await apiResponse.text() - getAllRewardFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.rewards.allTiers error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - + metricsGetApiRewardAllTiers.httpError(apiResponse) throw apiResponse } @@ -135,20 +66,14 @@ export async function getCachedAllTierRewards(token: string) { validateApiAllTiersSchema.safeParse(data) if (!validatedApiAllTierRewards.success) { - getAllRewardFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(validatedApiAllTierRewards.error), - }) - console.error(validatedApiAllTierRewards.error) - console.error( - "api.rewards validation error", - JSON.stringify({ - error: validatedApiAllTierRewards.error, - }) + metricsGetApiRewardAllTiers.validationError( + validatedApiAllTierRewards.error ) throw validatedApiAllTierRewards.error } + metricsGetApiRewardAllTiers.success() + return validatedApiAllTierRewards.data }, "1h" @@ -164,13 +89,15 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) { generateLoyaltyConfigTag(lang, "reward", id) ) - getAllCMSRewardRefsCounter.add(1, { lang, rewardIds }) - console.info( - "contentstack.reward.refs start", - JSON.stringify({ - query: { lang, rewardIds }, - }) + const getContentstackRewardAllRefsCounter = createCounter( + "trpc.contentstack", + "reward.all.refs" ) + const metricsGetContentstackRewardAllRefs = + getContentstackRewardAllRefsCounter.init({ lang, rewardIds }) + + metricsGetContentstackRewardAllRefs.start() + const refsResponse = await request( GetRewardsRef, { @@ -182,50 +109,30 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) { ttl: "max", } ) + if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getAllCMSRewardRefsFailCounter.add(1, { - lang, - rewardIds, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.reward.refs not found error", - JSON.stringify({ - query: { lang, rewardIds }, - error: { code: notFoundError.code }, - }) - ) + metricsGetContentstackRewardAllRefs.noDataError() throw notFoundError } const validatedRefsData = rewardRefsSchema.safeParse(refsResponse) if (!validatedRefsData.success) { - getAllCMSRewardRefsFailCounter.add(1, { - lang, - rewardIds, - error_type: "validation_error", - error: JSON.stringify(validatedRefsData.error), - }) - console.error( - "contentstack.reward.refs validation error", - JSON.stringify({ - query: { lang, rewardIds }, - error: validatedRefsData.error, - }) - ) + metricsGetContentstackRewardAllRefs.validationError(validatedRefsData.error) return null } - getAllCMSRewardRefsSuccessCounter.add(1, { lang, rewardIds }) - console.info( - "contentstack.startPage.refs success", - JSON.stringify({ - query: { lang, rewardIds }, - }) + metricsGetContentstackRewardAllRefs.success() + + const getContentstackRewardAllCounter = createCounter( + "trpc.contentstack", + "reward.all" ) + const metricsGetContentstackRewardAll = getContentstackRewardAllCounter.init({ + lang, + rewardIds, + }) const cmsRewardsResponse = await request( GetRewards, @@ -240,22 +147,8 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) { ) if (!cmsRewardsResponse.data) { - getAllRewardFailCounter.add(1, { - lang, - error_type: "validation_error", - error: JSON.stringify(cmsRewardsResponse.data), - }) const notFoundError = notFound(cmsRewardsResponse) - console.error( - "contentstack.rewards not found error", - JSON.stringify({ - query: { - locale: lang, - rewardIds, - }, - error: { code: notFoundError.code }, - }) - ) + metricsGetContentstackRewardAll.noDataError() throw notFoundError } @@ -263,22 +156,11 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) { validateCmsRewardsSchema.safeParse(cmsRewardsResponse) if (!validatedCmsRewards.success) { - getAllRewardFailCounter.add(1, { - locale: lang, - rewardIds, - error_type: "validation_error", - error: JSON.stringify(validatedCmsRewards.error), - }) - console.error(validatedCmsRewards.error) - console.error( - "contentstack.rewards validation error", - JSON.stringify({ - query: { locale: lang, rewardIds }, - error: validatedCmsRewards.error, - }) - ) + metricsGetContentstackRewardAll.validationError(validatedCmsRewards.error) return null } + metricsGetContentstackRewardAll.success() + return validatedCmsRewards.data } diff --git a/apps/scandic-web/server/routers/contentstack/startPage/query.ts b/apps/scandic-web/server/routers/contentstack/startPage/query.ts index 727ef50dd..dd9dbb3c6 100644 --- a/apps/scandic-web/server/routers/contentstack/startPage/query.ts +++ b/apps/scandic-web/server/routers/contentstack/startPage/query.ts @@ -4,6 +4,7 @@ import { } from "@/lib/graphql/Query/StartPage/StartPage.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { @@ -13,14 +14,6 @@ import { } from "@/utils/generateTag" import { startPageRefsSchema, startPageSchema } from "./output" -import { - getStartPageCounter, - getStartPageFailCounter, - getStartPageRefsCounter, - getStartPageRefsFailCounter, - getStartPageRefsSuccessCounter, - getStartPageSuccessCounter, -} from "./telemetry" import { getConnections } from "./utils" import { @@ -36,13 +29,14 @@ export const startPageQueryRouter = router({ get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { const { lang, uid } = ctx - getStartPageRefsCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.startPage.refs start", - JSON.stringify({ - query: { lang, uid }, - }) + const getStartPageRefsCounter = createCounter( + "trpc.contentstack", + "startPage.get.refs" ) + const metricsGetStartPageRefs = getStartPageRefsCounter.init({ lang, uid }) + + metricsGetStartPageRefs.start() + const refsResponse = await request( GetStartPageRefs, { @@ -56,56 +50,26 @@ export const startPageQueryRouter = router({ ) if (!refsResponse.data) { const notFoundError = notFound(refsResponse) - getStartPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.startPage.refs not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetStartPageRefs.noDataError() throw notFoundError } const validatedRefsData = startPageRefsSchema.safeParse(refsResponse.data) if (!validatedRefsData.success) { - getStartPageRefsFailCounter.add(1, { - lang, - uid, - error_type: "validation_error", - error: JSON.stringify(validatedRefsData.error), - }) - console.error( - "contentstack.startPage.refs validation error", - JSON.stringify({ - query: { lang, uid }, - error: validatedRefsData.error, - }) - ) + metricsGetStartPageRefs.validationError(validatedRefsData.error) return null } - getStartPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.startPage.refs success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + metricsGetStartPageRefs.success() - getStartPageCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.startPage start", - JSON.stringify({ - query: { lang, uid }, - }) + const getStartPageCounter = createCounter( + "trpc.contentstack", + "startPage.get" ) + const metricsGetStartPage = getStartPageCounter.init({ lang, uid }) + + metricsGetStartPage.start() const connections = getConnections(validatedRefsData.data) @@ -113,6 +77,7 @@ export const startPageQueryRouter = router({ generateTagsFromSystem(lang, connections), generateTag(lang, validatedRefsData.data.start_page.system.uid), ].flat() + const response = await request( GetStartPage, { @@ -127,48 +92,18 @@ export const startPageQueryRouter = router({ if (!response.data) { const notFoundError = notFound(response) - getStartPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "not_found", - error: JSON.stringify({ code: notFoundError.code }), - }) - console.error( - "contentstack.startPage not found error", - JSON.stringify({ - query: { lang, uid }, - error: { code: notFoundError.code }, - }) - ) + metricsGetStartPage.noDataError() throw notFoundError } const startPage = startPageSchema.safeParse(response.data) if (!startPage.success) { - getStartPageFailCounter.add(1, { - lang, - uid: `${uid}`, - error_type: "validation_error", - error: JSON.stringify(startPage.error), - }) - console.error( - "contentstack.startPage validation error", - JSON.stringify({ - query: { lang, uid }, - error: startPage.error, - }) - ) + metricsGetStartPage.validationError(startPage.error) return null } - getStartPageSuccessCounter.add(1, { lang, uid: `${uid}` }) - console.info( - "contentstack.startPage success", - JSON.stringify({ - query: { lang, uid }, - }) - ) + metricsGetStartPage.success() const system = startPage.data.start_page.system const tracking: TrackingSDKPageData = { diff --git a/apps/scandic-web/server/routers/contentstack/startPage/telemetry.ts b/apps/scandic-web/server/routers/contentstack/startPage/telemetry.ts deleted file mode 100644 index e5296f3e1..000000000 --- a/apps/scandic-web/server/routers/contentstack/startPage/telemetry.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.contentstack.startPage") - -export const getStartPageRefsCounter = meter.createCounter( - "trpc.contentstack.startPage.get" -) -export const getStartPageRefsFailCounter = meter.createCounter( - "trpc.contentstack.startPage.get-fail" -) -export const getStartPageRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.startPage.get-success" -) - -export const getStartPageCounter = meter.createCounter( - "trpc.contentstack.startPage.get" -) -export const getStartPageSuccessCounter = meter.createCounter( - "trpc.contentstack.startPage.get-success" -) -export const getStartPageFailCounter = meter.createCounter( - "trpc.contentstack.startPage.get-fail" -) diff --git a/apps/scandic-web/server/routers/hotels/metrics.ts b/apps/scandic-web/server/routers/hotels/metrics.ts deleted file mode 100644 index 690746226..000000000 --- a/apps/scandic-web/server/routers/hotels/metrics.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { metrics as opentelemetryMetrics } from "@opentelemetry/api" - -const meter = opentelemetryMetrics.getMeter("trpc.hotels") -export const metrics = { - additionalData: { - counter: meter.createCounter("trpc.hotels.additionalData"), - fail: meter.createCounter("trpc.hotels.additionalData-fail"), - success: meter.createCounter("trpc.hotels.additionalData-success"), - }, - breakfastPackage: { - counter: meter.createCounter("trpc.package.breakfast"), - fail: meter.createCounter("trpc.package.breakfast-fail"), - success: meter.createCounter("trpc.package.breakfast-success"), - }, - ancillaryPackage: { - counter: meter.createCounter("trpc.package.ancillary"), - fail: meter.createCounter("trpc.package.ancillary-fail"), - success: meter.createCounter("trpc.package.ancillary-success"), - }, - hotel: { - counter: meter.createCounter("trpc.hotel.get"), - fail: meter.createCounter("trpc.hotel.get-fail"), - success: meter.createCounter("trpc.hotel.get-success"), - }, - hotels: { - counter: meter.createCounter("trpc.hotel.hotels.get"), - fail: meter.createCounter("trpc.hotel.hotels.get-fail"), - success: meter.createCounter("trpc.hotel.hotels.get-success"), - }, - hotelIds: { - counter: meter.createCounter("trpc.hotel.hotel-ids.get"), - fail: meter.createCounter("trpc.hotel.hotel-ids.get-fail"), - success: meter.createCounter("trpc.hotel.hotel-ids.get-success"), - }, - hotelsAvailability: { - counter: meter.createCounter("trpc.hotel.availability.hotels"), - fail: meter.createCounter("trpc.hotel.availability.hotels-fail"), - success: meter.createCounter("trpc.hotel.availability.hotels-success"), - }, - hotelsAvailabilityBookingCode: { - counter: meter.createCounter("trpc.hotel.availability.hotels-booking-code"), - fail: meter.createCounter( - "trpc.hotel.availability.hotels-booking-code-fail" - ), - success: meter.createCounter( - "trpc.hotel.availability.hotels-booking-code-success" - ), - }, - hotelsByHotelIdAvailability: { - counter: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id"), - fail: meter.createCounter( - "trpc.hotel.availability.hotels-by-hotel-id-fail" - ), - success: meter.createCounter( - "trpc.hotel.availability.hotels-by-hotel-id-success" - ), - }, - meetingRooms: { - counter: meter.createCounter("trpc.hotels.meetingRooms"), - fail: meter.createCounter("trpc.hotels.meetingRooms-fail"), - success: meter.createCounter("trpc.hotels.meetingRooms-success"), - }, - nearbyHotelIds: { - counter: meter.createCounter("trpc.hotel.nearby-hotel-ids.get"), - fail: meter.createCounter("trpc.hotel.nearby-hotel-ids.get-fail"), - success: meter.createCounter("trpc.hotel.nearby-hotel-ids.get-success"), - }, - packages: { - counter: meter.createCounter("trpc.hotel.packages.get"), - fail: meter.createCounter("trpc.hotel.packages.get-fail"), - success: meter.createCounter("trpc.hotel.packages.get-success"), - }, - roomsAvailability: { - counter: meter.createCounter("trpc.hotel.roomsAvailability.rooms"), - fail: meter.createCounter("trpc.hotel.roomsAvailability.rooms-fail"), - success: meter.createCounter("trpc.hotel.roomsAvailability.rooms-success"), - }, - selectedRoomAvailability: { - counter: meter.createCounter("trpc.hotel.availability.room"), - fail: meter.createCounter("trpc.hotel.availability.room-fail"), - success: meter.createCounter("trpc.hotel.availability.room-success"), - }, - roomFeatures: { - counter: meter.createCounter("trpc.availability.roomfeature"), - fail: meter.createCounter("trpc.availability.roomfeature-fail"), - success: meter.createCounter("trpc.availability.roomfeature-success"), - }, -} diff --git a/apps/scandic-web/server/routers/hotels/query.ts b/apps/scandic-web/server/routers/hotels/query.ts index e4d181bb4..5090d83c4 100644 --- a/apps/scandic-web/server/routers/hotels/query.ts +++ b/apps/scandic-web/server/routers/hotels/query.ts @@ -5,6 +5,8 @@ import * as api from "@/lib/api" import { dt } from "@/lib/dt" import { badRequestError, unauthorizedError } from "@/server/errors/trpc" import { getCityPageUrls } from "@/server/routers/contentstack/destinationCityPage/utils" +import { getVerifiedUser } from "@/server/routers/user/utils" +import { createCounter } from "@/server/telemetry" import { contentStackBaseWithServiceProcedure, publicProcedure, @@ -17,7 +19,6 @@ import { toApiLang } from "@/server/utils" import { getCacheClient } from "@/services/dataCache" import { getHotelPageUrls } from "../contentstack/hotelPage/utils" -import { getVerifiedUser } from "../user/query" import { additionalDataSchema } from "./schemas/hotel/include/additionalData" import { meetingRoomsSchema } from "./schemas/meetingRoom" import { @@ -42,17 +43,11 @@ import { selectRateRoomAvailabilityInputSchema, selectRateRoomsAvailabilityInputSchema, } from "./input" -import { metrics } from "./metrics" import { ancillaryPackagesSchema, breakfastPackagesSchema, getNearbyHotelIdsSchema, } from "./output" -import { - locationsUrlsCounter, - locationsUrlsFailCounter, - locationsUrlsSuccessCounter, -} from "./telemetry" import { getBedTypes, getCitiesByCountry, @@ -431,36 +426,24 @@ export const hotelQueryRouter = router({ const { lang } = ctx const apiLang = toApiLang(lang) - metrics.hotelsAvailabilityBookingCode.counter.add(1, { - ...input, - }) const bookingCodeAvailabilityResponse = await getHotelsAvailabilityByCity(input, apiLang, ctx.serviceToken) - // If API or network failed with no response - if (!bookingCodeAvailabilityResponse) { - metrics.hotelsAvailabilityBookingCode.fail.add(1, { - ...input, - error_type: "unknown", - }) - return null - } - // Get regular availability of hotels which don't have availability with booking code. - const unavailableHotelIds = - bookingCodeAvailabilityResponse?.availability - .filter((hotel) => { - return hotel.status === "NotAvailable" - }) - .flatMap((hotel) => { - return hotel.hotelId - }) + const unavailableHotelIds = bookingCodeAvailabilityResponse.availability + .filter((hotel) => { + return hotel.status === "NotAvailable" + }) + .flatMap((hotel) => { + return hotel.hotelId + }) // All hotels have availability with booking code no need to fetch regular prices. // return response as is without any filtering as below. if (!unavailableHotelIds || !unavailableHotelIds.length) { return bookingCodeAvailabilityResponse } + const unavailableHotelsInput = { ...input, bookingCode: "", @@ -472,11 +455,6 @@ export const hotelQueryRouter = router({ ctx.serviceToken ) - metrics.hotelsAvailabilityBookingCode.success.add(1, { - ...input, - }) - console.info("api.hotels.hotelsAvailabilityBookingCode success") - // No regular rates available due to network or API failure (no need to filter & merge). if (!unavailableHotels) { return bookingCodeAvailabilityResponse @@ -537,19 +515,16 @@ export const hotelQueryRouter = router({ const language = ctx.lang let hotelsToFetch: string[] = [] - metrics.hotels.counter.add(1, { - input: JSON.stringify(input), + const getHotelsByCSFilterCounter = createCounter( + "trpc.hotel.hotels", + "byCSFilter" + ) + const metricsGetHotelsByCSFilter = getHotelsByCSFilterCounter.init({ + input, language, }) - console.info( - "api.hotel.hotels start", - JSON.stringify({ - query: { - ...input, - language, - }, - }) - ) + + metricsGetHotelsByCSFilter.start() if (hotelsToInclude.length) { hotelsToFetch = hotelsToInclude @@ -571,20 +546,13 @@ export const hotelQueryRouter = router({ .find((loc) => loc.cityIdentifier === locationFilter.city)?.id if (!cityId) { - metrics.hotels.fail.add(1, { - input: JSON.stringify(input), - language, - error_type: "not_found", - error: `CityId not found for cityIdentifier: ${locationFilter.city}`, - }) - - console.error( - "api.hotel.hotels not found error", - JSON.stringify({ - query: { ...input, language }, - error: `CityId not found for cityIdentifier: ${locationFilter.city}`, - }) + metricsGetHotelsByCSFilter.dataError( + `CityId not found for cityIdentifier: ${locationFilter.city}`, + { + cityIdentifier: locationFilter.city, + } ) + return [] } @@ -594,20 +562,13 @@ export const hotelQueryRouter = router({ }) if (!hotelIds?.length) { - metrics.hotels.fail.add(1, { - cityId, - language, - error_type: "not_found", - error: `No hotelIds found for cityId: ${cityId}`, - }) - - console.error( - "api.hotel.hotels not found error", - JSON.stringify({ - query: { cityId, language }, - error: `No hotelIds found for cityId: ${cityId}`, - }) + metricsGetHotelsByCSFilter.dataError( + `No hotelIds found for cityId: ${cityId}`, + { + cityId, + } ) + return [] } @@ -623,20 +584,13 @@ export const hotelQueryRouter = router({ }) if (!hotelIds?.length) { - metrics.hotels.fail.add(1, { - country: locationFilter.country, - language, - error_type: "not_found", - error: `No hotelIds found for country: ${locationFilter.country}`, - }) - - console.error( - "api.hotel.hotels not found error", - JSON.stringify({ - query: { country: locationFilter.country, language }, - error: `No hotelIds found for cityId: ${locationFilter.country}`, - }) + metricsGetHotelsByCSFilter.dataError( + `No hotelIds found for country: ${locationFilter.country}`, + { + country: locationFilter.country, + } ) + return [] } @@ -648,20 +602,11 @@ export const hotelQueryRouter = router({ } if (!hotelsToFetch.length) { - metrics.hotels.fail.add(1, { - input: JSON.stringify(input), - language, - error_type: "not_found", - error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`, - }) - - console.error( - "api.hotel.hotels not found error", - JSON.stringify({ - query: JSON.stringify(input), - error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`, - }) + metricsGetHotelsByCSFilter.dataError( + `Couldn't find any hotels for given input: ${JSON.stringify(input)}`, + input ) + return [] } const hotelPages = await getHotelPageUrls(language) @@ -684,20 +629,7 @@ export const hotelQueryRouter = router({ }) ) - metrics.hotels.success.add(1, { - input: JSON.stringify(input), - language, - }) - - console.info( - "api.hotels success", - JSON.stringify({ - query: { - input: JSON.stringify(input), - language, - }, - }) - ) + metricsGetHotelsByCSFilter.success() return hotels.filter((hotel): hotel is HotelDataWithUrl => !!hotel) }), @@ -769,13 +701,17 @@ export const hotelQueryRouter = router({ return cacheClient.cacheOrGet( `${apiLang}:nearbyHotels:${hotelId}`, async () => { - metrics.nearbyHotelIds.counter.add(1, { + const nearbyHotelsCounter = createCounter( + "trpc.hotel", + "nearbyHotelIds" + ) + const metricsNearbyHotels = nearbyHotelsCounter.init({ + params, hotelId, }) - console.info( - "api.hotels.nearbyHotelIds start", - JSON.stringify({ query: { hotelId, params } }) - ) + + metricsNearbyHotels.start() + const apiResponse = await api.get( api.endpoints.v1.Hotel.Hotels.nearbyHotels(hotelId), { @@ -786,55 +722,18 @@ export const hotelQueryRouter = router({ params ) if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.nearbyHotelIds.fail.add(1, { - hotelId, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.hotels.nearbyHotelIds error", - JSON.stringify({ - query: { hotelId, params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsNearbyHotels.httpError(apiResponse) return null } + const apiJson = await apiResponse.json() const validateHotelData = getNearbyHotelIdsSchema.safeParse(apiJson) if (!validateHotelData.success) { - metrics.nearbyHotelIds.fail.add(1, { - hotelId, - error_type: "validation_error", - error: JSON.stringify(validateHotelData.error), - }) - console.error( - "api.hotels.nearbyHotelIds validation error", - JSON.stringify({ - query: { hotelId, params }, - error: validateHotelData.error, - }) - ) + metricsNearbyHotels.validationError(validateHotelData.error) throw badRequestError() } - metrics.nearbyHotelIds.success.add(1, { - hotelId, - }) - console.info( - "api.hotels.nearbyHotelIds success", - JSON.stringify({ - query: { hotelId, params }, - }) - ) + + metricsNearbyHotels.success() return validateHotelData.data.map((id: string) => parseInt(id, 10)) }, @@ -884,16 +783,17 @@ export const hotelQueryRouter = router({ urls: publicProcedure .input(getLocationsUrlsInput) .query(async ({ input }) => { - const procedureName = "hotels.locations.urls" - const { lang } = input - locationsUrlsCounter.add(1, { lang }) - - console.info( - `${procedureName}: start`, - JSON.stringify({ query: { lang } }) + const locationsUrlsCounter = createCounter( + "trpc.hotel.locations", + "urls" ) + const metricsLocationsUrls = locationsUrlsCounter.init({ + lang, + }) + + metricsLocationsUrls.start() const [hotelPageUrlsResult, cityPageUrlsResult] = await Promise.allSettled([ @@ -905,32 +805,15 @@ export const hotelQueryRouter = router({ hotelPageUrlsResult.status === "rejected" || cityPageUrlsResult.status === "rejected" ) { - locationsUrlsFailCounter.add(1, { - lang, - error_type: "no_data", - response: JSON.stringify({ - hotelPageUrlsResult, - cityPageUrlsResult, - }), - }) - - console.error(`${procedureName}: no data`, { - variables: { lang }, - error_type: "no_data", - response: { - hotelPageUrlsResult, - cityPageUrlsResult, - }, + metricsLocationsUrls.dataError(`Failed to get data for page URLs`, { + hotelPageUrlsResult, + cityPageUrlsResult, }) return null } - locationsUrlsSuccessCounter.add(1, { lang }) - - console.info(`${procedureName}: success`, { - variables: { lang }, - }) + metricsLocationsUrls.success() return { hotels: hotelPageUrlsResult.value, @@ -992,12 +875,12 @@ export const hotelQueryRouter = router({ hotelId, language, } - const metricsData = { ...params, hotelId: input.hotelId } - metrics.meetingRooms.counter.add(1, metricsData) - console.info( - "api.hotels.meetingRooms start", - JSON.stringify({ query: { hotelId, params } }) - ) + const meetingRoomsCounter = createCounter("trpc.hotel", "meetingRooms") + const metricsMeetingRooms = meetingRoomsCounter.init({ + params, + }) + + metricsMeetingRooms.start() const cacheClient = await getCacheClient() return cacheClient.cacheOrGet( @@ -1014,28 +897,7 @@ export const hotelQueryRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.meetingRooms.fail.add(1, { - ...metricsData, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.hotels.meetingRooms error", - JSON.stringify({ - query: { params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - + await metricsMeetingRooms.httpError(apiResponse) throw new Error("Failed to fetch meeting rooms") } @@ -1043,22 +905,10 @@ export const hotelQueryRouter = router({ const validatedMeetingRooms = meetingRoomsSchema.safeParse(apiJson) if (!validatedMeetingRooms.success) { - console.error( - "api.hotels.meetingRooms validation error", - JSON.stringify({ - query: { params }, - error: validatedMeetingRooms.error, - }) - ) + metricsMeetingRooms.validationError(validatedMeetingRooms.error) throw badRequestError() } - metrics.meetingRooms.success.add(1, { - hotelId, - }) - console.info( - "api.hotels.meetingRooms success", - JSON.stringify({ query: { params } }) - ) + metricsMeetingRooms.success() return validatedMeetingRooms.data.data }, @@ -1074,12 +924,16 @@ export const hotelQueryRouter = router({ hotelId, language, } - const metricsData = { ...params, hotelId: input.hotelId } - metrics.additionalData.counter.add(1, metricsData) - console.info( - "api.hotels.additionalData start", - JSON.stringify({ query: { hotelId, params } }) + + const additionalDataCounter = createCounter( + "trpc.hotel", + "additionalData" ) + const metricsAdditionalData = additionalDataCounter.init({ + params, + }) + + metricsAdditionalData.start() const cacheClient = await getCacheClient() return cacheClient.cacheOrGet( @@ -1096,28 +950,7 @@ export const hotelQueryRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.additionalData.fail.add(1, { - ...metricsData, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.hotels.additionalData error", - JSON.stringify({ - query: { params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - + await metricsAdditionalData.httpError(apiResponse) throw new Error("Unable to fetch additional data for hotel") } @@ -1126,22 +959,11 @@ export const hotelQueryRouter = router({ additionalDataSchema.safeParse(apiJson) if (!validatedAdditionalData.success) { - console.error( - "api.hotels.additionalData validation error", - JSON.stringify({ - query: { params }, - error: validatedAdditionalData.error, - }) - ) + metricsAdditionalData.validationError(validatedAdditionalData.error) throw badRequestError() } - metrics.additionalData.success.add(1, { - hotelId, - }) - console.info( - "api.hotels.additionalData success", - JSON.stringify({ query: { params } }) - ) + + metricsAdditionalData.success() return validatedAdditionalData.data }, @@ -1167,12 +989,16 @@ export const hotelQueryRouter = router({ language: apiLang, } - const metricsData = { ...params, hotelId: input.hotelId } - metrics.breakfastPackage.counter.add(1, metricsData) - console.info( - "api.package.breakfast start", - JSON.stringify({ query: metricsData }) + const breakfastCounter = createCounter( + "trpc.hotel.packages", + "breakfast" ) + const metricsBreakfast = breakfastCounter.init({ + params, + hotelId: input.hotelId, + }) + + metricsBreakfast.start() const cacheClient = await getCacheClient() const breakfastPackages = await cacheClient.cacheOrGet( @@ -1189,57 +1015,17 @@ export const hotelQueryRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.breakfastPackage.fail.add(1, { - ...metricsData, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.package.breakfast error", - JSON.stringify({ - query: metricsData, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsBreakfast.httpError(apiResponse) throw new Error("Unable to fetch breakfast packages") } const apiJson = await apiResponse.json() const breakfastPackages = breakfastPackagesSchema.safeParse(apiJson) if (!breakfastPackages.success) { - metrics.breakfastPackage.fail.add(1, { - ...metricsData, - error_type: "validation_error", - error: JSON.stringify(breakfastPackages.error), - }) - console.error( - "api.package.breakfast validation error", - JSON.stringify({ - query: metricsData, - error: breakfastPackages.error, - }) - ) - + metricsBreakfast.validationError(breakfastPackages.error) throw new Error("Unable to parse breakfast packages") } - metrics.breakfastPackage.success.add(1, metricsData) - console.info( - "api.package.breakfast success", - JSON.stringify({ - query: metricsData, - }) - ) - return breakfastPackages.data }, "1h" @@ -1265,6 +1051,8 @@ export const hotelQueryRouter = router({ // } // } + metricsBreakfast.success() + return breakfastPackages.filter( (pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST ) @@ -1281,16 +1069,21 @@ export const hotelQueryRouter = router({ language: apiLang, } + const ancillaryCounter = createCounter( + "trpc.hotel.packages", + "ancillary" + ) + const metricsAncillary = ancillaryCounter.init({ + params, + hotelId: input.hotelId, + }) + + metricsAncillary.start() + const cacheClient = await getCacheClient() - return await cacheClient.cacheOrGet( + const result = await cacheClient.cacheOrGet( `${apiLang}:hotel:${input.hotelId}:ancillaries:startDate:${params.StartDate}:endDate:${params.EndDate}`, async () => { - const metricsData = { ...params, hotelId: input.hotelId } - metrics.ancillaryPackage.counter.add(1, metricsData) - console.info( - "api.package.ancillary start", - JSON.stringify({ query: metricsData }) - ) const apiResponse = await api.get( api.endpoints.v1.Package.Ancillary.hotel(input.hotelId), { @@ -1302,59 +1095,25 @@ export const hotelQueryRouter = router({ ) if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.ancillaryPackage.fail.add(1, { - ...metricsData, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.package.ancillary start error", - JSON.stringify({ - query: metricsData, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsAncillary.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const ancillaryPackages = ancillaryPackagesSchema.safeParse(apiJson) if (!ancillaryPackages.success) { - metrics.ancillaryPackage.fail.add(1, { - ...metricsData, - error_type: "validation_error", - error: JSON.stringify(ancillaryPackages.error), - }) - console.error( - "api.package.ancillary validation error", - JSON.stringify({ - query: metricsData, - error: ancillaryPackages.error, - }) - ) + metricsAncillary.validationError(ancillaryPackages.error) return null } - metrics.ancillaryPackage.success.add(1, metricsData) - console.info( - "api.package.ancillary success", - JSON.stringify({ - query: metricsData, - }) - ) return ancillaryPackages.data }, "1h" ) + + metricsAncillary.success() + + return result }), }), }) diff --git a/apps/scandic-web/server/routers/hotels/telemetry.ts b/apps/scandic-web/server/routers/hotels/telemetry.ts deleted file mode 100644 index 91e4d72eb..000000000 --- a/apps/scandic-web/server/routers/hotels/telemetry.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.hotels") -export const getHotelCounter = meter.createCounter("trpc.hotel.get") -export const getHotelSuccessCounter = meter.createCounter( - "trpc.hotel.get-success" -) -export const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail") - -export const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get") -export const getPackagesSuccessCounter = meter.createCounter( - "trpc.hotel.packages.get-success" -) -export const getPackagesFailCounter = meter.createCounter( - "trpc.hotel.packages.get-fail" -) - -export const hotelsAvailabilityCounter = meter.createCounter( - "trpc.hotel.availability.hotels" -) -export const hotelsAvailabilitySuccessCounter = meter.createCounter( - "trpc.hotel.availability.hotels-success" -) -export const hotelsAvailabilityFailCounter = meter.createCounter( - "trpc.hotel.availability.hotels-fail" -) - -export const hotelsByHotelIdAvailabilityCounter = meter.createCounter( - "trpc.hotel.availability.hotels-by-hotel-id" -) -export const hotelsByHotelIdAvailabilitySuccessCounter = meter.createCounter( - "trpc.hotel.availability.hotels-by-hotel-id-success" -) -export const hotelsByHotelIdAvailabilityFailCounter = meter.createCounter( - "trpc.hotel.availability.hotels-by-hotel-id-fail" -) - -export const selectedRoomAvailabilityCounter = meter.createCounter( - "trpc.hotel.availability.room" -) -export const selectedRoomAvailabilitySuccessCounter = meter.createCounter( - "trpc.hotel.availability.room-success" -) -export const selectedRoomAvailabilityFailCounter = meter.createCounter( - "trpc.hotel.availability.room-fail" -) - -export const breakfastPackagesCounter = meter.createCounter( - "trpc.package.breakfast" -) -export const breakfastPackagesSuccessCounter = meter.createCounter( - "trpc.package.breakfast-success" -) -export const breakfastPackagesFailCounter = meter.createCounter( - "trpc.package.breakfast-fail" -) - -export const getHotelsCounter = meter.createCounter("trpc.hotel.hotels.get") -export const getHotelsSuccessCounter = meter.createCounter( - "trpc.hotel.hotels.get-success" -) -export const getHotelsFailCounter = meter.createCounter( - "trpc.hotel.hotels.get-fail" -) - -export const getHotelIdsCounter = meter.createCounter( - "trpc.hotel.hotel-ids.get" -) -export const getHotelIdsSuccessCounter = meter.createCounter( - "trpc.hotel.hotel-ids.get-success" -) -export const getHotelIdsFailCounter = meter.createCounter( - "trpc.hotel.hotel-ids.get-fail" -) - -export const nearbyHotelIdsCounter = meter.createCounter( - "trpc.hotel.nearby-hotel-ids.get" -) -export const nearbyHotelIdsSuccessCounter = meter.createCounter( - "trpc.hotel.nearby-hotel-ids.get-success" -) -export const nearbyHotelIdsFailCounter = meter.createCounter( - "trpc.hotel.nearby-hotel-ids.get-fail" -) - -export const meetingRoomsCounter = meter.createCounter( - "trpc.hotels.meetingRooms" -) -export const meetingRoomsSuccessCounter = meter.createCounter( - "trpc.hotels.meetingRooms-success" -) -export const meetingRoomsFailCounter = meter.createCounter( - "trpc.hotels.meetingRooms-fail" -) - -export const additionalDataCounter = meter.createCounter( - "trpc.hotels.additionalData" -) -export const additionalDataSuccessCounter = meter.createCounter( - "trpc.hotels.additionalData-success" -) -export const additionalDataFailCounter = meter.createCounter( - "trpc.hotels.additionalData-fail" -) - -export const locationsUrlsCounter = meter.createCounter( - "trpc.hotels.locations.urls" -) -export const locationsUrlsSuccessCounter = meter.createCounter( - "trpc.hotels.locations.urls-success" -) -export const locationsUrlsFailCounter = meter.createCounter( - "trpc.hotels.locations.urls-fail" -) diff --git a/apps/scandic-web/server/routers/hotels/utils.ts b/apps/scandic-web/server/routers/hotels/utils.ts index bd3bac658..96cab68ee 100644 --- a/apps/scandic-web/server/routers/hotels/utils.ts +++ b/apps/scandic-web/server/routers/hotels/utils.ts @@ -6,6 +6,7 @@ import { Lang } from "@/constants/languages" import { env } from "@/env/server" import * as api from "@/lib/api" import { badRequestError } from "@/server/errors/trpc" +import { createCounter } from "@/server/telemetry" import { toApiLang } from "@/server/utils" import { generateChildrenString } from "@/components/HotelReservation/utils" @@ -13,8 +14,7 @@ import { getCacheClient } from "@/services/dataCache" import { cache } from "@/utils/cache" import { getHotelPageUrls } from "../contentstack/hotelPage/utils" -import { type RoomFeaturesInput, roomPackagesInputSchema } from "./input" -import { metrics } from "./metrics" +import { type RoomFeaturesInput } from "./input" import { type Cities, citiesByCountrySchema, @@ -38,12 +38,12 @@ import type { DestinationPagesHotelData, Room as RoomCategory, } from "@/types/hotel" -import type { PackagesInput } from "@/types/requests/packages" +import type { PackagesOutput } from "@/types/requests/packages" import type { HotelsAvailabilityInputSchema, HotelsByHotelIdsAvailabilityInputSchema, RoomsAvailabilityInputRoom, - RoomsAvailabilityInputSchema, + RoomsAvailabilityOutputSchema, } from "@/types/trpc/routers/hotel/availability" import type { HotelInput } from "@/types/trpc/routers/hotel/hotel" import type { @@ -332,18 +332,23 @@ export async function getHotelIdsByCityId({ cityId: string serviceToken: string }) { + const getHotelIdsByCityIdCounter = createCounter( + "hotel", + "getHotelIdsByCityId" + ) + const metricsGetHotelIdsByCityId = getHotelIdsByCityIdCounter.init({ + cityId, + }) + + metricsGetHotelIdsByCityId.start() + const cacheClient = await getCacheClient() - return await cacheClient.cacheOrGet( + const result = await cacheClient.cacheOrGet( `${cityId}:hotelsByCityId`, async () => { const searchParams = new URLSearchParams({ city: cityId, }) - metrics.hotelIds.counter.add(1, { params: searchParams.toString() }) - console.info( - "api.hotel.hotel-ids start", - JSON.stringify({ params: searchParams.toString() }) - ) const apiResponse = await api.get( api.endpoints.v1.Hotel.hotels, @@ -356,59 +361,25 @@ export async function getHotelIdsByCityId({ ) if (!apiResponse.ok) { - const responseMessage = await apiResponse.text() - metrics.hotelIds.fail.add(1, { - params: searchParams.toString(), - error_type: "http_error", - error: responseMessage, - }) - console.error( - "api.hotel.hotel-ids fetch error", - JSON.stringify({ - params: searchParams.toString(), - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text: responseMessage, - }, - }) - ) - + await metricsGetHotelIdsByCityId.httpError(apiResponse) throw new Error("Unable to fetch hotelIds by cityId") } const apiJson = await apiResponse.json() const validatedHotelIds = getHotelIdsSchema.safeParse(apiJson) if (!validatedHotelIds.success) { - metrics.hotelIds.fail.add(1, { - params: searchParams.toString(), - error_type: "validation_error", - error: JSON.stringify(validatedHotelIds.error), - }) - console.error( - "api.hotel.hotel-ids validation error", - JSON.stringify({ - params: searchParams.toString(), - error: validatedHotelIds.error, - }) - ) - + metricsGetHotelIdsByCityId.validationError(validatedHotelIds.error) throw new Error("Unable to parse data for hotelIds by cityId") } - metrics.hotelIds.success.add(1, { cityId }) - console.info( - "api.hotel.hotel-ids success", - JSON.stringify({ - params: searchParams.toString(), - response: validatedHotelIds.data, - }) - ) - return validatedHotelIds.data }, env.CACHE_TIME_HOTELS ) + + metricsGetHotelIdsByCityId.success() + + return result } export async function getHotelIdsByCountry({ @@ -418,17 +389,22 @@ export async function getHotelIdsByCountry({ country: string serviceToken: string }) { + const getHotelIdsByCountryCounter = createCounter( + "hotel", + "getHotelIdsByCountry" + ) + + const metricsGetHotelIdsByCountry = getHotelIdsByCountryCounter.init({ + country, + }) + + metricsGetHotelIdsByCountry.start() + const cacheClient = await getCacheClient() - return await cacheClient.cacheOrGet( + const result = await cacheClient.cacheOrGet( `${country}:hotelsByCountry`, async () => { - metrics.hotelIds.counter.add(1, { country }) - console.info( - "api.hotel.hotel-ids start", - JSON.stringify({ query: { country } }) - ) - const hotelIdsParams = new URLSearchParams({ country, }) @@ -444,55 +420,25 @@ export async function getHotelIdsByCountry({ ) if (!apiResponse.ok) { - const responseMessage = await apiResponse.text() - metrics.hotelIds.fail.add(1, { - country, - error_type: "http_error", - error: responseMessage, - }) - console.error( - "api.hotel.hotel-ids fetch error", - JSON.stringify({ - query: { country }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text: responseMessage, - }, - }) - ) - + await metricsGetHotelIdsByCountry.httpError(apiResponse) throw new Error("Unable to fetch hotelIds by country") } const apiJson = await apiResponse.json() const validatedHotelIds = getHotelIdsSchema.safeParse(apiJson) if (!validatedHotelIds.success) { - metrics.hotelIds.fail.add(1, { - country, - error_type: "validation_error", - error: JSON.stringify(validatedHotelIds.error), - }) - console.error( - "api.hotel.hotel-ids validation error", - JSON.stringify({ - query: { country }, - error: validatedHotelIds.error, - }) - ) + metricsGetHotelIdsByCountry.validationError(validatedHotelIds.error) throw new Error("Unable to parse hotelIds by country") } - metrics.hotelIds.success.add(1, { country }) - console.info( - "api.hotel.hotel-ids success", - JSON.stringify({ query: { country } }) - ) - return validatedHotelIds.data }, env.CACHE_TIME_HOTELS ) + + metricsGetHotelIdsByCountry.success() + + return result } export async function getHotelIdsByCityIdentifier( @@ -631,128 +577,84 @@ function findProduct(product: Products, rateDefinition: RateDefinition) { export const getHotel = cache( async (input: HotelInput, serviceToken: string) => { - const callable = async function ( - hotelId: HotelInput["hotelId"], - language: HotelInput["language"], - isCardOnlyPayment?: HotelInput["isCardOnlyPayment"] - ) { - /** - * Since API expects the params appended and not just - * a comma separated string we need to initialize the - * SearchParams with a sequence of pairs - * (include=City&include=NearbyHotels&include=Restaurants etc.) - **/ - const params = new URLSearchParams([ - ["include", "AdditionalData"], - ["include", "City"], - ["include", "NearbyHotels"], - ["include", "Restaurants"], - ["include", "RoomCategories"], - ["language", toApiLang(language)], - ]) - metrics.hotel.counter.add(1, { - hotelId, - language, - }) - console.info( - "api.hotels.hotelData start", - JSON.stringify({ query: { hotelId, params: params.toString() } }) - ) + const { hotelId, language, isCardOnlyPayment } = input - const apiResponse = await api.get( - api.endpoints.v1.Hotel.Hotels.hotel(hotelId), - { - headers: { - Authorization: `Bearer ${serviceToken}`, - }, - }, - params - ) + const getHotelCounter = createCounter("hotel", "getHotel") + const metricsGetHotel = getHotelCounter.init({ + hotelId, + language, + isCardOnlyPayment, + }) - if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.hotel.fail.add(1, { - hotelId, - language, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.hotels.hotelData error", - JSON.stringify({ - query: { hotelId, params: params.toString() }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - return null - } - - const apiJson = await apiResponse.json() - const validateHotelData = hotelSchema.safeParse(apiJson) - - if (!validateHotelData.success) { - metrics.hotel.fail.add(1, { - hotelId, - language, - error_type: "validation_error", - error: JSON.stringify(validateHotelData.error), - }) - - console.error( - "api.hotels.hotelData validation error", - JSON.stringify({ - query: { hotelId, params: params.toString() }, - error: validateHotelData.error, - }) - ) - throw badRequestError() - } - - metrics.hotel.success.add(1, { - hotelId, - language, - }) - console.info( - "api.hotels.hotelData success", - JSON.stringify({ - query: { hotelId, params: params.toString() }, - }) - ) - const hotelData = validateHotelData.data - - if (isCardOnlyPayment) { - hotelData.hotel.merchantInformationData.alternatePaymentOptions = [] - } - - const gallery = hotelData.additionalData?.gallery - if (gallery) { - const smallerImages = gallery.smallerImages - const hotelGalleryImages = - hotelData.hotel.hotelType === HotelTypeEnum.Signature - ? smallerImages.slice(0, 10) - : smallerImages.slice(0, 6) - hotelData.hotel.galleryImages = hotelGalleryImages - } - - return hotelData - } + metricsGetHotel.start() const cacheClient = await getCacheClient() - return await cacheClient.cacheOrGet( + + const result = await cacheClient.cacheOrGet( `${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`, async () => { - return callable(input.hotelId, input.language, input.isCardOnlyPayment) + /** + * Since API expects the params appended and not just + * a comma separated string we need to initialize the + * SearchParams with a sequence of pairs + * (include=City&include=NearbyHotels&include=Restaurants etc.) + **/ + const params = new URLSearchParams([ + ["include", "AdditionalData"], + ["include", "City"], + ["include", "NearbyHotels"], + ["include", "Restaurants"], + ["include", "RoomCategories"], + ["language", toApiLang(language)], + ]) + + const apiResponse = await api.get( + api.endpoints.v1.Hotel.Hotels.hotel(hotelId), + { + headers: { + Authorization: `Bearer ${serviceToken}`, + }, + }, + params + ) + + if (!apiResponse.ok) { + await metricsGetHotel.httpError(apiResponse) + return null + } + + const apiJson = await apiResponse.json() + const validateHotelData = hotelSchema.safeParse(apiJson) + + if (!validateHotelData.success) { + metricsGetHotel.validationError(validateHotelData.error) + throw badRequestError() + } + + const hotelData = validateHotelData.data + + if (isCardOnlyPayment) { + hotelData.hotel.merchantInformationData.alternatePaymentOptions = [] + } + + const gallery = hotelData.additionalData?.gallery + if (gallery) { + const smallerImages = gallery.smallerImages + const hotelGalleryImages = + hotelData.hotel.hotelType === HotelTypeEnum.Signature + ? smallerImages.slice(0, 10) + : smallerImages.slice(0, 6) + hotelData.hotel.galleryImages = hotelGalleryImages + } + + return hotelData }, env.CACHE_TIME_HOTELS ) + + metricsGetHotel.success() + + return result } ) @@ -781,19 +683,25 @@ export async function getHotelsAvailabilityByCity( ...(redemption ? { isRedemption: "true" } : {}), language: apiLang, } - metrics.hotelsAvailability.counter.add(1, { - cityId, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - redemption, - }) - console.info( - "api.hotels.hotelsAvailability start", - JSON.stringify({ query: { cityId, params } }) + + const getHotelsAvailabilityByCityCounter = createCounter( + "hotel", + "getHotelsAvailabilityByCity" ) + const metricsGetHotelsAvailabilityByCity = + getHotelsAvailabilityByCityCounter.init({ + apiLang, + cityId, + roomStayStartDate, + roomStayEndDate, + adults, + children, + bookingCode, + redemption, + }) + + metricsGetHotelsAvailabilityByCity.start() + const apiResponse = await api.get( api.endpoints.v1.Availability.city(cityId), { @@ -804,74 +712,19 @@ export async function getHotelsAvailabilityByCity( params ) if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.hotelsAvailability.fail.add(1, { - cityId, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.hotels.hotelsAvailability error", - JSON.stringify({ - query: { cityId, params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - + await metricsGetHotelsAvailabilityByCity.httpError(apiResponse) throw new Error("Failed to fetch hotels availability by city") } const apiJson = await apiResponse.json() const validateAvailabilityData = hotelsAvailabilitySchema.safeParse(apiJson) if (!validateAvailabilityData.success) { - metrics.hotelsAvailability.fail.add(1, { - cityId, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - redemption, - error_type: "validation_error", - error: JSON.stringify(validateAvailabilityData.error), - }) - console.error( - "api.hotels.hotelsAvailability validation error", - JSON.stringify({ - query: { cityId, params }, - error: validateAvailabilityData.error, - }) + metricsGetHotelsAvailabilityByCity.validationError( + validateAvailabilityData.error ) throw badRequestError() } - metrics.hotelsAvailability.success.add(1, { - cityId, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - redemption, - }) - console.info( - "api.hotels.hotelsAvailability success", - JSON.stringify({ - query: { cityId, params: params }, - }) - ) + if (redemption) { validateAvailabilityData.data.data.forEach((data) => { data.attributes.productType?.redemptions?.forEach((r) => { @@ -880,11 +733,15 @@ export async function getHotelsAvailabilityByCity( }) } - return { + const result = { availability: validateAvailabilityData.data.data.flatMap( (hotels) => hotels.attributes ), } + + metricsGetHotelsAvailabilityByCity.success() + + return result } export async function getHotelsAvailabilityByHotelIds( @@ -910,8 +767,26 @@ export async function getHotelsAvailabilityByHotelIds( ["language", apiLang], ]) + const getHotelsAvailabilityByHotelIdsCounter = createCounter( + "hotel", + "getHotelsAvailabilityByHotelIds" + ) + const metricsGetHotelsAvailabilityByHotelIds = + getHotelsAvailabilityByHotelIdsCounter.init({ + apiLang, + hotelIds, + roomStayStartDate, + roomStayEndDate, + adults, + children, + bookingCode, + }) + + metricsGetHotelsAvailabilityByHotelIds.start() + const cacheClient = await getCacheClient() - return cacheClient.cacheOrGet( + + const result = cacheClient.cacheOrGet( `${apiLang}:hotels:availability:${hotelIds.join(",")}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`, async () => { /** @@ -924,18 +799,7 @@ export async function getHotelsAvailabilityByHotelIds( hotelIds.forEach((hotelId) => params.append("hotelIds", hotelId.toString()) ) - metrics.hotelsByHotelIdAvailability.counter.add(1, { - hotelIds, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - }) - console.info( - "api.hotels.hotelsByHotelIdAvailability start", - JSON.stringify({ query: { params } }) - ) + const apiResponse = await api.get( api.endpoints.v1.Availability.hotels(), { @@ -946,72 +810,20 @@ export async function getHotelsAvailabilityByHotelIds( params ) if (!apiResponse.ok) { - const text = await apiResponse.text() - metrics.hotelsByHotelIdAvailability.fail.add(1, { - hotelIds, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.hotels.hotelsByHotelIdAvailability error", - JSON.stringify({ - query: { params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - + await metricsGetHotelsAvailabilityByHotelIds.httpError(apiResponse) throw new Error("Failed to fetch hotels availability by hotelIds") } + const apiJson = await apiResponse.json() const validateAvailabilityData = hotelsAvailabilitySchema.safeParse(apiJson) if (!validateAvailabilityData.success) { - metrics.hotelsByHotelIdAvailability.fail.add(1, { - hotelIds, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - error_type: "validation_error", - error: JSON.stringify(validateAvailabilityData.error), - }) - console.error( - "api.hotels.hotelsByHotelIdAvailability validation error", - JSON.stringify({ - query: { params }, - error: validateAvailabilityData.error, - }) + metricsGetHotelsAvailabilityByHotelIds.validationError( + validateAvailabilityData.error ) throw badRequestError() } - metrics.hotelsByHotelIdAvailability.success.add(1, { - hotelIds, - roomStayStartDate, - roomStayEndDate, - adults, - children, - bookingCode, - }) - console.info( - "api.hotels.hotelsByHotelIdAvailability success", - JSON.stringify({ - query: { params }, - }) - ) + return { availability: validateAvailabilityData.data.data.flatMap( (hotels) => hotels.attributes @@ -1020,6 +832,10 @@ export async function getHotelsAvailabilityByHotelIds( }, env.CACHE_TIME_CITY_SEARCH ) + + metricsGetHotelsAvailabilityByHotelIds.success() + + return result } async function getRoomFeaturesInventory( @@ -1034,23 +850,32 @@ async function getRoomFeaturesInventory( roomFeatureCodes, startDate, } = input + + const params = { + adults, + hotelId, + roomFeatureCode: roomFeatureCodes, + roomStayEndDate: endDate, + roomStayStartDate: startDate, + ...(childrenInRoom?.length && { + children: generateChildrenString(childrenInRoom), + }), + } + + const getRoomFeaturesInventoryCounter = createCounter( + "hotel", + "getRoomFeaturesInventory" + ) + const metricsGetRoomFeaturesInventory = + getRoomFeaturesInventoryCounter.init(params) + + metricsGetRoomFeaturesInventory.start() + const cacheClient = await getCacheClient() - return cacheClient.cacheOrGet( + + const result = cacheClient.cacheOrGet( stringify(input), async function () { - const params = { - adults, - hotelId, - roomFeatureCode: roomFeatureCodes, - roomStayEndDate: endDate, - roomStayStartDate: startDate, - ...(childrenInRoom?.length && { - children: generateChildrenString(childrenInRoom), - }), - } - - metrics.roomFeatures.counter.add(1, params) - const apiResponse = await api.get( api.endpoints.v1.Availability.roomFeatures(hotelId), { @@ -1062,67 +887,45 @@ async function getRoomFeaturesInventory( ) if (!apiResponse.ok) { - const text = apiResponse.text() - console.error( - "api.availability.roomfeature error", - JSON.stringify({ - query: { hotelId, params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - metrics.roomFeatures.fail.add(1, params) + await metricsGetRoomFeaturesInventory.httpError(apiResponse) return null } const data = await apiResponse.json() const validatedRoomFeaturesData = roomFeaturesSchema.safeParse(data) if (!validatedRoomFeaturesData.success) { - console.error( - "api.availability.roomfeature error", - JSON.stringify({ - query: { hotelId, params }, - error: validatedRoomFeaturesData.error, - }) + metricsGetRoomFeaturesInventory.validationError( + validatedRoomFeaturesData.error ) return null } - metrics.roomFeatures.success.add(1, params) - return validatedRoomFeaturesData.data }, "5m" ) + + metricsGetRoomFeaturesInventory.success() + + return result } -export async function getPackages( - rawInput: PackagesInput, - serviceToken: string -) { - const parsedInput = roomPackagesInputSchema.safeParse(rawInput) - if (!parsedInput.success) { - console.info(`Failed to parse input for Get Packages`) - console.error(parsedInput.error) - return null - } - const input = parsedInput.data +export async function getPackages(input: PackagesOutput, serviceToken: string) { + const { adults, children, endDate, hotelId, lang, packageCodes, startDate } = + input + + const getPackagesCounter = createCounter("hotel", "getPackages") + const metricsGetPackages = getPackagesCounter.init({ + input, + }) + + metricsGetPackages.start() + const cacheClient = await getCacheClient() - return cacheClient.cacheOrGet( + + const result = cacheClient.cacheOrGet( stringify(input), async function () { - const { - adults, - children, - endDate, - hotelId, - lang, - packageCodes, - startDate, - } = input const apiLang = toApiLang(lang) const searchParams = new URLSearchParams({ @@ -1137,16 +940,6 @@ export async function getPackages( searchParams.append("packageCodes", code) }) - const params = searchParams.toString() - - metrics.packages.counter.add(1, { - hotelId, - }) - console.info( - "api.hotels.packages start", - JSON.stringify({ query: { hotelId, params } }) - ) - const apiResponse = await api.get( api.endpoints.v1.Package.Packages.hotel(hotelId), { @@ -1158,56 +951,29 @@ export async function getPackages( ) if (!apiResponse.ok) { - metrics.packages.fail.add(1, { - hotelId, - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - }), - }) - console.error( - "api.hotels.packages error", - JSON.stringify({ query: { hotelId, params } }) - ) + await metricsGetPackages.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const validatedPackagesData = packagesSchema.safeParse(apiJson) if (!validatedPackagesData.success) { - metrics.packages.fail.add(1, { - hotelId, - error_type: "validation_error", - error: JSON.stringify(validatedPackagesData.error), - }) - - console.error( - "api.hotels.packages validation error", - JSON.stringify({ - query: { hotelId, params }, - error: validatedPackagesData.error, - }) - ) + metricsGetPackages.validationError(validatedPackagesData.error) return null } - metrics.packages.success.add(1, { - hotelId, - }) - console.info( - "api.hotels.packages success", - JSON.stringify({ query: { hotelId, params: params } }) - ) - return validatedPackagesData.data }, "3h" ) + + metricsGetPackages.success() + + return result } export async function getRoomsAvailability( - input: RoomsAvailabilityInputSchema, + input: RoomsAvailabilityOutputSchema, token: string, serviceToken: string, userPoints: number | undefined @@ -1219,27 +985,18 @@ export async function getRoomsAvailability( const redemption = searchType === REDEMPTION - const apiLang = toApiLang(lang) - - const kids = rooms - .map((r) => r.childrenInRoom) - .filter(Boolean) - .map((kid) => JSON.stringify(kid)) - const metricsData = { - adultsCount: rooms.map((r) => r.adults), - bookingCode, - childArray: kids.length ? kids : undefined, - hotelId, - roomStayEndDate: toDate, - roomStayStartDate: fromDate, - } - - metrics.roomsAvailability.counter.add(1, metricsData) - - console.info( - "api.hotels.roomsAvailability start", - JSON.stringify({ query: { hotelId, params: metricsData } }) + const getRoomsAvailabilityCounter = createCounter( + "hotel", + "getRoomsAvailability" ) + const metricsGetRoomsAvailability = getRoomsAvailabilityCounter.init({ + input, + redemption, + }) + + metricsGetRoomsAvailability.start() + + const apiLang = toApiLang(lang) const baseCacheKey = { bookingCode, @@ -1257,7 +1014,7 @@ export async function getRoomsAvailability( ...baseCacheKey, room, } - return cacheClient.cacheOrGet( + const result = cacheClient.cacheOrGet( stringify(cacheKey), async function () { { @@ -1287,9 +1044,8 @@ export async function getRoomsAvailability( ) if (!apiResponse.ok) { + await metricsGetRoomsAvailability.httpError(apiResponse) const text = await apiResponse.text() - metrics.roomsAvailability.fail.add(1, metricsData) - console.error("Failed API call", { params, text }) return { error: "http_error", details: text } } @@ -1297,11 +1053,10 @@ export async function getRoomsAvailability( const validateAvailabilityData = roomsAvailabilitySchema.safeParse(apiJson) if (!validateAvailabilityData.success) { - console.error("Validation error", { - params, - error: validateAvailabilityData.error, - }) - metrics.roomsAvailability.fail.add(1, metricsData) + metricsGetRoomsAvailability.validationError( + validateAvailabilityData.error + ) + return { error: "validation_error", details: validateAvailabilityData.error, @@ -1323,7 +1078,7 @@ export async function getRoomsAvailability( const roomFeatures = await getPackages( { adults: room.adults, - children: room.childrenInRoom?.length, + children: room.childrenInRoom?.length || 0, endDate: input.booking.toDate, hotelId: input.booking.hotelId, lang, @@ -1387,9 +1142,10 @@ export async function getRoomsAvailability( }, "1m" ) + + return result }) ) - metrics.roomsAvailability.success.add(1, metricsData) const data = availabilityResponses.map((availability) => { if (availability.status === "fulfilled") { @@ -1401,6 +1157,8 @@ export async function getRoomsAvailability( } }) + metricsGetRoomsAvailability.success() + return data } diff --git a/apps/scandic-web/server/routers/navigation/mypages/index.ts b/apps/scandic-web/server/routers/navigation/mypages/index.ts index 7b11787b7..1490ee7fc 100644 --- a/apps/scandic-web/server/routers/navigation/mypages/index.ts +++ b/apps/scandic-web/server/routers/navigation/mypages/index.ts @@ -2,11 +2,11 @@ import { TRPCError } from "@trpc/server" import { z } from "zod" import { Lang } from "@/constants/languages" +import { getVerifiedUser } from "@/server/routers/user/utils" import { safeProtectedProcedure } from "@/server/trpc" import { isValidSession } from "@/utils/session" -import { getVerifiedUser } from "../../user/query" import { getPrimaryLinks } from "./getPrimaryLinks" import { getSecondaryLinks } from "./getSecondaryLinks" diff --git a/apps/scandic-web/server/routers/partners/jobylon/query.ts b/apps/scandic-web/server/routers/partners/jobylon/query.ts index 44d3e23ec..3e7670d10 100644 --- a/apps/scandic-web/server/routers/partners/jobylon/query.ts +++ b/apps/scandic-web/server/routers/partners/jobylon/query.ts @@ -1,13 +1,9 @@ +import { createCounter } from "@/server/telemetry" import { publicProcedure, router } from "@/server/trpc" import { getCacheClient } from "@/services/dataCache" import { jobylonFeedSchema } from "./output" -import { - getJobylonFeedCounter, - getJobylonFeedFailCounter, - getJobylonFeedSuccessCounter, -} from "./telemetry" export const TWENTYFOUR_HOURS = 60 * 60 * 24 @@ -19,20 +15,19 @@ const feedUrl = export const jobylonQueryRouter = router({ feed: router({ get: publicProcedure.query(async function () { + const jobylonFeedGetCounter = createCounter("trpc.jobylon.feed", "get") + const metricsJobylonFeedGet = jobylonFeedGetCounter.init() + + metricsJobylonFeedGet.start() + const url = new URL(feedUrl) url.search = new URLSearchParams({ format: "json", }).toString() - const urlString = url.toString() - - getJobylonFeedCounter.add(1, { url: urlString }) - console.info( - "jobylon.feed start", - JSON.stringify({ query: { url: urlString } }) - ) const cacheClient = await getCacheClient() - return await cacheClient.cacheOrGet( + + const result = cacheClient.cacheOrGet( "jobylon:feed", async () => { const response = await fetch(url, { @@ -40,27 +35,14 @@ export const jobylonQueryRouter = router({ }) if (!response.ok) { + await metricsJobylonFeedGet.httpError(response) const text = await response.text() - const error = { - status: response.status, - statusText: response.statusText, - text, - } - getJobylonFeedFailCounter.add(1, { - url: urlString, - error_type: "http_error", - error: JSON.stringify(error), - }) - console.error( - "jobylon.feed error", - JSON.stringify({ - query: { url: urlString }, - error, - }) - ) - throw new Error( - `Failed to fetch Jobylon feed: ${JSON.stringify(error)}` + `Failed to fetch Jobylon feed: ${JSON.stringify({ + status: response.status, + statusText: response.statusText, + text, + })}` ) } @@ -68,37 +50,20 @@ export const jobylonQueryRouter = router({ const validatedResponse = jobylonFeedSchema.safeParse(responseJson) if (!validatedResponse.success) { - getJobylonFeedFailCounter.add(1, { - urlString, - error_type: "validation_error", - error: JSON.stringify(validatedResponse.error), - }) - - const errorData = JSON.stringify({ - query: { url: urlString }, - error: validatedResponse.error, - }) - - console.error("jobylon.feed error", errorData) + metricsJobylonFeedGet.validationError(validatedResponse.error) throw new Error( - `Failed to parse Jobylon feed: ${JSON.stringify(errorData)}` + `Failed to parse Jobylon feed: ${JSON.stringify(validatedResponse.error)}` ) } - getJobylonFeedSuccessCounter.add(1, { - url: urlString, - }) - console.info( - "jobylon.feed success", - JSON.stringify({ - query: { url: urlString }, - }) - ) - return validatedResponse.data }, "1d" ) + + metricsJobylonFeedGet.success() + + return result }), }), }) diff --git a/apps/scandic-web/server/routers/partners/jobylon/telemetry.ts b/apps/scandic-web/server/routers/partners/jobylon/telemetry.ts deleted file mode 100644 index 4434effb3..000000000 --- a/apps/scandic-web/server/routers/partners/jobylon/telemetry.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { metrics } from "@opentelemetry/api" - -const meter = metrics.getMeter("trpc.booking") -export const getJobylonFeedCounter = meter.createCounter("trpc.jobylon-feed") -export const getJobylonFeedSuccessCounter = meter.createCounter( - "trpc.jobylon-feed-success" -) -export const getJobylonFeedFailCounter = meter.createCounter( - "trpc.jobylon-feed-fail" -) diff --git a/apps/scandic-web/server/routers/user/input.ts b/apps/scandic-web/server/routers/user/input.ts index 6ce1e97c4..f3f81adbd 100644 --- a/apps/scandic-web/server/routers/user/input.ts +++ b/apps/scandic-web/server/routers/user/input.ts @@ -11,11 +11,7 @@ export const staysInput = z .number() .optional() .transform((num) => (num ? String(num) : undefined)), - limit: z - .number() - .min(0) - .default(6) - .transform((num) => String(num)), + limit: z.number().min(0).default(6), lang: z.nativeEnum(Lang).optional(), }) .default({}) diff --git a/apps/scandic-web/server/routers/user/mutation.ts b/apps/scandic-web/server/routers/user/mutation.ts index 689c43a26..44d942ba0 100644 --- a/apps/scandic-web/server/routers/user/mutation.ts +++ b/apps/scandic-web/server/routers/user/mutation.ts @@ -1,5 +1,3 @@ -import { metrics } from "@opentelemetry/api" - import { signupVerify } from "@/constants/routes/signup" import { env } from "@/env/server" import * as api from "@/lib/api" @@ -8,6 +6,7 @@ import { initiateSaveCardSchema, subscriberIdSchema, } from "@/server/routers/user/output" +import { createCounter } from "@/server/telemetry" import { protectedProcedure, router, serviceProcedure } from "@/server/trpc" import { @@ -17,20 +16,6 @@ import { signupInput, } from "./input" -const meter = metrics.getMeter("trpc.user") -const generatePreferencesLinkCounter = meter.createCounter( - "trpc.user.generatePreferencesLink" -) -const generatePreferencesLinkSuccessCounter = meter.createCounter( - "trpc.user.generatePreferencesLink-success" -) -const generatePreferencesLinkFailCounter = meter.createCounter( - "trpc.user.generatePreferencesLink-fail" -) -const signupCounter = meter.createCounter("trpc.user.signup") -const signupSuccessCounter = meter.createCounter("trpc.user.signup-success") -const signupFailCounter = meter.createCounter("trpc.user.signup-fail") - export const userMutationRouter = router({ creditCard: router({ add: protectedProcedure.input(addCreditCardInput).mutation(async function ({ @@ -159,7 +144,15 @@ export const userMutationRouter = router({ generatePreferencesLink: protectedProcedure.mutation(async function ({ ctx, }) { - generatePreferencesLinkCounter.add(1) + const generatePreferencesLinkCounter = createCounter( + "trpc.user", + "generatePreferencesLink" + ) + + const metricsGeneratePreferencesLink = generatePreferencesLinkCounter.init() + + metricsGeneratePreferencesLink.start() + const apiResponse = await api.get(api.endpoints.v1.Profile.subscriberId, { headers: { Authorization: `Bearer ${ctx.session.token.access_token}`, @@ -167,25 +160,7 @@ export const userMutationRouter = router({ }) if (!apiResponse.ok) { - const text = await apiResponse.text() - generatePreferencesLinkFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.user.subscriberId error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsGeneratePreferencesLink.httpError(apiResponse) return null } @@ -194,31 +169,23 @@ export const userMutationRouter = router({ const validatedData = subscriberIdSchema.safeParse(data) if (!validatedData.success) { - generatePreferencesLinkSuccessCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(validatedData.error), - }) - console.error( - "api.user.generatePreferencesLink validation error", - JSON.stringify({ - error: validatedData.error, - }) - ) - console.error(validatedData.error.format()) - + metricsGeneratePreferencesLink.validationError(validatedData.error) return null } + const preferencesLink = new URL(env.SALESFORCE_PREFERENCE_BASE_URL) preferencesLink.searchParams.set("subKey", validatedData.data.subscriberId) - generatePreferencesLinkSuccessCounter.add(1) + metricsGeneratePreferencesLink.success() + return preferencesLink.toString() }), signup: serviceProcedure.input(signupInput).mutation(async function ({ ctx, input, }) { - signupCounter.add(1) + const signupCounter = createCounter("trpc.user", "signup") + const metricsSignup = signupCounter.init() const apiResponse = await api.post(api.endpoints.v1.Profile.profile, { body: input, @@ -228,29 +195,13 @@ export const userMutationRouter = router({ }) if (!apiResponse.ok) { + await metricsSignup.httpError(apiResponse) const text = await apiResponse.text() - signupFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - error: text, - }), - }) - console.error( - "api.user.signup api error", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - error: text, - }, - }) - ) throw serverErrorByStatus(apiResponse.status, text) } - signupSuccessCounter.add(1) - console.info("api.user.signup success") + + metricsSignup.success() + return { success: true, redirectUrl: signupVerify[input.language], diff --git a/apps/scandic-web/server/routers/user/query.ts b/apps/scandic-web/server/routers/user/query.ts index 87f8eea95..269809cab 100644 --- a/apps/scandic-web/server/routers/user/query.ts +++ b/apps/scandic-web/server/routers/user/query.ts @@ -1,8 +1,5 @@ -import { metrics } from "@opentelemetry/api" - -import { countries } from "@/constants/countries" import * as api from "@/lib/api" -import { dt } from "@/lib/dt" +import { createCounter } from "@/server/telemetry" import { languageProtectedProcedure, protectedProcedure, @@ -10,8 +7,6 @@ import { safeProtectedProcedure, } from "@/server/trpc" -import { cache } from "@/utils/cache" -import * as maskValue from "@/utils/maskValue" import { isValidSession } from "@/utils/session" import { getFriendsMembership, getMembershipCards } from "@/utils/user" @@ -20,277 +15,21 @@ import { getSavedPaymentCardsInput, staysInput, } from "./input" +import { getFriendTransactionsSchema } from "./output" import { - creditCardsSchema, - getFriendTransactionsSchema, - getStaysSchema, - getUserSchema, -} from "./output" -import { updateStaysBookingUrl } from "./utils" - -import type { Session } from "next-auth" + getCreditCards, + getPreviousStays, + getUpcomingStays, + getVerifiedUser, + parsedUser, + updateStaysBookingUrl, +} from "./utils" import type { LoginType, TrackingSDKUserData, } from "@/types/components/tracking" import { Transactions } from "@/types/enums/transactions" -import type { User } from "@/types/user" - -// OpenTelemetry metrics: User -const meter = metrics.getMeter("trpc.user") -const getVerifiedUserCounter = meter.createCounter("trpc.user.get") -const getVerifiedUserSuccessCounter = meter.createCounter( - "trpc.user.get-success" -) -const getVerifiedUserFailCounter = meter.createCounter("trpc.user.get-fail") - -// OpenTelemetry metrics: Stays -const getPreviousStaysCounter = meter.createCounter("trpc.user.stays.previous") -const getPreviousStaysSuccessCounter = meter.createCounter( - "trpc.user.stays.previous-success" -) -const getPreviousStaysFailCounter = meter.createCounter( - "trpc.user.stays.previous-fail" -) -const getUpcomingStaysCounter = meter.createCounter("trpc.user.stays.upcoming") -const getUpcomingStaysSuccessCounter = meter.createCounter( - "trpc.user.stays.upcoming-success" -) -const getUpcomingStaysFailCounter = meter.createCounter( - "trpc.user.stays.upcoming-fail" -) - -// OpenTelemetry metrics: Transactions -const getFriendTransactionsCounter = meter.createCounter( - "trpc.user.transactions.friendTransactions" -) -const getFriendTransactionsSuccessCounter = meter.createCounter( - "trpc.user.transactions.friendTransactions-success" -) -const getFriendTransactionsFailCounter = meter.createCounter( - "trpc.user.transactions.friendTransactions-fail" -) - -// OpenTelemetry metrics: Credit Cards -const getCreditCardsCounter = meter.createCounter("trpc.user.creditCards") -const getCreditCardsSuccessCounter = meter.createCounter( - "trpc.user.creditCards-success" -) -const getCreditCardsFailCounter = meter.createCounter( - "trpc.user.creditCards-fail" -) - -export const getVerifiedUser = cache( - async ({ - session, - includeExtendedPartnerData, - }: { - session: Session - includeExtendedPartnerData?: boolean - }) => { - const now = Date.now() - if (session.token.expires_at && session.token.expires_at < now) { - return { error: true, cause: "token_expired" } as const - } - getVerifiedUserCounter.add(1) - console.info("api.user.profile getVerifiedUser start", JSON.stringify({})) - const apiResponse = await api.get( - api.endpoints.v2.Profile.profile, - { - headers: { - Authorization: `Bearer ${session.token.access_token}`, - }, - }, - includeExtendedPartnerData - ? { includes: "extendedPartnerInformation" } - : {} - ) - - if (!apiResponse.ok) { - const text = await apiResponse.text() - getVerifiedUserFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.user.profile getVerifiedUser error", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - if (apiResponse.status === 401) { - return { error: true, cause: "unauthorized" } as const - } else if (apiResponse.status === 403) { - return { error: true, cause: "forbidden" } as const - } else if (apiResponse.status === 404) { - return { error: true, cause: "notfound" } as const - } - return { - error: true, - cause: "unknown", - status: apiResponse.status, - } as const - } - - const apiJson = await apiResponse.json() - if (!apiJson.data?.attributes) { - getVerifiedUserFailCounter.add(1, { - error_type: "data_error", - }) - console.error( - "api.user.profile getVerifiedUser data error", - JSON.stringify({ - apiResponse: apiJson, - }) - ) - return null - } - - const verifiedData = getUserSchema.safeParse(apiJson) - if (!verifiedData.success) { - getVerifiedUserFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(verifiedData.error), - }) - console.error( - "api.user.profile validation error", - JSON.stringify({ - errors: verifiedData.error, - apiResponse: apiJson, - }) - ) - return null - } - getVerifiedUserSuccessCounter.add(1) - console.info("api.user.profile getVerifiedUser success", JSON.stringify({})) - return verifiedData - } -) - -export function parsedUser(data: User, isMFA: boolean) { - const country = countries.find((c) => c.code === data.address?.countryCode) - - const user = { - address: { - city: data.address?.city, - country: country?.name ?? "", - countryCode: data.address?.countryCode, - streetAddress: data.address?.streetAddress, - zipCode: data.address?.zipCode, - }, - dateOfBirth: data.dateOfBirth, - email: data.email, - firstName: data.firstName, - language: data.language, - lastName: data.lastName, - membershipNumber: data.membershipNumber, - membership: getFriendsMembership(data.loyalty), - loyalty: data.loyalty, - name: `${data.firstName} ${data.lastName}`, - phoneNumber: data.phoneNumber, - profileId: data.profileId, - } - - if (!isMFA) { - if (user.address.city) { - user.address.city = maskValue.text(user.address.city) - } - if (user.address.streetAddress) { - user.address.streetAddress = maskValue.text(user.address.streetAddress) - } - - user.address.zipCode = data.address?.zipCode - ? maskValue.text(data.address.zipCode) - : "" - - user.dateOfBirth = maskValue.all(user.dateOfBirth) - - user.email = maskValue.email(user.email) - - user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : "" - } - - return user -} - -const getCreditCards = cache( - async ({ - session, - onlyNonExpired, - }: { - session: Session - onlyNonExpired?: boolean - }) => { - getCreditCardsCounter.add(1) - console.info("api.profile.creditCards start", JSON.stringify({})) - const apiResponse = await api.get(api.endpoints.v1.Profile.creditCards, { - headers: { - Authorization: `Bearer ${session.token.access_token}`, - }, - }) - - if (!apiResponse.ok) { - const text = await apiResponse.text() - getCreditCardsFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.profile.creditCards error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - return null - } - - const apiJson = await apiResponse.json() - const verifiedData = creditCardsSchema.safeParse(apiJson) - if (!verifiedData.success) { - getCreditCardsFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(verifiedData.error), - }) - console.error( - "api.profile.creditCards validation error ", - JSON.stringify({ error: verifiedData.error }) - ) - return null - } - getCreditCardsSuccessCounter.add(1) - console.info("api.profile.creditCards success", JSON.stringify({})) - - return verifiedData.data.data.filter((card) => { - if (onlyNonExpired) { - try { - const expirationDate = dt(card.expirationDate).startOf("day") - const currentDate = dt().startOf("day") - return expirationDate > currentDate - } catch (_) { - return false - } - } - return true - }) - } -) export const userQueryRouter = router({ get: protectedProcedure @@ -385,11 +124,20 @@ export const userQueryRouter = router({ return membershipLevel }), userTrackingInfo: safeProtectedProcedure.query(async function ({ ctx }) { + const userTrackingInfoCounter = createCounter("user", "userTrackingInfo") + const metricsUserTrackingInfo = userTrackingInfoCounter.init() + + metricsUserTrackingInfo.start() + const notLoggedInUserTrackingData: TrackingSDKUserData = { loginStatus: "Non-logged in", } if (!isValidSession(ctx.session)) { + metricsUserTrackingInfo.success({ + reason: "invalid session", + data: notLoggedInUserTrackingData, + }) return notLoggedInUserTrackingData } @@ -397,62 +145,24 @@ export const userQueryRouter = router({ const verifiedUserData = await getVerifiedUser({ session: ctx.session }) if (!verifiedUserData || "error" in verifiedUserData) { - return notLoggedInUserTrackingData - } - - const params = new URLSearchParams() - params.set("limit", "1") - getPreviousStaysCounter.add(1, { query: JSON.stringify({ params }) }) - console.info( - "api.booking.stays.past start", - JSON.stringify({ query: { params } }) - ) - const previousStaysResponse = await api.get( - api.endpoints.v1.Booking.Stays.past, - { - headers: { - Authorization: `Bearer ${ctx.session.token.access_token}`, - }, - }, - params - ) - - if (!previousStaysResponse.ok) { - getPreviousStaysFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: previousStaysResponse.status, - statusText: previousStaysResponse.statusText, - }), + metricsUserTrackingInfo.success({ + reason: "invalid user data", + data: notLoggedInUserTrackingData, }) - console.error( - "api.booking.stays.past error", - JSON.stringify({ - error: { - status: previousStaysResponse.status, - statusText: previousStaysResponse.statusText, - }, - }) - ) return notLoggedInUserTrackingData } - const previousStaysApiJson = await previousStaysResponse.json() - const verifiedPreviousStaysData = - getStaysSchema.safeParse(previousStaysApiJson) - if (!verifiedPreviousStaysData.success) { - getPreviousStaysFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(verifiedPreviousStaysData.error), + const previousStaysData = await getPreviousStays( + ctx.session.token.access_token, + 1 + ) + if (!previousStaysData) { + metricsUserTrackingInfo.success({ + reason: "no previous stays data", + data: notLoggedInUserTrackingData, }) - console.error( - "api.booking.stays.past validation error, ", - JSON.stringify({ error: verifiedPreviousStaysData.error }) - ) return notLoggedInUserTrackingData } - getPreviousStaysSuccessCounter.add(1) - console.info("api.booking.stays.past success", JSON.stringify({})) const membership = getFriendsMembership(verifiedUserData.data.loyalty) @@ -462,13 +172,19 @@ export const userQueryRouter = router({ memberId: verifiedUserData.data.profileId, membershipNumber: membership?.membershipNumber, memberLevel: membership?.membershipLevel, - noOfNightsStayed: verifiedPreviousStaysData.data.links?.totalCount ?? 0, + noOfNightsStayed: previousStaysData.links?.totalCount ?? 0, totalPointsAvailableToSpend: membership?.currentPoints, loginAction: "login success", } + + metricsUserTrackingInfo.success({ + reason: "valid logged in", + data: loggedInUserTrackingData, + }) + return loggedInUserTrackingData } catch (error) { - console.error("Error in userTrackingInfo:", error) + metricsUserTrackingInfo.fail(error) return notLoggedInUserTrackingData } }), @@ -479,93 +195,31 @@ export const userQueryRouter = router({ .query(async ({ ctx, input }) => { const { limit, cursor, lang } = input const language = lang || ctx.lang - const params: Record = { limit } - if (cursor) { - params.offset = cursor - } - getPreviousStaysCounter.add(1, { query: JSON.stringify({ params }) }) - console.info( - "api.booking.stays.past start", - JSON.stringify({ query: { params } }) - ) - const apiResponse = await api.get( - api.endpoints.v1.Booking.Stays.past, - { - headers: { - Authorization: `Bearer ${ctx.session.token.access_token}`, - }, - }, - params - ) - - if (!apiResponse.ok) { - const text = await apiResponse.text() - getPreviousStaysFailCounter.add(1, { - query: JSON.stringify({ params }), - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.booking.stays.past error ", - JSON.stringify({ - query: { params }, - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - return null - } - - const apiJson = await apiResponse.json() - - const verifiedData = getStaysSchema.safeParse(apiJson) - if (!verifiedData.success) { - getPreviousStaysFailCounter.add(1, { - query: JSON.stringify({ params }), - error_type: "validation_error", - error: JSON.stringify(verifiedData.error), - }) - console.error( - "api.booking.stays.past validation error ", - JSON.stringify({ - query: { params }, - error: verifiedData.error, - }) - ) - - return null - } - getPreviousStaysSuccessCounter.add(1, { - query: JSON.stringify({ params }), - }) - console.info( - "api.booking.stays.past success", - JSON.stringify({ query: { params } }) - ) - const nextCursor = - verifiedData.data.links && - verifiedData.data.links.offset < verifiedData.data.links.totalCount - ? verifiedData.data.links.offset - : undefined - - const updatedData = await updateStaysBookingUrl( - verifiedData.data.data, + const data = await getPreviousStays( ctx.session.token.access_token, - language + limit, + cursor ) - return { - data: updatedData, - nextCursor, + if (data) { + const nextCursor = + data.links && data.links.offset < data.links.totalCount + ? data.links.offset + : undefined + + const updatedData = await updateStaysBookingUrl( + data.data, + ctx.session, + language + ) + + return { + data: updatedData, + nextCursor, + } } + return null }), upcoming: languageProtectedProcedure @@ -573,92 +227,31 @@ export const userQueryRouter = router({ .query(async ({ ctx, input }) => { const { limit, cursor, lang } = input const language = lang || ctx.lang - const params: Record = { limit } - if (cursor) { - params.offset = cursor - } - getUpcomingStaysCounter.add(1, { - query: JSON.stringify({ params }), - }) - console.info( - "api.booking.stays.future start", - JSON.stringify({ query: { params } }) - ) - const apiResponse = await api.get( - api.endpoints.v1.Booking.Stays.future, - { - headers: { - Authorization: `Bearer ${ctx.session.token.access_token}`, - }, - }, - params - ) - if (!apiResponse.ok) { - const text = await apiResponse.text() - getUpcomingStaysFailCounter.add(1, { - query: JSON.stringify({ params }), - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.booking.stays.future error ", - JSON.stringify({ - query: { params }, - error_type: "http_error", - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) - return null - } - - const apiJson = await apiResponse.json() - const verifiedData = getStaysSchema.safeParse(apiJson) - if (!verifiedData.success) { - getUpcomingStaysFailCounter.add(1, { - query: JSON.stringify({ params }), - error_type: "validation_error", - error: JSON.stringify(verifiedData.error), - }) - console.error( - "api.booking.stays.future validation error ", - JSON.stringify({ - query: { params }, - error: verifiedData.error, - }) - ) - return null - } - getUpcomingStaysSuccessCounter.add(1, { - query: JSON.stringify({ params }), - }) - console.info("api.booking.stays.future success", { - query: JSON.stringify({ params }), - }) - const nextCursor = - verifiedData.data.links && - verifiedData.data.links.offset < verifiedData.data.links.totalCount - ? verifiedData.data.links.offset - : undefined - - const updatedData = await updateStaysBookingUrl( - verifiedData.data.data, + const data = await getUpcomingStays( ctx.session.token.access_token, - language + limit, + cursor ) - return { - data: updatedData, - nextCursor, + if (data) { + const nextCursor = + data.links && data.links.offset < data.links.totalCount + ? data.links.offset + : undefined + + const updatedData = await updateStaysBookingUrl( + data.data, + ctx.session, + language + ) + + return { + data: updatedData, + nextCursor, + } } + return null }), }), transaction: router({ @@ -667,11 +260,18 @@ export const userQueryRouter = router({ .query(async ({ ctx, input }) => { const { limit, page } = input - getFriendTransactionsCounter.add(1) - console.info( - "api.transaction.friendTransactions start", - JSON.stringify({}) + const friendTransactionsCounter = createCounter( + "trpc.user.transactions", + "friendTransactions" ) + + const metricsFriendTransactions = friendTransactionsCounter.init({ + limit, + page, + }) + + metricsFriendTransactions.start() + const apiResponse = await api.get( api.endpoints.v1.Profile.Transaction.friendTransactions, { @@ -682,61 +282,20 @@ export const userQueryRouter = router({ ) if (!apiResponse.ok) { - // switch (apiResponse.status) { - // case 400: - // throw badRequestError() - // case 401: - // throw unauthorizedError() - // case 403: - // throw forbiddenError() - // default: - // throw internalServerError() - // } - const text = await apiResponse.text() - getFriendTransactionsFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify({ - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }), - }) - console.error( - "api.transaction.friendTransactions error ", - JSON.stringify({ - error: { - status: apiResponse.status, - statusText: apiResponse.statusText, - text, - }, - }) - ) + await metricsFriendTransactions.httpError(apiResponse) return null } const apiJson = await apiResponse.json() const verifiedData = getFriendTransactionsSchema.safeParse(apiJson) if (!verifiedData.success) { - getFriendTransactionsFailCounter.add(1, { - error_type: "validation_error", - error: JSON.stringify(verifiedData.error), - }) - console.error( - "api.transaction.friendTransactions validation error ", - JSON.stringify({ error: verifiedData.error }) - ) + metricsFriendTransactions.validationError(verifiedData.error) return null } - getFriendTransactionsSuccessCounter.add(1) - console.info( - "api.transaction.friendTransactions success", - JSON.stringify({}) - ) - const updatedData = await updateStaysBookingUrl( verifiedData.data.data, - ctx.session.token.access_token, + ctx.session, ctx.lang ) @@ -763,7 +322,7 @@ export const userQueryRouter = router({ const slicedData = pageData.slice(limit * (page - 1), limit * page) - return { + const result = { data: { transactions: slicedData.map(({ type, attributes }) => { return { @@ -786,6 +345,10 @@ export const userQueryRouter = router({ totalPages: Math.ceil(pageData.length / limit), }, } + + metricsFriendTransactions.success() + + return result }), }), diff --git a/apps/scandic-web/server/routers/user/utils.ts b/apps/scandic-web/server/routers/user/utils.ts index 9a0d6438c..d0eafd976 100644 --- a/apps/scandic-web/server/routers/user/utils.ts +++ b/apps/scandic-web/server/routers/user/utils.ts @@ -1,108 +1,363 @@ -import { metrics } from "@opentelemetry/api" - +import { countries } from "@/constants/countries" import { myBookingPath } from "@/constants/myBooking" import { myStay } from "@/constants/routes/myStay" import { env } from "@/env/server" import * as api from "@/lib/api" +import { dt } from "@/lib/dt" +import { encrypt } from "@/server/routers/utils/encryption" +import { createCounter } from "@/server/telemetry" +import { cache } from "@/utils/cache" +import * as maskValue from "@/utils/maskValue" +import { isValidSession } from "@/utils/session" import { getCurrentWebUrl } from "@/utils/url" +import { getFriendsMembership } from "@/utils/user" -import { encrypt } from "../utils/encryption" +import { + creditCardsSchema, + type FriendTransaction, + getStaysSchema, + getUserSchema, + type Stay, +} from "./output" +import type { Session } from "next-auth" + +import type { User } from "@/types/user" import type { Lang } from "@/constants/languages" -import type { FriendTransaction, Stay } from "./output" -const meter = metrics.getMeter("trpc.user") -const getProfileCounter = meter.createCounter("trpc.user.profile") -const getProfileSuccessCounter = meter.createCounter( - "trpc.user.profile-success" +export const getVerifiedUser = cache( + async ({ + session, + includeExtendedPartnerData, + }: { + session: Session + includeExtendedPartnerData?: boolean + }) => { + const getVerifiedUserCounter = createCounter("user", "getVerifiedUser") + const metricsGetVerifiedUser = getVerifiedUserCounter.init() + + metricsGetVerifiedUser.start() + + const now = Date.now() + if (session.token.expires_at && session.token.expires_at < now) { + metricsGetVerifiedUser.dataError(`Token expired`) + return { error: true, cause: "token_expired" } as const + } + + const apiResponse = await api.get( + api.endpoints.v2.Profile.profile, + { + headers: { + Authorization: `Bearer ${session.token.access_token}`, + }, + }, + includeExtendedPartnerData + ? { includes: "extendedPartnerInformation" } + : {} + ) + + if (!apiResponse.ok) { + await metricsGetVerifiedUser.httpError(apiResponse) + + if (apiResponse.status === 401) { + return { error: true, cause: "unauthorized" } as const + } else if (apiResponse.status === 403) { + return { error: true, cause: "forbidden" } as const + } else if (apiResponse.status === 404) { + return { error: true, cause: "notfound" } as const + } + + return { + error: true, + cause: "unknown", + status: apiResponse.status, + } as const + } + + const apiJson = await apiResponse.json() + if (!apiJson.data?.attributes) { + metricsGetVerifiedUser.dataError( + `Missing data attributes in API response`, + { + data: apiJson, + } + ) + return null + } + + const verifiedData = getUserSchema.safeParse(apiJson) + if (!verifiedData.success) { + metricsGetVerifiedUser.validationError(verifiedData.error) + return null + } + + metricsGetVerifiedUser.success() + + return verifiedData + } ) -const getProfileFailCounter = meter.createCounter("trpc.user.profile-fail") -async function updateStaysBookingUrl( - data: Stay[], - token: string, - lang: Lang -): Promise +export async function getMembershipNumber( + session: Session | null +): Promise { + if (!isValidSession(session)) return undefined -async function updateStaysBookingUrl( - data: FriendTransaction[], - token: string, - lang: Lang -): Promise + const verifiedUser = await getVerifiedUser({ session }) + if (!verifiedUser || "error" in verifiedUser) { + return undefined + } -async function updateStaysBookingUrl( - data: Stay[] | FriendTransaction[], - token: string, - lang: Lang + return verifiedUser.data.membershipNumber +} + +export async function getPreviousStays( + accessToken: string, + limit: number = 10, + cursor?: string ) { - // Temporary API call needed till we have user name in ctx session data - getProfileCounter.add(1) - console.info("api.user.profile updatebookingurl start", JSON.stringify({})) - const apiResponse = await api.get(api.endpoints.v1.Profile.profile, { - cache: "no-store", - headers: { - Authorization: `Bearer ${token}`, - }, + const getPreviousStaysCounter = createCounter("user", "getPreviousStays") + const metricsGetPreviousStays = getPreviousStaysCounter.init({ + limit, + cursor, }) + metricsGetPreviousStays.start() + + const apiResponse = await api.get( + api.endpoints.v1.Booking.Stays.past, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + { + limit, + cursor, + } + ) + if (!apiResponse.ok) { - getProfileFailCounter.add(1, { error: JSON.stringify(apiResponse) }) - console.info( - "api.user.profile updatebookingurl error", - JSON.stringify({ error: apiResponse }) - ) - return data + await metricsGetPreviousStays.httpError(apiResponse) + return null } const apiJson = await apiResponse.json() - if (!apiJson.data?.attributes) { - return data + + const verifiedData = getStaysSchema.safeParse(apiJson) + if (!verifiedData.success) { + metricsGetPreviousStays.validationError(verifiedData.error) + return null } - getProfileSuccessCounter.add(1) - console.info("api.user.profile updatebookingurl success", JSON.stringify({})) + metricsGetPreviousStays.success() - return data.map((d) => { - const originalString = - d.attributes.confirmationNumber.toString() + - "," + - apiJson.data.attributes.lastName - const encryptedBookingValue = encrypt(originalString) - - // Get base URL with fallback for ephemeral environments (like deploy previews). - const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" - - // Construct Booking URL. - const bookingUrl = env.HIDE_FOR_NEXT_RELEASE - ? new URL( - getCurrentWebUrl({ - path: myBookingPath[lang], - lang, - baseUrl, - }) - ) - : new URL(myStay[lang], baseUrl) - - // Add search parameters. - if (encryptedBookingValue) { - bookingUrl.searchParams.set("RefId", encryptedBookingValue) - } else { - bookingUrl.searchParams.set("lastName", apiJson.data.attributes.lastName) - bookingUrl.searchParams.set( - "bookingId", - d.attributes.confirmationNumber.toString() - ) - } - - return { - ...d, - attributes: { - ...d.attributes, - bookingUrl: bookingUrl.toString(), - }, - } - }) + return verifiedData.data } -export { updateStaysBookingUrl } +export async function getUpcomingStays( + accessToken: string, + limit: number = 10, + cursor?: string +) { + const getUpcomingStaysCounter = createCounter("user", "getUpcomingStays") + const metricsGetUpcomingStays = getUpcomingStaysCounter.init({ + limit, + cursor, + }) + + metricsGetUpcomingStays.start() + + const apiResponse = await api.get( + api.endpoints.v1.Booking.Stays.future, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + { + limit, + cursor, + } + ) + + if (!apiResponse.ok) { + await metricsGetUpcomingStays.httpError(apiResponse) + return null + } + + const apiJson = await apiResponse.json() + + const verifiedData = getStaysSchema.safeParse(apiJson) + if (!verifiedData.success) { + metricsGetUpcomingStays.validationError(verifiedData.error) + return null + } + + metricsGetUpcomingStays.success() + + return verifiedData.data +} + +export function parsedUser(data: User, isMFA: boolean) { + const country = countries.find((c) => c.code === data.address?.countryCode) + + const user = { + address: { + city: data.address?.city, + country: country?.name ?? "", + countryCode: data.address?.countryCode, + streetAddress: data.address?.streetAddress, + zipCode: data.address?.zipCode, + }, + dateOfBirth: data.dateOfBirth, + email: data.email, + firstName: data.firstName, + language: data.language, + lastName: data.lastName, + membershipNumber: data.membershipNumber, + membership: getFriendsMembership(data.loyalty), + loyalty: data.loyalty, + name: `${data.firstName} ${data.lastName}`, + phoneNumber: data.phoneNumber, + profileId: data.profileId, + } + + if (!isMFA) { + if (user.address.city) { + user.address.city = maskValue.text(user.address.city) + } + if (user.address.streetAddress) { + user.address.streetAddress = maskValue.text(user.address.streetAddress) + } + + user.address.zipCode = data.address?.zipCode + ? maskValue.text(data.address.zipCode) + : "" + + user.dateOfBirth = maskValue.all(user.dateOfBirth) + + user.email = maskValue.email(user.email) + + user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : "" + } + + return user +} + +export const getCreditCards = cache( + async ({ + session, + onlyNonExpired, + }: { + session: Session + onlyNonExpired?: boolean + }) => { + const getCreditCardsCounter = createCounter("user", "getCreditCards") + const metricsGetCreditCards = getCreditCardsCounter.init({ + onlyNonExpired, + }) + + metricsGetCreditCards.start() + + const apiResponse = await api.get(api.endpoints.v1.Profile.creditCards, { + headers: { + Authorization: `Bearer ${session.token.access_token}`, + }, + }) + + if (!apiResponse.ok) { + await metricsGetCreditCards.httpError(apiResponse) + return null + } + + const apiJson = await apiResponse.json() + const verifiedData = creditCardsSchema.safeParse(apiJson) + if (!verifiedData.success) { + metricsGetCreditCards.validationError(verifiedData.error) + return null + } + + const result = verifiedData.data.data.filter((card) => { + if (onlyNonExpired) { + try { + const expirationDate = dt(card.expirationDate).startOf("day") + const currentDate = dt().startOf("day") + return expirationDate > currentDate + } catch (_) { + return false + } + } + return true + }) + + metricsGetCreditCards.success() + + return result + } +) + +export async function updateStaysBookingUrl( + data: Stay[], + session: Session, + lang: Lang +): Promise + +export async function updateStaysBookingUrl( + data: FriendTransaction[], + session: Session, + lang: Lang +): Promise + +export async function updateStaysBookingUrl( + data: Stay[] | FriendTransaction[], + session: Session, + lang: Lang +) { + const user = await getVerifiedUser({ + session, + }) + + if (user && !("error" in user)) { + return data.map((d) => { + const originalString = + d.attributes.confirmationNumber.toString() + "," + user.data.lastName + const encryptedBookingValue = encrypt(originalString) + + // Get base URL with fallback for ephemeral environments (like deploy previews). + const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" + + // Construct Booking URL. + const bookingUrl = env.HIDE_FOR_NEXT_RELEASE + ? new URL( + getCurrentWebUrl({ + path: myBookingPath[lang], + lang, + baseUrl, + }) + ) + : new URL(myStay[lang], baseUrl) + + // Add search parameters. + if (encryptedBookingValue) { + bookingUrl.searchParams.set("RefId", encryptedBookingValue) + } else { + bookingUrl.searchParams.set("lastName", user.data.lastName) + bookingUrl.searchParams.set( + "bookingId", + d.attributes.confirmationNumber.toString() + ) + } + + return { + ...d, + attributes: { + ...d.attributes, + bookingUrl: bookingUrl.toString(), + }, + } + }) + } + + return data +} diff --git a/apps/scandic-web/server/telemetry/index.test.ts b/apps/scandic-web/server/telemetry/index.test.ts new file mode 100644 index 000000000..1d1065f86 --- /dev/null +++ b/apps/scandic-web/server/telemetry/index.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, test } from "@jest/globals" + +import { sanitize } from "./" + +describe("sanitize", () => { + test("should handle valid primitive attributes", () => { + const input = { + key1: "value1", + key2: 10, + key3: true, + } + const expected = { + key1: "value1", + key2: 10, + key3: true, + } + expect(sanitize(input)).toEqual(expected) + }) + + test("should handle valid array attributes", () => { + const input = { + key1: ["value1", "value2"], + key2: [1, 2, 3], + key3: [true, false, true], + key4: [null, undefined, "a", 1, true], + } + const expected = { + "key1.0": "value1", + "key1.1": "value2", + "key2.0": 1, + "key2.1": 2, + "key2.2": 3, + "key3.0": true, + "key3.1": false, + "key3.2": true, + "key4.0": null, + "key4.1": undefined, + "key4.2": "a", + "key4.3": 1, + "key4.4": true, + } + expect(sanitize(input)).toEqual(expected) + }) + + test("should stringify non-valid attributes", () => { + const input = { + key1: new Date("2024-08-08T12:00:00Z"), + key2: { nested: "object" }, + } + const expected = { + key1: '"2024-08-08T12:00:00.000Z"', + "key2.nested": "object", + } + expect(sanitize(input)).toEqual(expected) + }) + + test("should handle nested valid attributes", () => { + const input = { + key1: "Example", + key2: 10, + nested: { + nestedKey1: "Value", + nestedKey2: { + nestedKey2Key1: true, + }, + }, + } + const expected = { + key1: "Example", + key2: 10, + "nested.nestedKey1": "Value", + "nested.nestedKey2.nestedKey2Key1": true, + } + expect(sanitize(input)).toEqual(expected) + }) + + test("should handle a mix of valid and non-valid nested attributes", () => { + const input = { + key1: "Example", + key2: 10, + nested: { + nestedKey1: "Value", + nestedKey2: { + nestedKey2Key1: true, + nestedKey2Key2: new Date("2024-08-08T12:00:00Z"), + }, + nestedKey3: { + reallyNested: "hello", + }, + }, + nonPrimitive: new Date("2024-08-08T13:00:00Z"), + } + const expected = { + key1: "Example", + key2: 10, + "nested.nestedKey1": "Value", + "nested.nestedKey2.nestedKey2Key1": true, + "nested.nestedKey2.nestedKey2Key2": '"2024-08-08T12:00:00.000Z"', + "nested.nestedKey3.reallyNested": "hello", + nonPrimitive: '"2024-08-08T13:00:00.000Z"', + } + expect(sanitize(input)).toEqual(expected) + }) + + test("should throw an error when a function is passed", () => { + const input = { + key1: () => {}, + } + expect(() => sanitize(input)).toThrowError("Cannot sanitize function") + }) + + test("should throw an error when input not an object", () => { + // @ts-expect-error: array not allowed. We do this here to make sure the + // function not only relies on TS but actively blocks arrays as input. + expect(() => sanitize(null)).toThrowError() + + // @ts-expect-error: array not allowed. We do this here to make sure the + // function not only relies on TS but actively blocks arrays as input. + expect(() => sanitize(undefined)).toThrowError() + + // @ts-expect-error: array not allowed. We do this here to make sure the + // function not only relies on TS but actively blocks arrays as input. + expect(() => sanitize("")).toThrowError() + + // @ts-expect-error: array not allowed. We do this here to make sure the + // function not only relies on TS but actively blocks arrays as input. + expect(() => sanitize([1, 2, 3])).toThrowError() + }) + + test("should handle empty input", () => { + const input = {} + const expected = {} + expect(sanitize(input)).toEqual(expected) + }) +}) diff --git a/apps/scandic-web/server/telemetry/index.ts b/apps/scandic-web/server/telemetry/index.ts new file mode 100644 index 000000000..68513b197 --- /dev/null +++ b/apps/scandic-web/server/telemetry/index.ts @@ -0,0 +1,309 @@ +// Central place for telemetry +// TODO: Replace all of this with proper tracers and events + +import { + type Attributes, + type AttributeValue, + metrics, +} from "@opentelemetry/api" +import deepmerge from "deepmerge" +import { flatten } from "flat" +import { + every, + isArray, + isBoolean, + isFunction, + isNull, + isNumber, + isObject, + isPlainObject, + isString, + isUndefined, + keys, + mapValues, +} from "lodash-es" + +import type { ZodError } from "zod" + +type AttributesInput = Record + +function isAttributesInput(value: unknown): value is AttributesInput { + return ( + isObject(value) && + !isArray(value) && + !isNull(value) && + keys(value).length > 0 && + every(keys(value), isString) + ) +} + +/** + * Checks if a given value is a valid OpenTelemetry `AttributeValue`. + * An `AttributeValue` can be a `string`, `number`, `boolean`, or a homogenous + * array containing only `null`, `undefined`, `string`, `number`, or `boolean`. + * + * @param value The value to check. + * @returns `true` if the value is a valid `AttributeValue`, `false` otherwise. + */ +export function isValidAttributeValue(value: unknown): value is AttributeValue { + if (isString(value) || isNumber(value) || isBoolean(value)) { + return true + } + if (isArray(value)) { + return every( + value, + (item) => + isNull(item) || + isUndefined(item) || + isString(item) || + isNumber(item) || + isBoolean(item) + ) + } + return false +} + +/** + * Sanitizes an input object, ensuring its values are valid OpenTelemetry + * `AttributeValue` or `JSON.stringify()` representations as a fallback. + * It recursively processes nested objects and flattens the final object to one + * level deep with dot delimited keys for nested values. + * + * @param data The input object to sanitize. + * @returns The resulting object. + * + * @example + * ```typescript + * import { sanitize } from '@/server/telemetry'; + * + * const input = { + * key1: "Example", + * key2: 10, + * nested: { + * nestedKey1: "Value", + * nestedKey2: { + * nestedKey2Key1: true, + * }, + * }, + * }; + * + * const sanitized = sanitize(input); + * console.log(sanitized); + * // { + * // key1: "Example", + * // key2: 10, + * // "nested.nestedKey1": "Value", + * // "nested.nestedKey2.nestedKey2Key1": true, + * // } + * ``` + */ +export function sanitize(data: AttributesInput): Attributes { + if (!isPlainObject(data)) { + throw new Error(`Input must be an object, got ${JSON.stringify(data)}`) + } + + return flatten( + mapValues(data, (value) => { + if (isFunction(value)) { + throw new Error("Cannot sanitize function") + } else if (isValidAttributeValue(value)) { + return value + } else if (isAttributesInput(value)) { + return sanitize(value) + } + + return JSON.stringify(value) + }) + ) +} + +/** + * Creates an object that holds three OpenTelemetry counter instruments. One + * that represents the counter itself, one for the success and one for any fail. + * The object contains an `init method that acts as a factory to create the a + * final object that holds methods to record different types of events one the + * appropriate counter. + * + * @param meterName The name of the OpenTelemetry meter to create. + * @param counterName The name of the counter instrument to create. + * @returns An object with an `init` method that returns an object + * with methods for recording counter events. + * + * @example + * + * 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. + * These attributes will be included in all recorded events. + * + * @param baseAttrs - The base attributes to associate with the counter. Defaults to an empty object. + * @returns An object with methods to record specific counter events. + */ + init(baseAttrs: AttributesInput = {}) { + return { + /** + * Records an event for the main counter. + * + * @param attrs - Additional attributes specific to this 'start' event. Defaults to an empty object. + */ + start(attrs: AttributesInput = {}) { + const mergedAttrs = deepmerge.all([baseAttrs, attrs]) + const finalAttrs = sanitize(mergedAttrs) + + counter.add(1, finalAttrs) + console.info(`[${fullName}] start:`, finalAttrs) + }, + + /** + * Records an event for the success counter. + * + * @param attrs - Additional attributes specific to this 'success' event. Defaults to an empty object. + */ + success(attrs: AttributesInput = {}) { + const mergedAttrs = deepmerge.all([baseAttrs, attrs]) + const finalAttrs = sanitize(mergedAttrs) + + success.add(1, finalAttrs) + console.info(`[${fullName}] success:`, finalAttrs) + }, + + /** + * Records an event of type `data_error` for the fail counter. + * Used when some dependent data could not be resolved during the + * operation. Note that "no data" also exists and might be more + * appropriate in certain situations. + * + * @param errorMsg - A message describing the data error. + * @param attrs - Additional attributes specific to this 'dataError' event. Defaults to an empty object. + */ + dataError(errorMsg: string, attrs: AttributesInput = {}) { + const mergedAttrs = deepmerge.all([ + baseAttrs, + attrs, + { + error_type: "data_error", + error: errorMsg, + }, + ]) + const finalAttrs = sanitize(mergedAttrs) + + fail.add(1, finalAttrs) + console.error(`[${fullName}] dataError:`, finalAttrs) + }, + + /** + * Records an event of type `not_found` for the fail counter. + * Used when some dependent data could not be found during the operation. + * Note that when there is an error resolving the data, the + * `dataError` method might be more appropriate. + * + * @param attrs - Additional attributes specific to this 'noDataError' event. Defaults to an empty object. + */ + noDataError(attrs: AttributesInput = {}) { + const mergedAttrs = deepmerge.all([ + baseAttrs, + attrs, + { + error_type: "not_found", + }, + ]) + const finalAttrs = sanitize(mergedAttrs) + + fail.add(1, finalAttrs) + console.error(`[${fullName}] noDataError:`, finalAttrs) + }, + + /** + * Records an event of type `validation_error` for the fail counter. + * Used when a Zod schema fails validation. + * + * @param zodError - The {@link ZodError} object representing the validation error. + */ + validationError(zodError: ZodError) { + const mergedAttrs = deepmerge.all([ + baseAttrs, + { + error_type: "validation_error", + error: zodError, + }, + ]) + const finalAttrs = sanitize(mergedAttrs) + + fail.add(1, finalAttrs) + console.error(`[${fullName}] validationError:`, finalAttrs) + }, + + /** + * Records an event of type `http_error` for the fail counter. + * Used when a `fetch(...)` call fails. **Note**: This method must be + * `await`ed as it is asynchronous! + * The given {@link Response} must be unprocessed and will be cloned + * to avoid interfering with its consumption outside this function. + * + * @param response - The HTTP {@link Response} object. + */ + async httpError(response: Response) { + const res = response.clone() + const text = await res.text() + + const mergedAttrs = deepmerge.all([ + baseAttrs, + { + error_type: "http_error", + error: { + status: res.status, + statusText: res.statusText, + text, + }, + }, + ]) + const finalAttrs = sanitize(mergedAttrs) + + fail.add(1, finalAttrs) + console.error(`[${fullName}] httpError:`, finalAttrs) + }, + + /** + * Records an event of type `error` for the fail counter. + * Used when an error is thrown or an exception is caught, or as a + * general-purpose way to record an 'error' on the fail counter. + * + * @param err - An optional error object or message associated with the + * failure. Usually an instance of {@link Error} or a string. + */ + fail(err?: unknown) { + let msg = "unknown" + + if (err && err instanceof Error) { + msg = err.message + } else if (typeof err === "string") { + msg = err + } + + const mergedAttrs = deepmerge.all([ + baseAttrs, + { + error_type: "error", + error: msg, + }, + ]) + const finalAttrs = sanitize(mergedAttrs) + + fail.add(1, finalAttrs) + console.error(`[${fullName}] fail:`, finalAttrs) + }, + } + }, + } +} diff --git a/apps/scandic-web/server/tokenManager.ts b/apps/scandic-web/server/tokenManager.ts index 5552c8d3f..0044ed891 100644 --- a/apps/scandic-web/server/tokenManager.ts +++ b/apps/scandic-web/server/tokenManager.ts @@ -1,21 +1,12 @@ -import { metrics, trace, type Tracer } from "@opentelemetry/api" +import { trace, type Tracer } from "@opentelemetry/api" import { env } from "@/env/server" +import { createCounter } from "@/server/telemetry" import { getCacheClient } from "@/services/dataCache" import type { ServiceTokenResponse } from "@/types/tokens" -// OpenTelemetry metrics: Service token -const meter = metrics.getMeter("trpc.context.serviceToken") -const fetchServiceTokenCounter = meter.createCounter( - "trpc.context.serviceToken.fetch-new-token" -) - -const fetchServiceTokenFailCounter = meter.createCounter( - "trpc.context.serviceToken.fetch-fail" -) - export async function getServiceToken() { const tracer = trace.getTracer("getServiceToken") @@ -62,15 +53,32 @@ async function getOrSetServiceTokenFromCache( } async function getJwt(scopes: string[]) { - fetchServiceTokenCounter.add(1) + 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 } } async function fetchServiceToken(scopes: string[]) { - fetchServiceTokenCounter.add(1) + const fetchServiceTokenCounter = createCounter( + "tokenManager", + "fetchServiceToken" + ) + const metricsFetchServiceToken = fetchServiceTokenCounter.init({ + scopes, + }) + + metricsFetchServiceToken.start() const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, { method: "POST", @@ -87,36 +95,23 @@ async function fetchServiceToken(scopes: string[]) { }) if (!response.ok) { + await metricsFetchServiceToken.httpError(response) + const text = await response.text() - const error = { - status: response.status, - statusText: response.statusText, - text, - } - - fetchServiceTokenFailCounter.add(1, { - error_type: "http_error", - error: JSON.stringify(error), - }) - - console.error( - "fetchServiceToken error", - JSON.stringify({ - query: { - grant_type: "client_credentials", - client_id: env.CURITY_CLIENT_ID_SERVICE, - scope: scopes.join(" "), - }, - error, - }) - ) - throw new Error( - `[fetchServiceToken] Failed to obtain service token: ${JSON.stringify(error)}` + `[fetchServiceToken] Failed to obtain service token: ${JSON.stringify({ + status: response.status, + statusText: response.statusText, + text, + })}` ) } - return response.json() as Promise + const result = response.json() as Promise + + metricsFetchServiceToken.success() + + return result } function getServiceTokenCacheKey(scopes: string[]): string { diff --git a/apps/scandic-web/tsconfig.spec.json b/apps/scandic-web/tsconfig.spec.json new file mode 100644 index 000000000..c115f1c2b --- /dev/null +++ b/apps/scandic-web/tsconfig.spec.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "esModuleInterop": true + } +} diff --git a/apps/scandic-web/types/requests/packages.ts b/apps/scandic-web/types/requests/packages.ts index 1f5730d6b..e3d48d121 100644 --- a/apps/scandic-web/types/requests/packages.ts +++ b/apps/scandic-web/types/requests/packages.ts @@ -18,6 +18,9 @@ export interface AncillaryPackagesInput export interface PackagesInput extends z.input {} +export interface PackagesOutput + extends z.output {} + export type Packages = z.output export type Package = NonNullable[number] diff --git a/apps/scandic-web/types/trpc/routers/hotel/availability.ts b/apps/scandic-web/types/trpc/routers/hotel/availability.ts index 6b9f90d76..16b58dd2f 100644 --- a/apps/scandic-web/types/trpc/routers/hotel/availability.ts +++ b/apps/scandic-web/types/trpc/routers/hotel/availability.ts @@ -25,6 +25,9 @@ export type HotelsByHotelIdsAvailabilityInputSchema = z.output< export type RoomsAvailabilityInputSchema = z.input< typeof selectRateRoomsAvailabilityInputSchema > +export type RoomsAvailabilityOutputSchema = z.output< + typeof selectRateRoomsAvailabilityInputSchema +> export type RoomsAvailabilityInputRoom = RoomsAvailabilityInputSchema["booking"]["rooms"][number] export type RoomsAvailabilityExtendedInputSchema = z.input< diff --git a/yarn.lock b/yarn.lock index 512a269cf..b9c264678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6402,6 +6402,7 @@ __metadata: "@types/geojson": "npm:^7946.0.16" "@types/jest": "npm:^29.5.12" "@types/json-stable-stringify-without-jsonify": "npm:^1.0.2" + "@types/lodash-es": "npm:^4" "@types/node": "npm:^20" "@types/react": "npm:^18" "@types/react-dom": "npm:^18" @@ -6429,6 +6430,7 @@ __metadata: eslint-plugin-simple-import-sort: "npm:^12.1.0" fast-deep-equal: "npm:^3.1.3" fetch-retry: "npm:^6.0.0" + flat: "npm:^6.0.1" framer-motion: "npm:^11.3.28" fuse.js: "npm:^7.1.0" graphql: "npm:^16.8.1" @@ -6446,6 +6448,7 @@ __metadata: json-stable-stringify-without-jsonify: "npm:^1.0.1" libphonenumber-js: "npm:^1.10.60" lint-staged: "npm:^15.2.2" + lodash-es: "npm:^4.17.21" material-symbols: "npm:^0.29.0" nanoid: "npm:^5.0.9" netlify-plugin-cypress: "npm:^2.2.1" @@ -6464,13 +6467,13 @@ __metadata: react-material-symbols: "npm:^4.4.0" react-to-print: "npm:^3.0.2" schema-dts: "npm:^1.1.2" - secure-json-parse: "npm:^4.0.0" server-only: "npm:^0.0.1" slugify: "npm:^1.6.6" sonner: "npm:^1.7.0" start-server-and-test: "npm:^2.0.3" supercluster: "npm:^8.0.1" superjson: "npm:^2.2.1" + ts-jest: "npm:^29.3.2" ts-morph: "npm:^25.0.1" ts-node: "npm:^10.9.2" typescript: "npm:5.4.5" @@ -8227,6 +8230,22 @@ __metadata: languageName: node linkType: hard +"@types/lodash-es@npm:^4": + version: 4.17.12 + resolution: "@types/lodash-es@npm:4.17.12" + dependencies: + "@types/lodash": "npm:*" + checksum: 10c0/5d12d2cede07f07ab067541371ed1b838a33edb3c35cb81b73284e93c6fd0c4bbeaefee984e69294bffb53f62d7272c5d679fdba8e595ff71e11d00f2601dde0 + languageName: node + linkType: hard + +"@types/lodash@npm:*": + version: 4.17.16 + resolution: "@types/lodash@npm:4.17.16" + checksum: 10c0/cf017901b8ab1d7aabc86d5189d9288f4f99f19a75caf020c0e2c77b8d4cead4db0d0b842d009b029339f92399f49f34377dd7c2721053388f251778b4c23534 + languageName: node + linkType: hard + "@types/mdx@npm:^2.0.0": version: 2.0.13 resolution: "@types/mdx@npm:2.0.13" @@ -9608,7 +9627,7 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.0": +"async@npm:^3.2.0, async@npm:^3.2.3": version: 3.2.6 resolution: "async@npm:3.2.6" checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 @@ -9961,6 +9980,15 @@ __metadata: languageName: node linkType: hard +"bs-logger@npm:^0.2.6": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: "npm:2.x" + checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -10193,7 +10221,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4, chalk@npm:^4.0.0, chalk@npm:^4.1.0": +"chalk@npm:4, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -11588,6 +11616,17 @@ __metadata: languageName: node linkType: hard +"ejs@npm:^3.1.10": + version: 3.1.10 + resolution: "ejs@npm:3.1.10" + dependencies: + jake: "npm:^10.8.5" + bin: + ejs: bin/cli.js + checksum: 10c0/52eade9e68416ed04f7f92c492183340582a36482836b11eab97b159fcdcfdedc62233a1bf0bf5e5e1851c501f2dca0e2e9afd111db2599e4e7f53ee29429ae1 + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.5.73": version: 1.5.103 resolution: "electron-to-chromium@npm:1.5.103" @@ -12646,7 +12685,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b @@ -12758,6 +12797,15 @@ __metadata: languageName: node linkType: hard +"filelist@npm:^1.0.4": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41 + languageName: node + linkType: hard + "fill-range@npm:^7.1.1": version: 7.1.1 resolution: "fill-range@npm:7.1.1" @@ -12807,6 +12855,15 @@ __metadata: languageName: node linkType: hard +"flat@npm:^6.0.1": + version: 6.0.1 + resolution: "flat@npm:6.0.1" + bin: + flat: cli.js + checksum: 10c0/9dc0dbe6e2acc012512a53130d9ba1c82c1a596cdca91b23d11716348361c4a68928409bb4433c4493a17595c3efd0cab9f09e23dd3f9962a58af225c3efc23a + languageName: node + linkType: hard + "flatted@npm:^3.2.9": version: 3.3.3 resolution: "flatted@npm:3.3.3" @@ -14587,6 +14644,20 @@ __metadata: languageName: node linkType: hard +"jake@npm:^10.8.5": + version: 10.9.2 + resolution: "jake@npm:10.9.2" + dependencies: + async: "npm:^3.2.3" + chalk: "npm:^4.0.2" + filelist: "npm:^1.0.4" + minimatch: "npm:^3.1.2" + bin: + jake: bin/cli.js + checksum: 10c0/c4597b5ed9b6a908252feab296485a4f87cba9e26d6c20e0ca144fb69e0c40203d34a2efddb33b3d297b8bd59605e6c1f44f6221ca1e10e69175ecbf3ff5fe31 + languageName: node + linkType: hard + "jest-changed-files@npm:^29.7.0": version: 29.7.0 resolution: "jest-changed-files@npm:29.7.0" @@ -14972,7 +15043,7 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^29.7.0": +"jest-util@npm:^29.0.0, jest-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-util@npm:29.7.0" dependencies: @@ -15849,6 +15920,13 @@ __metadata: languageName: node linkType: hard +"lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 + languageName: node + linkType: hard + "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -16207,7 +16285,7 @@ __metadata: languageName: node linkType: hard -"make-error@npm:^1.1.1": +"make-error@npm:^1.1.1, make-error@npm:^1.3.6": version: 1.3.6 resolution: "make-error@npm:1.3.6" checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f @@ -16437,6 +16515,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + "minimatch@npm:^8.0.2": version: 8.0.4 resolution: "minimatch@npm:8.0.4" @@ -19418,13 +19505,6 @@ __metadata: languageName: node linkType: hard -"secure-json-parse@npm:^4.0.0": - version: 4.0.0 - resolution: "secure-json-parse@npm:4.0.0" - checksum: 10c0/1a298cf00e1de91e833cee5eb406d6e77fb2f7eca9bef3902047d49e7f5d3e6c21b5de61ff73466c831e716430bfe87d732a6e645a7dabb5f1e8a8e4d3e15eb4 - languageName: node - linkType: hard - "semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -19443,7 +19523,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3": +"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.1": version: 7.7.1 resolution: "semver@npm:7.7.1" bin: @@ -20799,6 +20879,44 @@ __metadata: languageName: node linkType: hard +"ts-jest@npm:^29.3.2": + version: 29.3.2 + resolution: "ts-jest@npm:29.3.2" + dependencies: + bs-logger: "npm:^0.2.6" + ejs: "npm:^3.1.10" + fast-json-stable-stringify: "npm:^2.1.0" + jest-util: "npm:^29.0.0" + json5: "npm:^2.2.3" + lodash.memoize: "npm:^4.1.2" + make-error: "npm:^1.3.6" + semver: "npm:^7.7.1" + type-fest: "npm:^4.39.1" + yargs-parser: "npm:^21.1.1" + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 + "@jest/types": ^29.0.0 + babel-jest: ^29.0.0 + jest: ^29.0.0 + typescript: ">=4.3 <6" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + bin: + ts-jest: cli.js + checksum: 10c0/84762720dbef45c1644348d67d0dcb8b7ad6369a16628c4752aceeb47f0ccdad63ae14485048b641c20ce096337a160ab816881361ef5517325bac6a5b3756e0 + languageName: node + linkType: hard + "ts-morph@npm:^25.0.1": version: 25.0.1 resolution: "ts-morph@npm:25.0.1" @@ -21029,6 +21147,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.39.1": + version: 4.40.0 + resolution: "type-fest@npm:4.40.0" + checksum: 10c0/b39d4da6f9a154e3db7e714cd05ccf56b53f4f0bbf74dd294cb6be4921b16ecca5cb00cb81b53ab621a31c8e8509c74b5101895ada47af9de368a317d24538a3 + languageName: node + linkType: hard + "type-is@npm:^1.6.16, type-is@npm:^1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18"