feat: harmonize log and metrics

This commit is contained in:
Michael Zetterberg
2025-04-17 07:16:11 +02:00
parent 858a81b16f
commit 5323a8e46e
58 changed files with 2324 additions and 4726 deletions

View File

@@ -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"),
},
}

View File

@@ -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
}),
}),
})

View File

@@ -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"
)

View File

@@ -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
}