go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/logd/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "flag" 6 "fmt" 7 "io" 8 "log" 9 "os" 10 "os/signal" 11 "path/filepath" 12 "syscall" 13 14 "cloud.google.com/go/logging" 15 "github.com/fsnotify/fsnotify" 16 "golang.org/x/sync/errgroup" 17 "google.golang.org/api/option" 18 ) 19 20 const ( 21 logDir = "/var/log/" 22 ) 23 24 var ( 25 stackdriverCreds string 26 cloudProject string 27 controller string 28 ) 29 30 type logMsg struct { 31 // Message is the actual line in the log. 32 Message string `json:"message"` 33 // LogName is the name of the log within /var/log this came from. 34 LogName string `json:"log_name"` 35 // Controller is the name of the control server this message came from. 36 Controller string `json:"controller"` 37 } 38 39 func init() { 40 flag.StringVar(&stackdriverCreds, "stackdriver-creds", "", "Path to the stackdriver credentials file") 41 flag.StringVar(&cloudProject, "cloud-project", "", "Name of the cloud project to stream logs to") 42 flag.StringVar(&controller, "controller", "", "Name of the control server logd is running on") 43 } 44 45 func execute(ctx context.Context, logsToPersist []string) error { 46 // Create a stackdriver logging client. 47 client, err := logging.NewClient(ctx, cloudProject, option.WithCredentialsFile(stackdriverCreds)) 48 if err != nil { 49 return err 50 } 51 defer client.Close() 52 // Iterate through each of the logs passed in and: 53 // 1) Ensure that it is in /var/log/ 54 // 2) Spin up a goroutine to persist messages to cloud. 55 eg, ctx := errgroup.WithContext(ctx) 56 for _, name := range logsToPersist { 57 logName := name 58 logPath := filepath.Join(logDir, logName) 59 logger := client.Logger(fmt.Sprintf("%s-%s", controller, logName)) 60 watcher, err := fsnotify.NewWatcher() 61 if err != nil { 62 return err 63 } 64 defer watcher.Close() 65 66 logFile, err := os.Open(logPath) 67 if err != nil { 68 return err 69 } 70 defer logFile.Close() 71 72 if _, err := logFile.Seek(0, os.SEEK_END); err != nil { 73 return err 74 } 75 76 eg.Go(func() error { 77 for { 78 select { 79 case event, ok := <-watcher.Events: 80 if !ok { 81 return nil 82 } 83 if event.Op&fsnotify.Write == fsnotify.Write { 84 data, err := io.ReadAll(logFile) 85 if err != nil { 86 return err 87 } 88 logger.Log(logging.Entry{ 89 Payload: logMsg{ 90 Message: string(data), 91 LogName: logName, 92 Controller: controller, 93 }, 94 }) 95 96 } 97 case err, ok := <-watcher.Errors: 98 if !ok { 99 return nil 100 } 101 return err 102 case <-ctx.Done(): 103 return nil 104 } 105 } 106 }) 107 if err := watcher.Add(logPath); err != nil { 108 return err 109 } 110 } 111 return eg.Wait() 112 } 113 114 func main() { 115 flag.Parse() 116 logsToPersist := flag.Args() 117 ctx, cancel := context.WithCancel(context.Background()) 118 defer cancel() 119 120 signals := make(chan os.Signal) 121 signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) 122 123 go func() { 124 select { 125 case <-signals: 126 cancel() 127 case <-ctx.Done(): 128 } 129 }() 130 131 if err := execute(ctx, logsToPersist); err != nil { 132 log.Fatalf("failed to run logd: %s", err) 133 } 134 }