diff --git a/index.ts b/index.ts index 73f7d5b..24d3f09 100644 --- a/index.ts +++ b/index.ts @@ -1,17 +1,15 @@ import { config } from "./src/config.js"; import { logger } from "./src/logger.js"; import GET from "./src/fetch/GET.js"; -import * as prometheus from "./src/prometheus.js"; - -if (config.verbose) logger.enable(); +import { APIError } from "./src/fetch/utils.js"; const app = Bun.serve({ hostname: config.hostname, port: config.port, fetch(req: Request) { + let pathname = new URL(req.url).pathname; if (req.method === "GET") return GET(req); - prometheus.request_error.inc({ pathname: new URL(req.url).pathname, status: 400 }); - return new Response("Invalid request", { status: 400 }); + return APIError(pathname, 405, "invalid_request_method", "Invalid request method, only GET allowed"); } }); diff --git a/src/clickhouse/makeQuery.ts b/src/clickhouse/makeQuery.ts index c58492b..7922578 100644 --- a/src/clickhouse/makeQuery.ts +++ b/src/clickhouse/makeQuery.ts @@ -27,12 +27,10 @@ export async function makeQuery(query: string) { prometheus.bytes_read.inc(data.statistics.bytes_read); prometheus.rows_read.inc(data.statistics.rows_read); prometheus.elapsed.inc(data.statistics.elapsed); - logger.info({ query, statistics: data.statistics, rows: data.rows }); + logger.trace("\n", { query, statistics: data.statistics, rows: data.rows }); return data; } catch (e: any) { - logger.error(e.message); - throw new Error(e.message); } } \ No newline at end of file diff --git a/src/clickhouse/ping.ts b/src/clickhouse/ping.ts index 625573a..f751036 100644 --- a/src/clickhouse/ping.ts +++ b/src/clickhouse/ping.ts @@ -1,10 +1,12 @@ import { PingResult } from "@clickhouse/client-web"; import client from "./createClient.js"; +import { logger } from "../logger.js"; // Does not work with Bun's implementation of Node streams. export async function ping(): Promise { try { await client.exec({ query: "SELECT 1" }); + logger.info("Successfully pinged database"); return { success: true }; } catch (err) { const message = typeof err === "string" ? err : JSON.stringify(err); diff --git a/src/fetch/GET.ts b/src/fetch/GET.ts index 179ebfd..5fc3396 100644 --- a/src/fetch/GET.ts +++ b/src/fetch/GET.ts @@ -10,9 +10,11 @@ import swaggerFavicon from "../../swagger/favicon.png" import transfers from "./transfers.js"; import { APIError, toJSON } from "./utils.js"; import { APP_VERSION } from "../config.js"; +import { logger } from "../logger.js"; export default async function (req: Request) { const { pathname } = new URL(req.url); + logger.trace(`Incoming request: [${pathname}]`) prometheus.request.inc({ pathname }); // Landing page diff --git a/src/fetch/balance.ts b/src/fetch/balance.ts index 6c6f446..57243a0 100644 --- a/src/fetch/balance.ts +++ b/src/fetch/balance.ts @@ -1,7 +1,6 @@ import { makeQuery } from "../clickhouse/makeQuery.js"; import { logger } from "../logger.js"; import { getBalanceChanges } from "../queries.js"; -import * as prometheus from "../prometheus.js"; import { APIError, addMetadata, toJSON } from "./utils.js"; import { parseLimit, parsePage } from "../utils.js"; @@ -13,25 +12,25 @@ function verifyParams(searchParams: URLSearchParams) { } export default async function (req: Request) { - try { - const { pathname, searchParams } = new URL(req.url); - logger.info({ searchParams: Object.fromEntries(Array.from(searchParams)) }); + const { pathname, searchParams } = new URL(req.url); + logger.trace("\n", { searchParams: Object.fromEntries(Array.from(searchParams)) }); - try { - verifyParams(searchParams); - } catch (e: any) { - return APIError(pathname, 400, "bad_query_input", e.message); - } + try { + verifyParams(searchParams); + } catch (e: any) { + return APIError(pathname, 400, "bad_query_input", e.message); + } - const query = getBalanceChanges(searchParams); - let response; + const query = getBalanceChanges(searchParams); + let response; - try { - response = await makeQuery(query); - } catch (e: any) { - return APIError(pathname, 500, "failed_database_query", e.message); - } + try { + response = await makeQuery(query); + } catch (e: any) { + return APIError(pathname, 500, "failed_database_query", e.message); + } + try { return toJSON( addMetadata( response, @@ -40,9 +39,6 @@ export default async function (req: Request) { ) ); } catch (e: any) { - logger.error(e); - prometheus.request_error.inc({ pathname: "/balance", status: 400 }); - - return new Response(e.message, { status: 400 }); + return APIError(pathname, 500, "failed_response", e.message); } } \ No newline at end of file diff --git a/src/fetch/head.ts b/src/fetch/head.ts index 378324e..1778183 100644 --- a/src/fetch/head.ts +++ b/src/fetch/head.ts @@ -3,13 +3,18 @@ import { makeQuery } from "../clickhouse/makeQuery.js"; export default async function (req: Request) { let query = "SELECT block_num FROM cursors ORDER BY block_num DESC LIMIT 1"; + let pathname = new URL(req.url).pathname; let response; try { response = await makeQuery(query); } catch (e: any) { - return APIError(new URL(req.url).pathname, 500, "failed_database_query", e.message); + return APIError(pathname, 500, "failed_database_query", e.message); } - return toJSON(addMetadata(response)); + try { + return toJSON(addMetadata(response)); + } catch (e: any) { + return APIError(pathname, 500, "failed_response", e.message); + } } \ No newline at end of file diff --git a/src/fetch/health.ts b/src/fetch/health.ts index af86916..920c77b 100644 --- a/src/fetch/health.ts +++ b/src/fetch/health.ts @@ -1,20 +1,13 @@ import client from "../clickhouse/createClient.js"; -import { logger } from "../logger.js"; -import * as prometheus from "../prometheus.js"; +import { APIError } from "./utils.js"; // TODO: Add log entry -export default async function (_req: Request) { - try { - const response = await client.ping(); - - if (response.success === false) throw new Error(response.error.message); - if (response.success === true) return new Response("OK"); - - return new Response("Unknown response from ClickHouse"); - } catch (e: any) { - logger.error(e); - prometheus.request_error.inc({ pathname: "/health", status: 503 }); - - return new Response(e.message, { status: 503 }); +export default async function (req: Request) { + const response = await client.ping(); + + if (!response.success) { + return APIError(new URL(req.url).pathname, 503, "failed_ping_database", response.error.message); } + + return new Response("OK"); } \ No newline at end of file diff --git a/src/fetch/openapi.ts b/src/fetch/openapi.ts index 6e5aeb4..80d95c2 100644 --- a/src/fetch/openapi.ts +++ b/src/fetch/openapi.ts @@ -6,6 +6,7 @@ import { registry } from "../prometheus.js"; import { makeQuery } from "../clickhouse/makeQuery.js"; import { getBalanceChanges, getTotalSupply, getTransfers } from "../queries.js"; import { APIError, addMetadata } from "./utils.js"; +import { logger } from "../logger.js"; const TAGS = { MONITORING: "Monitoring", HEALTH: "Health", @@ -29,6 +30,7 @@ const head_example = addMetadata({ } }); +logger.debug("Querying examples for OpenAPI..."); const supply_example = await makeQuery( getTotalSupply(new URLSearchParams({ limit: "1" }), true) ).then( diff --git a/src/fetch/supply.ts b/src/fetch/supply.ts index b4beb10..4671aeb 100644 --- a/src/fetch/supply.ts +++ b/src/fetch/supply.ts @@ -1,7 +1,6 @@ import { makeQuery } from "../clickhouse/makeQuery.js"; import { logger } from "../logger.js"; import { getTotalSupply } from "../queries.js"; -import * as prometheus from "../prometheus.js"; import { APIError, addMetadata, toJSON } from "./utils.js"; import { parseLimit, parsePage } from "../utils.js"; @@ -13,25 +12,25 @@ function verifyParams(searchParams: URLSearchParams) { } export default async function (req: Request) { - try { - const { pathname, searchParams } = new URL(req.url); - logger.info({ searchParams: Object.fromEntries(Array.from(searchParams)) }); + const { pathname, searchParams } = new URL(req.url); + logger.trace("\n", { searchParams: Object.fromEntries(Array.from(searchParams)) }); - try { - verifyParams(searchParams); - } catch (e: any) { - return APIError(pathname, 400, "bad_query_input", e.message); - } + try { + verifyParams(searchParams); + } catch (e: any) { + return APIError(pathname, 400, "bad_query_input", e.message); + } - const query = getTotalSupply(searchParams); - let response; + const query = getTotalSupply(searchParams); + let response; - try { - response = await makeQuery(query); - } catch (e: any) { - return APIError(pathname, 500, "failed_database_query", e.message); - } + try { + response = await makeQuery(query); + } catch (e: any) { + return APIError(pathname, 500, "failed_database_query", e.message); + } + try { return toJSON( addMetadata( response, @@ -40,9 +39,6 @@ export default async function (req: Request) { ) ); } catch (e: any) { - logger.error(e); - prometheus.request_error.inc({ pathname: "/supply", status: 400 }); - - return new Response(e.message, { status: 400 }); + return APIError(pathname, 500, "failed_response", e.message); } } \ No newline at end of file diff --git a/src/fetch/transfers.ts b/src/fetch/transfers.ts index 73f506b..622892a 100644 --- a/src/fetch/transfers.ts +++ b/src/fetch/transfers.ts @@ -1,24 +1,23 @@ import { makeQuery } from "../clickhouse/makeQuery.js"; import { logger } from "../logger.js"; import { getTransfers } from "../queries.js"; -import * as prometheus from "../prometheus.js"; import { APIError, addMetadata, toJSON } from "./utils.js"; import { parseLimit, parsePage } from "../utils.js"; export default async function (req: Request) { + const { pathname, searchParams } = new URL(req.url); + logger.trace("\n", { searchParams: Object.fromEntries(Array.from(searchParams)) }); + + const query = getTransfers(searchParams); + let response; + try { - const { pathname, searchParams } = new URL(req.url); - logger.info({ searchParams: Object.fromEntries(Array.from(searchParams)) }); - - const query = getTransfers(searchParams); - let response; + response = await makeQuery(query); + } catch (e: any) { + return APIError(pathname, 500, "failed_database_query", e.message); + } - try { - response = await makeQuery(query); - } catch (e: any) { - return APIError(pathname, 500, "failed_database_query", e.message); - } - + try { return toJSON( addMetadata( response, @@ -27,9 +26,6 @@ export default async function (req: Request) { ) ); } catch (e: any) { - logger.error(e); - prometheus.request_error.inc({ pathname: "/transfers", status: 400 }); - - return new Response(e.message, { status: 400 }); + return APIError(pathname, 500, "failed_response", e.message); } } \ No newline at end of file diff --git a/src/fetch/utils.ts b/src/fetch/utils.ts index 5e1f83b..3d141b1 100644 --- a/src/fetch/utils.ts +++ b/src/fetch/utils.ts @@ -15,7 +15,7 @@ export function APIError(pathname: string, status: number, code?: string, detail detail: detail ? detail : "" } - logger.error(api_error); + logger.error("\n", api_error); prometheus.request_error.inc({ pathname, status }); return toJSON(api_error, status); } diff --git a/src/logger.ts b/src/logger.ts index 1eac7ab..f6b718f 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,5 @@ import { Logger, type ILogObj } from "tslog"; -import { APP_NAME, APP_VERSION } from "./config.js"; +import { APP_NAME, APP_VERSION, config } from "./config.js"; class TsLogger extends Logger { constructor() { @@ -11,11 +11,14 @@ class TsLogger extends Logger { public enable(type: "pretty" | "json" = "pretty") { this.settings.type = type; this.settings.minLevel = 0; + this.info("Enabled logger"); } public disable() { this.settings.type = "hidden"; + this.info("Disabled logger"); } } -export const logger = new TsLogger(); \ No newline at end of file +export const logger = new TsLogger(); +if (config.verbose) logger.enable(); \ No newline at end of file diff --git a/src/prometheus.ts b/src/prometheus.ts index a9c0b98..f86e91c 100644 --- a/src/prometheus.ts +++ b/src/prometheus.ts @@ -8,9 +8,10 @@ export const registry = new client.Registry(); export function registerCounter(name: string, help = "help", labelNames: string[] = [], config?: CounterConfiguration) { try { registry.registerMetric(new Counter({ name, help, labelNames, ...config })); + logger.debug(`Registered new counter metric: ${name}`); return registry.getSingleMetric(name) as Counter; } catch (e) { - logger.error({ name, e }); + logger.error("Error registering counter:", { name, e }); throw new Error(`${e}`); } } @@ -18,9 +19,10 @@ export function registerCounter(name: string, help = "help", labelNames: string[ export function registerGauge(name: string, help = "help", labelNames: string[] = [], config?: GaugeConfiguration) { try { registry.registerMetric(new Gauge({ name, help, labelNames, ...config })); + logger.debug(`Registered new gauge metric: ${name}`); return registry.getSingleMetric(name) as Gauge; } catch (e) { - logger.error({ name, e }); + logger.error("Error registering gauge:", { name, e }); throw new Error(`${e}`); } }