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