mirror of
https://github.com/taigrr/log-socket
synced 2026-03-20 14:52:27 -07:00
Compare commits
1 Commits
burrow/6-a
...
cd/9-color
| Author | SHA1 | Date | |
|---|---|---|---|
| 0453539d29 |
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
|
||||
- **Frontend namespace selector**: Filter logs by namespace in the web UI
|
||||
- **Namespace API**: GET `/api/namespaces` to list all active namespaces
|
||||
- **Colored terminal output**: Log levels are color-coded in terminal (no external packages)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -188,6 +189,40 @@ ws://localhost:8080/ws?namespaces=api,database # Multiple namespaces
|
||||
- **Color Coding**: Different log levels are color-coded
|
||||
- **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
|
||||
|
||||
### 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() {
|
||||
namespaces = make(map[string]bool)
|
||||
initColorEnabled()
|
||||
stderrClient = CreateClient(DefaultNamespace)
|
||||
stderrClient.SetLogLevel(LTrace)
|
||||
stderrFinished = make(chan bool, 1)
|
||||
@@ -31,7 +32,10 @@ func init() {
|
||||
func (c *Client) logStdErr() {
|
||||
for e := range c.writer {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user