Skip to content

Commit 0577b54

Browse files
committed
feat(common): add force-console option to console logger
1 parent d559b81 commit 0577b54

File tree

2 files changed

+110
-4
lines changed

2 files changed

+110
-4
lines changed

packages/common/services/console-logger.service.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export interface ConsoleLoggerOptions {
4343
* The context of the logger.
4444
*/
4545
context?: string;
46+
/**
47+
* If enabled, will force the use of console.log/console.error instead of process.stdout/stderr.write.
48+
* This is useful for test environments like Jest that can buffer console calls.
49+
* @default false
50+
*/
51+
forceConsole?: boolean;
4652
/**
4753
* If enabled, will print the log message in a single line, even if it is an object with multiple properties.
4854
* If set to a number, the most n inner elements are united on a single line as long as all properties fit into breakLength. Short array elements are also grouped together.
@@ -334,7 +340,15 @@ export class ConsoleLogger implements LoggerService {
334340
timestampDiff,
335341
);
336342

337-
process[writeStreamType ?? 'stdout'].write(formattedMessage);
343+
if (this.options.forceConsole) {
344+
if (writeStreamType === 'stderr') {
345+
console.error(formattedMessage.trim());
346+
} else {
347+
console.log(formattedMessage.trim());
348+
}
349+
} else {
350+
process[writeStreamType ?? 'stdout'].write(formattedMessage);
351+
}
338352
});
339353
}
340354

@@ -352,7 +366,17 @@ export class ConsoleLogger implements LoggerService {
352366
!this.options.colors && this.inspectOptions.compact === true
353367
? JSON.stringify(logObject, this.stringifyReplacer)
354368
: inspect(logObject, this.inspectOptions);
355-
process[options.writeStreamType ?? 'stdout'].write(`${formattedMessage}\n`);
369+
if (this.options.forceConsole) {
370+
if (options.writeStreamType === 'stderr') {
371+
console.error(formattedMessage);
372+
} else {
373+
console.log(formattedMessage);
374+
}
375+
} else {
376+
process[options.writeStreamType ?? 'stdout'].write(
377+
`${formattedMessage}\n`,
378+
);
379+
}
356380
}
357381

358382
protected getJsonLogObject(
@@ -455,7 +479,11 @@ export class ConsoleLogger implements LoggerService {
455479
if (!stack || this.options.json) {
456480
return;
457481
}
458-
process.stderr.write(`${stack}\n`);
482+
if (this.options.forceConsole) {
483+
console.error(stack);
484+
} else {
485+
process.stderr.write(`${stack}\n`);
486+
}
459487
}
460488

461489
protected updateAndGetTimestampDiff(): string {

packages/common/test/services/logger.service.spec.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { expect } from 'chai';
2-
import 'reflect-metadata';
32
import * as sinon from 'sinon';
43
import { ConsoleLogger, Logger, LoggerService, LogLevel } from '../../services';
54

@@ -475,6 +474,85 @@ describe('Logger', () => {
475474
expect(processStdoutWriteSpy.secondCall.firstArg).to.include(Test.name);
476475
});
477476
});
477+
478+
describe('forceConsole option', () => {
479+
let consoleLogSpy: sinon.SinonSpy;
480+
let consoleErrorSpy: sinon.SinonSpy;
481+
let processStdoutWriteStub: sinon.SinonStub;
482+
let processStderrWriteStub: sinon.SinonStub;
483+
484+
beforeEach(() => {
485+
// Stub process.stdout.write to prevent actual output and track calls
486+
processStdoutWriteStub = sinon.stub(process.stdout, 'write');
487+
processStderrWriteStub = sinon.stub(process.stderr, 'write');
488+
consoleLogSpy = sinon.spy(console, 'log');
489+
consoleErrorSpy = sinon.spy(console, 'error');
490+
});
491+
492+
afterEach(() => {
493+
processStdoutWriteStub.restore();
494+
processStderrWriteStub.restore();
495+
consoleLogSpy.restore();
496+
consoleErrorSpy.restore();
497+
});
498+
499+
it('should use console.log instead of process.stdout.write when forceConsole is true', () => {
500+
const logger = new ConsoleLogger({ forceConsole: true });
501+
const message = 'test message';
502+
503+
logger.log(message);
504+
505+
// When forceConsole is true, console.log should be called
506+
expect(consoleLogSpy.called).to.be.true;
507+
expect(consoleLogSpy.firstCall.firstArg).to.include(message);
508+
});
509+
510+
it('should use console.error instead of process.stderr.write when forceConsole is true', () => {
511+
const logger = new ConsoleLogger({ forceConsole: true });
512+
const message = 'error message';
513+
514+
logger.error(message);
515+
516+
expect(consoleErrorSpy.called).to.be.true;
517+
expect(consoleErrorSpy.firstCall.firstArg).to.include(message);
518+
});
519+
520+
it('should use console.error for stack traces when forceConsole is true', () => {
521+
const logger = new ConsoleLogger({ forceConsole: true });
522+
const message = 'error with stack';
523+
const stack = 'Error: test\n at <anonymous>:1:1';
524+
525+
logger.error(message, stack);
526+
527+
expect(consoleErrorSpy.calledTwice).to.be.true;
528+
expect(consoleErrorSpy.firstCall.firstArg).to.include(message);
529+
expect(consoleErrorSpy.secondCall.firstArg).to.equal(stack);
530+
});
531+
532+
it('should use process.stdout.write when forceConsole is false', () => {
533+
const logger = new ConsoleLogger({ forceConsole: false });
534+
const message = 'test message';
535+
536+
logger.log(message);
537+
538+
expect(processStdoutWriteStub.called).to.be.true;
539+
expect(processStdoutWriteStub.firstCall.firstArg).to.include(message);
540+
expect(consoleLogSpy.called).to.be.false;
541+
});
542+
543+
it('should work with JSON mode and forceConsole', () => {
544+
const logger = new ConsoleLogger({ json: true, forceConsole: true });
545+
const message = 'json message';
546+
547+
logger.log(message);
548+
549+
expect(consoleLogSpy.called).to.be.true;
550+
551+
const output = consoleLogSpy.firstCall.firstArg;
552+
const json = JSON.parse(output);
553+
expect(json.message).to.equal(message);
554+
});
555+
});
478556
});
479557

480558
describe('[instance methods]', () => {

0 commit comments

Comments
 (0)