github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/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 := formatAllocListStubs(allocs, verbose, length) 139 l.Ui.Error(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", out)) 140 return 1 141 } 142 // Prefix lookup matched a single allocation 143 alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) 144 if err != nil { 145 l.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) 146 return 1 147 } 148 149 var task string 150 if len(args) >= 2 { 151 task = args[1] 152 if task == "" { 153 l.Ui.Error("Task name required") 154 return 1 155 } 156 157 } else { 158 // Try to determine the tasks name from the allocation 159 var tasks []*api.Task 160 for _, tg := range alloc.Job.TaskGroups { 161 if *tg.Name == alloc.TaskGroup { 162 if len(tg.Tasks) == 1 { 163 task = tg.Tasks[0].Name 164 break 165 } 166 167 tasks = tg.Tasks 168 break 169 } 170 } 171 172 if task == "" { 173 l.Ui.Error(fmt.Sprintf("Allocation %q is running the following tasks:", limit(alloc.ID, length))) 174 for _, t := range tasks { 175 l.Ui.Error(fmt.Sprintf(" * %s", t.Name)) 176 } 177 l.Ui.Error("\nPlease specify the task.") 178 return 1 179 } 180 } 181 182 logType := "stdout" 183 if stderr { 184 logType = "stderr" 185 } 186 187 // We have a file, output it. 188 var r io.ReadCloser 189 var readErr error 190 if !tail { 191 r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginStart, 0) 192 if readErr != nil { 193 readErr = fmt.Errorf("Error reading file: %v", readErr) 194 } 195 } else { 196 // Parse the offset 197 var offset int64 = defaultTailLines * bytesToLines 198 199 if nLines, nBytes := numLines != -1, numBytes != -1; nLines && nBytes { 200 l.Ui.Error("Both -n and -c set") 201 return 1 202 } else if nLines { 203 offset = numLines * bytesToLines 204 } else if nBytes { 205 offset = numBytes 206 } else { 207 numLines = defaultTailLines 208 } 209 210 r, readErr = l.followFile(client, alloc, follow, task, logType, api.OriginEnd, offset) 211 212 // If numLines is set, wrap the reader 213 if numLines != -1 { 214 r = NewLineLimitReader(r, int(numLines), int(numLines*bytesToLines), 1*time.Second) 215 } 216 217 if readErr != nil { 218 readErr = fmt.Errorf("Error tailing file: %v", readErr) 219 } 220 } 221 222 if readErr != nil { 223 l.Ui.Error(readErr.Error()) 224 return 1 225 } 226 227 defer r.Close() 228 io.Copy(os.Stdout, r) 229 return 0 230 } 231 232 // followFile outputs the contents of the file to stdout relative to the end of 233 // the file. 234 func (l *LogsCommand) followFile(client *api.Client, alloc *api.Allocation, 235 follow bool, task, logType, origin string, offset int64) (io.ReadCloser, error) { 236 237 cancel := make(chan struct{}) 238 frames, err := client.AllocFS().Logs(alloc, follow, task, logType, origin, offset, cancel, nil) 239 if err != nil { 240 return nil, err 241 } 242 signalCh := make(chan os.Signal, 1) 243 signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM) 244 245 // Create a reader 246 var r io.ReadCloser 247 frameReader := api.NewFrameReader(frames, cancel) 248 frameReader.SetUnblockTime(500 * time.Millisecond) 249 r = frameReader 250 251 go func() { 252 <-signalCh 253 254 // End the streaming 255 r.Close() 256 }() 257 258 return r, nil 259 }