github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/logstream/logstream.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package logstream 5 6 import ( 7 "io" 8 "sync" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/version" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/api/base" 16 "github.com/juju/juju/api/common/stream" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/logfwd" 19 ) 20 21 // jsonReadCloser provides the functionality to send JSON-serialized 22 // values over a streaming connection. 23 type jsonReadCloser interface { 24 io.Closer 25 26 // ReadJSON decodes the next JSON value from the connection and 27 // sets the value at the provided pointer to that newly decoded one. 28 ReadJSON(interface{}) error 29 } 30 31 // LogStream streams log entries of the /logstream API endpoint over 32 // a websocket connection. 33 type LogStream struct { 34 mu sync.Mutex 35 stream jsonReadCloser 36 controllerUUID string 37 } 38 39 // Open opens a websocket to the API's /logstream endpoint and returns 40 // a stream of log records from that connection. 41 func Open(conn base.StreamConnector, cfg params.LogStreamConfig, controllerUUID string) (*LogStream, error) { 42 wsStream, err := stream.Open(conn, "/logstream", &cfg) 43 if err != nil { 44 return nil, errors.Trace(err) 45 } 46 ls := &LogStream{ 47 stream: wsStream, 48 controllerUUID: controllerUUID, 49 } 50 return ls, nil 51 } 52 53 // Next returns the next batch of log records from the server. The records are 54 // converted from the wire format into logfwd.Record. The first returned 55 // record will be the one after the last successfully sent record. If no 56 // records have been sent yet then it will be the oldest log record. 57 // 58 // An error indicates either the streaming connection is closed, the 59 // connection failed, or the data read from the connection is corrupted. 60 // In each of these cases the stream should be re-opened. It will start 61 // at the record after the one marked as successfully sent. So the 62 // the record at which Next() failed previously will be streamed again. 63 // 64 // That is only a problem when the same record is consistently streamed 65 // as invalid data. This will happen only if the record was invalid 66 // before being stored in the DB or if the DB on-disk storage for the 67 // record becomes corrupted. Both scenarios are highly unlikely and 68 // the respective systems are managed such that neither should happen. 69 func (ls *LogStream) Next() ([]logfwd.Record, error) { 70 apiRecords, err := ls.next() 71 if err != nil { 72 return nil, errors.Trace(err) 73 } 74 records, err := recordsFromAPI(apiRecords, ls.controllerUUID) 75 if err != nil { 76 // This should only happen if the data got corrupted over the 77 // network. Any other cause should be addressed by fixing the 78 // code that resulted in the bad data that caused the error 79 // here. If that code is between the DB on-disk storage and 80 // the server-side stream then care should be taken to not 81 // block on a consistently invalid record or to throw away 82 // a record. The log stream needs to maintain a high level 83 // of reliable delivery. 84 return nil, errors.Trace(err) 85 } 86 return records, nil 87 } 88 89 func (ls *LogStream) next() (params.LogStreamRecords, error) { 90 ls.mu.Lock() 91 defer ls.mu.Unlock() 92 93 var result params.LogStreamRecords 94 if ls.stream == nil { 95 return result, errors.Errorf("cannot read from closed stream") 96 } 97 98 err := ls.stream.ReadJSON(&result) 99 if err != nil { 100 return result, errors.Trace(err) 101 } 102 return result, nil 103 } 104 105 // Close closes the stream. 106 func (ls *LogStream) Close() error { 107 ls.mu.Lock() 108 defer ls.mu.Unlock() 109 110 if ls.stream == nil { 111 return nil 112 } 113 if err := ls.stream.Close(); err != nil { 114 return errors.Trace(err) 115 } 116 ls.stream = nil 117 return nil 118 } 119 120 // See the counterpart in apiserver/logstream.go. 121 func recordsFromAPI(apiRecords params.LogStreamRecords, controllerUUID string) ([]logfwd.Record, error) { 122 result := make([]logfwd.Record, len(apiRecords.Records)) 123 for i, apiRec := range apiRecords.Records { 124 rec, err := recordFromAPI(apiRec, controllerUUID) 125 if err != nil { 126 return nil, errors.Trace(err) 127 } 128 result[i] = rec 129 } 130 return result, nil 131 } 132 133 func recordFromAPI(apiRec params.LogStreamRecord, controllerUUID string) (logfwd.Record, error) { 134 rec := logfwd.Record{ 135 ID: apiRec.ID, 136 Timestamp: apiRec.Timestamp, 137 Message: apiRec.Message, 138 } 139 140 origin, err := originFromAPI(apiRec, controllerUUID) 141 if err != nil { 142 return rec, errors.Trace(err) 143 } 144 rec.Origin = origin 145 146 loc, err := logfwd.ParseLocation(apiRec.Module, apiRec.Location) 147 if err != nil { 148 return rec, errors.Trace(err) 149 } 150 rec.Location = loc 151 152 level, ok := loggo.ParseLevel(apiRec.Level) 153 if !ok { 154 return rec, errors.Errorf("unrecognized log level %q", apiRec.Level) 155 } 156 rec.Level = level 157 158 if err := rec.Validate(); err != nil { 159 return rec, errors.Trace(err) 160 } 161 162 return rec, nil 163 } 164 165 func originFromAPI(apiRec params.LogStreamRecord, controllerUUID string) (logfwd.Origin, error) { 166 var origin logfwd.Origin 167 168 tag, err := names.ParseTag(apiRec.Entity) 169 if err != nil { 170 return origin, errors.Annotate(err, "invalid entity") 171 } 172 173 ver, err := version.Parse(apiRec.Version) 174 if err != nil { 175 return origin, errors.Annotatef(err, "invalid version %q", apiRec.Version) 176 } 177 178 switch tag := tag.(type) { 179 case names.MachineTag: 180 origin = logfwd.OriginForMachineAgent(tag, controllerUUID, apiRec.ModelUUID, ver) 181 case names.UnitTag: 182 origin = logfwd.OriginForUnitAgent(tag, controllerUUID, apiRec.ModelUUID, ver) 183 default: 184 origin, err = logfwd.OriginForJuju(tag, controllerUUID, apiRec.ModelUUID, ver) 185 if err != nil { 186 return origin, errors.Annotate(err, "could not extract origin") 187 } 188 } 189 return origin, nil 190 }