github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/utils" 13 "golang.org/x/net/websocket" 14 15 "github.com/juju/juju/api" 16 "github.com/juju/juju/apiserver" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/worker" 19 ) 20 21 // LogRecord represents a log message in an agent which is to be 22 // transmitted to the JES. 23 type LogRecord struct { 24 Time time.Time 25 Module string 26 Location string 27 Level loggo.Level 28 Message string 29 } 30 31 var logger = loggo.GetLogger("juju.worker.logsender") 32 33 // New starts a logsender worker which reads log message structs from 34 // a channel and sends them to the JES via the logsink API. 35 func New(logs chan *LogRecord, apiInfo *api.Info) worker.Worker { 36 loop := func(stop <-chan struct{}) error { 37 logger.Debugf("starting logsender worker") 38 39 conn, err := dialLogsinkAPI(apiInfo) 40 if err != nil { 41 return errors.Annotate(err, "logsender dial failed") 42 } 43 defer conn.Close() 44 45 for { 46 select { 47 case rec := <-logs: 48 err := websocket.JSON.Send(conn, &apiserver.LogMessage{ 49 Time: rec.Time, 50 Module: rec.Module, 51 Location: rec.Location, 52 Level: rec.Level, 53 Message: rec.Message, 54 }) 55 if err != nil { 56 // Note: due to the fire-and-forget nature of the 57 // logsink API, it is possible that when the 58 // connection dies, any logs that were "in-flight" 59 // will not be recorded on the server side. 60 return errors.Annotate(err, "logsink connection failed") 61 } 62 case <-stop: 63 return nil 64 } 65 } 66 } 67 return worker.NewSimpleWorker(loop) 68 } 69 70 func dialLogsinkAPI(apiInfo *api.Info) (*websocket.Conn, error) { 71 // TODO(mjs) Most of this should be extracted to be shared for 72 // connections to both /log (debuglog) and /logsink. 73 header := utils.BasicAuthHeader(apiInfo.Tag.String(), apiInfo.Password) 74 header.Set("X-Juju-Nonce", apiInfo.Nonce) 75 conn, err := api.Connect(apiInfo, "/logsink", header, api.DefaultDialOpts()) 76 if err != nil { 77 return nil, errors.Annotate(err, "failed to connect to logsink API") 78 } 79 80 // Read the initial error and translate to a real error. 81 // Read up to the first new line character. We can't use bufio here as it 82 // reads too much from the reader. 83 line := make([]byte, 4096) 84 n, err := conn.Read(line) 85 if err != nil { 86 return nil, errors.Annotate(err, "unable to read initial response") 87 } 88 line = line[0:n] 89 90 var errResult params.ErrorResult 91 err = json.Unmarshal(line, &errResult) 92 if err != nil { 93 return nil, errors.Annotate(err, "unable to unmarshal initial response") 94 } 95 if errResult.Error != nil { 96 return nil, errors.Annotatef(err, "initial server error") 97 } 98 99 return conn, nil 100 }