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  }