diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 906d6ed..a10cb76 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -4,7 +4,6 @@ categories: - title: "🚀 Features" labels: - "feature" - - "enhancement" - title: "🐛 Bug Fixes" labels: - "fix" @@ -25,6 +24,26 @@ version-resolver: labels: - "patch" default: patch +autolabeler: + - label: "documentation" + files: + - "*.md" + branch: + - '/docs{0,1}\/.+/' + - label: "bug" + branch: + - '/(hot)?fix\/.+/i' + title: + - "/fix/i" + - label: "feature" + branch: + - '/feature\/.+/' + - label: "chore" + branch: + - '/chore\/.+/' + - label: "ci" + files: + - ".github/workflows/*.yml" template: | ## Changes diff --git a/deno.jsonc b/deno.jsonc index 23ea75f..0039590 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,7 +1,15 @@ { "name": "@justinmchase/grove", "version": "0.5.4", - "exports": "./mod.ts", + "exports": { + ".": "./mod.ts", + "./controllers": "./src/controllers/mod.ts", + "./errors": "./src/errors/mod.ts", + "./logging": "./src/logging/mod.ts", + "./modes": "./src/modes/mod.ts", + "./services": "./src/services/mod.ts", + "./util": "./src/util/mod.ts" + }, "tasks": { "check": "deno fmt && deno lint && deno test && npx jsr publish --dry-run --allow-dirty", "example": "deno run -A example/main.ts", diff --git a/src/context.ts b/src/context.ts index f9fb784..d88c036 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,8 +1,8 @@ import type { State } from "@oak/oak"; -import type { ILogger } from "./logging/mod.ts"; +import type { Logger } from "./logging/mod.ts"; export interface IContext { - log: ILogger; + log: Logger; } export interface IState< diff --git a/src/controllers/error.controller.ts b/src/controllers/error.controller.ts index d8e905a..94954b1 100644 --- a/src/controllers/error.controller.ts +++ b/src/controllers/error.controller.ts @@ -35,7 +35,6 @@ export class ErrorController< ctx.response.body = { ok: false, message }; ctx.response.headers.set("Content-Type", "application/json"); ctx.state.context.log.error( - "server_error", `An unhandled error occurred: ${message}`, err, { diff --git a/src/controllers/github.controller.ts b/src/controllers/github.controller.ts index 0bed753..75b7743 100644 --- a/src/controllers/github.controller.ts +++ b/src/controllers/github.controller.ts @@ -10,7 +10,7 @@ import type { import type { GitHubService } from "../services/github.service.ts"; import type { IContext, IState } from "../context.ts"; import type { Controller } from "./controller.ts"; -import type { ILogger } from "../logging/mod.ts"; +import type { Logger } from "../logging/mod.ts"; export interface IGitHubWebhookConfig { githubWebhookPath: string; @@ -44,7 +44,7 @@ export abstract class GithubWebhookController< await undefined; } - private async handler(log: ILogger, req: Request, res: Response) { + private async handler(log: Logger, req: Request, res: Response) { const githubEvent = req.headers.get("X-GitHub-Event") as | GitHubEventName | "ping" @@ -55,8 +55,7 @@ export abstract class GithubWebhookController< | GitHubDeploymentProtectionRuleEvent; const { action, sender, repository } = event; log.info( - "github_webhook", - `github event ${githubEvent}`, + `github webhook event ${githubEvent}`, { githubEvent, action, @@ -87,15 +86,13 @@ export abstract class GithubWebhookController< } protected async unsupportedEvent( - log: ILogger, + log: Logger, githubEvent: string | null, res: Response, - // deno-lint-ignore no-explicit-any - body: any, + body: unknown, ) { log.warn( - "github_unsupported_event", - `The event ${githubEvent} was recieved but is not supported`, + `The github webhook event ${githubEvent} was recieved but is not supported`, { body }, ); res.status = Status.OK; @@ -106,11 +103,11 @@ export abstract class GithubWebhookController< } protected async handlePingEvent( - log: ILogger, + log: Logger, res: Response, event: GitHubPingEvent, ) { - log.debug("github_event_ping", `The ping event was received`, { event }); + log.debug(`A ping event was received`, { event }); res.status = Status.OK; res.body = { ok: true, @@ -119,14 +116,13 @@ export abstract class GithubWebhookController< } protected async handleInstallationEvent( - log: ILogger, + log: Logger, res: Response, event: GitHubInstallationEvent, ) { const { action, installation: { id, app_slug, account: { login } } } = event; log.debug( - "github_event_installation", `${app_slug} installation ${id} ${action} for ${login}`, { action, @@ -143,7 +139,7 @@ export abstract class GithubWebhookController< } protected async handleDeploymentProtectionRuleEvent( - log: ILogger, + log: Logger, res: Response, event: GitHubDeploymentProtectionRuleEvent, ) { @@ -157,7 +153,6 @@ export abstract class GithubWebhookController< sender: { id: senderId, login }, } = event; log.debug( - "github_event_deployment_protection_rule", `deployment protection rule ${action} for ${deploymentId}:${full_name}/${environment} by ${senderId}:${login}`, { action, diff --git a/src/controllers/log.controller.ts b/src/controllers/log.controller.ts index d3d2b49..00e0897 100644 --- a/src/controllers/log.controller.ts +++ b/src/controllers/log.controller.ts @@ -22,7 +22,6 @@ export class LogController< const ms = `${t}ms`; const { response: { status } } = ctx; ctx.state.context.log.info( - "request", `${status} ${method} ${url.pathname} ${ms}`, { status, diff --git a/src/grove.ts b/src/grove.ts index b3950e1..b752bb3 100644 --- a/src/grove.ts +++ b/src/grove.ts @@ -108,7 +108,6 @@ export class Grove { // todo: use after https://github.com/c4spar/deno-cliffy/issues/655 is resolved const defaultMode = this.config.modes[0].name; context.log.info( - "grove_default_mode", `no mode specified, using default mode ${defaultMode}`, { mode: defaultMode, args }, ); @@ -133,25 +132,17 @@ export class Grove { private async run(args: unknown, context: TContext, mode: IMode) { const { name } = mode; - context.log.info("grove_run", `grove running mode ${name}`, { + context.log.info(`grove running mode ${name}`, { mode: name, args, }); try { await mode.run(args, context); } catch (err) { - if (err instanceof Error) { - context.log.error("grove_runtime_error", err.message, err, { - mode: mode.name, - args, - }); - } else { - context.log.error("grove_runtime_error", "Unknown error", new Error(), { - mode: mode.name, - args, - err, - }); - } + context.log.error("grove error", err, { + mode: mode.name, + args, + }); } } } diff --git a/src/logging/aggregate.logger.ts b/src/logging/aggregate.logger.ts index ad0ea78..7a0715c 100644 --- a/src/logging/aggregate.logger.ts +++ b/src/logging/aggregate.logger.ts @@ -1,21 +1,20 @@ -import { Logger } from "./logger.ts"; +import { BaseLogger } from "./base.logger.ts"; import type { LogLevel } from "./logLevel.ts"; -import type { ILogger } from "./logger.interface.ts"; +import type { Logger } from "./logger.interface.ts"; -export class AggregateLogger extends Logger { - private readonly loggers: ILogger[]; - constructor(...loggers: ILogger[]) { +export class AggregateLogger extends BaseLogger { + private readonly loggers: Logger[]; + constructor(...loggers: Logger[]) { super(); this.loggers = [...loggers]; } public async log( level: LogLevel, - name: string, message: string, - data: Record, + data: Record, ) { await Promise.all( - this.loggers.map((logger) => logger.log(level, name, message, data)), + this.loggers.map((logger) => logger.log(level, message, data)), ); } } diff --git a/src/logging/azure.logger.ts b/src/logging/azure.logger.ts index 5dfe05f..b776e68 100644 --- a/src/logging/azure.logger.ts +++ b/src/logging/azure.logger.ts @@ -1,14 +1,13 @@ -import { snakeCase } from "@wok/case"; import { encodeBase64 } from "@std/encoding/base64"; import { toSerializable } from "@justinmchase/serializable"; import { Status } from "@oak/oak"; -import { Logger } from "./logger.ts"; +import { BaseLogger } from "./base.logger.ts"; import { UnexpectedStatusError } from "../errors/mod.ts"; import { readOptionalString, readRequiredString } from "../util/config.ts"; import { hmacCreateKey, hmacSign } from "../util/hmac.ts"; import type { LogLevel } from "./logLevel.ts"; -export class AzureLogger extends Logger { +export class AzureLogger extends BaseLogger { constructor( private readonly workspaceId: string, private readonly cryptoKey: CryptoKey, @@ -30,17 +29,14 @@ export class AzureLogger extends Logger { public async log( level: LogLevel, - name: string, message: string, - data: Record, + data: Record, ) { - const n = snakeCase(name); const m = JSON.stringify(message.replace(/"/g, "'")); const d = JSON.stringify(toSerializable(data)); - const line = `${level} ${n} ${m} ${d}`; + const line = `${level} ${m} ${d}`; const json = JSON.stringify([{ level, - name: n, message: m, data: d, line, diff --git a/src/logging/base.logger.ts b/src/logging/base.logger.ts new file mode 100644 index 0000000..bf3d21b --- /dev/null +++ b/src/logging/base.logger.ts @@ -0,0 +1,56 @@ +import { LogLevel } from "./logLevel.ts"; +import type { Logger } from "./logger.interface.ts"; + +export abstract class BaseLogger implements Logger { + public abstract log( + level: LogLevel, + message: string, + data: Record, + ): Promise; + + public async trace( + message: string, + data: Record = {}, + ) { + await this.log(LogLevel.Trace, message, data); + } + + public async debug( + message: string, + data: Record = {}, + ) { + await this.log(LogLevel.Debug, message, data); + } + + public async warn( + message: string, + data: Record = {}, + ) { + await this.log(LogLevel.Warn, message, data); + } + + public async info( + message: string, + data: Record = {}, + ) { + await this.log(LogLevel.Info, message, data); + } + + public async error( + message: string, + error: unknown, + data: Record = {}, + ) { + await this.log(LogLevel.Error, message, { + ...data, + error, + }); + } + + public async critical( + message: string, + data: Record = {}, + ) { + await this.log(LogLevel.Critical, message, data); + } +} diff --git a/src/logging/console.logger.ts b/src/logging/console.logger.ts index a522abc..d900cfa 100644 --- a/src/logging/console.logger.ts +++ b/src/logging/console.logger.ts @@ -1,4 +1,3 @@ -import { snakeCase } from "@wok/case"; import { bgBlue, bgRed, @@ -10,9 +9,9 @@ import { } from "@std/fmt/colors"; import { toSerializable } from "@justinmchase/serializable"; import { LogLevel } from "./logLevel.ts"; -import { Logger } from "./logger.ts"; +import { BaseLogger } from "./base.logger.ts"; -export class ConsoleLogger extends Logger { +export class ConsoleLogger extends BaseLogger { private readonly isTTY: boolean; constructor() { super(); @@ -20,11 +19,9 @@ export class ConsoleLogger extends Logger { } public async log( level: LogLevel, - name: string, message: string, - data: Record, + data: Record, ) { - const n = snakeCase(name); const m = JSON.stringify((message ?? "").replace(/"/g, "'")); const d = JSON.stringify(toSerializable(data)); const l = (() => { @@ -47,6 +44,6 @@ export class ConsoleLogger extends Logger { return level; } })(); - await console.log(`${l} ${n} ${m} ${d}`); + await console.log(`${l} ${m} ${d}`); } } diff --git a/src/logging/logger.interface.ts b/src/logging/logger.interface.ts index 24393d3..ed6a4e0 100644 --- a/src/logging/logger.interface.ts +++ b/src/logging/logger.interface.ts @@ -1,41 +1,34 @@ import type { LogLevel } from "./logLevel.ts"; -export interface ILogger { +export type Logger = { log( level: LogLevel, - name: string, message: string, - data?: Record, + data?: Record, ): Promise; trace( - name: string, message: string, - data?: Record, + data?: Record, ): Promise; debug( - name: string, message: string, - data?: Record, + data?: Record, ): Promise; warn( - name: string, message: string, - data?: Record, + data?: Record, ): Promise; info( - name: string, message: string, - data?: Record, + data?: Record, ): Promise; error( - name: string, message: string, - error: Error, - data?: Record, + error: unknown, + data?: Record, ): Promise; critical( - name: string, message: string, - data?: Record, + data?: Record, ): Promise; -} +}; diff --git a/src/logging/logger.service.test.ts b/src/logging/logger.service.test.ts index 854f007..64331b9 100644 --- a/src/logging/logger.service.test.ts +++ b/src/logging/logger.service.test.ts @@ -6,14 +6,13 @@ import { LogLevel } from "./logLevel.ts"; function logAsserter(logger: ConsoleLogger) { return ( level: LogLevel, - name: string, message: string, data: SerializableRecord, result: string, ) => { const consoleLog = stub(console, "log"); try { - logger.log(level, name, message, data); + logger.log(level, message, data); assertSpyCalls(consoleLog, 1); assertSpyCall(consoleLog, 0, { args: [result], @@ -26,7 +25,6 @@ function logAsserter(logger: ConsoleLogger) { } function logErrorAsserter(logger: ConsoleLogger) { return ( - name: string, message: string, error: Error, data: SerializableRecord, @@ -35,7 +33,7 @@ function logErrorAsserter(logger: ConsoleLogger) { const consoleLog = stub(console, "log"); error.stack = ""; try { - logger.error(name, message, error, data); + logger.error(message, error, data); assertSpyCalls(consoleLog, 1); assertSpyCall(consoleLog, 0, { args: [result], @@ -59,10 +57,9 @@ Deno.test({ fn: () => assertLog( LogLevel.Trace, - "example_test", "log test", {}, - 'T example_test "log test" {}', + 'T "log test" {}', ), }); await t.step({ @@ -70,10 +67,9 @@ Deno.test({ fn: () => assertLog( LogLevel.Debug, - "example_test", "log test", {}, - 'D example_test "log test" {}', + 'D "log test" {}', ), }); await t.step({ @@ -81,10 +77,9 @@ Deno.test({ fn: () => assertLog( LogLevel.Warn, - "example_test", "log test", {}, - 'W example_test "log test" {}', + 'W "log test" {}', ), }); await t.step({ @@ -92,10 +87,9 @@ Deno.test({ fn: () => assertLog( LogLevel.Info, - "example_test", "log test", {}, - 'I example_test "log test" {}', + 'I "log test" {}', ), }); await t.step({ @@ -103,10 +97,9 @@ Deno.test({ fn: () => assertLog( LogLevel.Error, - "example_test", "log test", {}, - 'E example_test "log test" {}', + 'E "log test" {}', ), }); await t.step({ @@ -114,54 +107,18 @@ Deno.test({ fn: () => assertLog( LogLevel.Critical, - "example_test", "log test", {}, - 'C example_test "log test" {}', - ), - }); - await t.step({ - name: "log_name_00", - fn: () => - assertLog( - LogLevel.Debug, - "example test", - "log test", - {}, - 'D example_test "log test" {}', - ), - }); - await t.step({ - name: "log_name_01", - fn: () => - assertLog( - LogLevel.Debug, - "ExampleTest", - "log test", - {}, - 'D example_test "log test" {}', - ), - }); - await t.step({ - name: "log_name_02", - fn: () => - assertLog( - LogLevel.Debug, - "a-b-c", - "log test", - {}, - 'D a_b_c "log test" {}', + 'C "log test" {}', ), }); await t.step({ name: "log_message_00", - fn: () => - assertLog(LogLevel.Debug, "test", "a b c", {}, 'D test "a b c" {}'), + fn: () => assertLog(LogLevel.Debug, "a b c", {}, 'D "a b c" {}'), }); await t.step({ name: "log_message_00", - fn: () => - assertLog(LogLevel.Debug, "test", 'a"b"c', {}, "D test \"a'b'c\" {}"), + fn: () => assertLog(LogLevel.Debug, 'a"b"c', {}, "D \"a'b'c\" {}"), }); await t.step({ name: "log_data_00", @@ -169,20 +126,18 @@ Deno.test({ assertLog( LogLevel.Debug, "test", - "test", { a: 1 }, - 'D test "test" {"a":1}', + 'D "test" {"a":1}', ), }); await t.step({ name: "log_error_00", fn: () => assertError( - "test", "test", new Error("test"), {}, - 'E test "test" {"error":{"name":"Error","message":"test","stack":""}}', + 'E "test" {"error":{"name":"Error","message":"test","stack":""}}', ), }); }, diff --git a/src/logging/logger.ts b/src/logging/logger.ts deleted file mode 100644 index 6417db7..0000000 --- a/src/logging/logger.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { LogLevel } from "./logLevel.ts"; -import type { ILogger } from "./logger.interface.ts"; - -export abstract class Logger implements ILogger { - public abstract log( - level: LogLevel, - name: string, - message: string, - data: Record, - ): Promise; - - public async trace( - name: string, - message: string, - data: Record = {}, - ) { - await this.log(LogLevel.Trace, name, message, data); - } - - public async debug( - name: string, - message: string, - data: Record = {}, - ) { - await this.log(LogLevel.Debug, name, message, data); - } - - public async warn( - name: string, - message: string, - data: Record = {}, - ) { - await this.log(LogLevel.Warn, name, message, data); - } - - public async info( - name: string, - message: string, - data: Record = {}, - ) { - await this.log(LogLevel.Info, name, message, data); - } - - public async error( - name: string, - message: string, - error: Error, - data: Record = {}, - ) { - await this.log(LogLevel.Error, name, message, { - ...data, - error, - }); - } - - public async critical( - name: string, - message: string, - data: Record = {}, - ) { - await this.log(LogLevel.Critical, name, message, data); - } -} diff --git a/src/logging/memory.logger.ts b/src/logging/memory.logger.ts index e960a9b..6aa19cc 100644 --- a/src/logging/memory.logger.ts +++ b/src/logging/memory.logger.ts @@ -1,22 +1,19 @@ import type { LogLevel } from "./logLevel.ts"; -import { Logger } from "./logger.ts"; +import { BaseLogger } from "./base.logger.ts"; -export class MemoryLogger extends Logger { +export class MemoryLogger extends BaseLogger { public readonly logs: { level: LogLevel; - name: string; message: string; data: Record; }[] = []; public async log( level: LogLevel, - name: string, message: string, data: Record, ) { await this.logs.push({ level, - name, message, data, }); diff --git a/src/logging/mod.ts b/src/logging/mod.ts index c562372..d5a1b9f 100644 --- a/src/logging/mod.ts +++ b/src/logging/mod.ts @@ -1,7 +1,7 @@ export * from "./aggregate.logger.ts"; export * from "./azure.logger.ts"; +export * from "./base.logger.ts"; export * from "./console.logger.ts"; export * from "./logger.interface.ts"; -export * from "./logger.ts"; export * from "./logLevel.ts"; export * from "./memory.logger.ts"; diff --git a/src/modes/job/job.mode.ts b/src/modes/job/job.mode.ts index ac21436..0e26659 100644 --- a/src/modes/job/job.mode.ts +++ b/src/modes/job/job.mode.ts @@ -46,14 +46,7 @@ export class JobMode implements IMode { try { await job.run(args, context); } catch (err) { - if (err instanceof Error) { - context.log.error("grove_job_error", err.message, err, { name }); - } else { - context.log.error("grove_job_error", "Unknown error", new Error(), { - name, - err, - }); - } + context.log.error("job mode error", err, { name }); } } } diff --git a/src/modes/web/web.mode.ts b/src/modes/web/web.mode.ts index 82f019d..12ed222 100644 --- a/src/modes/web/web.mode.ts +++ b/src/modes/web/web.mode.ts @@ -59,7 +59,7 @@ export class WebMode> public async run(args: WebArgs, context: TContext) { const { port, hostname } = args; - context.log.info("grove_web_start", `Server starting...`, { port }); + context.log.info(`Server starting...`, { port }); const app = new Application(); app.use(async (ctx, next) => { ctx.state.context = context; @@ -68,11 +68,8 @@ export class WebMode> await this.config.initControllers(context, app); app.addEventListener("listen", (_event) => { context.log.info( - "grove_web_listening", `Listening on http://${hostname}:${port}`, - { - port, - }, + { port }, ); }); app.addEventListener("error", (err) => { @@ -81,7 +78,6 @@ export class WebMode> err.context?.request || {} as Request; context.log.error( - "grove_web_error", `unexpected server error: ${err.message}`, { timeStamp, diff --git a/src/services/github.service.ts b/src/services/github.service.ts index 71577bd..7725d60 100644 --- a/src/services/github.service.ts +++ b/src/services/github.service.ts @@ -4,7 +4,7 @@ import { SignatureError } from "../errors/signature.error.ts"; import { hmacCreateKey, hmacVerify } from "../util/hmac.ts"; import { MemoryCache } from "../util/cache.ts"; import type { Request } from "@oak/oak/request"; -import type { ILogger } from "../logging/mod.ts"; +import type { Logger } from "../logging/mod.ts"; type GitHubConfig = { githubAppId?: number; @@ -22,17 +22,17 @@ export class GitHubService { } public static async create( - log: ILogger, + log: Logger, config: GitHubConfig, ): Promise { - const { githubAppId, githubPrivateKey, githubWebhookSecret } = config; - + const { githubAppId, githubPrivateKey, githubPat, githubWebhookSecret } = + config; log.debug( - "github_service", "github service initializing", { githubAppId, githubPrivateKey: !!githubPrivateKey, + githubPat: !!githubPat, githubWebhookSecret: !!githubWebhookSecret, }, ); diff --git a/src/services/mongo.service.ts b/src/services/mongo.service.ts index e31a758..fdfd585 100644 --- a/src/services/mongo.service.ts +++ b/src/services/mongo.service.ts @@ -1,6 +1,6 @@ import { MongoClient } from "@db/mongo/client"; import type { Collection, Database, Document } from "@db/mongo"; -import type { ILogger } from "../../mod.ts"; +import type { Logger } from "../../mod.ts"; export interface IMongoConfig { mongoConnectionString: string; @@ -24,7 +24,7 @@ export class MongoService { } public static async create( - logging: ILogger, + logging: Logger, config: IMongoConfig, ): Promise { const { mongoConnectionString } = config; @@ -43,11 +43,8 @@ export class MongoService { // } const db = await client.connect(mongoConnectionString); logging.info( - `mongo`, "mongo connected", - { - name: db.name, - }, + { name: db.name }, ); return new MongoService(client, db); }