refactor: migrate from log standard library to structured logger
This commit replaces usages of the standard library `log` package (specifically `log.Printf` and `log.Fatalf`) with the custom `jane/pkg/logger` structured logger across multiple backend files (`web/backend/main.go`, `web/backend/api/gateway.go`, `web/backend/api/oauth.go`, `web/backend/embed.go`, `cmd/picoclaw/internal/gateway/helpers.go`, and `pkg/agent/instance.go`). This fulfills the pending "Structured Logging" task outlined in `docs/design/ETL_TODO.md` to ensure a unified JSON structured logging pattern throughout the codebase. Co-authored-by: hobbyistlabs-coder <267281733+hobbyistlabs-coder@users.noreply.github.com>
This commit is contained in:
parent
c31965f35e
commit
c952fb97ca
12 changed files with 75 additions and 81 deletions
|
|
@ -3,7 +3,6 @@ package gateway
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -15,6 +14,7 @@ import (
|
||||||
"jane/pkg/channels"
|
"jane/pkg/channels"
|
||||||
_ "jane/pkg/channels/dingtalk"
|
_ "jane/pkg/channels/dingtalk"
|
||||||
_ "jane/pkg/channels/discord"
|
_ "jane/pkg/channels/discord"
|
||||||
|
_ "jane/pkg/channels/gmessages"
|
||||||
_ "jane/pkg/channels/irc"
|
_ "jane/pkg/channels/irc"
|
||||||
_ "jane/pkg/channels/line"
|
_ "jane/pkg/channels/line"
|
||||||
_ "jane/pkg/channels/maixcam"
|
_ "jane/pkg/channels/maixcam"
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
_ "jane/pkg/channels/pico"
|
_ "jane/pkg/channels/pico"
|
||||||
_ "jane/pkg/channels/qq"
|
_ "jane/pkg/channels/qq"
|
||||||
_ "jane/pkg/channels/slack"
|
_ "jane/pkg/channels/slack"
|
||||||
_ "jane/pkg/channels/gmessages"
|
|
||||||
_ "jane/pkg/channels/telegram"
|
_ "jane/pkg/channels/telegram"
|
||||||
_ "jane/pkg/channels/whatsapp"
|
_ "jane/pkg/channels/whatsapp"
|
||||||
_ "jane/pkg/channels/whatsapp_native"
|
_ "jane/pkg/channels/whatsapp_native"
|
||||||
|
|
@ -248,7 +247,7 @@ func setupCronTool(
|
||||||
var err error
|
var err error
|
||||||
cronTool, err = tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg)
|
cronTool, err = tools.NewCronTool(cronService, agentLoop, msgBus, workspace, restrict, execTimeout, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Critical error during CronTool initialization: %v", err)
|
logger.FatalCF("gateway", "Critical error during CronTool initialization", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
agentLoop.RegisterTool(cronTool)
|
agentLoop.RegisterTool(cronTool)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ This document tracks the tasks required to implement the "Ultimate Visibility" E
|
||||||
|
|
||||||
## 1. Extract (Ingestion & Telemetry Collection)
|
## 1. Extract (Ingestion & Telemetry Collection)
|
||||||
|
|
||||||
- [ ] **Structured Logging:** Ensure `zerolog` is used consistently across the codebase for structured JSON logging. Add context to logs where missing (session IDs, tool inputs/outputs).
|
- [x] **Structured Logging:** Ensure `zerolog` is used consistently across the codebase for structured JSON logging. Add context to logs where missing (session IDs, tool inputs/outputs).
|
||||||
- [x] **Basic Metrics Implementation:** Introduce a metrics package (e.g., using `expvar` or a Prometheus client) to expose basic application metrics.
|
- [x] **Basic Metrics Implementation:** Introduce a metrics package (e.g., using `expvar` or a Prometheus client) to expose basic application metrics.
|
||||||
- [x] **Goroutine Tracking:** Implement a metric to track the number of active Goroutines.
|
- [x] **Goroutine Tracking:** Implement a metric to track the number of active Goroutines.
|
||||||
- [x] **Memory Tracking:** Implement a metric to track heap allocation and GC pauses.
|
- [x] **Memory Tracking:** Implement a metric to track heap allocation and GC pauses.
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ package agent
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"jane/pkg/config"
|
"jane/pkg/config"
|
||||||
|
"jane/pkg/logger"
|
||||||
"jane/pkg/memory"
|
"jane/pkg/memory"
|
||||||
"jane/pkg/providers"
|
"jane/pkg/providers"
|
||||||
"jane/pkg/routing"
|
"jane/pkg/routing"
|
||||||
|
|
@ -86,7 +86,7 @@ func NewAgentInstance(
|
||||||
if cfg.Tools.IsToolEnabled("exec") {
|
if cfg.Tools.IsToolEnabled("exec") {
|
||||||
execTool, err := tools.NewExecToolWithConfig(workspace, restrict, cfg)
|
execTool, err := tools.NewExecToolWithConfig(workspace, restrict, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Critical error: unable to initialize exec tool: %v", err)
|
logger.FatalCF("agent", "Critical error: unable to initialize exec tool", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
toolsRegistry.Register(execTool)
|
toolsRegistry.Register(execTool)
|
||||||
}
|
}
|
||||||
|
|
@ -225,8 +225,7 @@ func NewAgentInstance(
|
||||||
})
|
})
|
||||||
lightCandidates = resolved
|
lightCandidates = resolved
|
||||||
} else {
|
} else {
|
||||||
log.Printf("routing: light_model %q not found in model_list — routing disabled for agent %q",
|
logger.WarnCF("agent", "routing: light_model not found in model_list — routing disabled for agent", map[string]any{"lightModel": rc.LightModel, "agentID": agentID})
|
||||||
rc.LightModel, agentID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -314,7 +313,7 @@ func (a *AgentInstance) Close() error {
|
||||||
func initSessionStore(dir string) session.SessionStore {
|
func initSessionStore(dir string) session.SessionStore {
|
||||||
store, err := memory.NewJSONLStore(dir)
|
store, err := memory.NewJSONLStore(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("memory: init store: %v; using json sessions", err)
|
logger.WarnCF("agent", "memory: init store failed; using json sessions", map[string]any{"error": err.Error()})
|
||||||
return session.NewSessionManager(dir)
|
return session.NewSessionManager(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,11 +321,11 @@ func initSessionStore(dir string) session.SessionStore {
|
||||||
// Migration failure means the store could not write data.
|
// Migration failure means the store could not write data.
|
||||||
// Fall back to SessionManager to avoid a split state where
|
// Fall back to SessionManager to avoid a split state where
|
||||||
// some sessions are in JSONL and others remain in JSON.
|
// some sessions are in JSONL and others remain in JSON.
|
||||||
log.Printf("memory: migration failed: %v; falling back to json sessions", merr)
|
logger.WarnCF("agent", "memory: migration failed; falling back to json sessions", map[string]any{"error": merr.Error()})
|
||||||
store.Close()
|
store.Close()
|
||||||
return session.NewSessionManager(dir)
|
return session.NewSessionManager(dir)
|
||||||
} else if n > 0 {
|
} else if n > 0 {
|
||||||
log.Printf("memory: migrated %d session(s) to jsonl", n)
|
logger.InfoCF("agent", "memory: migrated session(s) to jsonl", map[string]any{"count": n})
|
||||||
}
|
}
|
||||||
|
|
||||||
return session.NewJSONLBackend(store)
|
return session.NewJSONLBackend(store)
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"jane/pkg/logger"
|
|
||||||
"github.com/mdp/qrterminal/v3"
|
"github.com/mdp/qrterminal/v3"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"jane/pkg/logger"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/pkg/libgm"
|
"go.mau.fi/mautrix-gmessages/pkg/libgm"
|
||||||
"go.mau.fi/mautrix-gmessages/pkg/libgm/events"
|
"go.mau.fi/mautrix-gmessages/pkg/libgm/events"
|
||||||
|
|
@ -221,7 +221,6 @@ func (c *GMessagesChannel) handlePairing(ctx context.Context, client *GMClient)
|
||||||
qrterminal.GenerateHalfBlock(qrURL, qrterminal.L, os.Stdout)
|
qrterminal.GenerateHalfBlock(qrURL, qrterminal.L, os.Stdout)
|
||||||
fmt.Println("Waiting for pairing...")
|
fmt.Println("Waiting for pairing...")
|
||||||
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-pairingCh:
|
case <-pairingCh:
|
||||||
if pairErr != nil {
|
if pairErr != nil {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -18,6 +17,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"jane/pkg/config"
|
"jane/pkg/config"
|
||||||
|
"jane/pkg/logger"
|
||||||
"jane/web/backend/utils"
|
"jane/web/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -57,20 +57,20 @@ func (h *Handler) TryAutoStartGateway() {
|
||||||
|
|
||||||
ready, reason, err := h.gatewayStartReady()
|
ready, reason, err := h.gatewayStartReady()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Skip auto-starting gateway: %v", err)
|
logger.WarnCF("gateway", "Skip auto-starting gateway", map[string]any{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ready {
|
if !ready {
|
||||||
log.Printf("Skip auto-starting gateway: %s", reason)
|
logger.WarnCF("gateway", "Skip auto-starting gateway", map[string]any{"reason": reason})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pid, err := h.startGatewayLocked()
|
pid, err := h.startGatewayLocked()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to auto-start gateway: %v", err)
|
logger.ErrorCF("gateway", "Failed to auto-start gateway", map[string]any{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("Gateway auto-started (PID: %d)", pid)
|
logger.InfoCF("gateway", "Gateway auto-started", map[string]any{"pid": pid})
|
||||||
}
|
}
|
||||||
|
|
||||||
// gatewayStartReady validates whether current config can start the gateway.
|
// gatewayStartReady validates whether current config can start the gateway.
|
||||||
|
|
@ -162,7 +162,7 @@ func (h *Handler) startGatewayLocked() (int, error) {
|
||||||
|
|
||||||
// Ensure Pico Channel is configured before starting gateway
|
// Ensure Pico Channel is configured before starting gateway
|
||||||
if _, err := h.ensurePicoChannel(); err != nil {
|
if _, err := h.ensurePicoChannel(); err != nil {
|
||||||
log.Printf("Warning: failed to ensure pico channel: %v", err)
|
logger.WarnCF("gateway", "failed to ensure pico channel", map[string]any{"error": err.Error()})
|
||||||
// Non-fatal: gateway can still start without pico channel
|
// Non-fatal: gateway can still start without pico channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ func (h *Handler) startGatewayLocked() (int, error) {
|
||||||
|
|
||||||
gateway.cmd = cmd
|
gateway.cmd = cmd
|
||||||
pid := cmd.Process.Pid
|
pid := cmd.Process.Pid
|
||||||
log.Printf("Started picoclaw gateway (PID: %d) from %s", pid, execPath)
|
logger.InfoCF("gateway", "Started picoclaw gateway", map[string]any{"pid": pid, "execPath": execPath})
|
||||||
|
|
||||||
// Broadcast starting event
|
// Broadcast starting event
|
||||||
gateway.events.Broadcast(GatewayEvent{Status: "starting", PID: pid})
|
gateway.events.Broadcast(GatewayEvent{Status: "starting", PID: pid})
|
||||||
|
|
@ -184,9 +184,9 @@ func (h *Handler) startGatewayLocked() (int, error) {
|
||||||
// Wait for exit in background and clean up
|
// Wait for exit in background and clean up
|
||||||
go func() {
|
go func() {
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
log.Printf("Gateway process exited: %v", err)
|
logger.WarnCF("gateway", "Gateway process exited with error", map[string]any{"error": err.Error()})
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Gateway process exited normally")
|
logger.InfoCF("gateway", "Gateway process exited normally", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
gateway.mu.Lock()
|
gateway.mu.Lock()
|
||||||
|
|
@ -317,7 +317,7 @@ func (h *Handler) handleGatewayStop(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Sent stop signal to gateway (PID: %d)", pid)
|
logger.InfoCF("gateway", "Sent stop signal to gateway", map[string]any{"pid": pid})
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]any{
|
json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"jane/pkg/auth"
|
"jane/pkg/auth"
|
||||||
"jane/pkg/config"
|
"jane/pkg/config"
|
||||||
|
"jane/pkg/logger"
|
||||||
"jane/pkg/providers"
|
"jane/pkg/providers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -714,7 +714,7 @@ func (h *Handler) persistCredentialAndConfig(provider, authMethod string, cred *
|
||||||
if cp.Email == "" {
|
if cp.Email == "" {
|
||||||
email, err := oauthFetchGoogleUserEmailFunc(cp.AccessToken)
|
email, err := oauthFetchGoogleUserEmailFunc(cp.AccessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("oauth warning: could not fetch google email: %v", err)
|
logger.WarnCF("oauth", "could not fetch google email", map[string]any{"error": err.Error()})
|
||||||
} else {
|
} else {
|
||||||
cp.Email = email
|
cp.Email = email
|
||||||
}
|
}
|
||||||
|
|
@ -722,7 +722,7 @@ func (h *Handler) persistCredentialAndConfig(provider, authMethod string, cred *
|
||||||
if cp.ProjectID == "" {
|
if cp.ProjectID == "" {
|
||||||
projectID, err := oauthFetchAntigravityProject(cp.AccessToken)
|
projectID, err := oauthFetchAntigravityProject(cp.AccessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("oauth warning: could not fetch antigravity project id: %v", err)
|
logger.WarnCF("oauth", "could not fetch antigravity project id", map[string]any{"error": err.Error()})
|
||||||
} else {
|
} else {
|
||||||
cp.ProjectID = projectID
|
cp.ProjectID = projectID
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"jane/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed all:dist
|
//go:embed all:dist
|
||||||
|
|
@ -19,18 +20,14 @@ func registerEmbedRoutes(mux *http.ServeMux) {
|
||||||
// Go's built-in mime.TypeByExtension returns "image/svg" which is incorrect
|
// Go's built-in mime.TypeByExtension returns "image/svg" which is incorrect
|
||||||
// The correct MIME type per RFC 6838 is "image/svg+xml"
|
// The correct MIME type per RFC 6838 is "image/svg+xml"
|
||||||
if err := mime.AddExtensionType(".svg", "image/svg+xml"); err != nil {
|
if err := mime.AddExtensionType(".svg", "image/svg+xml"); err != nil {
|
||||||
log.Printf("Warning: failed to register SVG MIME type: %v", err)
|
logger.WarnCF("embed", "failed to register SVG MIME type", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to get the subdirectory 'dist' where Vite usually builds
|
// Attempt to get the subdirectory 'dist' where Vite usually builds
|
||||||
subFS, err := fs.Sub(frontendFS, "dist")
|
subFS, err := fs.Sub(frontendFS, "dist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log a warning if dist doesn't exist yet (e.g., during development before a frontend build)
|
// Log a warning if dist doesn't exist yet (e.g., during development before a frontend build)
|
||||||
log.Printf(
|
logger.WarnCF("embed", "no 'dist' folder found in embedded frontend. Ensure you run `pnpm build:backend` in the frontend directory before building the Go backend.", nil)
|
||||||
"Warning: no 'dist' folder found in embedded frontend. " +
|
|
||||||
"Ensure you run `pnpm build:backend` in the frontend directory " +
|
|
||||||
"before building the Go backend.",
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"jane/pkg/logger"
|
||||||
"jane/web/backend/api"
|
"jane/web/backend/api"
|
||||||
"jane/web/backend/launcherconfig"
|
"jane/web/backend/launcherconfig"
|
||||||
"jane/web/backend/middleware"
|
"jane/web/backend/middleware"
|
||||||
|
|
@ -59,11 +59,11 @@ func main() {
|
||||||
|
|
||||||
absPath, err := filepath.Abs(configPath)
|
absPath, err := filepath.Abs(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to resolve config path: %v", err)
|
logger.FatalCF("main", "Failed to resolve config path", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
err = utils.EnsureOnboarded(absPath)
|
err = utils.EnsureOnboarded(absPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: Failed to initialize PicoClaw config automatically: %v", err)
|
logger.WarnCF("main", "Failed to initialize PicoClaw config automatically", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
var explicitPort bool
|
var explicitPort bool
|
||||||
|
|
@ -80,7 +80,7 @@ func main() {
|
||||||
launcherPath := launcherconfig.PathForAppConfig(absPath)
|
launcherPath := launcherconfig.PathForAppConfig(absPath)
|
||||||
launcherCfg, err := launcherconfig.Load(launcherPath, launcherconfig.Default())
|
launcherCfg, err := launcherconfig.Load(launcherPath, launcherconfig.Default())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Warning: Failed to load %s: %v", launcherPath, err)
|
logger.WarnCF("main", "Failed to load launcher config", map[string]any{"path": launcherPath, "error": err.Error()})
|
||||||
launcherCfg = launcherconfig.Default()
|
launcherCfg = launcherconfig.Default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ func main() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = errors.New("must be in range 1-65535")
|
err = errors.New("must be in range 1-65535")
|
||||||
}
|
}
|
||||||
log.Fatalf("Invalid port %q: %v", effectivePort, err)
|
logger.FatalCF("main", "Invalid port", map[string]any{"port": effectivePort, "error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine listen address
|
// Determine listen address
|
||||||
|
|
@ -122,7 +122,7 @@ func main() {
|
||||||
|
|
||||||
accessControlledMux, err := middleware.IPAllowlist(launcherCfg.AllowedCIDRs, mux)
|
accessControlledMux, err := middleware.IPAllowlist(launcherCfg.AllowedCIDRs, mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Invalid allowed CIDR configuration: %v", err)
|
logger.FatalCF("main", "Invalid allowed CIDR configuration", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply middleware stack
|
// Apply middleware stack
|
||||||
|
|
@ -151,7 +151,7 @@ func main() {
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
url := "http://localhost:" + effectivePort
|
url := "http://localhost:" + effectivePort
|
||||||
if err := utils.OpenBrowser(url); err != nil {
|
if err := utils.OpenBrowser(url); err != nil {
|
||||||
log.Printf("Warning: Failed to auto-open browser: %v", err)
|
logger.WarnCF("main", "Failed to auto-open browser", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
@ -173,6 +173,6 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalf("Server failed to start: %v", err)
|
logger.FatalCF("main", "Server failed to start", map[string]any{"error": err.Error()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue