github.com/djenriquez/nomad-1@v0.8.1/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) Run(args []string) int { 94 var verbose, job, tail, stderr, follow bool 95 var numLines, numBytes int64 96 97 flags := l.Meta.FlagSet("alloc logs", FlagSetClient) 98 flags.Usage = func() { l.Ui.Output(l.Help()) } 99 flags.BoolVar(&verbose, "verbose", false, "") 100 flags.BoolVar(&job, "job", false, "") 101 flags.BoolVar(&tail, "tail", false, "") 102 flags.BoolVar(&follow, "f", false, "") 103 flags.BoolVar(&stderr, "stderr", false, "") 104 flags.Int64Var(&numLines, "n", -1, "") 105 flags.Int64Var(&numBytes, "c", -1, "") 106 107 if err := flags.Parse(args); err != nil { 108 return 1 109 } 110 args = flags.Args() 111 112 if numArgs := len(args); numArgs < 1 { 113 if job { 114 l.Ui.Error("Job ID required. See help:\n") 115 } else { 116 l.Ui.Error("Allocation ID required. See help:\n") 117 } 118 119 l.Ui.Error(l.Help()) 120 return 1 121 } else if numArgs > 2 { 122 l.Ui.Error(l.Help()) 123 return 1 124 } 125 126 client, err := l.Meta.Client() 127 if err != nil { 128 l.Ui.Error(fmt.Sprintf("Error initializing client: %v", err)) 129 return 1 130 } 131 132 // If -job is specified, use random allocation, otherwise use provided allocation 133 allocID := args[0] 134 if job { 135 allocID, err = getRandomJobAlloc(client, args[0]) 136 if err != nil { 137 l.Ui.Error(fmt.Sprintf("Error fetching allocations: %v", err)) 138 return 1 139 } 140 } 141 142 // Truncate the id unless full length is requested 143 length := shortId 144 if verbose { 145 length = fullId 146 } 147 // Query the allocation info 148 if len(allocID) == 1 { 149 l.Ui.Error(fmt.Sprintf("Alloc ID must contain at least two characters.")) 150 return 1 151 } 152 153 allocID = sanitizeUUIDPrefix(allocID) 154 allocs, _, err := client.Allocations().PrefixList(allocID) 155 if err != nil { 156 l.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) 157 return 1 158 } 159 if len(allocs) == 0 { 160 l.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) 161 return 1 162 } 163 if len(allocs) > 1 { 164 // Format the allocs 165 out := formatAllocListStubs(allocs, verbose, length) 166 l.Ui.Error(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", out)) 167 return 1 168 } 169 // Prefix lookup matched a single allocation 170 alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) 171 if err != nil { 172 l.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) 173 return 1 174 } 175 176 var task string 177 if len(args) >= 2 { 178 task = args[1] 179 if task == "" { 180 l.Ui.Error("Task name required") 181 return 1 182 } 183 184 } else { 185 // Try to determine the tasks name from the allocation 186 var tasks []*api.Task 187 for _, tg := range alloc.Job.TaskGroups { 188 if *tg.Name == alloc.TaskGroup { 189 if len(tg.Tasks) == 1 { 190 task = tg.Tasks[0].Name 191 break 192 } 193 194 tasks = tg.Tasks 195 break 196 } 197 } 198 199 if task == "" { 200 l.Ui.Error(fmt.Sprintf("Allocation %q is running the following tasks:", limit(alloc.ID, length))) 201 for _, t := range tasks { 202 l.Ui.Error(fmt.Sprintf(" * %s", t.Name)) 203 } 204 l.Ui.Error("\nPlease specify the task.") 205 return 1 206 } 207 } 208 209 logType := "stdout" 210 if stderr { 211 logType = "stderr" 212 } 213 214 // We have a file, output it. 215 var r io.ReadCloser 216 var readErr error 217 if !tail { 218 r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginStart, 0) 219 if readErr != nil { 220 readErr = fmt.Errorf("Error reading file: %v", readErr) 221 } 222 } else { 223 // Parse the offset 224 var offset int64 = defaultTailLines * bytesToLines 225 226 if nLines, nBytes := numLines != -1, numBytes != -1; nLines && nBytes { 227 l.Ui.Error("Both -n and -c set") 228 return 1 229 } else if nLines { 230 offset = numLines * bytesToLines 231 } else if nBytes { 232 offset = numBytes 233 } else { 234 numLines = defaultTailLines 235 } 236 237 r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginEnd, offset) 238 239 // If numLines is set, wrap the reader 240 if numLines != -1 { 241 r = NewLineLimitReader(r, int(numLines), int(numLines*bytesToLines), 1*time.Second) 242 } 243 244 if readErr != nil { 245 readErr = fmt.Errorf("Error tailing file: %v", readErr) 246 } 247 } 248 249 if readErr != nil { 250 l.Ui.Error(readErr.Error()) 251 return 1 252 } 253 254 defer r.Close() 255 _, err = io.Copy(os.Stdout, r) 256 if err != nil { 257 l.Ui.Error(fmt.Sprintf("error following logs: %s", err)) 258 return 1 259 } 260 261 return 0 262 } 263 264 // followFile outputs the contents of the file to stdout relative to the end of 265 // the file. 266 func (l *AllocLogsCommand) followFile(client *api.Client, alloc *api.Allocation, 267 follow bool, task, logType, origin string, offset int64) (io.ReadCloser, error) { 268 269 cancel := make(chan struct{}) 270 frames, errCh := client.AllocFS().Logs(alloc, follow, task, logType, origin, offset, cancel, nil) 271 select { 272 case err := <-errCh: 273 return nil, err 274 default: 275 } 276 signalCh := make(chan os.Signal, 1) 277 signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM) 278 279 // Create a reader 280 var r io.ReadCloser 281 frameReader := api.NewFrameReader(frames, errCh, cancel) 282 frameReader.SetUnblockTime(500 * time.Millisecond) 283 r = frameReader 284 285 go func() { 286 <-signalCh 287 288 // End the streaming 289 r.Close() 290 }() 291 292 return r, nil 293 }