diff --git a/config/config.example.toml b/config/config.example.toml index 1482ce7..fa20ebc 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -1,5 +1,5 @@ [server.log] -level = "info" #crit, error, warning, info, debug +level = "info" # crit, error, warning, info, debug # file = "/var/log/diadem.log" [server.golbat] diff --git a/src/hooks.server.ts b/src/hooks.server.ts index d35144f..0f669fa 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -13,6 +13,19 @@ import type { User } from "@/lib/server/db/internal/schema"; import { DISCORD_REFRESH_INTERVAL, PERMISSION_UPDATE_INTERVAL } from "@/lib/constants"; import { getDiscordAuth } from "@/lib/server/auth/discord"; import type { Perms } from "@/lib/utils/features"; +import { getServerLogger } from "@/lib/server/logging"; +import { setServerLoggerFactory } from "@/lib/utils/logger"; +import { getServerConfig } from "@/lib/services/config/config.server"; + +setServerLoggerFactory((name) => { + const winstonLogger = getServerLogger(name); + return { + debug: (message, ...args) => winstonLogger.debug(message, ...args), + info: (message, ...args) => winstonLogger.info(message, ...args), + warning: (message, ...args) => winstonLogger.warning(message, ...args), + error: (message, ...args) => winstonLogger.error(message, ...args), + }; +}); const permissionCache: TTLCache = new TTLCache({ ttl: PERMISSION_UPDATE_INTERVAL * 1000 diff --git a/src/lib/server/api/golbatApi.ts b/src/lib/server/api/golbatApi.ts index bd94164..427c108 100644 --- a/src/lib/server/api/golbatApi.ts +++ b/src/lib/server/api/golbatApi.ts @@ -1,8 +1,8 @@ import { getServerConfig } from "@/lib/services/config/config.server"; import type { PokemonData } from "@/lib/types/mapObjectData/pokemon"; -import { getLogger } from "@/lib/server/logging"; import type { Coords } from "@/lib/utils/coordinates"; import type { GymData } from "@/lib/types/mapObjectData/gym"; +import { getLogger } from "@/lib/utils/logger"; export type PokemonResponse = { pokemon: PokemonData[]; @@ -52,11 +52,11 @@ async function callGolbat( } export async function getSinglePokemon(id: string, thisFetch: typeof fetch = fetch) { - return await callGolbat("api/pokemon/id/" + id, "GET", undefined, thisFetch) + return await callGolbat("api/pokemon/id/" + id, "GET", undefined, thisFetch); } export async function getMultiplePokemon(body: any) { - return await callGolbat("api/pokemon/v3/scan", "POST", JSON.stringify(body)) + return await callGolbat("api/pokemon/v3/scan", "POST", JSON.stringify(body)); } export async function searchGyms(query: string, coords: Coords, range: number) { @@ -71,6 +71,6 @@ export async function searchGyms(query: string, coords: Coords, range: number) { } ], limit: 15 - } - return await callGolbat("api/gym/search", "POST", JSON.stringify(body)) -} \ No newline at end of file + }; + return await callGolbat("api/gym/search", "POST", JSON.stringify(body)); +} diff --git a/src/lib/server/api/kojiApi.ts b/src/lib/server/api/kojiApi.ts index cf0e3c9..e902f76 100644 --- a/src/lib/server/api/kojiApi.ts +++ b/src/lib/server/api/kojiApi.ts @@ -1,31 +1,32 @@ -import { getServerConfig } from '@/lib/services/config/config.server'; -import { getLogger } from '@/lib/server/logging'; -import { error, json } from "@sveltejs/kit"; +import { getServerConfig } from "@/lib/services/config/config.server"; import type { KojiFeatures } from "@/lib/features/koji"; +import { getLogger } from "@/lib/utils/logger"; -const log = getLogger("koji") +const log = getLogger("koji"); -export async function fetchKojiGeofences(thisFetch?: typeof fetch): Promise { +export async function fetchKojiGeofences( + thisFetch?: typeof fetch +): Promise { const config = getServerConfig(); if (!config.koji || !config.koji.url) { - log.warning("Koji was called, but is not configured") - return + log.warning("Koji was called, but is not configured"); + return; } - const url = config.koji.url + '/api/v1/geofence/FeatureCollection/' + config.koji.projectName; + const url = config.koji.url + "/api/v1/geofence/FeatureCollection/" + config.koji.projectName; const response = await (thisFetch ?? fetch)(url, { - method: 'GET', + method: "GET", headers: { Authorization: `Bearer ${config.koji.secret}`, - 'Content-Type': 'application/json' + "Content-Type": "application/json" } }); if (!response.ok) { - log.error("Koji Error: %d (%s)", response.status, await response.text()) - return + log.error("Koji Error: %d (%s)", response.status, await response.text()); + return; } const data = await response.json(); - return data?.data?.features ?? [] as KojiFeatures + return data?.data?.features ?? ([] as KojiFeatures); } \ No newline at end of file diff --git a/src/lib/server/auth/permissions.ts b/src/lib/server/auth/permissions.ts index 3b69961..697e757 100644 --- a/src/lib/server/auth/permissions.ts +++ b/src/lib/server/auth/permissions.ts @@ -5,8 +5,8 @@ import { getServerConfig } from "@/lib/services/config/config.server"; import type { Permissions as ConfigRule } from "@/lib/services/config/configTypes"; import { type KojiFeatures } from "@/lib/features/koji"; import { fetchKojiGeofences } from "@/lib/server/api/kojiApi"; -import { getLogger } from "@/lib/server/logging"; import type { FeaturesKey, PermArea, Perms } from "@/lib/utils/features"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("permissions"); diff --git a/src/lib/server/db/external/internalQuery.ts b/src/lib/server/db/external/internalQuery.ts index 89fb232..7ad4e93 100644 --- a/src/lib/server/db/external/internalQuery.ts +++ b/src/lib/server/db/external/internalQuery.ts @@ -1,9 +1,9 @@ -import mysql from 'mysql2/promise'; -import { getServerConfig } from '@/lib/services/config/config.server'; -import { getDbUri } from '@/lib/services/config/dbUri.server'; -import { getLogger } from '@/lib/server/logging'; +import mysql from "mysql2/promise"; +import { getServerConfig } from "@/lib/services/config/config.server"; +import { getDbUri } from "@/lib/services/config/dbUri.server"; +import { getLogger } from "@/lib/utils/logger"; -const log = getLogger("query") +const log = getLogger("query"); const connection = mysql.createPool(getDbUri(getServerConfig().db)); @@ -14,7 +14,7 @@ export async function query( error: number | undefined; result: T; }> { - const start = performance.now() + const start = performance.now(); let result: mysql.QueryResult = []; let error: number | undefined = undefined; @@ -28,7 +28,7 @@ export async function query( ); result = queryResult[0]; } catch (e) { - log.error('SQL exception', e); + log.error("SQL exception", e); error = 500; } @@ -61,7 +61,7 @@ export async function query( } } - log.debug(`Query took %fms: %s`, (performance.now() - start).toFixed(1), sql) + log.debug(`Query took %fms: %s`, (performance.now() - start).toFixed(1), sql); return { error, result: parsedResult diff --git a/src/lib/server/logging.ts b/src/lib/server/logging.ts index 4ec3184..c884418 100644 --- a/src/lib/server/logging.ts +++ b/src/lib/server/logging.ts @@ -46,6 +46,6 @@ if (config.file) { log.add(new DailyRotateFile({ filename: config.file })) } -export function getLogger(name: string) { +export function getServerLogger(name: string) { return log.child({ label: name }) } diff --git a/src/lib/services/user/checkPerm.ts b/src/lib/services/user/checkPerm.ts index 92fc383..5bd62b2 100644 --- a/src/lib/services/user/checkPerm.ts +++ b/src/lib/services/user/checkPerm.ts @@ -2,6 +2,9 @@ import type { Bounds } from "@/lib/mapObjects/mapBounds"; import { bbox, feature as makeFeature, featureCollection, intersect, polygon } from "@turf/turf"; import type { Feature, Polygon } from "geojson"; import { Features, type FeaturesKey, type Perms } from "@/lib/utils/features"; +import { getLogger } from "@/lib/utils/logger"; + +const log = getLogger("permissions"); function isFeatureInFeatureList(featureList: FeaturesKey[] | undefined, feature: FeaturesKey) { if (featureList === undefined) return false; diff --git a/src/lib/utils/logger.ts b/src/lib/utils/logger.ts new file mode 100644 index 0000000..b8a62e5 --- /dev/null +++ b/src/lib/utils/logger.ts @@ -0,0 +1,37 @@ +// Universal logger that works on both the server and in the browser + +type LogFn = (message: string, ...args: unknown[]) => void; + +export interface Logger { + debug: LogFn; + info: LogFn; + warning: LogFn; + error: LogFn; +} + +export type DebugCategories = { + permissions?: boolean; +}; + +let serverLoggerFactory: ((name: string) => Logger) | null = null; + +export function setServerLoggerFactory(factory: (name: string) => Logger,) { + serverLoggerFactory = factory; +} + +function createBrowserLogger(name: string): Logger { + const prefix = `[${name}]`; + return { + debug: (message, ...args) => console.debug(prefix, message, ...args), + info: (message, ...args) => console.info(prefix, message, ...args), + warning: (message, ...args) => console.warn(prefix, message, ...args), + error: (message, ...args) => console.error(prefix, message, ...args), + }; +} + +export function getLogger(name: string): Logger { + if (serverLoggerFactory) { + return serverLoggerFactory(name); + } + return createBrowserLogger(name); +} diff --git a/src/routes/(share)/[directLink=mapObject]/[id]/+page.server.ts b/src/routes/(share)/[directLink=mapObject]/[id]/+page.server.ts index 069fdc8..973ca92 100644 --- a/src/routes/(share)/[directLink=mapObject]/[id]/+page.server.ts +++ b/src/routes/(share)/[directLink=mapObject]/[id]/+page.server.ts @@ -4,9 +4,9 @@ import { initAllIconSets } from "@/lib/services/uicons.svelte.js"; import { loadRemoteLocale } from "@/lib/services/ingameLocale"; import { querySingleMapObject } from "@/lib/server/api/querySingleMapObject"; import { makeMapObject } from "@/lib/mapObjects/makeMapObject"; -import { getLogger } from "@/lib/server/logging"; import { getClientConfig } from "@/lib/services/config/config.server"; import { type MapData, MapObjectType } from "@/lib/mapObjects/mapObjectTypes"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("directlink"); export const ssr = true; diff --git a/src/routes/(share)/a/[area]/+page.server.ts b/src/routes/(share)/a/[area]/+page.server.ts index e74851b..f5edee2 100644 --- a/src/routes/(share)/a/[area]/+page.server.ts +++ b/src/routes/(share)/a/[area]/+page.server.ts @@ -1,35 +1,38 @@ -import { getConfig, setConfig } from "@/lib/services/config/config"; -import { loadRemoteLocale } from "@/lib/services/ingameLocale"; import type { PageServerLoad } from "./$types"; -import { getLogger } from "@/lib/server/logging"; -import { getServerConfig } from "@/lib/services/config/config.server"; import { error } from "@sveltejs/kit"; -import { getKojiGeofences } from "@/lib/features/koji"; import { fetchKojiGeofences } from "@/lib/server/api/kojiApi"; import { getFeatureJump } from "@/lib/utils/geo"; +import { getLogger } from "@/lib/utils/logger"; -const log = getLogger("arealink") +const log = getLogger("arealink"); export const load: PageServerLoad = async ({ params, fetch }) => { - const areaName = params.area + const areaName = params.area; - log.info("Direct area link called to %s", areaName) + log.info("Direct area link called to %s", areaName); - const features = await fetchKojiGeofences(fetch) + const features = await fetchKojiGeofences(fetch); if (!features) { - log.error("Error fetching features, returning 404") - error(404) + log.error("Error fetching features, returning 404"); + error(404); } - const feature = features.find(a => a.properties.name.toLowerCase().includes(areaName.toLowerCase())) + const feature = features.find((a) => + a.properties.name.toLowerCase().includes(areaName.toLowerCase()) + ); if (!feature) { - log.info("Area %s not found", areaName) - error(404) + log.info("Area %s not found", areaName); + error(404); } - const jumpTo = getFeatureJump(feature) + const jumpTo = getFeatureJump(feature); - return { lat: jumpTo.coords.lat, lon: jumpTo.coords.lon, zoom: jumpTo.zoom, name: feature.properties.name }; + return { + lat: jumpTo.coords.lat, + lon: jumpTo.coords.lon, + zoom: jumpTo.zoom, + name: feature.properties.name + }; }; \ No newline at end of file diff --git a/src/routes/(share)/filter/[majorCategory]/[[subCategory]]/[encodedFilter]/+page.server.ts b/src/routes/(share)/filter/[majorCategory]/[[subCategory]]/[encodedFilter]/+page.server.ts index 7e74cd4..98bcc38 100644 --- a/src/routes/(share)/filter/[majorCategory]/[[subCategory]]/[encodedFilter]/+page.server.ts +++ b/src/routes/(share)/filter/[majorCategory]/[[subCategory]]/[encodedFilter]/+page.server.ts @@ -10,10 +10,10 @@ import { FiltersetRaidSchema } from "@/lib/features/filters/filtersetSchemas"; import * as m from "@/lib/paraglide/messages"; -import { getLogger } from "@/lib/server/logging"; import type { ZodSafeParseResult } from "zod"; +import { getLogger } from "@/lib/utils/logger"; -const log = getLogger("filtershare") +const log = getLogger("filtershare"); function decodeFilterset( majorCategory: FilterCategory | string, @@ -21,30 +21,30 @@ function decodeFilterset( str: string ) { const decoded: AnyFilterset = JSON.parse(decodeURIComponent(atob(str))); - log.info("Decoding filterset: %s", decoded) + log.info("Decoding filterset: %s", decoded); decoded.id = getId(); - let zodResult: ZodSafeParseResult | undefined = undefined + let zodResult: ZodSafeParseResult | undefined = undefined; if (majorCategory === "pokemon") { - zodResult = FiltersetPokemonSchema.safeParse(decoded) + zodResult = FiltersetPokemonSchema.safeParse(decoded); } else if (majorCategory === "gym" && subCategory === "raid") { - zodResult = FiltersetRaidSchema.safeParse(decoded) + zodResult = FiltersetRaidSchema.safeParse(decoded); } else if (majorCategory === "pokestop" && subCategory === "invasion") { - zodResult = FiltersetInvasionSchema.safeParse(decoded) + zodResult = FiltersetInvasionSchema.safeParse(decoded); } - if (!zodResult) return undefined + if (!zodResult) return undefined; if (zodResult?.error) { - log.crit("Decoding failed!!", zodResult?.error) - return undefined + log.crit("Decoding failed!!", zodResult?.error); + return undefined; } - const safe = zodResult.data + const safe = zodResult.data; if (safe?.title?.message && !Object.keys(m).includes(safe.title.message)) { - log.crit("Tried to send invalid message!!", safe.title.message) + log.crit("Tried to send invalid message!!", safe.title.message); return undefined; } diff --git a/src/routes/api/[queryMapObject=mapObject]/+server.ts b/src/routes/api/[queryMapObject=mapObject]/+server.ts index 2d33fac..41a4dc6 100644 --- a/src/routes/api/[queryMapObject=mapObject]/+server.ts +++ b/src/routes/api/[queryMapObject=mapObject]/+server.ts @@ -2,9 +2,10 @@ import { error, json } from "@sveltejs/kit"; import { checkFeatureInBounds } from "@/lib/services/user/checkPerm"; import { queryMapObjects } from "@/lib/server/api/queryMapObjects"; import type { MapObjectRequestData } from "@/lib/mapObjects/updateMapObject"; -import { getLogger } from "@/lib/server/logging"; +import { getServerLogger } from "@/lib/server/logging"; import { hasFeatureAnywhereServer } from "@/lib/server/auth/checkIfAuthed"; import { MapObjectType } from "@/lib/mapObjects/mapObjectTypes"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("mapobjects"); diff --git a/src/routes/api/address/[query]/+server.ts b/src/routes/api/address/[query]/+server.ts index 44a0630..faf63f4 100644 --- a/src/routes/api/address/[query]/+server.ts +++ b/src/routes/api/address/[query]/+server.ts @@ -1,7 +1,8 @@ import { error, json } from "@sveltejs/kit"; import { getServerConfig } from "@/lib/services/config/config.server"; import type { FeatureCollection, Point } from "geojson"; -import { getLogger } from "@/lib/server/logging"; +import { getServerLogger } from "@/lib/server/logging"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("nominatim"); diff --git a/src/routes/api/locale/[tag]/+server.ts b/src/routes/api/locale/[tag]/+server.ts index b5406bd..abe1261 100644 --- a/src/routes/api/locale/[tag]/+server.ts +++ b/src/routes/api/locale/[tag]/+server.ts @@ -1,8 +1,9 @@ import { json } from "@sveltejs/kit"; import { prefixes as localePrefixesObject } from "@/lib/services/ingameLocale"; import TTLCache from "@isaacs/ttlcache"; -import { getLogger } from "@/lib/server/logging"; +import { getServerLogger } from "@/lib/server/logging"; import { locales } from "@/lib/paraglide/runtime"; +import { getLogger } from "@/lib/utils/logger"; type Locale = (typeof locales)[number]; type RemoteLocale = { [key: string]: string }; diff --git a/src/routes/api/scout/+server.ts b/src/routes/api/scout/+server.ts index 6c85aca..9c42678 100644 --- a/src/routes/api/scout/+server.ts +++ b/src/routes/api/scout/+server.ts @@ -3,9 +3,10 @@ import type { ScoutRequest } from "@/lib/features/scout.svelte.js"; import { addScoutEntries, getScoutQueue } from "@/lib/server/api/dragoniteApi"; import { result } from "@/lib/server/api/results"; -import { getLogger } from "@/lib/server/logging"; +import { getServerLogger } from "@/lib/server/logging"; import { hasFeatureAnywhereServer } from "@/lib/server/auth/checkIfAuthed"; import { Features } from "@/lib/utils/features"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("scout"); diff --git a/src/routes/api/search/[type=searchType]/+server.ts b/src/routes/api/search/[type=searchType]/+server.ts index 0141893..d0e8669 100644 --- a/src/routes/api/search/[type=searchType]/+server.ts +++ b/src/routes/api/search/[type=searchType]/+server.ts @@ -1,9 +1,10 @@ import { error, json } from "@sveltejs/kit"; -import { getLogger } from "@/lib/server/logging"; +import { getServerLogger } from "@/lib/server/logging"; import { hasFeatureAnywhereServer } from "@/lib/server/auth/checkIfAuthed"; import { searchGyms } from "@/lib/server/api/golbatApi"; import { Coords } from "@/lib/utils/coordinates"; import { type SearchPayload, SearchType, sortSearchResults } from "@/lib/services/search.svelte"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("search"); diff --git a/src/routes/api/stats/+server.ts b/src/routes/api/stats/+server.ts index c034ae8..08019d0 100644 --- a/src/routes/api/stats/+server.ts +++ b/src/routes/api/stats/+server.ts @@ -1,9 +1,9 @@ import { json } from "@sveltejs/kit"; import { type MasterStats, queryMasterStats } from "@/lib/server/api/queryStats"; import TTLCache from "@isaacs/ttlcache"; -import { getLogger } from '@/lib/server/logging'; +import { getLogger } from "@/lib/utils/logger"; -const log = getLogger("stats") +const log = getLogger("stats"); const UPDATE_INTERVAL = 60 * 60 * 1000; const statsCache: TTLCache<"stats", MasterStats> = new TTLCache({ @@ -14,14 +14,14 @@ const statsCache: TTLCache<"stats", MasterStats> = new TTLCache({ export async function GET() { const stats = statsCache.get("stats"); if (stats) { - log.info("Serving cached master stats") - return json(stats) - }; + log.info("Serving cached master stats"); + return json(stats); + } try { - const start = performance.now() + const start = performance.now(); const stats = await queryMasterStats(); - log.info("Generated fresh master stats / time: %dms", (performance.now() - start).toFixed(1)) + log.info("Generated fresh master stats / time: %dms", (performance.now() - start).toFixed(1)); statsCache.set("stats", stats); return json(stats); } catch (e) { diff --git a/src/routes/api/stats/live/+server.ts b/src/routes/api/stats/live/+server.ts index da08da2..cc348a0 100644 --- a/src/routes/api/stats/live/+server.ts +++ b/src/routes/api/stats/live/+server.ts @@ -1,13 +1,12 @@ import { json } from "@sveltejs/kit"; -import { type MasterStats, queryMasterStats } from "@/lib/server/api/queryStats"; import TTLCache from "@isaacs/ttlcache"; -import { getLogger } from "@/lib/server/logging"; import { type FortLiveStats, type LiveStats, queryLiveFortStats, queryLivePokemonStats } from "@/lib/server/api/queryLiveStats"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("livestats"); diff --git a/src/routes/api/weather/[id]/+server.ts b/src/routes/api/weather/[id]/+server.ts index 18c1ec4..466dd33 100644 --- a/src/routes/api/weather/[id]/+server.ts +++ b/src/routes/api/weather/[id]/+server.ts @@ -1,8 +1,9 @@ import { error, json } from "@sveltejs/kit"; import { query } from "@/lib/server/db/external/internalQuery"; -import { getLogger } from "@/lib/server/logging"; +import { getServerLogger } from "@/lib/server/logging"; import { hasFeatureAnywhereServer } from "@/lib/server/auth/checkIfAuthed"; import { Features } from '@/lib/utils/features'; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("mapobjects"); diff --git a/src/routes/assets/[iconset]/[...path]/+server.ts b/src/routes/assets/[iconset]/[...path]/+server.ts index 409c446..7c03087 100644 --- a/src/routes/assets/[iconset]/[...path]/+server.ts +++ b/src/routes/assets/[iconset]/[...path]/+server.ts @@ -1,8 +1,9 @@ import { error } from "@sveltejs/kit"; import { getClientConfig } from "@/lib/services/config/config.server"; import sharp, { type ResizeOptions } from "sharp"; -import { getLogger } from "@/lib/server/logging"; +import { getServerLogger } from "@/lib/server/logging"; import { ALLOWED_WIDTHS } from "@/lib/services/assets"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("uicons"); const CACHE_AGE = 86400 * 7; // 7 days diff --git a/src/routes/assets/[iconset]/index.json/+server.ts b/src/routes/assets/[iconset]/index.json/+server.ts index 09a81a4..598a283 100644 --- a/src/routes/assets/[iconset]/index.json/+server.ts +++ b/src/routes/assets/[iconset]/index.json/+server.ts @@ -1,7 +1,7 @@ import { error } from "@sveltejs/kit"; import { getClientConfig } from "@/lib/services/config/config.server"; -import { getLogger } from "@/lib/server/logging"; import TTLCache from "@isaacs/ttlcache"; +import { getLogger } from "@/lib/utils/logger"; const log = getLogger("uicons"); const config = getClientConfig(); @@ -29,7 +29,7 @@ export async function GET({ params, fetch }) { }); } - const url = iconSet.url + "/index.json" + const url = iconSet.url + "/index.json"; const res = await fetch(url); log.info("[%s] Serving fresh index.json", iconSetId); @@ -38,7 +38,7 @@ export async function GET({ params, fetch }) { error(500, "Fetching index.json failed"); } - const text = await res.text() + const text = await res.text(); indexCache.set(iconSetId, text);