1package cmd
2
3import (
4 "encoding/json"
5 "fmt"
6 "path/filepath"
7 "time"
8
9 "github.com/charmbracelet/crush/pkg/config"
10 "github.com/charmbracelet/crush/pkg/env"
11 "github.com/charmbracelet/log/v2"
12 "github.com/nxadm/tail"
13 "github.com/spf13/cobra"
14)
15
16func init() {
17 rootCmd.AddCommand(logsCmd)
18}
19
20var logsCmd = &cobra.Command{
21 Use: "logs",
22 Short: "View crush logs",
23 Long: `View the logs generated by Crush. This command allows you to see the log output for debugging and monitoring purposes.`,
24 RunE: func(cmd *cobra.Command, args []string) error {
25 cwd, err := cmd.Flags().GetString("cwd")
26 if err != nil {
27 return fmt.Errorf("failed to get current working directory: %v", err)
28 }
29 cfg, err := config.Load(cwd, env.New())
30 if err != nil {
31 return fmt.Errorf("failed to load configuration: %v", err)
32 }
33 t, err := tail.TailFile(filepath.Join(cfg.Options.DataDirectory, "logs", "crush.log"), tail.Config{Follow: true, ReOpen: true, Logger: tail.DiscardingLogger})
34 if err != nil {
35 return fmt.Errorf("failed to tail log file: %v", err)
36 }
37
38 // Print the text of each received line
39 for line := range t.Lines {
40 var data map[string]any
41 if err := json.Unmarshal([]byte(line.Text), &data); err != nil {
42 continue
43 }
44 msg := data["msg"]
45 level := data["level"]
46 otherData := []any{}
47 for k, v := range data {
48 switch k {
49 case "msg", "level", "time":
50 continue
51 case "source":
52 source, ok := v.(map[string]any)
53 if !ok {
54 continue
55 }
56 sourceFile := fmt.Sprintf("%s:%d", source["file"], int(source["line"].(float64)))
57 otherData = append(otherData, "source", sourceFile)
58
59 default:
60 otherData = append(otherData, k, v)
61 }
62 }
63 log.SetTimeFunction(func(_ time.Time) time.Time {
64 // parse the timestamp from the log line if available
65 t, err := time.Parse(time.RFC3339, data["time"].(string))
66 if err != nil {
67 return time.Now() // fallback to current time if parsing fails
68 }
69 return t
70 })
71 switch level {
72 case "INFO":
73 log.Info(msg, otherData...)
74 case "DEBUG":
75 log.Debug(msg, otherData...)
76 case "ERROR":
77 log.Error(msg, otherData...)
78 case "WARN":
79 log.Warn(msg, otherData...)
80 default:
81 log.Info(msg, otherData...)
82 }
83 }
84 return nil
85 },
86}