mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-28 19:31:03 -05:00
Added a logger that writes logs to stdout
This commit is contained in:
parent
19993c6f5b
commit
d043e7f23f
2 changed files with 260 additions and 0 deletions
131
src/utils/logging/process-logger.ts
Normal file
131
src/utils/logging/process-logger.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import { DEFAULT_NEWLINE } from "@/utils/environment";
|
||||
import { Logger } from "./logger";
|
||||
|
||||
/**
|
||||
* Represents a delegate type that consumes log messages.
|
||||
*/
|
||||
interface LogConsumer {
|
||||
/**
|
||||
* Processes a given log message.
|
||||
*
|
||||
* @param message - A log message to process.
|
||||
*/
|
||||
(message: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `process` object provides information about, and control over, the current Node.js process.
|
||||
*/
|
||||
interface Process {
|
||||
/**
|
||||
* A stream connected to `stdout` (fd `1`).
|
||||
*/
|
||||
stdout?: {
|
||||
/**
|
||||
* Sends data on the socket.
|
||||
*/
|
||||
write: LogConsumer;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A logger implementation that dumps formatted log messages to `stdout`.
|
||||
*
|
||||
* Compatible with GitHub Actions.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-debug-message
|
||||
*/
|
||||
export class ProcessLogger implements Logger {
|
||||
/**
|
||||
* A function to consume produced log messages.
|
||||
*/
|
||||
private readonly _logConsumer: LogConsumer;
|
||||
|
||||
/**
|
||||
* The newline sequence to use when writing logs.
|
||||
*/
|
||||
private readonly _newline: string;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ProcessLogger} instance.
|
||||
*
|
||||
* @param process - The process this logger is attached to. Defaults to `globalThis.process`.
|
||||
* @param newline - The newline sequence to use when writing logs. Defaults to `os.EOL`.
|
||||
*/
|
||||
constructor(process?: Process, newline?: string);
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ProcessLogger} instance.
|
||||
*
|
||||
* @param logConsumer - The function to consume log messages.
|
||||
* @param newline - The newline sequence to use when writing logs. Defaults to `os.EOL`.
|
||||
*/
|
||||
constructor(logConsumer: LogConsumer, newline?: string);
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ProcessLogger} instance.
|
||||
*
|
||||
* @param processOrLogConsumer - A process this logger is attached to, or a function to consume log messages.
|
||||
* @param newline - The newline sequence to use when writing logs. Defaults to `os.EOL`.
|
||||
*/
|
||||
constructor(processOrLogConsumer: Process | LogConsumer, newline?: string) {
|
||||
if (typeof processOrLogConsumer === "function") {
|
||||
this._logConsumer = processOrLogConsumer;
|
||||
} else {
|
||||
const process = processOrLogConsumer ?? globalThis.process;
|
||||
this._logConsumer =
|
||||
typeof process.stdout?.write === "function"
|
||||
? msg => process.stdout.write(msg)
|
||||
: (() => {});
|
||||
}
|
||||
this._newline = newline ?? DEFAULT_NEWLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
fatal(message: string | Error): void {
|
||||
this.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
error(message: string | Error): void {
|
||||
this.log(message, "error")
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
warn(message: string | Error): void {
|
||||
this.log(message, "warning")
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
info(message: string | Error): void {
|
||||
this.log(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
debug(message: string | Error): void {
|
||||
this.log(message, "debug");
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message with an optional log level.
|
||||
*
|
||||
* @param message - The message to log.
|
||||
* @param level - Optional log level string.
|
||||
*/
|
||||
private log(message: string | Error, level?: string): void {
|
||||
const cmd = level ? `::${level}::` : "";
|
||||
this._logConsumer(`${cmd}${message}${this._newline}`);
|
||||
}
|
||||
}
|
129
tests/unit/utils/logging/process-logger.spec.ts
Normal file
129
tests/unit/utils/logging/process-logger.spec.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
import { ProcessLogger } from "@/utils/logging/process-logger";
|
||||
|
||||
interface MockProcess {
|
||||
stdout: {
|
||||
write: jest.Mock;
|
||||
};
|
||||
}
|
||||
|
||||
function createMockProcess(): MockProcess {
|
||||
return {
|
||||
stdout: {
|
||||
write: jest.fn(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("ProcessLogger", () => {
|
||||
describe("constructor", () => {
|
||||
test("constructs a new instance with the provided process", () => {
|
||||
const process = createMockProcess();
|
||||
const logger = new ProcessLogger(process, "\n");
|
||||
|
||||
logger.info("Info");
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(1);
|
||||
expect(process.stdout.write).toHaveBeenCalledWith("Info\n");
|
||||
});
|
||||
|
||||
test("constructor uses provided newline sequence", () => {
|
||||
const process = createMockProcess();
|
||||
const logger = new ProcessLogger(process, "\n\n");
|
||||
|
||||
logger.info("Info");
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(1);
|
||||
expect(process.stdout.write).toHaveBeenCalledWith("Info\n\n");
|
||||
});
|
||||
|
||||
test("constructor uses provided log consumer", () => {
|
||||
const logConsumer = jest.fn();
|
||||
const logger = new ProcessLogger(logConsumer, "\n");
|
||||
|
||||
logger.info("Info");
|
||||
|
||||
expect(logConsumer).toHaveBeenCalledTimes(1);
|
||||
expect(logConsumer).toHaveBeenCalledWith("Info\n");
|
||||
});
|
||||
|
||||
test("constructor uses provided log consumer and newline sequence", () => {
|
||||
const logConsumer = jest.fn();
|
||||
const logger = new ProcessLogger(logConsumer, "\n\n");
|
||||
|
||||
logger.info("Info");
|
||||
|
||||
expect(logConsumer).toHaveBeenCalledTimes(1);
|
||||
expect(logConsumer).toHaveBeenCalledWith("Info\n\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fatal", () => {
|
||||
test("redirects the call to process.stdout.write", () => {
|
||||
const process = createMockProcess();
|
||||
const logger = new ProcessLogger(process, "\n");
|
||||
|
||||
logger.fatal("Fatal Error");
|
||||
logger.fatal(new Error("Fatal Error"));
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(2);
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(1, "::error::Fatal Error\n");
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(2, "::error::Error: Fatal Error\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("error", () => {
|
||||
test("redirects the call to process.stdout.write", () => {
|
||||
const process = createMockProcess();
|
||||
const logger = new ProcessLogger(process, "\n");
|
||||
|
||||
logger.error("Error");
|
||||
logger.error(new Error("Error"));
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(2);
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(1, "::error::Error\n");
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(2, "::error::Error: Error\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("warn", () => {
|
||||
test("redirects the call to process.stdout.write", () => {
|
||||
const process = createMockProcess();
|
||||
const logger = new ProcessLogger(process, "\n");
|
||||
|
||||
logger.warn("Warning");
|
||||
logger.warn(new Error("Warning"));
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(2);
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(1, "::warning::Warning\n");
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(2, "::warning::Error: Warning\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("info", () => {
|
||||
test("redirects the call to process.stdout.write", () => {
|
||||
const process = createMockProcess();
|
||||
const logger = new ProcessLogger(process, "\n");
|
||||
|
||||
logger.info("Info");
|
||||
logger.info(new Error("Info"));
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(2);
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(1, "Info\n");
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(2, "Error: Info\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("debug", () => {
|
||||
test("redirects the call to process.stdout.write", () => {
|
||||
const process = createMockProcess();
|
||||
const logger = new ProcessLogger(process, "\n");
|
||||
|
||||
logger.debug("Debug Info");
|
||||
logger.debug(new Error("Debug Info"));
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalledTimes(2);
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(1, "::debug::Debug Info\n");
|
||||
expect(process.stdout.write).toHaveBeenNthCalledWith(2, "::debug::Error: Debug Info\n");
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue