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  }