diff --git a/go.mod b/go.mod index 780375f..892f757 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.25 require ( github.com/gin-gonic/gin v1.11.0 + github.com/sirupsen/logrus v1.9.3 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index bdae7bb..e29ec95 100644 --- a/go.sum +++ b/go.sum @@ -48,10 +48,13 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -73,6 +76,7 @@ golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/main.go b/main.go index b30e904..8ab3f58 100644 --- a/main.go +++ b/main.go @@ -7,13 +7,12 @@ import ( "fmt" "html/template" "io/ioutil" - "log" "net/http" "os" "strings" - "time" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) @@ -85,12 +84,12 @@ func loadConfig(path string) error { func (llm *LLMClient) ExtractKeywords(ctx context.Context, message string) ([]string, error) { prompt, err := renderPrompt(appConfig.LLM.ExtractKeywordsPrompt, map[string]string{"Message": message}) if err != nil { - log.Printf("[CONFIG] Failed to render ExtractKeywords prompt: %v", err) + logrus.WithError(err).Error("[CONFIG] Failed to render ExtractKeywords prompt") return nil, err } - log.Printf("[LLM] ExtractKeywords prompt: %q", prompt) + logrus.WithField("prompt", prompt).Info("[LLM] ExtractKeywords prompt") resp, err := llm.openAICompletion(ctx, prompt) - log.Printf("[LLM] ExtractKeywords response: %q, err: %v", resp, err) + logrus.WithFields(logrus.Fields{"response": resp, "err": err}).Info("[LLM] ExtractKeywords response") if err != nil { return nil, err } @@ -113,12 +112,12 @@ func (llm *LLMClient) DisambiguateBestMatch(ctx context.Context, message string, entries, _ := json.Marshal(candidates) prompt, err := renderPrompt(appConfig.LLM.DisambiguatePrompt, map[string]string{"Entries": string(entries), "Message": message}) if err != nil { - log.Printf("[CONFIG] Failed to render Disambiguate prompt: %v", err) + logrus.WithError(err).Error("[CONFIG] Failed to render Disambiguate prompt") return "", err } - log.Printf("[LLM] DisambiguateBestMatch prompt: %q", prompt) + logrus.WithField("prompt", prompt).Info("[LLM] DisambiguateBestMatch prompt") resp, err := llm.openAICompletion(ctx, prompt) - log.Printf("[LLM] DisambiguateBestMatch response: %q, err: %v", resp, err) + logrus.WithFields(logrus.Fields{"response": resp, "err": err}).Info("[LLM] DisambiguateBestMatch response") if err != nil { return "", err } @@ -135,7 +134,7 @@ func (llm *LLMClient) openAICompletion(ctx context.Context, prompt string) (stri if apiURL == "" { apiURL = "https://api.openai.com/v1/completions" } - log.Printf("[LLM] openAICompletion POST %s | prompt: %q", apiURL, prompt) + logrus.WithFields(logrus.Fields{"api_url": apiURL, "prompt": prompt}).Info("[LLM] openAICompletion POST") body := map[string]interface{}{ "model": "text-davinci-003", "prompt": prompt, @@ -151,7 +150,7 @@ func (llm *LLMClient) openAICompletion(ctx context.Context, prompt string) (stri client := &http.Client{} resp, err := client.Do(req) if err != nil { - log.Printf("[LLM] openAICompletion error: %v", err) + logrus.WithError(err).Error("[LLM] openAICompletion error") return "", err } defer resp.Body.Close() @@ -161,14 +160,14 @@ func (llm *LLMClient) openAICompletion(ctx context.Context, prompt string) (stri } `json:"choices"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - log.Printf("[LLM] openAICompletion decode error: %v", err) + logrus.WithError(err).Error("[LLM] openAICompletion decode error") return "", err } if len(result.Choices) == 0 { - log.Printf("[LLM] openAICompletion: no choices returned") + logrus.Warn("[LLM] openAICompletion: no choices returned") return "", nil } - log.Printf("[LLM] openAICompletion: got text: %q", result.Choices[0].Text) + logrus.WithField("text", result.Choices[0].Text).Info("[LLM] openAICompletion: got text") return result.Choices[0].Text, nil } @@ -242,16 +241,19 @@ func loadUITemplate(path string) error { } func main() { + logrus.SetFormatter(&logrus.TextFormatter{FullTimestamp: true}) + logrus.SetLevel(logrus.InfoLevel) + if err := loadConfig("config.yaml"); err != nil { - log.Fatalf("Failed to load config.yaml: %v", err) + logrus.Fatalf("Failed to load config.yaml: %v", err) } - log.Printf("Loaded config: %+v", appConfig) + logrus.Infof("Loaded config: %+v", appConfig) if err := loadYAMLDB("db.yaml"); err != nil { - log.Fatalf("Failed to load db.yaml: %v", err) + logrus.Fatalf("Failed to load db.yaml: %v", err) } fmt.Printf("Loaded %d reasons from db.yaml\n", len(reasonsDB)) if err := loadUITemplate("ui.html"); err != nil { - log.Fatalf("Failed to load ui.html: %v", err) + logrus.Fatalf("Failed to load ui.html: %v", err) } llm := &LLMClient{ APIKey: os.Getenv("OPENAI_API_KEY"), @@ -265,7 +267,7 @@ func main() { r.POST("/chat", func(c *gin.Context) { var req ChatRequest if err := c.ShouldBindJSON(&req); err != nil { - log.Printf("[ERROR] Invalid request: %v", err) + logrus.WithError(err).Error("Invalid request") c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) return } @@ -293,7 +295,12 @@ func main() { return } totalPrice, totalDuration := sumProcedures(best.Procedures) - log.Printf("[TRACE] Responding with match: %q, totalPrice: %d, totalDuration: %d, notes: %q", best.ID, totalPrice, totalDuration, best.Notes) + logrus.WithFields(logrus.Fields{ + "match": best.ID, + "total_price": totalPrice, + "total_duration": totalDuration, + "notes": best.Notes, + }).Info("Responding with match") c.JSON(http.StatusOK, ChatResponse{ Match: &best.ID, Procedures: best.Procedures, @@ -308,8 +315,13 @@ func main() { // logRequest logs incoming chat requests and extracted info func logRequest(req ChatRequest, keywords []string, candidates []Reason, bestID string, err error) { - log.Printf("[TRACE] %s | message: %q | keywords: %v | candidates: %v | bestID: %q | err: %v", - time.Now().Format(time.RFC3339), req.Message, keywords, getCandidateIDs(candidates), bestID, err) + logrus.WithFields(logrus.Fields{ + "message": req.Message, + "keywords": keywords, + "candidates": getCandidateIDs(candidates), + "bestID": bestID, + "err": err, + }).Info("Chat request trace") } func getCandidateIDs(candidates []Reason) []string {