Create Logger class for debugging

This commit is contained in:
2026-05-23 11:30:35 +02:00
parent adc758dfa5
commit a9f280914d
9 changed files with 81 additions and 40 deletions

View File

@@ -1,4 +1,4 @@
import Debug from "debug"; import { Logger } from "./utils/logger.js";
import { Engine } from "@xo-cash/engine"; import { Engine } from "@xo-cash/engine";
import { Config } from "./services/config.js"; import { Config } from "./services/config.js";
@@ -22,7 +22,7 @@ type VendingMachineDeps = {
export class VendingMachine { export class VendingMachine {
static async from(config: Config) { static async from(config: Config) {
const debug = Debug("vending-machine"); const debug = new Logger("vending-machine");
debug("Config: %O", config); debug("Config: %O", config);
@@ -65,7 +65,7 @@ export class VendingMachine {
const httpService = new HTTPService({ const httpService = new HTTPService({
routes, routes,
config: config.server, config: config.server,
debug, debug: new Logger("vending-machine"),
}); });
debug("Creating vending machine"); debug("Creating vending machine");

View File

@@ -1,4 +1,4 @@
import type { Debugger as Debug } from "debug"; import type { Logger } from '../utils/logger.js';
import type { RouteOptions, FastifyRequest, FastifyReply } from "fastify"; import type { RouteOptions, FastifyRequest, FastifyReply } from "fastify";
import type { Engine } from "@xo-cash/engine"; import type { Engine } from "@xo-cash/engine";
import type { Database } from "../services/database/database.js"; import type { Database } from "../services/database/database.js";
@@ -8,7 +8,7 @@ import { z } from "zod";
export type ItemsRouteDeps = { export type ItemsRouteDeps = {
database: Database; database: Database;
engine: Engine; engine: Engine;
debug: Debug; debug: Logger;
}; };
export class ItemsRoute { export class ItemsRoute {

View File

@@ -1,4 +1,4 @@
import type { Debugger as Debug } from "debug"; import type { Logger } from '../utils/logger.js';
import type { RouteOptions, FastifyRequest, FastifyReply } from "fastify"; import type { RouteOptions, FastifyRequest, FastifyReply } from "fastify";
import { z } from "zod"; import { z } from "zod";
@@ -13,7 +13,7 @@ export type OrdersRouteDeps = {
database: Database; database: Database;
orderPaymentService: OrderPaymentService; orderPaymentService: OrderPaymentService;
syncServerUrl: string; syncServerUrl: string;
debug: Debug; debug: Logger;
}; };
export class OrdersRoute { export class OrdersRoute {

View File

@@ -1,18 +1,19 @@
import { type Debugger } from "debug"; import { Logger } from '../../utils/logger.js';
import DatabaseConstructor from "better-sqlite3"; import DatabaseConstructor from "better-sqlite3";
import { Kysely, SqliteDialect } from "kysely"; import { Kysely, SqliteDialect } from "kysely";
import type { Database as DatabaseTables } from "./tables.js"; import type { Database as DatabaseTables } from "./tables.js";
import { z } from "zod"; import { z } from "zod";
import { debuggerSchema } from "../../types.js";
export const databaseOptionsSchema = z.object({ export const databaseOptionsSchema = z.object({
config: z.object({ config: z.object({
path: z.string(), path: z.string(),
}), }),
debug: debuggerSchema, debug: z.custom<Logger>(Logger.isLogger, {
message: "Expected a Logger instance",
}),
}); });
type DatabaseOptionsInput = z.input<typeof databaseOptionsSchema>; type DatabaseOptionsInput = z.input<typeof databaseOptionsSchema>;
@@ -26,7 +27,7 @@ type DatabaseOptionsInput = z.input<typeof databaseOptionsSchema>;
*/ */
export class Database { export class Database {
// Debugger instance used for logging. // Debugger instance used for logging.
private readonly debug: Debugger; private readonly debug: Logger;
// SQLite connection instance. // SQLite connection instance.
private readonly sqlite: DatabaseConstructor.Database; private readonly sqlite: DatabaseConstructor.Database;

View File

@@ -1,10 +1,9 @@
import Debug from "debug";
import { z } from "zod"; import { z } from "zod";
import fastify, { type FastifyInstance, type RouteOptions } from "fastify"; import fastify, { type FastifyInstance, type RouteOptions } from "fastify";
import cors from "@fastify/cors"; import cors from "@fastify/cors";
import { debuggerSchema } from "../types.js"; import { Logger } from "../utils/logger.js";
// Interface to add to our route classes so that we can register them. // Interface to add to our route classes so that we can register them.
// NOTE: I hate this pattern. But ExpressJS is odd in that it is structured as a singleton that still needs registration. // NOTE: I hate this pattern. But ExpressJS is odd in that it is structured as a singleton that still needs registration.
@@ -33,9 +32,12 @@ export const serverConfigSchema = z.object({
}); });
// Zod schema for the server debug instance. // Zod schema for the server debug instance.
export const serverDebugSchema = debuggerSchema export const serverDebugSchema = z
.custom<Logger>(Logger.isLogger, {
message: "Expected a Logger instance",
})
.optional() .optional()
.default(Debug("vending-machine")); .default(new Logger("vending-machine"));
// Zod schema for the HTTP service options. // Zod schema for the HTTP service options.
export const HTTPOptions = z.object({ export const HTTPOptions = z.object({
@@ -61,7 +63,7 @@ export class HTTPService {
private server: FastifyInstance; private server: FastifyInstance;
// Private properties. // Private properties.
private debug: debug.Debugger; private debug: Logger;
private config: HTTPServiceOptions["config"]; private config: HTTPServiceOptions["config"];
private routes: HTTPServiceOptions["routes"]; private routes: HTTPServiceOptions["routes"];

View File

@@ -1,4 +1,4 @@
import type { Debugger } from "debug"; import type { Logger } from '../utils/logger.js';
import type { Engine } from "@xo-cash/engine"; import type { Engine } from "@xo-cash/engine";
import type { XOInvitation, XOInvitationCommit } from "@xo-cash/types"; import type { XOInvitation, XOInvitationCommit } from "@xo-cash/types";
@@ -11,7 +11,7 @@ export type OrderInvitationTrackerDeps = {
database: Database; database: Database;
orderId: string; orderId: string;
invitation: XOInvitation; invitation: XOInvitation;
debug: Debugger; debug: Logger;
}; };
/** /**

View File

@@ -1,11 +1,10 @@
import type { Debugger } from "debug"; import type { Logger } from '../utils/logger.js';
import type { Engine } from "@xo-cash/engine"; import type { Engine } from "@xo-cash/engine";
import { serializeInvitation } from "@xo-cash/engine"; import { serializeInvitation } from "@xo-cash/engine";
import { vendingMachineTemplate } from "../templates/vending-machine.js"; import { vendingMachineTemplate } from "../templates/vending-machine.js";
import type { Config } from "./config.js"; import type { Config } from "./config.js";
import type { Database } from "./database/database.js"; import type { Database } from "./database/database.js";
import type { ItemsTable } from "./database/tables.js";
import { InvitationSyncClient } from "./invitation-sync-client.js"; import { InvitationSyncClient } from "./invitation-sync-client.js";
import { OrderInvitationTracker } from "./order-invitation-tracker.js"; import { OrderInvitationTracker } from "./order-invitation-tracker.js";
@@ -40,7 +39,7 @@ export type OrderPaymentServiceDeps = {
database: Database; database: Database;
config: Config; config: Config;
syncClient: InvitationSyncClient; syncClient: InvitationSyncClient;
debug: Debugger; debug: Logger;
templateIdentifier: string; templateIdentifier: string;
trackers: Map<string, OrderInvitationTracker>; trackers: Map<string, OrderInvitationTracker>;
}; };
@@ -55,7 +54,7 @@ export class OrderPaymentService {
engine: Engine; engine: Engine;
database: Database; database: Database;
config: Config; config: Config;
debug: Debugger; debug: Logger;
trackers: Map<string, OrderInvitationTracker>; trackers: Map<string, OrderInvitationTracker>;
}): Promise<OrderPaymentService> { }): Promise<OrderPaymentService> {
const { templateIdentifier } = await deps.engine.importTemplate( const { templateIdentifier } = await deps.engine.importTemplate(

View File

@@ -1,18 +0,0 @@
import { type Debugger } from "debug";
import { z } from "zod";
export const isDebugger = (value: unknown): value is Debugger => {
if (typeof value !== "function") return false;
const candidate = value as Partial<Debugger>;
return (
typeof candidate.namespace === "string" &&
typeof candidate.extend === "function" &&
typeof candidate.enabled === "boolean"
);
};
export const debuggerSchema = z.custom<Debugger>(isDebugger, {
message: "Expected a debug.Debugger instance",
});

57
src/utils/logger.ts Normal file
View File

@@ -0,0 +1,57 @@
import Debug, { type Debugger } from "debug";
type LogHandler = {
(...args: Parameters<Debugger>): void;
extend: (namespace: string) => LogHandler;
}
/**
* Declares that Logger instances may also be invoked as functions.
*/
export interface Logger {
(...args: unknown[]): void;
}
export class Logger {
public readonly namespace!: string;
private readonly handler!: LogHandler;
public constructor(
namespace: string,
handler: LogHandler = Debug(namespace),
) {
const logger = ((...args: Parameters<Debugger>): void => {
handler(...args);
}) as Logger;
/*
* This makes `logger instanceof Logger` true and provides
* access to instance methods such as `extend`.
*/
Object.setPrototypeOf(logger, new.target.prototype);
Object.defineProperties(logger, {
namespace: {
value: namespace,
enumerable: true,
},
handler: {
value: handler,
},
});
return logger;
}
public extend(childNamespace: string): Logger {
return new Logger(
childNamespace,
this.handler.extend(childNamespace),
);
}
static isLogger(value: unknown): value is Logger {
return value instanceof Logger;
}
}