Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
99 changes: 99 additions & 0 deletions log/color.go
Original file line number Diff line number Diff line change
@@ -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))
}
6 changes: 5 additions & 1 deletion log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (

func init() {
namespaces = make(map[string]bool)
initColorEnabled()
stderrClient = CreateClient(DefaultNamespace)
stderrClient.SetLogLevel(LTrace)
stderrFinished = make(chan bool, 1)
Expand All @@ -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
Expand Down