github.com/koron/hk@v0.0.0-20150303213137-b8aeaa3ab34c/log.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "regexp" 10 11 "github.com/heroku/hk/Godeps/_workspace/src/github.com/bgentry/heroku-go" 12 "github.com/heroku/hk/Godeps/_workspace/src/github.com/mgutz/ansi" 13 ) 14 15 var ( 16 lines int 17 source string 18 dyno string 19 ) 20 21 var cmdLog = &Command{ 22 Run: runLog, 23 Usage: "log [-n <lines>] [-s <source>] [-d <dyno>]", 24 NeedsApp: true, 25 Category: "app", 26 Short: "stream app log lines", 27 Long: ` 28 Log prints the streaming application log. 29 30 Options: 31 32 -n <N> print at most N log lines 33 -s <source> filter log source 34 -d <dyno> filter dyno or process type 35 36 Examples: 37 38 $ hk log 39 2013-10-17T00:17:35.066089+00:00 app[web.1]: Completed 302 Found in 0ms 40 2013-10-17T00:17:35.079095+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.1 connect=1ms service=6ms status=302 bytes=95 41 2013-10-17T00:17:35.505389+00:00 heroku[nginx]: 1.2.3.4 - - [17/Oct/2013:00:17:35 +0000] "GET / HTTP/1.1" 301 5 "-" "Amazon Route 53 Health Check Service" www.heroku.com 42 ... 43 44 $ hk log -n 2 -s app -d web 45 2013-10-17T00:17:34.288521+00:00 app[web.1]: Completed 200 OK in 10ms (Views: 10.0ms) 46 2013-10-17T00:17:33.918946+00:00 heroku[web.5]: Started GET "/" for 1.2.3.4 at 2013-10-17 00:17:32 +0000 47 2013-10-17T00:17:34.667654+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.5 connect=3ms service=8ms status=301 bytes=0 48 2013-10-17T00:17:35.079095+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.1 connect=1ms service=6ms status=302 bytes=95 49 ... 50 51 $ hk log -d web.5 52 2013-10-17T00:17:33.918946+00:00 app[web.5]: Started GET "/" for 1.2.3.4 at 2013-10-17 00:17:32 +0000 53 2013-10-17T00:17:33.918658+00:00 app[web.5]: Processing by PagesController#root as HTML 54 ... 55 `, 56 } 57 58 func init() { 59 cmdLog.Flag.IntVarP(&lines, "number", "n", -1, "max number of log lines to request") 60 cmdLog.Flag.StringVarP(&source, "source", "s", "", "only display logs from the given source") 61 cmdLog.Flag.StringVarP(&dyno, "dyno", "d", "", "only display logs from the given dyno or process type") 62 } 63 64 func runLog(cmd *Command, args []string) { 65 if len(args) != 0 { 66 cmd.PrintUsage() 67 os.Exit(2) 68 } 69 70 opts := heroku.LogSessionCreateOpts{} 71 if dyno != "" { 72 opts.Dyno = &dyno 73 } 74 if source != "" { 75 opts.Source = &source 76 } 77 78 if lines != -1 { 79 opts.Lines = &lines 80 } else { 81 tailopt := true 82 lineopt := 10 83 opts.Tail = &tailopt 84 opts.Lines = &lineopt 85 } 86 87 session, err := client.LogSessionCreate(mustApp(), &opts) 88 if err != nil { 89 printFatal(err.Error()) 90 } 91 resp, err := http.Get(session.LogplexURL) 92 if err != nil { 93 printFatal(err.Error()) 94 } 95 if warning := resp.Header.Get("X-Heroku-Warning"); warning != "" { 96 printWarning(warning) 97 } 98 if resp.StatusCode/100 != 2 { 99 if resp.StatusCode/100 == 4 { 100 printFatal("Unauthorized") 101 } else { 102 printFatal("Unexpected error: " + resp.Status) 103 } 104 } 105 106 // colors are disabled globally in main() depending on term.IsTerminal() 107 writer := newColorizer(os.Stdout) 108 109 scanner := bufio.NewScanner(resp.Body) 110 scanner.Split(bufio.ScanLines) 111 112 for scanner.Scan() { 113 _, err = writer.Writeln(scanner.Text()) 114 must(err) 115 } 116 117 resp.Body.Close() 118 } 119 120 type colorizer struct { 121 colors map[string]string 122 colorScheme []string 123 filter *regexp.Regexp 124 writer io.Writer 125 } 126 127 func newColorizer(writer io.Writer) *colorizer { 128 return &colorizer{ 129 colors: make(map[string]string), 130 colorScheme: []string{ 131 "cyan", 132 "yellow", 133 "green", 134 "magenta", 135 "red", 136 }, 137 filter: regexp.MustCompile(`(?s)^(.*?\[([\w-]+)(?:[\d\.]+)?\]:)(.*)?$`), 138 writer: writer, 139 } 140 } 141 142 func (c *colorizer) resolve(p string) string { 143 if color, ok := c.colors[p]; ok { 144 return color 145 } 146 147 color := c.colorScheme[len(c.colors)%len(c.colorScheme)] 148 c.colors[p] = color 149 return color 150 } 151 152 func (c *colorizer) Writeln(p string) (n int, err error) { 153 if c.filter.MatchString(p) { 154 submatches := c.filter.FindStringSubmatch(p) 155 return fmt.Fprintln(c.writer, ansi.Color(submatches[1], c.resolve(submatches[2]))+ansi.ColorCode("reset")+submatches[3]) 156 } 157 158 return fmt.Fprintln(c.writer, p) 159 }