1
0
mirror of https://github.com/taigrr/log-socket synced 2026-03-20 16:02:28 -07:00
Files
log-socket/CRUSH.md
Tai Groot 99882832c0 feat(log): add Logger.Debugln, expand test coverage, update Go/CI (#21)
- Add missing Logger.Debugln method (package-level Debugln existed but
  Logger type lacked it)
- Replace empty test stubs with real tests for Debug, Debugf, Info,
  Infof, Print, Printf, Notice, Warn, Warnf, Error, Errorf, Panic,
  Panicf, Panicln
- Add tests for namespace filtering, multi-namespace clients,
  namespace registry, level storage, colorize, parseLevelString,
  Broadcast, matchesNamespace, fileInfo, Logger.Debugln,
  empty namespace default
- Update go.mod to Go 1.26.1
- Update CI to actions/checkout@v4, actions/setup-go@v5, Go 1.26,
  add -race flag, trigger on pull_request
- Fix stale CRUSH.md references (Go version, CI config, stderr bug)
2026-03-06 11:38:31 -05:00

493 lines
12 KiB
Markdown

# CRUSH.md - Log Socket v2 Development Guide
This document provides context and conventions for working on **log-socket v2** - a real-time log viewer with WebSocket support and namespace filtering, written in Go.
## Project Overview
Log Socket v2 is a Go library and standalone application that provides:
- Real-time log streaming via WebSocket
- **Namespace-based log organization** (NEW in v2)
- Web-based log viewer with namespace filtering
- Support for multiple log levels (TRACE, DEBUG, INFO, NOTICE, WARN, ERROR, PANIC, FATAL)
- Client architecture allowing multiple subscribers to filtered log streams
**Key insight**: This is both a library (importable Go package) and a standalone application. The main.go serves as an example implementation.
## Essential Commands
### Build
```bash
go build -v ./...
```
### Run Server
```bash
# Default (runs on 0.0.0.0:8080)
go run main.go
# Custom address
go run main.go -addr localhost:8080
```
Once running, open browser to `http://localhost:8080` to view logs.
### Test
```bash
go test -v ./...
```
### Install
```bash
go install github.com/taigrr/log-socket/v2@latest
```
### Dependencies
```bash
go get .
```
## Project Structure
```
.
├── main.go # Example server with multiple namespaces
├── log/ # Core logging package
│ ├── log.go # Package-level logging functions + namespace tracking
│ ├── logger.go # Logger type with namespace support
│ ├── types.go # Type definitions (includes Namespace fields)
│ └── log_test.go # Tests
├── ws/ # WebSocket server
│ ├── server.go # LogSocketHandler with namespace filtering
│ └── namespaces.go # HTTP handler for namespace list API
└── browser/ # Web UI
├── browser.go # HTTP handler serving embedded HTML
└── viewer.html # Embedded web interface with namespace filter
```
## Major Changes in v2
### Module Path
- **v1**: `github.com/taigrr/log-socket`
- **v2**: `github.com/taigrr/log-socket/v2`
### Namespace Support
**Core concept**: Namespaces allow organizing logs by component, service, or domain (e.g., "api", "database", "auth").
#### Types Changes
**Entry** now includes:
```go
type Entry struct {
Timestamp time.Time `json:"timestamp"`
Output string `json:"output"`
File string `json:"file"`
Level string `json:"level"`
Namespace string `json:"namespace"` // NEW
level Level
}
```
**Client** now uses a slice for filtering:
```go
type Client struct {
LogLevel Level `json:"level"`
Namespaces []string `json:"namespaces"` // Empty = all namespaces
writer LogWriter
initialized bool
}
```
**Logger** has namespace field:
```go
type Logger struct {
FileInfoDepth int
Namespace string // NEW
}
```
#### API Changes
**CreateClient** now variadic:
```go
// v1
func CreateClient() *Client
// v2
func CreateClient(namespaces ...string) *Client
// Examples:
client := log.CreateClient() // All namespaces
client := log.CreateClient("api") // Single namespace
client := log.CreateClient("api", "database") // Multiple namespaces
```
**NewLogger** constructor added:
```go
func NewLogger(namespace string) *Logger
// Example:
apiLogger := log.NewLogger("api")
apiLogger.Info("API request received")
```
### Namespace Tracking
Global namespace registry tracks all used namespaces:
```go
var (
namespaces map[string]bool
namespacesMux sync.RWMutex
)
func GetNamespaces() []string
```
Namespaces are automatically registered when logs are created.
### WebSocket Changes
**Query parameter for filtering**:
```
ws://localhost:8080/ws?namespaces=api,database
```
The handler parses comma-separated namespace list and creates a filtered client.
### Web UI Changes
- Namespace filter input field added to controls
- Namespace column added to log table
- Reconnect button to apply namespace filter
- WebSocket URL includes namespace query parameter
## Code Organization & Architecture
### Log Package (`log/`)
Dual API remains, but with namespace support:
1. **Package-level functions**: Use "default" namespace
- `log.Info()`, `log.Debug()`, etc.
- All entry creations include `Namespace: DefaultNamespace`
2. **Logger instances**: Use custom namespace
- Create with `log.NewLogger(namespace)`
- All entry creations include `Namespace: l.Namespace`
### Client Architecture (Updated)
**Client filtering by namespace**:
1. **Empty Namespaces slice**: Receives all logs regardless of namespace
2. **Non-empty Namespaces**: Only receives logs matching one of the specified namespaces
**matchesNamespace helper**:
```go
func (c *Client) matchesNamespace(namespace string) bool {
// Empty Namespaces slice means match all
if len(c.Namespaces) == 0 {
return true
}
for _, ns := range c.Namespaces {
if ns == namespace {
return true
}
}
return false
}
```
**Entry flow with namespace filtering**:
1. Log function called with namespace
2. `Entry` created with namespace field
3. Namespace registered in global map
4. `createLog()` sends to all clients
5. Each client checks `matchesNamespace()`
6. Only matching clients receive the entry
### WebSocket Handler (`ws/`)
**Namespace parameter parsing**:
```go
namespacesParam := r.URL.Query().Get("namespaces")
var namespaces []string
if namespacesParam != "" {
namespaces = strings.Split(namespacesParam, ",")
}
lc := logger.CreateClient(namespaces...)
```
**Namespaces API handler** (`ws/namespaces.go`):
```go
func NamespacesHandler(w http.ResponseWriter, r *http.Request) {
namespaces := logger.GetNamespaces()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"namespaces": namespaces,
})
}
```
### Browser Package (`browser/`)
Still embeds viewer.html, but HTML now includes:
- Namespace filter input
- Namespace column in grid (5 columns instead of 4)
- `reconnectWithNamespace()` method
- WebSocket URL construction with query parameter
## Go Version & Dependencies
- **Go version**: 1.26.1 (specified in go.mod)
- **Only external dependency**: `github.com/gorilla/websocket v1.5.3`
## Naming Conventions & Style
### Namespaces
- Use lowercase strings: `"api"`, `"database"`, `"auth"`, `"cache"`
- Default constant: `DefaultNamespace = "default"`
- Comma-separated in query params: `?namespaces=api,database`
### Log Levels
Unchanged from v1 - still use uppercase strings and iota constants.
### Variable Names
- Use descriptive names (`apiLogger`, `dbLogger`, `namespaces`)
- Exception: Loop variables, short-lived scopes
## Important Patterns & Gotchas
### 1. Namespace Tracking is Automatic
When any log is created, its namespace is automatically added to the global registry:
```go
func createLog(e Entry) {
// Track namespace
namespacesMux.Lock()
namespaces[e.Namespace] = true
namespacesMux.Unlock()
// ... rest of function
}
```
**No manual registration needed** - just log and the namespace appears.
### 2. Empty Namespace List = All Logs
Both for clients and WebSocket connections:
```go
client := log.CreateClient() // Gets ALL logs
ws://localhost:8080/ws // Gets ALL logs
```
This is the default behavior to maintain backward compatibility.
### 3. Client Namespace Filtering is Inclusive (OR)
If a client has multiple namespaces, it receives logs matching ANY of them:
```go
client := log.CreateClient("api", "database")
// Receives logs from "api" OR "database", not "auth"
```
### 4. Namespace Field Always Set
All logging functions set namespace:
- Package functions: `DefaultNamespace`
- Logger methods: `l.Namespace`
**Never nil or empty** - there's always a namespace.
### 5. WebSocket Namespace Reconnection
Changing namespace filter requires reconnecting WebSocket:
```javascript
reconnectWithNamespace() {
if (this.ws) {
this.ws.onclose = null; // Prevent auto-reconnect
this.ws.close();
this.ws = null;
}
this.reconnectAttempts = 0;
this.connectWebSocket(); // Creates new connection with new filter
}
```
UI provides "Reconnect" button for this purpose.
### 6. Stderr Client Uses All Namespaces
The built-in stderr client (created in `init()`) listens to all namespaces:
```go
stderrClient = CreateClient() // No args = all namespaces
```
It prints logs matching its level and namespace filter in `logStdErr()`:
```go
if e.level >= c.LogLevel && c.matchesNamespace(e.Namespace) {
fmt.Fprintf(os.Stderr, "%s\t%s\t[%s]\t%s\t%s\n", ...)
}
```
Since `CreateClient()` is called with no arguments, the Namespaces slice is empty, which means it matches all namespaces.
### 7. Grid Layout Updated
The log viewer grid changed from 4 to 5 columns:
```css
/* v1 */
grid-template-columns: 180px 80px 1fr 120px;
/* v2 */
grid-template-columns: 180px 80px 100px 1fr 120px;
```
Order: Timestamp, Level, Namespace, Message, Source
## Testing
### Test Updates for v2
All `CreateClient()` calls in tests now pass namespace:
```go
// v1
c := CreateClient()
// v2
c := CreateClient("test")
c := CreateClient(DefaultNamespace)
```
Tests verify namespace appears in output (see stderr format).
### Running Tests
```bash
go test -v ./...
```
All existing tests pass with namespace support added.
## CI/CD
GitHub Actions workflow (`.github/workflows/ci.yaml`):
- Uses Go 1.26, actions/checkout@v4, actions/setup-go@v5
- Runs tests with `-race` flag
- Triggers on push and pull_request
## Common Tasks
### Adding a New Namespace
No code changes needed! Just create a logger:
```go
cacheLogger := log.NewLogger("cache")
cacheLogger.Info("Cache initialized")
```
Namespace automatically tracked and available via API.
### Creating Namespace-Specific Client
```go
// Subscribe only to API logs
apiClient := log.CreateClient("api")
defer apiClient.Destroy()
for {
entry := apiClient.Get()
// Only receives logs from "api" namespace
processAPILog(entry)
}
```
### Filtering WebSocket by Namespace
Frontend:
```javascript
// Set namespace filter
document.getElementById('namespaceFilter').value = 'api,database';
// Click reconnect button or call:
logViewer.reconnectWithNamespace();
```
Backend automatically creates filtered client based on query param.
### Getting All Active Namespaces
```go
namespaces := log.GetNamespaces()
// Returns: ["default", "api", "database", "auth", ...]
```
Or via HTTP:
```bash
GET /api/namespaces
```
Returns:
```json
{
"namespaces": ["default", "api", "database", "auth"]
}
```
## Migration from v1 to v2
### Import Paths
```go
// v1
import "github.com/taigrr/log-socket/log"
import "github.com/taigrr/log-socket/ws"
import "github.com/taigrr/log-socket/browser"
// v2
import "github.com/taigrr/log-socket/v2/log"
import "github.com/taigrr/log-socket/v2/ws"
import "github.com/taigrr/log-socket/v2/browser"
```
### CreateClient Calls
```go
// v1
client := log.CreateClient()
// v2 - same behavior
client := log.CreateClient() // Empty = all namespaces
// v2 - new filtering capability
client := log.CreateClient("api")
client := log.CreateClient("api", "database")
```
### Default() Logger
```go
// v1
logger := log.Default()
// v2 - uses default namespace
logger := log.Default()
// v2 - new namespaced option
logger := log.NewLogger("api")
```
### WebSocket URL
```
v1: ws://host/ws
v2: ws://host/ws # All namespaces (backward compatible)
v2: ws://host/ws?namespaces=api # Filtered
v2: ws://host/ws?namespaces=api,database # Multiple
```
## Repository Context
- **License**: Check LICENSE file
- **Funding**: GitHub sponsors (.github/FUNDING.yml)
- **Open Source**: github.com/taigrr/log-socket
- **Version**: v2.x.x (major version bump for breaking changes)
## Future Agent Notes
- **v2 is a breaking change**: Major version bump follows Go modules convention
- Namespace support is the primary new feature
- Backward compatible behavior: empty namespace list = all logs
- Namespace tracking is automatic via global registry
- Web UI has been significantly updated for namespace support
- Possible bug: stderr client might need to use `CreateClient()` instead of `CreateClient(DefaultNamespace)` to see all logs
- All tests updated and passing
- Example in main.go demonstrates multiple namespaces