github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/command/alloc_logs.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "os/signal" 8 "strings" 9 "syscall" 10 "time" 11 12 "github.com/hashicorp/nomad/api" 13 "github.com/hashicorp/nomad/api/contexts" 14 "github.com/posener/complete" 15 ) 16 17 type AllocLogsCommand struct { 18 Meta 19 } 20 21 func (l *AllocLogsCommand) Help() string { 22 helpText := ` 23 Usage: nomad alloc logs [options] <allocation> <task> 24 Alias: nomad logs 25 26 Streams the stdout/stderr of the given allocation and task. 27 28 General Options: 29 30 ` + generalOptionsUsage() + ` 31 32 Logs Specific Options: 33 34 -stderr 35 Display stderr logs. 36 37 -verbose 38 Show full information. 39 40 -job <job-id> 41 Use a random allocation from the specified job ID. 42 43 -f 44 Causes the output to not stop when the end of the logs are reached, but 45 rather to wait for additional output. 46 47 -tail 48 Show the logs contents with offsets relative to the end of the logs. If no 49 offset is given, -n is defaulted to 10. 50 51 -n 52 Sets the tail location in best-efforted number of lines relative to the end 53 of the logs. 54 55 -c 56 Sets the tail location in number of bytes relative to the end of the logs. 57 ` 58 return strings.TrimSpace(helpText) 59 } 60 61 func (l *AllocLogsCommand) Synopsis() string { 62 return "Streams the logs of a task." 63 } 64 65 func (c *AllocLogsCommand) AutocompleteFlags() complete.Flags { 66 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 67 complete.Flags{ 68 "-stderr": complete.PredictNothing, 69 "-verbose": complete.PredictNothing, 70 "-job": complete.PredictAnything, 71 "-f": complete.PredictNothing, 72 "-tail": complete.PredictAnything, 73 "-n": complete.PredictAnything, 74 "-c": complete.PredictAnything, 75 }) 76 } 77 78 func (l *AllocLogsCommand) AutocompleteArgs() complete.Predictor { 79 return complete.PredictFunc(func(a complete.Args) []string { 80 client, err := l.Meta.Client() 81 if err != nil { 82 return nil 83 } 84 85 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Allocs, nil) 86 if err != nil { 87 return []string{} 88 } 89 return resp.Matches[contexts.Allocs] 90 }) 91 } 92 93 func (l *AllocLogsCommand) Name() string { return "alloc logs" } 94 95 func (l *AllocLogsCommand) Run(args []string) int { 96 var verbose, job, tail, stderr, follow bool 97 var numLines, numBytes int64 98 99 flags := l.Meta.FlagSet(l.Name(), FlagSetClient) 100 flags.Usage = func() { l.Ui.Output(l.Help()) } 101 flags.BoolVar(&verbose, "verbose", false, "") 102 flags.BoolVar(&job, "job", false, "") 103 flags.BoolVar(&tail, "tail", false, "") 104 flags.BoolVar(&follow, "f", false, "") 105 flags.BoolVar(&stderr, "stderr", false, "") 106 flags.Int64Var(&numLines, "n", -1, "") 107 flags.Int64Var(&numBytes, "c", -1, "") 108 109 if err := flags.Parse(args); err != nil { 110 return 1 111 } 112 args = flags.Args() 113 114 if numArgs := len(args); numArgs < 1 { 115 if job { 116 l.Ui.Error("A job ID is required") 117 } else { 118 l.Ui.Error("An allocation ID is required") 119 } 120 121 l.Ui.Error(commandErrorText(l)) 122 return 1 123 } else if numArgs > 2 { 124 l.Ui.Error("This command takes one or two arguments") 125 l.Ui.Error(commandErrorText(l)) 126 return 1 127 } 128 129 client, err := l.Meta.Client() 130 if err != nil { 131 l.Ui.Error(fmt.Sprintf("Error initializing client: %v", err)) 132 return 1 133 } 134 135 // If -job is specified, use random allocation, otherwise use provided allocation 136 allocID := args[0] 137 if job { 138 allocID, err = getRandomJobAlloc(client, args[0]) 139 if err != nil { 140 l.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err)) 141 return 1 142 } 143 } 144 145 // Truncate the id unless full length is requested 146 length := shortId 147 if verbose { 148 length = fullId 149 } 150 // Query the allocation info 151 if len(allocID) == 1 { 152 l.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters.")) 153 return 1 154 } 155 156 allocID = sanitizeUUIDPrefix(allocID) 157 allocs, _, err := client.Allocations().PrefixList(allocID) 158 if err != nil { 159 l.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) 160 return 1 161 } 162 if len(allocs) == 0 { 163 l.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) 164 return 1 165 } 166 if len(allocs) > 1 { 167 // Format the allocs 168 out := formatAllocListStubs(allocs, verbose, length) 169 l.Ui.Error(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", out)) 170 return 1 171 } 172 // Prefix lookup matched a single allocation 173 alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) 174 if err != nil { 175 l.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) 176 return 1 177 } 178 179 var task string 180 if len(args) >= 2 { 181 task = args[1] 182 if task == "" { 183 l.Ui.Error("Task name required") 184 return 1 185 } 186 187 } else { 188 // Try to determine the tasks name from the allocation 189 var tasks []*api.Task 190 for _, tg := range alloc.Job.TaskGroups { 191 if *tg.Name == alloc.TaskGroup { 192 if len(tg.Tasks) == 1 { 193 task = tg.Tasks[0].Name 194 break 195 } 196 197 tasks = tg.Tasks 198 break 199 } 200 } 201 202 if task == "" { 203 l.Ui.Error(fmt.Sprintf("Allocation %q is running the following tasks:", limit(alloc.ID, length))) 204 for _, t := range tasks { 205 l.Ui.Error(fmt.Sprintf(" * %s", t.Name)) 206 } 207 l.Ui.Error("\nPlease specify the task.") 208 return 1 209 } 210 } 211 212 logType := "stdout" 213 if stderr { 214 logType = "stderr" 215 } 216 217 // We have a file, output it. 218 var r io.ReadCloser 219 var readErr error 220 if !tail { 221 r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginStart, 0) 222 if readErr != nil { 223 readErr = fmt.Errorf("Error reading file: %v", readErr) 224 } 225 } else { 226 // Parse the offset 227 var offset int64 = defaultTailLines * bytesToLines 228 229 if nLines, nBytes := numLines != -1, numBytes != -1; nLines && nBytes { 230 l.Ui.Error("Both -n and -c set") 231 return 1 232 } else if nLines { 233 offset = numLines * bytesToLines 234 } else if nBytes { 235 offset = numBytes 236 } else { 237 numLines = defaultTailLines 238 } 239 240 r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginEnd, offset) 241 242 // If numLines is set, wrap the reader 243 if numLines != -1 { 244 r = NewLineLimitReader(r, int(numLines), int(numLines*bytesToLines), 1*time.Second) 245 } 246 247 if readErr != nil { 248 readErr = fmt.Errorf("Error tailing file: %v", readErr) 249 } 250 } 251 252 if readErr != nil { 253 l.Ui.Error(readErr.Error()) 254 return 1 255 } 256 257 defer r.Close() 258 _, err = io.Copy(os.Stdout, r) 259 if err != nil { 260 l.Ui.Error(fmt.Sprintf("error following logs: %s", err)) 261 return 1 262 } 263 264 return 0 265 } 266 267 // followFile outputs the contents of the file to stdout relative to the end of 268 // the file. 269 func (l *AllocLogsCommand) followFile(client *api.Client, alloc *api.Allocation, 270 follow bool, task, logType, origin string, offset int64) (io.ReadCloser, error) { 271 272 cancel := make(chan struct{}) 273 frames, errCh := client.AllocFS().Logs(alloc, follow, task, logType, origin, offset, cancel, nil) 274 select { 275 case err := <-errCh: 276 return nil, err 277 default: 278 } 279 signalCh := make(chan os.Signal, 1) 280 signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM) 281 282 // Create a reader 283 var r io.ReadCloser 284 frameReader := api.NewFrameReader(frames, errCh, cancel) 285 frameReader.SetUnblockTime(500 * time.Millisecond) 286 r = frameReader 287 288 go func() { 289 <-signalCh 290 291 // End the streaming 292 r.Close() 293 }() 294 295 return r, nil 296 }