feat(logger): Improve logging output and add --logfile support for persistent logs
This commit is contained in:
parent
dc0f859fad
commit
56f0f0be16
10
changelog.md
10
changelog.md
@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-05-15 - 1.3.0 - feat(logger)
|
||||
Improve logging output and add --logfile support for persistent logs
|
||||
|
||||
- Add new .claude/settings.local.json with logging permissions configuration
|
||||
- Remove obsolete readme.plan.md
|
||||
- Introduce test/test.console.ts to capture and display console outputs during tests
|
||||
- Update CLI in ts/index.ts to replace '--log-file' with '--logfile' flag
|
||||
- Enhance TsTestLogger to support file logging, clean ANSI sequences, and improved JSON output
|
||||
- Forward TAP protocol logs to testConsoleOutput in TapParser for better console distinction
|
||||
|
||||
## 2025-05-15 - 1.2.0 - feat(logging)
|
||||
Improve logging output, CLI option parsing, and test report formatting.
|
||||
|
||||
|
199
readme.plan.md
199
readme.plan.md
@ -1,199 +0,0 @@
|
||||
# Plan for improving logging and output in tstest
|
||||
|
||||
!! FIRST: Reread /home/philkunz/.claude/CLAUDE.md to ensure following all guidelines !!
|
||||
|
||||
## Goal - ✅ MOSTLY COMPLETED
|
||||
- ✅ Make test output cleaner and more visually appealing
|
||||
- ✅ Add structured logging capabilities
|
||||
- ✅ Support different verbosity levels
|
||||
- ✅ Improve CI/CD compatibility
|
||||
- ✅ Add progress indicators and timing summaries
|
||||
|
||||
## Current State - UPDATED
|
||||
- ✅ Clean, modern visual design with Unicode characters
|
||||
- ✅ Structured output format (JSON support)
|
||||
- ✅ Multiple verbosity levels (quiet, normal, verbose)
|
||||
- ✅ Real-time output with cleaner formatting
|
||||
- ✅ Better error aggregation and display
|
||||
- ✅ TAP protocol support integrated with new logger
|
||||
|
||||
## Completed Improvements
|
||||
|
||||
### 1. ✅ Created new TsTestLogger class
|
||||
- ✅ Centralized logging with consistent formatting
|
||||
- ✅ Support for different output modes (normal, quiet, verbose)
|
||||
- ✅ Better visual hierarchy with modern Unicode characters
|
||||
- ✅ Progress indicators for multiple test files
|
||||
- ✅ Structured error collection and display
|
||||
|
||||
### 2. ✅ Updated visual design
|
||||
- ✅ Replaced heavy separators with cleaner alternatives
|
||||
- ✅ Used better emoji and Unicode characters
|
||||
- ✅ Added indentation for hierarchical display
|
||||
- ✅ Grouped related information visually
|
||||
- ✅ Added color coding consistently
|
||||
|
||||
### 3. ✅ Added command-line options
|
||||
- ✅ `--quiet` for minimal CI-friendly output
|
||||
- ✅ `--verbose` for detailed debugging information
|
||||
- ✅ `--no-color` for environments without color support
|
||||
- ✅ `--json` for structured JSON output
|
||||
- ⏳ `--log-file <path>` for persistent logging (TODO)
|
||||
|
||||
### 4. ✅ Improved progress feedback
|
||||
- ⏳ Show progress bar for multiple files (TODO)
|
||||
- ✅ Display current file being executed
|
||||
- ✅ Show real-time test counts
|
||||
- ⏳ Add ETA for long test suites (TODO)
|
||||
|
||||
### 5. ✅ Better error and summary display
|
||||
- ✅ Collect all errors and display at end
|
||||
- ✅ Show timing metrics and performance summary (in verbose mode)
|
||||
- ✅ Highlight slowest tests (in verbose mode)
|
||||
- ✅ Add test failure context
|
||||
|
||||
### 6. ✅ Browser console integration
|
||||
- ✅ Clearly separate browser logs from test output
|
||||
- ⏳ Add browser log filtering options (TODO)
|
||||
- ✅ Format browser errors specially
|
||||
|
||||
## Implementation Steps - COMPLETED
|
||||
|
||||
### Phase 1: ✅ Core Logger Implementation
|
||||
1. ✅ Created `tstest.logging.ts` with TsTestLogger class
|
||||
2. ✅ Added LogOptions interface for configuration
|
||||
3. ✅ Implemented basic formatting methods
|
||||
4. ✅ Added progress and summary methods
|
||||
|
||||
### Phase 2: ✅ Integration
|
||||
1. ✅ Updated CLI to accept new command-line options
|
||||
2. ✅ Modified TsTest class to use new logger
|
||||
3. ✅ Updated TapParser to use structured logging
|
||||
4. ✅ Updated TapCombinator for better summaries
|
||||
|
||||
### Phase 3: ✅ Visual Improvements
|
||||
1. ✅ Replaced all existing separators
|
||||
2. ✅ Updated color scheme
|
||||
3. ✅ Added emoji and Unicode characters
|
||||
4. ✅ Implemented hierarchical output
|
||||
|
||||
### Phase 4: ✅ Advanced Features
|
||||
1. ✅ Add JSON output format
|
||||
2. ⏳ Implement file-based logging (TODO)
|
||||
3. ✅ Add performance metrics collection
|
||||
4. ✅ Create error aggregation system
|
||||
|
||||
### Phase 5: ✅ Browser Integration
|
||||
1. ✅ Update browser console forwarding
|
||||
2. ✅ Add browser log formatting
|
||||
3. ✅ Implement browser-specific indicators
|
||||
|
||||
## Files modified
|
||||
- ✅ `ts/tstest.logging.ts` - New logger implementation (created)
|
||||
- ✅ `ts/index.ts` - Added CLI options parsing
|
||||
- ✅ `ts/tstest.classes.tstest.ts` - Integrated new logger
|
||||
- ✅ `ts/tstest.classes.tap.parser.ts` - Updated output formatting
|
||||
- ✅ `ts/tstest.classes.tap.combinator.ts` - Improved summary display
|
||||
- ❌ `ts/tstest.logprefixes.ts` - Still in use, can be deprecated later
|
||||
- ❌ `package.json` - No new dependencies needed
|
||||
|
||||
## Success Criteria - ACHIEVED
|
||||
- ✅ Cleaner, more readable test output
|
||||
- ✅ Configurable verbosity levels
|
||||
- ✅ Better CI/CD integration
|
||||
- ✅ Improved error visibility
|
||||
- ✅ Performance metrics available
|
||||
- ✅ Consistent visual design
|
||||
- ✅ Maintained backward compatibility
|
||||
|
||||
## Example Output Comparison
|
||||
|
||||
### Current Output
|
||||
```
|
||||
☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰☰
|
||||
**TSTEST** FOUND 4 TESTFILE(S):
|
||||
**TSTEST** test/test.ts
|
||||
------------------------------------------------
|
||||
=> Running test/test.ts in node.js runtime.
|
||||
= = = = = = = = = = = = = = = = = = = = = = = =
|
||||
```
|
||||
|
||||
### Actual Output (IMPLEMENTED)
|
||||
```
|
||||
🔍 Test Discovery
|
||||
Mode: directory
|
||||
Pattern: test
|
||||
Found: 4 test file(s)
|
||||
|
||||
▶️ test/test.ts (1/4)
|
||||
Runtime: node.js
|
||||
✅ prepare test (0ms)
|
||||
Summary: 1/1 PASSED
|
||||
|
||||
▶️ test/test.single.ts (2/4)
|
||||
Runtime: node.js
|
||||
✅ single file test execution (1ms)
|
||||
Summary: 1/1 PASSED
|
||||
|
||||
📊 Test Summary
|
||||
┌────────────────────────────────┐
|
||||
│ Total Files: 4 │
|
||||
│ Total Tests: 4 │
|
||||
│ Passed: 4 │
|
||||
│ Failed: 0 │
|
||||
│ Duration: 5739ms │
|
||||
└────────────────────────────────┘
|
||||
|
||||
ALL TESTS PASSED! 🎉
|
||||
```
|
||||
|
||||
### Additional Features Implemented
|
||||
|
||||
1. **Quiet Mode**:
|
||||
```
|
||||
Found 1 tests
|
||||
✅ single file test execution
|
||||
|
||||
Summary: 1/1 | 1210ms | PASSED
|
||||
```
|
||||
|
||||
2. **JSON Mode**:
|
||||
```json
|
||||
{"event":"discovery","count":1,"pattern":"test/test.single.ts","executionMode":"file"}
|
||||
{"event":"fileStart","filename":"test/test.single.ts","runtime":"node.js","index":1,"total":1}
|
||||
{"event":"testResult","testName":"single file test execution","passed":true,"duration":0}
|
||||
{"event":"summary","summary":{"totalFiles":1,"totalTests":1,"totalPassed":1,"totalFailed":0,"totalDuration":1223,"fileResults":[...]}}
|
||||
```
|
||||
|
||||
3. **Error Display**:
|
||||
```
|
||||
❌ Failed Tests:
|
||||
|
||||
test/test.fail.ts
|
||||
❌ This test should fail
|
||||
|
||||
SOME TESTS FAILED! ❌
|
||||
```
|
||||
|
||||
## Summary of Implementation
|
||||
|
||||
The logging improvement plan has been successfully implemented with the following achievements:
|
||||
|
||||
1. **Created a new centralized TsTestLogger class** that handles all output formatting
|
||||
2. **Added multiple output modes**: quiet, normal, verbose, and JSON
|
||||
3. **Improved visual design** with modern Unicode characters and emojis
|
||||
4. **Added CLI argument parsing** for all new options
|
||||
5. **Integrated the logger throughout the codebase** (TsTest, TapParser, TapCombinator)
|
||||
6. **Better error aggregation and display** with failed tests shown at the end
|
||||
7. **Performance metrics** available in verbose mode
|
||||
8. **Clean, hierarchical output** that's much more readable
|
||||
|
||||
### Remaining TODOs
|
||||
|
||||
Only a few minor features remain unimplemented:
|
||||
- File-based logging (--log-file option)
|
||||
- Progress bar visualization
|
||||
- ETA for long test suites
|
||||
- Browser log filtering options
|
||||
|
||||
The core logging improvements are complete and provide a much better user experience!
|
21
test-output.log
Normal file
21
test-output.log
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
🔍 Test Discovery
|
||||
Mode: file
|
||||
Pattern: test/test.single.ts
|
||||
Found: 1 test file(s)
|
||||
|
||||
▶️ test/test.single.ts (1/1)
|
||||
Runtime: node.js
|
||||
✅ single file test execution (1ms)
|
||||
Summary: 1/1 PASSED
|
||||
|
||||
📊 Test Summary
|
||||
┌────────────────────────────────┐
|
||||
│ Total Files: 1 │
|
||||
│ Total Tests: 1 │
|
||||
│ Passed: 1 │
|
||||
│ Failed: 0 │
|
||||
│ Duration: 1230ms │
|
||||
└────────────────────────────────┘
|
||||
|
||||
ALL TESTS PASSED! 🎉
|
11
test/test.console.ts
Normal file
11
test/test.console.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
|
||||
tap.test('Test with console output', async () => {
|
||||
console.log('Log message 1 from test');
|
||||
console.log('Log message 2 from test');
|
||||
console.error('Error message from test');
|
||||
console.warn('Warning message from test');
|
||||
expect(true).toBeTrue();
|
||||
});
|
||||
|
||||
tap.start();
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tstest',
|
||||
version: '1.2.0',
|
||||
version: '1.3.0',
|
||||
description: 'a test utility to run tests that match test/**/*.ts'
|
||||
}
|
||||
|
@ -33,9 +33,8 @@ export const runCli = async () => {
|
||||
logOptions.json = true;
|
||||
break;
|
||||
case '--log-file':
|
||||
if (i + 1 < args.length) {
|
||||
logOptions.logFile = args[++i];
|
||||
}
|
||||
case '--logfile':
|
||||
logOptions.logFile = true; // Set this as a flag, not a value
|
||||
break;
|
||||
default:
|
||||
if (!arg.startsWith('-')) {
|
||||
@ -52,7 +51,7 @@ export const runCli = async () => {
|
||||
console.error(' --verbose, -v Verbose output');
|
||||
console.error(' --no-color Disable colored output');
|
||||
console.error(' --json Output results as JSON');
|
||||
console.error(' --log-file Write logs to file');
|
||||
console.error(' --logfile Write logs to .nogit/testlogs/[testfile].log');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,8 @@ export class TapParser {
|
||||
this.activeTapTestResult.addLogLine(logLine);
|
||||
}
|
||||
if (this.logger) {
|
||||
this.logger.tapOutput(logLine);
|
||||
// This is console output from the test file, not TAP protocol
|
||||
this.logger.testConsoleOutput(logLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { coloredString as cs } from '@push.rocks/consolecolor';
|
||||
import * as plugins from './tstest.plugins.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface LogOptions {
|
||||
quiet?: boolean;
|
||||
verbose?: boolean;
|
||||
noColor?: boolean;
|
||||
json?: boolean;
|
||||
logFile?: string;
|
||||
logFile?: boolean;
|
||||
}
|
||||
|
||||
export interface TestFileResult {
|
||||
@ -37,6 +39,7 @@ export class TsTestLogger {
|
||||
private startTime: number;
|
||||
private fileResults: TestFileResult[] = [];
|
||||
private currentFileResult: TestFileResult | null = null;
|
||||
private currentTestLogFile: string | null = null;
|
||||
|
||||
constructor(options: LogOptions = {}) {
|
||||
this.options = options;
|
||||
@ -51,11 +54,44 @@ export class TsTestLogger {
|
||||
}
|
||||
|
||||
private log(message: string) {
|
||||
if (this.options.json) return;
|
||||
if (this.options.json) {
|
||||
// For JSON mode, skip console output
|
||||
// JSON output is handled by logJson method
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(message);
|
||||
|
||||
if (this.options.logFile) {
|
||||
// TODO: Implement file logging
|
||||
// Log to the current test file log if we're in a test and --logfile is specified
|
||||
if (this.currentTestLogFile) {
|
||||
this.logToTestFile(message);
|
||||
}
|
||||
}
|
||||
|
||||
private logToFile(message: string) {
|
||||
// This method is no longer used since we use logToTestFile for individual test logs
|
||||
// Keeping it for potential future use with a global log file
|
||||
}
|
||||
|
||||
private logToTestFile(message: string) {
|
||||
try {
|
||||
// Remove ANSI color codes for file logging
|
||||
const cleanMessage = message.replace(/\u001b\[[0-9;]*m/g, '');
|
||||
|
||||
// Append to test log file
|
||||
fs.appendFileSync(this.currentTestLogFile, cleanMessage + '\n');
|
||||
} catch (error) {
|
||||
// Silently fail to avoid disrupting the test run
|
||||
}
|
||||
}
|
||||
|
||||
private logJson(data: any) {
|
||||
const jsonString = JSON.stringify(data);
|
||||
console.log(jsonString);
|
||||
|
||||
// Also log to test file if --logfile is specified
|
||||
if (this.currentTestLogFile) {
|
||||
this.logToTestFile(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +120,7 @@ export class TsTestLogger {
|
||||
// Test discovery
|
||||
testDiscovery(count: number, pattern: string, executionMode: string) {
|
||||
if (this.options.json) {
|
||||
console.log(JSON.stringify({ event: 'discovery', count, pattern, executionMode }));
|
||||
this.logJson({ event: 'discovery', count, pattern, executionMode });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -109,8 +145,23 @@ export class TsTestLogger {
|
||||
tests: []
|
||||
};
|
||||
|
||||
// Only set up test log file if --logfile option is specified
|
||||
if (this.options.logFile) {
|
||||
const baseFilename = path.basename(filename, '.ts');
|
||||
this.currentTestLogFile = path.join('.nogit', 'testlogs', `${baseFilename}.log`);
|
||||
|
||||
// Ensure the directory exists
|
||||
const logDir = path.dirname(this.currentTestLogFile);
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Clear the log file for this test
|
||||
fs.writeFileSync(this.currentTestLogFile, '');
|
||||
}
|
||||
|
||||
if (this.options.json) {
|
||||
console.log(JSON.stringify({ event: 'fileStart', filename, runtime, index, total }));
|
||||
this.logJson({ event: 'fileStart', filename, runtime, index, total });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -133,7 +184,7 @@ export class TsTestLogger {
|
||||
}
|
||||
|
||||
if (this.options.json) {
|
||||
console.log(JSON.stringify({ event: 'testResult', testName, passed, duration, error }));
|
||||
this.logJson({ event: 'testResult', testName, passed, duration, error });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -157,7 +208,7 @@ export class TsTestLogger {
|
||||
}
|
||||
|
||||
if (this.options.json) {
|
||||
console.log(JSON.stringify({ event: 'fileEnd', passed, failed, duration }));
|
||||
this.logJson({ event: 'fileEnd', passed, failed, duration });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -167,23 +218,45 @@ export class TsTestLogger {
|
||||
const color = failed === 0 ? 'green' : 'red';
|
||||
this.log(this.format(` Summary: ${passed}/${total} ${status}`, color));
|
||||
}
|
||||
|
||||
// Clear the current test log file reference only if using --logfile
|
||||
if (this.options.logFile) {
|
||||
this.currentTestLogFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
// TAP output forwarding
|
||||
// TAP output forwarding (for TAP protocol messages)
|
||||
tapOutput(message: string, isError: boolean = false) {
|
||||
if (this.options.json) return;
|
||||
|
||||
if (this.options.verbose || isError) {
|
||||
const prefix = isError ? ' ⚠️ ' : ' ';
|
||||
const color = isError ? 'red' : 'dim';
|
||||
this.log(this.format(`${prefix}${message}`, color));
|
||||
// Never show raw TAP protocol messages in console
|
||||
// They are already processed by TapParser and shown in our format
|
||||
|
||||
// Always log to test file if --logfile is specified
|
||||
if (this.currentTestLogFile) {
|
||||
this.logToTestFile(` ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Console output from test files (non-TAP output)
|
||||
testConsoleOutput(message: string) {
|
||||
if (this.options.json) return;
|
||||
|
||||
// Show console output from test files only in verbose mode
|
||||
if (this.options.verbose) {
|
||||
this.log(this.format(` ${message}`, 'dim'));
|
||||
}
|
||||
|
||||
// Always log to test file if --logfile is specified
|
||||
if (this.currentTestLogFile) {
|
||||
this.logToTestFile(` ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Browser console
|
||||
browserConsole(message: string, level: string = 'log') {
|
||||
if (this.options.json) {
|
||||
console.log(JSON.stringify({ event: 'browserConsole', message, level }));
|
||||
this.logJson({ event: 'browserConsole', message, level });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -207,7 +280,7 @@ export class TsTestLogger {
|
||||
};
|
||||
|
||||
if (this.options.json) {
|
||||
console.log(JSON.stringify({ event: 'summary', summary }));
|
||||
this.logJson({ event: 'summary', summary });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -266,7 +339,7 @@ export class TsTestLogger {
|
||||
// Error display
|
||||
error(message: string, file?: string, stack?: string) {
|
||||
if (this.options.json) {
|
||||
console.log(JSON.stringify({ event: 'error', message, file, stack }));
|
||||
this.logJson({ event: 'error', message, file, stack });
|
||||
return;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user