github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/logsender/worker.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package logsender 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/utils" 14 "golang.org/x/net/websocket" 15 16 "github.com/juju/juju/agent" 17 "github.com/juju/juju/api" 18 "github.com/juju/juju/apiserver" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/worker" 21 "github.com/juju/juju/worker/gate" 22 ) 23 24 const loggerName = "juju.worker.logsender" 25 26 var logger = loggo.GetLogger(loggerName) 27 28 // New starts a logsender worker which reads log message structs from 29 // a channel and sends them to the JES via the logsink API. 30 func New(logs LogRecordCh, apiInfoGate gate.Waiter, agent agent.Agent) worker.Worker { 31 loop := func(stop <-chan struct{}) error { 32 logger.Debugf("started log-sender worker; waiting for api info") 33 select { 34 case <-apiInfoGate.Unlocked(): 35 case <-stop: 36 return nil 37 } 38 39 logger.Debugf("dialing log-sender connection") 40 apiInfo := agent.CurrentConfig().APIInfo() 41 conn, err := dialLogsinkAPI(apiInfo) 42 if err != nil { 43 return errors.Annotate(err, "logsender dial failed") 44 } 45 defer conn.Close() 46 47 for { 48 select { 49 case rec := <-logs: 50 err := sendLogRecord(conn, rec.Time, rec.Module, rec.Location, rec.Level, rec.Message) 51 if err != nil { 52 return errors.Trace(err) 53 } 54 if rec.DroppedAfter > 0 { 55 // If messages were dropped after this one, report 56 // the count (the source of the log messages - 57 // BufferedLogWriter - handles the actual dropping 58 // and counting). 59 // 60 // Any logs indicated as dropped here are will 61 // never end up in the logs DB in the JES 62 // (although will still be in the local agent log 63 // file). Message dropping by the 64 // BufferedLogWriter is last resort protection 65 // against memory exhaustion and should only 66 // happen if API connectivity is lost for extended 67 // periods. The maximum in-memory log buffer is 68 // quite large (see the InstallBufferedLogWriter 69 // call in jujuDMain). 70 err := sendLogRecord(conn, rec.Time, loggerName, "", loggo.WARNING, 71 fmt.Sprintf("%d log messages dropped due to lack of API connectivity", rec.DroppedAfter)) 72 if err != nil { 73 return errors.Trace(err) 74 } 75 } 76 77 case <-stop: 78 return nil 79 } 80 } 81 } 82 return worker.NewSimpleWorker(loop) 83 } 84 85 func dialLogsinkAPI(apiInfo *api.Info) (*websocket.Conn, error) { 86 // TODO(mjs) Most of this should be extracted to be shared for 87 // connections to both /log (debuglog) and /logsink. 88 header := utils.BasicAuthHeader(apiInfo.Tag.String(), apiInfo.Password) 89 header.Set("X-Juju-Nonce", apiInfo.Nonce) 90 conn, err := api.Connect(apiInfo, "/logsink", header, api.DialOpts{}) 91 if err != nil { 92 return nil, errors.Annotate(err, "failed to connect to logsink API") 93 } 94 95 // Read the initial error and translate to a real error. 96 // Read up to the first new line character. We can't use bufio here as it 97 // reads too much from the reader. 98 line := make([]byte, 4096) 99 n, err := conn.Read(line) 100 if err != nil { 101 return nil, errors.Annotate(err, "unable to read initial response") 102 } 103 line = line[0:n] 104 105 var errResult params.ErrorResult 106 err = json.Unmarshal(line, &errResult) 107 if err != nil { 108 return nil, errors.Annotate(err, "unable to unmarshal initial response") 109 } 110 if errResult.Error != nil { 111 return nil, errors.Annotatef(errResult.Error, "initial server error") 112 } 113 114 return conn, nil 115 } 116 117 func sendLogRecord(conn *websocket.Conn, ts time.Time, module, location string, level loggo.Level, msg string) error { 118 err := websocket.JSON.Send(conn, &apiserver.LogMessage{ 119 Time: ts, 120 Module: module, 121 Location: location, 122 Level: level, 123 Message: msg, 124 }) 125 // Note: due to the fire-and-forget nature of the 126 // logsink API, it is possible that when the 127 // connection dies, any logs that were "in-flight" 128 // will not be recorded on the server side. 129 return errors.Annotate(err, "logsink connection failed") 130 }