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