Skip to content

Commit

Permalink
fix(logger): files needs to migrated from Tape to Jest
Browse files Browse the repository at this point in the history
Primary Changes
---------------
1. Updated logger-provider.ts and logger.ts for LoggerProvider configuration
and added a way for optional custom writable stream
2. Using the custom writable stream, fixed logger.test.ts after removing the
skip on the test and UUID marker now appears in the custom stream output
3. Migrated test file from tape to jest
4. Removed file path of logger.test.ts in testPathIgnorePatterns to run jest
test

Fixes:

Signed-off-by: ruzell22 <[email protected]>
  • Loading branch information
ruzell22 committed Jan 28, 2025
1 parent 6af5fda commit 42a9bcb
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 60 deletions.
1 change: 0 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ module.exports = {
`./packages/cactus-plugin-ledger-connector-xdai/src/test/typescript/integration/invoke-contract-xdai-json-object.test.ts`,
`./packages/cactus-plugin-ledger-connector-xdai/src/test/typescript/integration/openapi/openapi-validation.test.ts`,
`./packages/cactus-plugin-ledger-connector-xdai/src/test/typescript/integration/openapi/openapi-validation-no-keychain.test.ts`,
`./packages/cactus-common/src/test/typescript/unit/logging/logger.test.ts`,
`./packages/cactus-test-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-validator-besu/v21-sign-transaction-endpoint.test.ts`,
`./packages/cactus-plugin-keychain-vault/src/test/typescript/integration/cactus-keychain-vault-server.test.ts`,
`./packages/cactus-plugin-keychain-vault/src/test/typescript/integration/plugin-keychain-vault.test.ts`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export class LoggerProvider {
if (!logger) {
logger = new Logger(loggerOptions);
LoggerProvider.loggers.set(loggerOptions.label, logger);
} else if (loggerOptions.stream) {
// Dynamically update the logger with a new stream if provided
logger["stream"] = loggerOptions.stream;
}
return logger;
}
Expand Down
20 changes: 19 additions & 1 deletion packages/cactus-common/src/main/typescript/logging/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ prefix.apply(libLogLevel, {
export interface ILoggerOptions {
label: string;
level?: LogLevelDesc;
stream?: NodeJS.WritableStream; // Optional custom writable stream
}

/**
Expand All @@ -30,12 +31,14 @@ export interface ILoggerOptions {
* - trace: 4
*/
export class Logger {
private stream?: NodeJS.WritableStream;
private readonly backend: LogLevelLogger;

constructor(public readonly options: ILoggerOptions) {
const level: LogLevelDesc = options.level || "warn";
this.backend = libLogLevel.getLogger(options.label);
this.backend.setLevel(level);
this.stream = options.stream; // Save the custom stream if provided
}

public setLogLevel(logLevel: LogLevelDesc): void {
Expand All @@ -45,21 +48,36 @@ export class Logger {
public async shutdown(): Promise<void> {
this.backend.info("Shut down logger OK.");
}

private logToStream(level: string, ...msg: unknown[]): void {
if (this.stream) {
const timestamp = new Date().toISOString();
const label = this.options.label || "global";
const formattedMessage = `[${timestamp}] ${level} (${label}): ${msg
.map((m) => (typeof m === "object" ? JSON.stringify(m) : String(m)))
.join(" ")}`;
this.stream.write(formattedMessage + "\n");
}
}

public error(...msg: unknown[]): void {
this.logToStream("ERROR", ...msg);
this.backend.error(...msg);
}

public warn(...msg: unknown[]): void {
this.logToStream("WARN", ...msg);
this.backend.warn(...msg);
}
public info(...msg: unknown[]): void {
this.logToStream("INFO", ...msg);
this.backend.info(...msg);
}
public debug(...msg: unknown[]): void {
this.logToStream("DEBUG", ...msg);
this.backend.debug(...msg);
}
public trace(...msg: unknown[]): void {
this.logToStream("TRACE", ...msg);
this.backend.trace(...msg);
}
}
Original file line number Diff line number Diff line change
@@ -1,66 +1,34 @@
import test, { Test } from "tape";

Check failure on line 1 in packages/cactus-common/src/test/typescript/unit/logging/logger.test.ts

View workflow job for this annotation

GitHub Actions / yarn_lint

'test' is defined but never used

Check failure on line 1 in packages/cactus-common/src/test/typescript/unit/logging/logger.test.ts

View workflow job for this annotation

GitHub Actions / yarn_lint

'Test' is defined but never used
import { v4 as uuidv4 } from "uuid";
import { LoggerProvider } from "../../../../main/typescript/public-api";
import { Writable } from "stream";

describe("Logger Tests", () => {
it("Logger#debug/error writes to stdout/stderr", async () => {
const outputData: string[] = [];
const customStream = new Writable({
write(chunk, encoding, callback) {
outputData.push(chunk.toString());
callback();
},
});

// FIXME(2020-11-12) this does not work because for some reason the stdout
// stream does not emit 'data' events with anything even though it should.
// Suspecting that the test runner library does some internal magic with
// piping the stream somewhere else or similar foul play at hand.
// Until we can fix this, marked the test to be skipped.
test.skip("Logger#debug/error writes to stdout/stderr", async (t: Test) => {
const log = LoggerProvider.getOrCreate({
level: "TRACE",
label: "logger-test",
});

// generate random UUID v4 to guarantee we don't mistake something else as the marker
const marker = uuidv4();
interface DataHandler {
(data: Buffer): void;
}
// wait for the marker to appear on stdout OR crash with timeout if it never comes
let aggregateStdOut = "";
let stdOutDataHandler: undefined | DataHandler;
let didNotThrow: boolean;

try {
// hook up to the stdout data stream and wrap it in a promise that can be awaited
// for when the marker does appear on stdout (which would be a passing test)
// or when it times out (which would mean the test is failing).
// Certain issues could happen here if the stream is chunking data and then you never
// actually get the complete marker string at once but instead different parts of it
// but I did not consider this because the uuid is only a few dozen bytes when stored as a hex string
// so I'm pretty confident it wouldn't get chunked (probably not impossible either though so
// if you are paranoid about that happening (which would make the test flaky) then you can
// bake in some stream data aggregation instead where you collect and continually append
// the incoming data chunks and test for marker presence in the aggregate variable not the chunk
// that is provided in the 'data' event handler callback.
await new Promise<void>((resolve, reject) => {
const timeoutMsg = "Timed out waiting for marker to appear on stdout";
const timerId = setTimeout(() => reject(new Error(timeoutMsg)), 5000);

const stdOutDataHandler: DataHandler = (data: Buffer) => {
const msg = data.toString("utf-8");
aggregateStdOut = aggregateStdOut.concat(msg);
if (msg.includes(marker)) {
clearInterval(timerId);
resolve();
}
};

process.stdout.on("data", stdOutDataHandler);

// send the log now that we have hooked into the stream waiting for the marker to appear
log.info(marker);
const log = LoggerProvider.getOrCreate({
level: "TRACE",
label: "logger-test",
stream: customStream,
});
didNotThrow = true;
} catch (ex) {
didNotThrow = false;
}

process.stdout.off("data", stdOutDataHandler as DataHandler);
t.comment(`Aggregate std out messages: ${aggregateStdOut}`);
t.true(didNotThrow, "Marker appeared on stdout on time OK");
// generate random UUID v4 to guarantee we don't mistake something else as the marker
const marker = uuidv4();
log.info(marker);

t.end();
// Delay verification to ensure all writes are flushed
await new Promise((resolve) => setImmediate(resolve));

// Verify marker in output
const capturedOutput = outputData.join("");
expect(capturedOutput).toContain(marker);
log.info(`Marker (${marker}) appeared in custom stream output`);
});
});

0 comments on commit 42a9bcb

Please sign in to comment.