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