@@ -78,6 +78,7 @@ interface ExtendedLoggerOptions extends LoggerOptions {
78
78
showTags ?: boolean // Enable/disable tags in console output
79
79
tagFormat ?: TagFormat // Custom format for tags
80
80
timestampPosition ?: 'left' | 'right' // Control timestamp position in console output
81
+ environment ?: string // Environment prefix for log entries
81
82
}
82
83
83
84
export class Logger {
@@ -104,6 +105,7 @@ export class Logger {
104
105
private fancy : boolean // Whether to use fancy terminal output
105
106
private tagFormat : TagFormat
106
107
private timestampPosition : 'left' | 'right'
108
+ private environment : string // Environment prefix for log entries
107
109
// eslint-disable-next-line no-control-regex
108
110
private readonly ANSI_PATTERN = / \u001B \[ .* ?m / g // Use Unicode escape for ANSI sequence
109
111
private activeProgressBar : { // State for the active progress bar
@@ -123,6 +125,7 @@ export class Logger {
123
125
this . fancy = options . fancy ?? true // Enable fancy output by default
124
126
this . tagFormat = options . tagFormat ?? { prefix : '[' , suffix : ']' } // Default square bracket format
125
127
this . timestampPosition = options . timestampPosition ?? 'right' // Default to left position
128
+ this . environment = options . environment ?? process . env . APP_ENV ?? 'local' // Use APP_ENV or default to 'local'
126
129
127
130
// Initialize fingers-crossed config based on flag
128
131
this . fingersCrossedConfig = this . initializeFingersCrossedConfig ( options )
@@ -999,14 +1002,14 @@ export class Logger {
999
1002
}
1000
1003
else if ( ! isBrowserProcess ( ) ) {
1001
1004
// Simple console output without styling
1002
- console . error ( `${ fileTime } ${ level . toUpperCase ( ) } : ${ formattedMessage } ` )
1005
+ console . error ( `${ fileTime } ${ this . environment } . ${ level . toUpperCase ( ) } : ${ formattedMessage } ` )
1003
1006
if ( errorStack ) {
1004
1007
console . error ( errorStack )
1005
1008
}
1006
1009
}
1007
1010
1008
1011
// Create the log entry for file logging
1009
- let logEntry = `${ fileTime } local .${ level . toUpperCase ( ) } : ${ formattedMessage } \n`
1012
+ let logEntry = `${ fileTime } ${ this . environment } .${ level . toUpperCase ( ) } : ${ formattedMessage } \n`
1010
1013
if ( errorStack ) {
1011
1014
logEntry += `${ errorStack } \n`
1012
1015
}
@@ -1050,7 +1053,7 @@ export class Logger {
1050
1053
const consoleTime = this . formatConsoleTimestamp ( timestamp )
1051
1054
const fileTime = this . formatFileTimestamp ( timestamp )
1052
1055
1053
- let logEntry = `${ fileTime } local .INFO: ${ completionMessage } `
1056
+ let logEntry = `${ fileTime } ${ this . environment } .INFO: ${ completionMessage } `
1054
1057
if ( metadata ) {
1055
1058
logEntry += ` ${ JSON . stringify ( metadata ) } `
1056
1059
}
@@ -1337,11 +1340,11 @@ export class Logger {
1337
1340
}
1338
1341
else if ( ! isBrowserProcess ( ) ) {
1339
1342
// Simple console output without styling
1340
- console . error ( `${ fileTime } BOX: ${ message } ` )
1343
+ console . error ( `${ fileTime } ${ this . environment } .INFO: [BOX] ${ message } ` )
1341
1344
}
1342
1345
1343
1346
// Write directly to file instead of using this.info()
1344
- const logEntry = `${ fileTime } local .INFO: [BOX] ${ message } \n`
1347
+ const logEntry = `${ fileTime } ${ this . environment } .INFO: [BOX] ${ message } \n`
1345
1348
await this . writeToFile ( logEntry )
1346
1349
}
1347
1350
@@ -1477,7 +1480,7 @@ export class Logger {
1477
1480
// Log to file directly instead of using this.info()
1478
1481
const timestamp = new Date ( )
1479
1482
const formattedDate = timestamp . toISOString ( )
1480
- const logEntry = `[${ formattedDate } ] local .INFO: [START] ${ formattedMessage } \n`
1483
+ const logEntry = `[${ formattedDate } ] ${ this . environment } .INFO: [START] ${ formattedMessage } \n`
1481
1484
1482
1485
await this . writeToFile ( logEntry )
1483
1486
}
@@ -1591,7 +1594,10 @@ export class Logger {
1591
1594
// Use a simpler icon for progress
1592
1595
const icon = isFinished || percent === 100 ? terminalStyles . green ( '✓' ) : terminalStyles . blue ( '▶' )
1593
1596
1594
- const line = `\r${ icon } ${ bar } ${ percentageText } ${ messageText } `
1597
+ // Add tag if enabled
1598
+ const tag = this . options . showTags !== false && this . name ? ` ${ terminalStyles . gray ( this . formatTag ( this . name ) ) } ` : ''
1599
+
1600
+ const line = `\r${ icon } ${ tag } ${ bar } ${ percentageText } ${ messageText } `
1595
1601
1596
1602
// Clear the rest of the line and write the new progress
1597
1603
const terminalWidth = process . stdout . columns || 80
@@ -1626,6 +1632,80 @@ export class Logger {
1626
1632
1627
1633
this . activeProgressBar = null // Clear the active state
1628
1634
}
1635
+
1636
+ /**
1637
+ * Clears log files based on specified filters.
1638
+ * @param filters Optional filters for clearing logs.
1639
+ * @param filters.name A pattern to match logger names (e.g., 'api-*'). Only files matching this pattern will be considered.
1640
+ * @param filters.before A Date object. Only log files with a last modified timestamp before this date will be deleted.
1641
+ */
1642
+ async clear ( filters : { name ?: string , before ?: Date } = { } ) : Promise < void > {
1643
+ if ( isBrowserProcess ( ) ) {
1644
+ console . warn ( 'Log clearing is not supported in browser environments.' )
1645
+ return
1646
+ }
1647
+
1648
+ try {
1649
+ console . warn ( 'Clearing logs...' , this . config . logDirectory )
1650
+ const files = await readdir ( this . config . logDirectory )
1651
+ const logFilesToDelete : string [ ] = [ ]
1652
+
1653
+ for ( const file of files ) {
1654
+ // Basic check: Must be a log file associated with this logger instance's base name
1655
+ // or match the provided name filter if any.
1656
+ const nameMatches = filters . name
1657
+ ? new RegExp ( filters . name . replace ( '*' , '.*' ) ) . test ( file )
1658
+ : file . startsWith ( this . name )
1659
+
1660
+ if ( ! nameMatches || ! file . endsWith ( '.log' ) ) {
1661
+ continue // Skip files not matching the name or not ending in .log
1662
+ }
1663
+
1664
+ const filePath = join ( this . config . logDirectory , file )
1665
+
1666
+ // Date check: If 'before' filter is provided, check the file's last modified time.
1667
+ if ( filters . before ) {
1668
+ try {
1669
+ const fileStats = await stat ( filePath )
1670
+ if ( fileStats . mtime >= filters . before ) {
1671
+ continue // Skip files modified on or after the 'before' date
1672
+ }
1673
+ }
1674
+ catch ( statErr ) {
1675
+ console . error ( `Failed to get stats for file ${ filePath } :` , statErr )
1676
+ continue // Skip if we cannot get stats
1677
+ }
1678
+ }
1679
+
1680
+ // If all checks pass, add the file to the deletion list.
1681
+ logFilesToDelete . push ( filePath )
1682
+ }
1683
+
1684
+ if ( logFilesToDelete . length === 0 ) {
1685
+ console . warn ( 'No log files matched the criteria for clearing.' )
1686
+ return
1687
+ }
1688
+
1689
+ console . warn ( `Preparing to delete ${ logFilesToDelete . length } log file(s)...` )
1690
+
1691
+ // Delete the files
1692
+ for ( const filePath of logFilesToDelete ) {
1693
+ try {
1694
+ await unlink ( filePath )
1695
+ console . warn ( `Deleted log file: ${ filePath } ` )
1696
+ }
1697
+ catch ( unlinkErr ) {
1698
+ console . error ( `Failed to delete log file ${ filePath } :` , unlinkErr )
1699
+ }
1700
+ }
1701
+
1702
+ console . warn ( 'Log clearing process finished.' )
1703
+ }
1704
+ catch ( err ) {
1705
+ console . error ( 'Error during log clearing process:' , err )
1706
+ // Optionally re-throw or handle the error more specifically
1707
+ }
1708
+ }
1629
1709
}
1630
1710
1631
1711
export const logger : Logger = new Logger ( 'stacks' )
0 commit comments