github.com/franciscocpg/up@v0.1.10/platform/lambda/logs.go (about) 1 package lambda 2 3 import ( 4 "encoding/json" 5 "io" 6 "os" 7 "strings" 8 "time" 9 10 "github.com/apex/log" 11 jsonlog "github.com/apex/log/handlers/json" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/session" 15 "github.com/aws/aws-sdk-go/service/cloudwatchlogs" 16 "github.com/mattn/go-isatty" 17 18 "github.com/apex/up/internal/logs/parser" 19 "github.com/apex/up/internal/logs/text" 20 "github.com/apex/up/internal/util" 21 "github.com/apex/up/platform" 22 "github.com/apex/up/platform/lambda/logs" 23 ) 24 25 // TODO: move formatting logic outside of platform, reader interface 26 // TODO: optionally expand fields 27 28 // Logs implementation. 29 type Logs struct { 30 platform *Platform 31 region string 32 query string 33 follow bool 34 since time.Time 35 w io.WriteCloser 36 io.Reader 37 } 38 39 // NewLogs returns a new logs tailer. 40 func NewLogs(p *Platform, region, query string) platform.Logs { 41 r, w := io.Pipe() 42 43 query, err := parseQuery(query) 44 if err != nil { 45 w.CloseWithError(err) 46 } 47 48 l := &Logs{ 49 platform: p, 50 region: region, 51 query: query, 52 Reader: r, 53 w: w, 54 } 55 56 go l.start() 57 58 return l 59 } 60 61 // Since implementation. 62 func (l *Logs) Since(t time.Time) { 63 l.since = t 64 } 65 66 // Follow implementation. 67 func (l *Logs) Follow() { 68 log.Debug("follow") 69 l.follow = true 70 } 71 72 // start fetching logs. 73 func (l *Logs) start() { 74 // TODO: flag to override and allow querying other groups 75 // TODO: apply backoff instead of PollInterval 76 group := "/aws/lambda/" + l.platform.config.Name 77 78 config := logs.Config{ 79 Service: cloudwatchlogs.New(session.New(aws.NewConfig().WithRegion(l.region))), 80 StartTime: l.since, 81 PollInterval: 2 * time.Second, 82 Follow: l.follow, 83 FilterPattern: l.query, 84 } 85 86 tailer := &logs.Logs{ 87 Config: config, 88 GroupNames: []string{group}, 89 } 90 91 // TODO: delegate isatty stuff... 92 93 var handler log.Handler 94 95 if isatty.IsTerminal(os.Stdout.Fd()) { 96 handler = text.New(os.Stdout) 97 } else { 98 handler = jsonlog.New(os.Stdout) 99 } 100 101 // TODO: transform to reader of nl-delimited json, move to apex/log? 102 for l := range tailer.Start() { 103 line := strings.TrimSpace(l.Message) 104 105 if !util.IsJSON(line) { 106 // fmt.Fprint(l.w, e.Message) // TODO: ignore? json-ify? 107 continue 108 } 109 110 var e log.Entry 111 err := json.Unmarshal([]byte(line), &e) 112 if err != nil { 113 log.Fatalf("error parsing json: %s", err) 114 } 115 116 handler.HandleLog(&e) 117 } 118 119 // TODO: refactor interface to delegate 120 if err := tailer.Err(); err != nil { 121 panic(err) 122 } 123 124 l.w.Close() 125 } 126 127 // parseQuery parses and converts the query to a CW friendly syntax. 128 func parseQuery(s string) (string, error) { 129 if s == "" { 130 return s, nil 131 } 132 133 n, err := parser.Parse(s) 134 if err != nil { 135 return "", err 136 } 137 138 return n.String(), nil 139 }