mirror of
https://github.com/taigrr/log-socket
synced 2026-03-20 18:22:24 -07:00
feat(log): add colored terminal output without external packages
Adds ANSI color-coded log levels for terminal output: - TRACE: Gray - DEBUG: Cyan - INFO: Green - NOTICE: Blue - WARN: Yellow - ERROR/PANIC/FATAL: Red (bold for PANIC/FATAL) Colors are auto-detected based on terminal capability and can be controlled via SetColorEnabled()/ColorEnabled(). Fixes #9
This commit is contained in:
35
README.md
35
README.md
@@ -15,6 +15,7 @@ A real-time log viewer with WebSocket support and namespace filtering, written i
|
|||||||
- **Namespace filtering**: Subscribe to specific namespaces via WebSocket
|
- **Namespace filtering**: Subscribe to specific namespaces via WebSocket
|
||||||
- **Frontend namespace selector**: Filter logs by namespace in the web UI
|
- **Frontend namespace selector**: Filter logs by namespace in the web UI
|
||||||
- **Namespace API**: GET `/api/namespaces` to list all active namespaces
|
- **Namespace API**: GET `/api/namespaces` to list all active namespaces
|
||||||
|
- **Colored terminal output**: Log levels are color-coded in terminal (no external packages)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -188,6 +189,40 @@ ws://localhost:8080/ws?namespaces=api,database # Multiple namespaces
|
|||||||
- **Color Coding**: Different log levels are color-coded
|
- **Color Coding**: Different log levels are color-coded
|
||||||
- **Reconnect**: Reconnect WebSocket with new namespace filter
|
- **Reconnect**: Reconnect WebSocket with new namespace filter
|
||||||
|
|
||||||
|
## Terminal Colors
|
||||||
|
|
||||||
|
Log output to stderr is automatically colorized when writing to a terminal. Colors are disabled when output is piped or redirected to a file.
|
||||||
|
|
||||||
|
### Color Scheme
|
||||||
|
|
||||||
|
| Level | Color |
|
||||||
|
|--------|--------------|
|
||||||
|
| TRACE | Gray |
|
||||||
|
| DEBUG | Cyan |
|
||||||
|
| INFO | Green |
|
||||||
|
| NOTICE | Blue |
|
||||||
|
| WARN | Yellow |
|
||||||
|
| ERROR | Red |
|
||||||
|
| PANIC | Bold Red |
|
||||||
|
| FATAL | Bold Red |
|
||||||
|
|
||||||
|
### Controlling Colors
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Disable colors (e.g., for CI/CD or file output)
|
||||||
|
logger.SetColorEnabled(false)
|
||||||
|
|
||||||
|
// Enable colors explicitly
|
||||||
|
logger.SetColorEnabled(true)
|
||||||
|
|
||||||
|
// Check current state
|
||||||
|
if logger.ColorEnabled() {
|
||||||
|
// colors are on
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Colors are implemented using standard ANSI escape codes with no external dependencies.
|
||||||
|
|
||||||
## Migration from v1
|
## Migration from v1
|
||||||
|
|
||||||
### Import Path
|
### Import Path
|
||||||
|
|||||||
99
log/color.go
Normal file
99
log/color.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ANSI color codes for terminal output
|
||||||
|
const (
|
||||||
|
colorReset = "\033[0m"
|
||||||
|
colorRed = "\033[31m"
|
||||||
|
colorGreen = "\033[32m"
|
||||||
|
colorYellow = "\033[33m"
|
||||||
|
colorBlue = "\033[34m"
|
||||||
|
colorPurple = "\033[35m"
|
||||||
|
colorCyan = "\033[36m"
|
||||||
|
colorWhite = "\033[37m"
|
||||||
|
colorGray = "\033[90m"
|
||||||
|
|
||||||
|
// Bold variants
|
||||||
|
colorBoldRed = "\033[1;31m"
|
||||||
|
colorBoldYellow = "\033[1;33m"
|
||||||
|
colorBoldWhite = "\033[1;37m"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
colorEnabled = true
|
||||||
|
colorEnabledOnce sync.Once
|
||||||
|
colorMux sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetColorEnabled enables or disables colored output for stderr logging.
|
||||||
|
// By default, color is enabled when stderr is a terminal.
|
||||||
|
func SetColorEnabled(enabled bool) {
|
||||||
|
colorMux.Lock()
|
||||||
|
colorEnabled = enabled
|
||||||
|
colorMux.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorEnabled returns whether colored output is currently enabled.
|
||||||
|
func ColorEnabled() bool {
|
||||||
|
colorMux.RLock()
|
||||||
|
defer colorMux.RUnlock()
|
||||||
|
return colorEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTerminal checks if the given file descriptor is a terminal.
|
||||||
|
// This is a simple heuristic that works on Unix-like systems.
|
||||||
|
func isTerminal(f *os.File) bool {
|
||||||
|
stat, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (stat.Mode() & os.ModeCharDevice) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// initColorEnabled sets the default color state based on whether stderr is a terminal.
|
||||||
|
func initColorEnabled() {
|
||||||
|
colorEnabledOnce.Do(func() {
|
||||||
|
colorEnabled = isTerminal(os.Stderr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// levelColor returns the ANSI color code for a given log level.
|
||||||
|
func levelColor(level Level) string {
|
||||||
|
switch level {
|
||||||
|
case LTrace:
|
||||||
|
return colorGray
|
||||||
|
case LDebug:
|
||||||
|
return colorCyan
|
||||||
|
case LInfo:
|
||||||
|
return colorGreen
|
||||||
|
case LNotice:
|
||||||
|
return colorBlue
|
||||||
|
case LWarn:
|
||||||
|
return colorYellow
|
||||||
|
case LError:
|
||||||
|
return colorRed
|
||||||
|
case LPanic:
|
||||||
|
return colorBoldRed
|
||||||
|
case LFatal:
|
||||||
|
return colorBoldRed
|
||||||
|
default:
|
||||||
|
return colorReset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// colorize wraps text with ANSI color codes if color is enabled.
|
||||||
|
func colorize(text string, color string) string {
|
||||||
|
if !ColorEnabled() {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return color + text + colorReset
|
||||||
|
}
|
||||||
|
|
||||||
|
// colorizeLevelText returns the level string with appropriate color.
|
||||||
|
func colorizeLevelText(level string, lvl Level) string {
|
||||||
|
return colorize(level, levelColor(lvl))
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
namespaces = make(map[string]bool)
|
namespaces = make(map[string]bool)
|
||||||
|
initColorEnabled()
|
||||||
stderrClient = CreateClient(DefaultNamespace)
|
stderrClient = CreateClient(DefaultNamespace)
|
||||||
stderrClient.SetLogLevel(LTrace)
|
stderrClient.SetLogLevel(LTrace)
|
||||||
stderrFinished = make(chan bool, 1)
|
stderrFinished = make(chan bool, 1)
|
||||||
@@ -31,7 +32,10 @@ func init() {
|
|||||||
func (c *Client) logStdErr() {
|
func (c *Client) logStdErr() {
|
||||||
for e := range c.writer {
|
for e := range c.writer {
|
||||||
if e.level >= c.LogLevel && c.matchesNamespace(e.Namespace) {
|
if e.level >= c.LogLevel && c.matchesNamespace(e.Namespace) {
|
||||||
fmt.Fprintf(os.Stderr, "%s\t%s\t[%s]\t%s\t%s\n", e.Timestamp.String(), e.Level, e.Namespace, e.Output, e.File)
|
levelStr := colorizeLevelText(e.Level, e.level)
|
||||||
|
nsStr := colorize("["+e.Namespace+"]", colorPurple)
|
||||||
|
fileStr := colorize(e.File, colorGray)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\t%s\t%s\t%s\t%s\n", e.Timestamp.String(), levelStr, nsStr, e.Output, fileStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stderrFinished <- true
|
stderrFinished <- true
|
||||||
|
|||||||
Reference in New Issue
Block a user