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 { Config } from "./services/config.js";
@@ -22,7 +22,7 @@ type VendingMachineDeps = {
export class VendingMachine {
static async from(config: Config) {
const debug = Debug("vending-machine");
const debug = new Logger("vending-machine");
debug("Config: %O", config);
@@ -65,7 +65,7 @@ export class VendingMachine {
const httpService = new HTTPService({
routes,
config: config.server,
debug,
debug: new Logger("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 { Engine } from "@xo-cash/engine";
import type { Database } from "../services/database/database.js";
@@ -8,7 +8,7 @@ import { z } from "zod";
export type ItemsRouteDeps = {
database: Database;
engine: Engine;
debug: Debug;
debug: Logger;
};
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 { z } from "zod";
@@ -13,7 +13,7 @@ export type OrdersRouteDeps = {
database: Database;
orderPaymentService: OrderPaymentService;
syncServerUrl: string;
debug: Debug;
debug: Logger;
};
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 { Kysely, SqliteDialect } from "kysely";
import type { Database as DatabaseTables } from "./tables.js";
import { z } from "zod";
import { debuggerSchema } from "../../types.js";
export const databaseOptionsSchema = z.object({
config: z.object({
path: z.string(),
}),
debug: debuggerSchema,
debug: z.custom<Logger>(Logger.isLogger, {
message: "Expected a Logger instance",
}),
});
type DatabaseOptionsInput = z.input<typeof databaseOptionsSchema>;
@@ -26,7 +27,7 @@ type DatabaseOptionsInput = z.input<typeof databaseOptionsSchema>;
*/
export class Database {
// Debugger instance used for logging.
private readonly debug: Debugger;
private readonly debug: Logger;
// SQLite connection instance.
private readonly sqlite: DatabaseConstructor.Database;

View File

@@ -1,10 +1,9 @@
import Debug from "debug";
import { z } from "zod";
import fastify, { type FastifyInstance, type RouteOptions } from "fastify";
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.
// 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.
export const serverDebugSchema = debuggerSchema
export const serverDebugSchema = z
.custom<Logger>(Logger.isLogger, {
message: "Expected a Logger instance",
})
.optional()
.default(Debug("vending-machine"));
.default(new Logger("vending-machine"));
// Zod schema for the HTTP service options.
export const HTTPOptions = z.object({
@@ -61,7 +63,7 @@ export class HTTPService {
private server: FastifyInstance;
// Private properties.
private debug: debug.Debugger;
private debug: Logger;
private config: HTTPServiceOptions["config"];
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 { XOInvitation, XOInvitationCommit } from "@xo-cash/types";
@@ -11,7 +11,7 @@ export type OrderInvitationTrackerDeps = {
database: Database;
orderId: string;
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 { serializeInvitation } from "@xo-cash/engine";
import { vendingMachineTemplate } from "../templates/vending-machine.js";
import type { Config } from "./config.js";
import type { Database } from "./database/database.js";
import type { ItemsTable } from "./database/tables.js";
import { InvitationSyncClient } from "./invitation-sync-client.js";
import { OrderInvitationTracker } from "./order-invitation-tracker.js";
@@ -40,7 +39,7 @@ export type OrderPaymentServiceDeps = {
database: Database;
config: Config;
syncClient: InvitationSyncClient;
debug: Debugger;
debug: Logger;
templateIdentifier: string;
trackers: Map<string, OrderInvitationTracker>;
};
@@ -55,7 +54,7 @@ export class OrderPaymentService {
engine: Engine;
database: Database;
config: Config;
debug: Debugger;
debug: Logger;
trackers: Map<string, OrderInvitationTracker>;
}): Promise<OrderPaymentService> {
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;
}
}