mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat: refactor logging system to slog with DI and component tagging
This commit is contained in:
+65
-47
@@ -3,13 +3,14 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/memohai/memoh/internal/chat"
|
||||
"github.com/memohai/memoh/internal/config"
|
||||
"github.com/memohai/memoh/internal/logger"
|
||||
ctr "github.com/memohai/memoh/internal/containerd"
|
||||
"github.com/memohai/memoh/internal/db"
|
||||
dbsqlc "github.com/memohai/memoh/internal/db/sqlc"
|
||||
@@ -34,15 +35,20 @@ func main() {
|
||||
cfgPath := os.Getenv("CONFIG_PATH")
|
||||
cfg, err := config.Load(cfgPath)
|
||||
if err != nil {
|
||||
log.Fatalf("load config: %v", err)
|
||||
fmt.Fprintf(os.Stderr, "load config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Init(cfg.Log.Level, cfg.Log.Format)
|
||||
|
||||
if strings.TrimSpace(cfg.Auth.JWTSecret) == "" {
|
||||
log.Fatalf("jwt secret is required")
|
||||
logger.Error("jwt secret is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
jwtExpiresIn, err := time.ParseDuration(cfg.Auth.JWTExpiresIn)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid jwt expires in: %v", err)
|
||||
logger.Error("invalid jwt expires in", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
addr := cfg.Server.Addr
|
||||
@@ -57,30 +63,33 @@ func main() {
|
||||
factory := ctr.DefaultClientFactory{SocketPath: socketPath}
|
||||
client, err := factory.New(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("connect containerd: %v", err)
|
||||
logger.Error("connect containerd", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
service := ctr.NewDefaultService(client, cfg.Containerd.Namespace)
|
||||
manager := mcp.NewManager(service, cfg.MCP)
|
||||
service := ctr.NewDefaultService(logger.L, client, cfg.Containerd.Namespace)
|
||||
manager := mcp.NewManager(logger.L, service, cfg.MCP)
|
||||
|
||||
pingHandler := handlers.NewPingHandler()
|
||||
containerdHandler := handlers.NewContainerdHandler(service, cfg.MCP, cfg.Containerd.Namespace)
|
||||
pingHandler := handlers.NewPingHandler(logger.L)
|
||||
containerdHandler := handlers.NewContainerdHandler(logger.L, service, cfg.MCP, cfg.Containerd.Namespace)
|
||||
|
||||
conn, err := db.Open(ctx, cfg.Postgres)
|
||||
if err != nil {
|
||||
log.Fatalf("db connect: %v", err)
|
||||
logger.Error("db connect", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
manager.WithDB(conn)
|
||||
queries := dbsqlc.New(conn)
|
||||
modelsService := models.NewService(queries)
|
||||
modelsService := models.NewService(logger.L, queries)
|
||||
|
||||
if err := ensureAdminUser(ctx, queries, cfg); err != nil {
|
||||
log.Fatalf("ensure admin user: %v", err)
|
||||
if err := ensureAdminUser(ctx, logger.L, queries, cfg); err != nil {
|
||||
logger.Error("ensure admin user", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
authHandler := handlers.NewAuthHandler(conn, cfg.Auth.JWTSecret, jwtExpiresIn)
|
||||
authHandler := handlers.NewAuthHandler(logger.L, conn, cfg.Auth.JWTSecret, jwtExpiresIn)
|
||||
|
||||
// Initialize chat resolver after memory service is configured.
|
||||
var chatResolver *chat.Resolver
|
||||
@@ -90,27 +99,29 @@ func main() {
|
||||
modelsService: modelsService,
|
||||
queries: queries,
|
||||
timeout: 30 * time.Second,
|
||||
logger: logger.L,
|
||||
}
|
||||
|
||||
resolver := embeddings.NewResolver(modelsService, queries, 10*time.Second)
|
||||
resolver := embeddings.NewResolver(logger.L, modelsService, queries, 10*time.Second)
|
||||
vectors, textModel, multimodalModel, hasModels, err := embeddings.CollectEmbeddingVectors(ctx, modelsService)
|
||||
if err != nil {
|
||||
log.Fatalf("embedding models: %v", err)
|
||||
logger.Error("embedding models", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var memoryService *memory.Service
|
||||
var memoryHandler *handlers.MemoryHandler
|
||||
|
||||
if !hasModels {
|
||||
log.Println("WARNING: No embedding models configured. Memory service will not be available.")
|
||||
log.Println("You can add embedding models via the /models API endpoint.")
|
||||
memoryHandler = handlers.NewMemoryHandler(nil)
|
||||
logger.Warn("No embedding models configured. Memory service will not be available.")
|
||||
logger.Warn("You can add embedding models via the /models API endpoint.")
|
||||
memoryHandler = handlers.NewMemoryHandler(logger.L, nil)
|
||||
} else {
|
||||
if textModel.ModelID == "" {
|
||||
log.Println("WARNING: No text embedding model configured. Text embedding features will be limited.")
|
||||
logger.Warn("No text embedding model configured. Text embedding features will be limited.")
|
||||
}
|
||||
if multimodalModel.ModelID == "" {
|
||||
log.Println("WARNING: No multimodal embedding model configured. Multimodal embedding features will be limited.")
|
||||
logger.Warn("No multimodal embedding model configured. Multimodal embedding features will be limited.")
|
||||
}
|
||||
|
||||
var textEmbedder embeddings.Embedder
|
||||
@@ -125,6 +136,7 @@ func main() {
|
||||
|
||||
if len(vectors) > 0 {
|
||||
store, err = memory.NewQdrantStoreWithVectors(
|
||||
logger.L,
|
||||
cfg.Qdrant.BaseURL,
|
||||
cfg.Qdrant.APIKey,
|
||||
cfg.Qdrant.Collection,
|
||||
@@ -132,10 +144,12 @@ func main() {
|
||||
time.Duration(cfg.Qdrant.TimeoutSeconds)*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("qdrant named vectors init: %v", err)
|
||||
logger.Error("qdrant named vectors init", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
store, err = memory.NewQdrantStore(
|
||||
logger.L,
|
||||
cfg.Qdrant.BaseURL,
|
||||
cfg.Qdrant.APIKey,
|
||||
cfg.Qdrant.Collection,
|
||||
@@ -143,42 +157,45 @@ func main() {
|
||||
time.Duration(cfg.Qdrant.TimeoutSeconds)*time.Second,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("qdrant init: %v", err)
|
||||
logger.Error("qdrant init", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memoryService = memory.NewService(llmClient, textEmbedder, store, resolver, textModel.ModelID, multimodalModel.ModelID)
|
||||
memoryHandler = handlers.NewMemoryHandler(memoryService)
|
||||
memoryService = memory.NewService(logger.L, llmClient, textEmbedder, store, resolver, textModel.ModelID, multimodalModel.ModelID)
|
||||
memoryHandler = handlers.NewMemoryHandler(logger.L, memoryService)
|
||||
}
|
||||
chatResolver = chat.NewResolver(modelsService, queries, memoryService, cfg.AgentGateway.BaseURL(), 30*time.Second)
|
||||
embeddingsHandler := handlers.NewEmbeddingsHandler(modelsService, queries)
|
||||
swaggerHandler := handlers.NewSwaggerHandler()
|
||||
chatHandler := handlers.NewChatHandler(chatResolver)
|
||||
chatResolver = chat.NewResolver(logger.L, modelsService, queries, memoryService, cfg.AgentGateway.BaseURL(), 30*time.Second)
|
||||
embeddingsHandler := handlers.NewEmbeddingsHandler(logger.L, modelsService, queries)
|
||||
swaggerHandler := handlers.NewSwaggerHandler(logger.L)
|
||||
chatHandler := handlers.NewChatHandler(logger.L, chatResolver)
|
||||
|
||||
// Initialize providers and models handlers
|
||||
providersService := providers.NewService(queries)
|
||||
providersHandler := handlers.NewProvidersHandler(providersService)
|
||||
modelsHandler := handlers.NewModelsHandler(modelsService)
|
||||
settingsService := settings.NewService(queries)
|
||||
settingsHandler := handlers.NewSettingsHandler(settingsService)
|
||||
historyService := history.NewService(queries)
|
||||
historyHandler := handlers.NewHistoryHandler(historyService)
|
||||
scheduleService := schedule.NewService(queries, chatResolver, cfg.Auth.JWTSecret)
|
||||
providersService := providers.NewService(logger.L, queries)
|
||||
providersHandler := handlers.NewProvidersHandler(logger.L, providersService)
|
||||
modelsHandler := handlers.NewModelsHandler(logger.L, modelsService)
|
||||
settingsService := settings.NewService(logger.L, queries)
|
||||
settingsHandler := handlers.NewSettingsHandler(logger.L, settingsService)
|
||||
historyService := history.NewService(logger.L, queries)
|
||||
historyHandler := handlers.NewHistoryHandler(logger.L, historyService)
|
||||
scheduleService := schedule.NewService(logger.L, queries, chatResolver, cfg.Auth.JWTSecret)
|
||||
if err := scheduleService.Bootstrap(ctx); err != nil {
|
||||
log.Fatalf("schedule bootstrap: %v", err)
|
||||
logger.Error("schedule bootstrap", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
scheduleHandler := handlers.NewScheduleHandler(scheduleService)
|
||||
subagentService := subagent.NewService(queries)
|
||||
subagentHandler := handlers.NewSubagentHandler(subagentService)
|
||||
srv := server.NewServer(addr, cfg.Auth.JWTSecret, pingHandler, authHandler, memoryHandler, embeddingsHandler, chatHandler, swaggerHandler, providersHandler, modelsHandler, settingsHandler, historyHandler, scheduleHandler, subagentHandler, containerdHandler)
|
||||
scheduleHandler := handlers.NewScheduleHandler(logger.L, scheduleService)
|
||||
subagentService := subagent.NewService(logger.L, queries)
|
||||
subagentHandler := handlers.NewSubagentHandler(logger.L, subagentService)
|
||||
srv := server.NewServer(logger.L, addr, cfg.Auth.JWTSecret, pingHandler, authHandler, memoryHandler, embeddingsHandler, chatHandler, swaggerHandler, providersHandler, modelsHandler, settingsHandler, historyHandler, scheduleHandler, subagentHandler, containerdHandler)
|
||||
|
||||
if err := srv.Start(); err != nil {
|
||||
log.Fatalf("server failed: %v", err)
|
||||
logger.Error("server failed", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureAdminUser(ctx context.Context, queries *dbsqlc.Queries, cfg config.Config) error {
|
||||
func ensureAdminUser(ctx context.Context, log *slog.Logger, queries *dbsqlc.Queries, cfg config.Config) error {
|
||||
if queries == nil {
|
||||
return fmt.Errorf("db queries not configured")
|
||||
}
|
||||
@@ -197,7 +214,7 @@ func ensureAdminUser(ctx context.Context, queries *dbsqlc.Queries, cfg config.Co
|
||||
return fmt.Errorf("admin username/password required in config.toml")
|
||||
}
|
||||
if password == "change-your-password-here" {
|
||||
log.Printf("WARNING: admin password uses default placeholder; please update config.toml")
|
||||
log.Warn("admin password uses default placeholder; please update config.toml")
|
||||
}
|
||||
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
@@ -225,7 +242,7 @@ func ensureAdminUser(ctx context.Context, queries *dbsqlc.Queries, cfg config.Co
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Admin user created: %s", username)
|
||||
log.Info("Admin user created", slog.String("username", username))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -233,6 +250,7 @@ type lazyLLMClient struct {
|
||||
modelsService *models.Service
|
||||
queries *dbsqlc.Queries
|
||||
timeout time.Duration
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (c *lazyLLMClient) Extract(ctx context.Context, req memory.ExtractRequest) (memory.ExtractResponse, error) {
|
||||
@@ -263,5 +281,5 @@ func (c *lazyLLMClient) resolve(ctx context.Context) (memory.LLM, error) {
|
||||
if clientType != "openai" && clientType != "openai-compat" {
|
||||
return nil, fmt.Errorf("memory provider client type not supported: %s", memoryProvider.ClientType)
|
||||
}
|
||||
return memory.NewLLMClient(memoryProvider.BaseUrl, memoryProvider.ApiKey, memoryModel.ModelID, c.timeout), nil
|
||||
return memory.NewLLMClient(c.logger, memoryProvider.BaseUrl, memoryProvider.ApiKey, memoryModel.ModelID, c.timeout), nil
|
||||
}
|
||||
|
||||
+17
-7
@@ -8,7 +8,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/memohai/memoh/internal/chat"
|
||||
"github.com/memohai/memoh/internal/config"
|
||||
"github.com/memohai/memoh/internal/logger"
|
||||
)
|
||||
|
||||
type cliOptions struct {
|
||||
@@ -33,13 +34,18 @@ func main() {
|
||||
|
||||
cfg, err := config.Load(opts.configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("load config: %v", err)
|
||||
fmt.Fprintf(os.Stderr, "load config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Init(cfg.Log.Level, cfg.Log.Format)
|
||||
|
||||
if strings.TrimSpace(opts.apiBaseURL) == "" {
|
||||
opts.apiBaseURL = defaultAPIBaseURL(cfg.Server.Addr)
|
||||
}
|
||||
if strings.TrimSpace(opts.apiBaseURL) == "" {
|
||||
log.Fatalf("api url is required")
|
||||
logger.Error("api url is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
opts.apiBaseURL = normalizeBaseURL(opts.apiBaseURL)
|
||||
|
||||
@@ -48,7 +54,8 @@ func main() {
|
||||
if jwtToken == "" {
|
||||
username, password, err := resolveLoginCredentials(opts, cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("resolve login: %v", err)
|
||||
logger.Error("resolve login", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
loginCtx := ctx
|
||||
if opts.timeout > 0 {
|
||||
@@ -58,19 +65,22 @@ func main() {
|
||||
}
|
||||
jwtToken, err = resolveJWTToken(loginCtx, client, opts.apiBaseURL, username, password)
|
||||
if err != nil {
|
||||
log.Fatalf("resolve jwt: %v", err)
|
||||
logger.Error("resolve jwt", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
query := strings.TrimSpace(strings.Join(flag.Args(), " "))
|
||||
if query != "" {
|
||||
if err := sendChat(ctx, client, opts.apiBaseURL, jwtToken, query); err != nil {
|
||||
log.Fatalf("chat failed: %v", err)
|
||||
logger.Error("chat failed", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := runInteractive(ctx, client, opts.apiBaseURL, jwtToken); err != nil {
|
||||
log.Fatalf("chat failed: %v", err)
|
||||
logger.Error("chat failed", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/memohai/memoh/internal/logger"
|
||||
"github.com/memohai/memoh/internal/mcp"
|
||||
gomcp "github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
@@ -17,12 +19,24 @@ func main() {
|
||||
if version == "unknown" {
|
||||
version = "v0.0.0-dev+" + commitHash
|
||||
}
|
||||
|
||||
logLevel := os.Getenv("LOG_LEVEL")
|
||||
if logLevel == "" {
|
||||
logLevel = "info"
|
||||
}
|
||||
logFormat := os.Getenv("LOG_FORMAT")
|
||||
if logFormat == "" {
|
||||
logFormat = "text"
|
||||
}
|
||||
logger.Init(logLevel, logFormat)
|
||||
|
||||
server := gomcp.NewServer(
|
||||
&gomcp.Implementation{Name: "memoh-mcp", Version: version},
|
||||
nil,
|
||||
)
|
||||
mcp.RegisterTools(server)
|
||||
if err := server.Run(context.Background(), &gomcp.StdioTransport{}); err != nil {
|
||||
log.Fatal(err)
|
||||
logger.Error("mcp server failed", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user