diff --git a/package.json b/package.json index 44684ba..c92cc2d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@octokit/webhooks": "^13.1.0", "@sinclair/typebox": "^0.32.5", "@supabase/supabase-js": "2.42.0", + "@ubiquity-dao/ubiquibot-logger": "^1.2.0", "dotenv": "^16.4.4", "ms": "^2.1.3", "typebox-validators": "^0.3.5" @@ -85,4 +86,4 @@ ] }, "packageManager": "yarn@4.2.2" -} \ No newline at end of file +} diff --git a/src/adapters/supabase/pretty-logs.ts b/src/adapters/supabase/pretty-logs.ts deleted file mode 100644 index 1b55b14..0000000 --- a/src/adapters/supabase/pretty-logs.ts +++ /dev/null @@ -1,200 +0,0 @@ -import util from "util"; -type PrettyLogsWithOk = "ok" | LogLevel; -export class PrettyLogs { - constructor() { - this.ok = this.ok.bind(this); - this.info = this.info.bind(this); - this.warn = this.warn.bind(this); - this.error = this.error.bind(this); - this.fatal = this.fatal.bind(this); - this.debug = this.debug.bind(this); - this.verbose = this.verbose.bind(this); - } - public fatal(message: string, metadata?: Metadata | unknown) { - this._logWithStack(LogLevel.FATAL, message, metadata); - } - - public error(message: string, metadata?: Metadata | unknown) { - this._logWithStack(LogLevel.ERROR, message, metadata); - } - - public ok(message: string, metadata?: Metadata | unknown) { - this._logWithStack("ok", message, metadata); - } - - public warn(message: string, metadata?: Metadata | unknown) { - this._logWithStack(LogLevel.ERROR, message, metadata); - } - - public info(message: string, metadata?: Metadata | unknown) { - this._logWithStack(LogLevel.INFO, message, metadata); - } - - public debug(message: string, metadata?: Metadata | unknown) { - this._logWithStack(LogLevel.DEBUG, message, metadata); - } - - public verbose(message: string, metadata?: Metadata | unknown) { - this._logWithStack(LogLevel.VERBOSE, message, metadata); - } - - private _logWithStack(type: "ok" | LogLevel, message: string, metadata?: Metadata | unknown | string) { - this._log(type, message); - if (typeof metadata === "string") { - this._log(type, metadata); - return; - } - - if (!metadata) return; - - let stack: string | undefined; - const data = metadata as Metadata; - - if (data && data.error instanceof Error) { - stack = data.error.stack; - } - - if (!stack && data) { - stack = data.stack as string; - } - - if (!stack) { - // generate and remove the top four lines of the stack trace - const stackTrace = new Error().stack?.split("\n"); - if (stackTrace) { - stackTrace.splice(0, 4); - stack = stackTrace.filter((line) => line.includes(".ts:")).join("\n"); - } - } - - const newMetadata: Metadata = { ...metadata }; - delete newMetadata.message; - delete newMetadata.name; - delete newMetadata.stack; - - if (!this._isEmpty(newMetadata)) { - this._log(type, newMetadata); - } - - if (typeof stack == "string") { - const prettyStack = this._formatStackTrace(stack, 1); - const colorizedStack = this._colorizeText(prettyStack, Colors.dim); - this._log(type, colorizedStack); - } else if (stack) { - const prettyStack = this._formatStackTrace((stack as unknown as string[]).join("\n"), 1); - const colorizedStack = this._colorizeText(prettyStack, Colors.dim); - this._log(type, colorizedStack); - } else { - throw new Error("Stack is null"); - } - } - - private _colorizeText(text: string, color: Colors): string { - if (!color) { - throw new Error(`Invalid color: ${color}`); - } - return color.concat(text).concat(Colors.reset); - } - - private _formatStackTrace(stack: string, linesToRemove = 0, prefix = ""): string { - const lines = stack.split("\n"); - for (let i = 0; i < linesToRemove; i++) { - lines.shift(); // Remove the top line - } - return lines - .map((line) => `${prefix}${line.replace(/\s*at\s*/, " ↳ ")}`) // Replace 'at' and prefix every line - .join("\n"); - } - - private _isEmpty(obj: Record) { - return !Reflect.ownKeys(obj).some((key) => typeof obj[String(key)] !== "function"); - } - - private _log(type: PrettyLogsWithOk, message: string | Metadata) { - const defaultSymbols: Record = { - fatal: "×", - ok: "✓", - error: "⚠", - info: "›", - debug: "››", - verbose: "💬", - }; - - const symbol = defaultSymbols[type]; - - // Formatting the message - const messageFormatted = typeof message === "string" ? message : util.inspect(message, { showHidden: true, depth: null, breakLength: Infinity }); - // const messageFormatted = - // typeof message === "string" ? message : JSON.stringify(Logs.convertErrorsIntoObjects(message)); - - // Constructing the full log string with the prefix symbol - const lines = messageFormatted.split("\n"); - const logString = lines - .map((line, index) => { - // Add the symbol only to the first line and keep the indentation for the rest - const prefix = index === 0 ? `\t${symbol}` : `\t${" ".repeat(symbol.length)}`; - return `${prefix} ${line}`; - }) - .join("\n"); - - const fullLogString = logString; - - const colorMap: Record = { - fatal: ["error", Colors.fgRed], - ok: ["log", Colors.fgGreen], - error: ["warn", Colors.fgYellow], - info: ["info", Colors.dim], - debug: ["debug", Colors.fgMagenta], - verbose: ["debug", Colors.dim], - }; - - const _console = console[colorMap[type][0] as keyof typeof console] as (...args: string[]) => void; - if (typeof _console === "function") { - _console(this._colorizeText(fullLogString, colorMap[type][1])); - } else { - throw new Error(fullLogString); - } - } -} -export interface Metadata { - error?: { stack?: string }; - stack?: string; - message?: string; - name?: string; - [key: string]: NonNullable | undefined; -} - -enum Colors { - reset = "\x1b[0m", - bright = "\x1b[1m", - dim = "\x1b[2m", - underscore = "\x1b[4m", - blink = "\x1b[5m", - reverse = "\x1b[7m", - hidden = "\x1b[8m", - - fgBlack = "\x1b[30m", - fgRed = "\x1b[31m", - fgGreen = "\x1b[32m", - fgYellow = "\x1b[33m", - fgBlue = "\x1b[34m", - fgMagenta = "\x1b[35m", - fgCyan = "\x1b[36m", - fgWhite = "\x1b[37m", - - bgBlack = "\x1b[40m", - bgRed = "\x1b[41m", - bgGreen = "\x1b[42m", - bgYellow = "\x1b[43m", - bgBlue = "\x1b[44m", - bgMagenta = "\x1b[45m", - bgCyan = "\x1b[46m", - bgWhite = "\x1b[47m", -} -export enum LogLevel { - FATAL = "fatal", - ERROR = "error", - INFO = "info", - VERBOSE = "verbose", - DEBUG = "debug", -} diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index 1238180..42c87bc 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -31,7 +31,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] }); commitHash = hashResponse.data.sha; } catch (e) { - logger.error("Error while getting commit hash", e); + logger.error("Error while getting commit hash", { error: e as Error }); } // check max assigned issues @@ -40,7 +40,7 @@ export async function start(context: Context, issue: Context["payload"]["issue"] logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: ${JSON.stringify(openedPullRequests)}`); const assignedIssues = await getAssignedIssues(context, sender.login); - logger.info("Max issue allowed is", maxConcurrentTasks); + logger.info("Max issue allowed is", { maxConcurrentTasks }); // check for max and enforce max @@ -75,13 +75,12 @@ export async function start(context: Context, issue: Context["payload"]["issue"] const duration: number = calculateDurations(labels).shift() ?? 0; const { id, login } = sender; - const toCreate = { duration, priceLabel, revision: commitHash?.substring(0, 7) }; + const logMessage = logger.info("Task assigned successfully", { duration, priceLabel, revision: commitHash?.substring(0, 7) }); const assignmentComment = await generateAssignmentComment(context, issue.created_at, issue.number, id, duration); - const metadata = structuredMetadata.create("Assignment", toCreate); + const metadata = structuredMetadata.create("Assignment", logMessage); // add assignee - if (!assignees.map((i) => i?.login).includes(login)) { await addAssignees(context, issue.number, [login]); } diff --git a/src/handlers/shared/structured-metadata.ts b/src/handlers/shared/structured-metadata.ts index 66d884a..5dc22ad 100644 --- a/src/handlers/shared/structured-metadata.ts +++ b/src/handlers/shared/structured-metadata.ts @@ -1,25 +1,21 @@ -import { LogLevel } from "../../adapters/supabase/pretty-logs"; - -type Metadata = T & { - revision?: string; - logMessage?: { - type?: LogLevel; - message?: string; - }; - [key: string]: unknown; -}; +import { L as LogReturn } from "@ubiquity-dao/ubiquibot-logger/dist/pretty-logs--Bv8yJMk"; +function createStructuredMetadata(className: string, logReturn: LogReturn | null) { + let logMessage, metadata + if (logReturn) { + logMessage = logReturn.logMessage; + metadata = logReturn.metadata; + } -function createStructuredMetadata(className: string, metadata: Metadata) { const jsonPretty = JSON.stringify(metadata, null, 2); const stackLine = new Error().stack?.split("\n")[2] ?? ""; const caller = stackLine.match(/at (\S+)/)?.[1] ?? ""; - const ubiquityMetadataHeader = `"].join("\n"); - if (metadata.logMessage?.type === LogLevel.FATAL) { + if (logMessage?.type === "fatal") { // if the log message is fatal, then we want to show the metadata metadataSerialized = [metadataSerializedVisible, metadataSerializedHidden].join("\n"); } else { diff --git a/src/plugin.ts b/src/plugin.ts index 554bcc6..918d5a1 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -5,7 +5,7 @@ import { Env, PluginInputs } from "./types"; import { Octokit } from "@octokit/rest"; import { createClient } from "@supabase/supabase-js"; import { createAdapters } from "./adapters"; -import { PrettyLogs } from "./adapters/supabase/pretty-logs"; +import { Logs } from "@ubiquity-dao/ubiquibot-logger"; export async function startStopTask(inputs: PluginInputs, env: Env) { const octokit = new Octokit({ auth: inputs.authToken }); @@ -17,7 +17,7 @@ export async function startStopTask(inputs: PluginInputs, env: Env) { config: inputs.settings, octokit, env, - logger: new PrettyLogs(), + logger: new Logs("info"), adapters: {} as ReturnType, }; diff --git a/src/types/context.ts b/src/types/context.ts index 831faa3..ac13fb2 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -3,7 +3,7 @@ import { Octokit } from "@octokit/rest"; import { StartStopSettings } from "./plugin-input"; import { createAdapters } from "../adapters"; import { Env } from "./env"; -import { PrettyLogs } from "../adapters/supabase/pretty-logs"; +import { Logs } from "@ubiquity-dao/ubiquibot-logger/."; export type SupportedEventsU = "issue_comment.created"; @@ -18,5 +18,5 @@ export interface Context; config: StartStopSettings; env: Env; - logger: PrettyLogs; + logger: Logs } diff --git a/src/utils/issue.ts b/src/utils/issue.ts index b5eec94..42af22a 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -22,7 +22,7 @@ export async function getAssignedIssues(context: Context, username: string): Pro ({ data: issues }) => issues.filter((issue) => !issue.pull_request && issue.assignee && issue.assignee.login === username) ); } catch (err: unknown) { - context.logger.error("Fetching assigned issues failed!", err); + context.logger.error("Fetching assigned issues failed!", { error: err as Error }); return []; } } @@ -41,7 +41,7 @@ export async function addCommentToIssue(context: Context, message: string | null body: comment, }); } catch (e: unknown) { - context.logger.error("Adding a comment failed!", e); + context.logger.error("Adding a comment failed!", { error: e as Error }); } } @@ -57,7 +57,7 @@ export async function closePullRequest(context: Context, pullNumber: number) { state: "closed", }); } catch (err: unknown) { - context.logger.error("Closing pull requests failed!", err); + context.logger.error("Closing pull requests failed!", { error: err as Error }); } } @@ -77,7 +77,7 @@ export async function closePullRequestForAnIssue(context: Context, issueNumber: return logger.info(`No linked pull requests to close`); } - logger.info(`Opened prs`, linkedPullRequests); + logger.info(`Opened prs`, { message: JSON.stringify(linkedPullRequests) }); let comment = `These linked pull requests are closed: `; for (let i = 0; i < linkedPullRequests.length; i++) { await closePullRequest(context, linkedPullRequests[i].number); @@ -98,7 +98,7 @@ export async function addAssignees(context: Context, issueNo: number, assignees: assignees, }); } catch (e: unknown) { - throw context.logger.error("Adding the assignee failed", { assignee: assignees, issueNo, error: e }); + throw context.logger.error("Adding the assignee failed", { assignee: assignees, issueNo, error: e as Error }); } } @@ -113,7 +113,7 @@ export async function getAllPullRequests(context: Context, state: "open" | "clos per_page: 100, }); } catch (err: unknown) { - context.logger.error("Fetching all pull requests failed!", err); + context.logger.error("Fetching all pull requests failed!", { error: err as Error }); return []; } } @@ -135,7 +135,7 @@ export async function getAllPullRequestReviews(context: Context, pullNumber: num }, }); } catch (err: unknown) { - context.logger.error("Fetching all pull request reviews failed!", err); + context.logger.error("Fetching all pull request reviews failed!", { error: err as Error }); return []; } } diff --git a/tests/main.test.ts b/tests/main.test.ts index 78ab31b..c0334c1 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -9,7 +9,7 @@ import issueTemplate from "./__mocks__/issue-template"; import { createAdapters } from "../src/adapters"; import { createClient } from "@supabase/supabase-js"; import dotenv from "dotenv"; -import { PrettyLogs } from "../src/adapters/supabase/pretty-logs"; +import { Logs } from "@ubiquity-dao/ubiquibot-logger/."; dotenv.config(); type Issue = Context["payload"]["issue"]; @@ -396,7 +396,7 @@ function createContext(issue: Record, sender: Record