github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent_monitor.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"os/signal"
     8  	"strconv"
     9  	"strings"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/hashicorp/nomad/api"
    14  	"github.com/mitchellh/cli"
    15  )
    16  
    17  type MonitorCommand struct {
    18  	Meta
    19  }
    20  
    21  func (c *MonitorCommand) Help() string {
    22  	helpText := `
    23  Usage: nomad monitor [options]
    24  
    25    Stream log messages of a nomad agent. The monitor command lets you
    26    listen for log levels that may be filtered out of the Nomad agent. For
    27    example your agent may only be logging at INFO level, but with the monitor
    28    command you can set -log-level DEBUG
    29  
    30    When ACLs are enabled, this command requires a token with the 'agent:read'
    31    capability.
    32  
    33  General Options:
    34  
    35    ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
    36  
    37  Monitor Specific Options:
    38  
    39    -log-level <level>
    40      Sets the log level to monitor (default: INFO)
    41  
    42    -node-id <node-id>
    43      Sets the specific node to monitor
    44  
    45    -server-id <server-id>
    46      Sets the specific server to monitor
    47  
    48    -json
    49      Sets log output to JSON format
    50    `
    51  	return strings.TrimSpace(helpText)
    52  }
    53  
    54  func (c *MonitorCommand) Synopsis() string {
    55  	return "Stream logs from a Nomad agent"
    56  }
    57  
    58  func (c *MonitorCommand) Name() string { return "monitor" }
    59  
    60  func (c *MonitorCommand) Run(args []string) int {
    61  	c.Ui = &cli.PrefixedUi{
    62  		OutputPrefix: "    ",
    63  		InfoPrefix:   "    ",
    64  		ErrorPrefix:  "==> ",
    65  		Ui:           c.Ui,
    66  	}
    67  
    68  	var logLevel string
    69  	var nodeID string
    70  	var serverID string
    71  	var logJSON bool
    72  
    73  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
    74  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    75  	flags.StringVar(&logLevel, "log-level", "", "")
    76  	flags.StringVar(&nodeID, "node-id", "", "")
    77  	flags.StringVar(&serverID, "server-id", "", "")
    78  	flags.BoolVar(&logJSON, "json", false, "")
    79  
    80  	if err := flags.Parse(args); err != nil {
    81  		return 1
    82  	}
    83  
    84  	args = flags.Args()
    85  	if l := len(args); l != 0 {
    86  		c.Ui.Error("This command takes no arguments")
    87  		c.Ui.Error(commandErrorText(c))
    88  		return 1
    89  	}
    90  
    91  	client, err := c.Meta.Client()
    92  	if err != nil {
    93  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    94  		c.Ui.Error(commandErrorText(c))
    95  		return 1
    96  	}
    97  
    98  	// Query the node info and lookup prefix
    99  	if len(nodeID) == 1 {
   100  		c.Ui.Error("Node identifier must contain at least two characters.")
   101  		return 1
   102  	}
   103  
   104  	if nodeID != "" {
   105  		nodeID = sanitizeUUIDPrefix(nodeID)
   106  		nodes, _, err := client.Nodes().PrefixList(nodeID)
   107  		if err != nil {
   108  			c.Ui.Error(fmt.Sprintf("Error querying node: %v", err))
   109  			return 1
   110  		}
   111  
   112  		if len(nodes) == 0 {
   113  			c.Ui.Error(fmt.Sprintf("No node(s) with prefix or id %q found", nodeID))
   114  			return 1
   115  		}
   116  
   117  		if len(nodes) > 1 {
   118  			out := formatNodeStubList(nodes, false)
   119  			c.Ui.Output(fmt.Sprintf("Prefix matched multiple nodes\n\n%s", out))
   120  			return 1
   121  		}
   122  		nodeID = nodes[0].ID
   123  	}
   124  
   125  	params := map[string]string{
   126  		"log_level": logLevel,
   127  		"node_id":   nodeID,
   128  		"server_id": serverID,
   129  		"log_json":  strconv.FormatBool(logJSON),
   130  	}
   131  
   132  	query := &api.QueryOptions{
   133  		Params: params,
   134  	}
   135  
   136  	eventDoneCh := make(chan struct{})
   137  	frames, errCh := client.Agent().Monitor(eventDoneCh, query)
   138  	select {
   139  	case err := <-errCh:
   140  		c.Ui.Error(fmt.Sprintf("Error starting monitor: %s", err))
   141  		c.Ui.Error(commandErrorText(c))
   142  		return 1
   143  	default:
   144  	}
   145  
   146  	// Create a reader
   147  	var r io.ReadCloser
   148  	frameReader := api.NewFrameReader(frames, errCh, eventDoneCh)
   149  	frameReader.SetUnblockTime(500 * time.Millisecond)
   150  	r = frameReader
   151  
   152  	defer r.Close()
   153  
   154  	signalCh := make(chan os.Signal, 1)
   155  	signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
   156  
   157  	go func() {
   158  		<-signalCh
   159  		// End the streaming
   160  		r.Close()
   161  	}()
   162  
   163  	_, err = io.Copy(os.Stdout, r)
   164  	if err != nil {
   165  		c.Ui.Error(fmt.Sprintf("error monitoring logs: %s", err))
   166  		return 1
   167  	}
   168  
   169  	return 0
   170  }