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