Skip to content
Open
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
4 changes: 3 additions & 1 deletion cmd/picoclaw/internal/gateway/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ import (

func NewGatewayCommand() *cobra.Command {
var debug bool
var logFilter string

cmd := &cobra.Command{
Use: "gateway",
Aliases: []string{"g"},
Short: "Start picoclaw gateway",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
return gatewayCmd(debug)
return gatewayCmd(debug, logFilter)
},
}

cmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
cmd.Flags().StringVar(&logFilter, "log-filter", "", "Filter logs by component (comma separated)")

return cmd
}
7 changes: 6 additions & 1 deletion cmd/picoclaw/internal/gateway/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,17 @@ import (
"github.com/sipeed/picoclaw/pkg/tools"
)

func gatewayCmd(debug bool) error {
func gatewayCmd(debug bool, logFilter string) error {
if debug {
logger.SetLevel(logger.DEBUG)
fmt.Println("πŸ” Debug mode enabled")
}

if logFilter != "" {
logger.SetComponentFilter(logFilter)
fmt.Printf("πŸ” Log filter enabled: %s\n", logFilter)
}

cfg, err := internal.LoadConfig()
if err != nil {
return fmt.Errorf("error loading config: %w", err)
Expand Down
36 changes: 35 additions & 1 deletion pkg/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ var (
logger *Logger
once sync.Once
mu sync.RWMutex

// componentFilter is a list of components to allow.
// If empty, all components are allowed.
componentFilter map[string]bool
)

type Logger struct {
Expand Down Expand Up @@ -67,6 +71,25 @@ func GetLevel() LogLevel {
return currentLevel
}

func SetComponentFilter(filter string) {
mu.Lock()
defer mu.Unlock()

if filter == "" {
componentFilter = nil
return
}

componentFilter = make(map[string]bool)
parts := strings.Split(filter, ",")
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
componentFilter[p] = true
}
}
}

func EnableFileLogging(filePath string) error {
mu.Lock()
defer mu.Unlock()
Expand Down Expand Up @@ -97,7 +120,18 @@ func DisableFileLogging() {
}

func logMessage(level LogLevel, component string, message string, fields map[string]any) {
if level < currentLevel {
mu.RLock()
// Check filter first if it exists
if componentFilter != nil && component != "" {
if !componentFilter[component] {
mu.RUnlock()
return
}
}
lvl := currentLevel
mu.RUnlock()

if level < lvl {
return
}

Expand Down
60 changes: 60 additions & 0 deletions pkg/logger/logger_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package logger

import (
"bytes"
"log"
"os"
"strings"
"testing"
)

func TestSetComponentFilter(t *testing.T) {
// Capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer log.SetOutput(os.Stderr)

// Reset filter
SetComponentFilter("")

// Test 1: No filter
InfoC("comp1", "msg1")
if !strings.Contains(buf.String(), "msg1") {
t.Error("Expected msg1 to be logged")
}
buf.Reset()

// Test 2: Filter comp1
SetComponentFilter("comp1")
InfoC("comp1", "msg2") // Should be logged
InfoC("comp2", "msg3") // Should NOT be logged

output := buf.String()
if !strings.Contains(output, "msg2") {
t.Error("Expected msg2 to be logged")
}
if strings.Contains(output, "msg3") {
t.Error("Expected msg3 NOT to be logged")
}
buf.Reset()

// Test 3: Multiple filters
SetComponentFilter("comp1,comp2")
InfoC("comp1", "msg4") // Logged
InfoC("comp2", "msg5") // Logged
InfoC("comp3", "msg6") // Not logged

output = buf.String()
if !strings.Contains(output, "msg4") {
t.Error("Expected msg4 to be logged")
}
if !strings.Contains(output, "msg5") {
t.Error("Expected msg5 to be logged")
}
if strings.Contains(output, "msg6") {
t.Error("Expected msg6 NOT to be logged")
}

// Reset filter at end
SetComponentFilter("")
}