github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/model/task_log.go (about) 1 package model 2 3 import ( 4 "time" 5 6 "github.com/evergreen-ci/evergreen/db" 7 "github.com/evergreen-ci/evergreen/db/bsonutil" 8 "github.com/evergreen-ci/evergreen/util" 9 "gopkg.in/mgo.v2" 10 "gopkg.in/mgo.v2/bson" 11 ) 12 13 const ( 14 TaskLogDB = "logs" 15 TaskLogCollection = "task_logg" 16 MessagesPerLog = 10 17 ) 18 19 // for the different types of remote logging 20 const ( 21 SystemLogPrefix = "S" 22 AgentLogPrefix = "E" 23 TaskLogPrefix = "T" 24 25 LogErrorPrefix = "E" 26 LogWarnPrefix = "W" 27 LogDebugPrefix = "D" 28 LogInfoPrefix = "I" 29 ) 30 31 type LogMessage struct { 32 Type string `bson:"t" json:"t"` 33 Severity string `bson:"s" json:"s"` 34 Message string `bson:"m" json:"m"` 35 Timestamp time.Time `bson:"ts" json:"ts"` 36 Version int `bson:"v" json:"v"` 37 } 38 39 // a single chunk of a task log 40 type TaskLog struct { 41 Id bson.ObjectId `bson:"_id,omitempty" json:"_id,omitempty"` 42 TaskId string `bson:"t_id" json:"t_id"` 43 Execution int `bson:"e" json:"e"` 44 Timestamp time.Time `bson:"ts" json:"ts"` 45 MessageCount int `bson:"c" json:"c"` 46 Messages []LogMessage `bson:"m" json:"m"` 47 } 48 49 var ( 50 // bson fields for the task log struct 51 TaskLogIdKey = bsonutil.MustHaveTag(TaskLog{}, "Id") 52 TaskLogTaskIdKey = bsonutil.MustHaveTag(TaskLog{}, "TaskId") 53 TaskLogExecutionKey = bsonutil.MustHaveTag(TaskLog{}, "Execution") 54 TaskLogTimestampKey = bsonutil.MustHaveTag(TaskLog{}, "Timestamp") 55 TaskLogMessageCountKey = bsonutil.MustHaveTag(TaskLog{}, "MessageCount") 56 TaskLogMessagesKey = bsonutil.MustHaveTag(TaskLog{}, "Messages") 57 58 // bson fields for the log message struct 59 LogMessageTypeKey = bsonutil.MustHaveTag(LogMessage{}, "Type") 60 LogMessageSeverityKey = bsonutil.MustHaveTag(LogMessage{}, "Severity") 61 LogMessageMessageKey = bsonutil.MustHaveTag(LogMessage{}, "Message") 62 LogMessageTimestampKey = bsonutil.MustHaveTag(LogMessage{}, "Timestamp") 63 ) 64 65 // helper for getting the correct db 66 func getSessionAndDB() (*mgo.Session, *mgo.Database, error) { 67 session, _, err := db.GetGlobalSessionFactory().GetSession() 68 if err != nil { 69 return nil, nil, err 70 } 71 return session, session.DB(TaskLogDB), nil 72 } 73 74 /****************************************************** 75 Functions that operate on entire TaskLog documents 76 ******************************************************/ 77 78 func (self *TaskLog) Insert() error { 79 session, db, err := getSessionAndDB() 80 if err != nil { 81 return err 82 } 83 defer session.Close() 84 return db.C(TaskLogCollection).Insert(self) 85 } 86 87 func (self *TaskLog) AddLogMessage(msg LogMessage) error { 88 session, db, err := getSessionAndDB() 89 if err != nil { 90 return err 91 } 92 defer session.Close() 93 94 // set the mode to unsafe - it's not a total disaster 95 // if this gets lost and it'll save bandwidth 96 session.SetSafe(nil) 97 98 self.Messages = append(self.Messages, msg) 99 self.MessageCount = self.MessageCount + 1 100 101 return db.C(TaskLogCollection).UpdateId(self.Id, 102 bson.M{ 103 "$inc": bson.M{ 104 TaskLogMessageCountKey: 1, 105 }, 106 "$push": bson.M{ 107 TaskLogMessagesKey: msg, 108 }, 109 }, 110 ) 111 } 112 113 func FindAllTaskLogs(taskId string, execution int) ([]TaskLog, error) { 114 session, db, err := getSessionAndDB() 115 if err != nil { 116 return nil, err 117 } 118 defer session.Close() 119 120 result := []TaskLog{} 121 err = db.C(TaskLogCollection).Find( 122 bson.M{ 123 TaskLogTaskIdKey: taskId, 124 TaskLogExecutionKey: execution, 125 }, 126 ).Sort("-" + TaskLogTimestampKey).All(&result) 127 if err == mgo.ErrNotFound { 128 return nil, nil 129 } 130 return result, err 131 } 132 133 func FindMostRecentTaskLogs(taskId string, execution int, limit int) ([]TaskLog, error) { 134 session, db, err := getSessionAndDB() 135 if err != nil { 136 return nil, err 137 } 138 defer session.Close() 139 140 result := []TaskLog{} 141 err = db.C(TaskLogCollection).Find( 142 bson.M{ 143 TaskLogTaskIdKey: taskId, 144 TaskLogExecutionKey: execution, 145 }, 146 ).Sort("-" + TaskLogTimestampKey).Limit(limit).All(&result) 147 if err == mgo.ErrNotFound { 148 return nil, nil 149 } 150 return result, err 151 } 152 153 func FindTaskLogsBeforeTime(taskId string, execution int, ts time.Time, limit int) ([]TaskLog, error) { 154 session, db, err := getSessionAndDB() 155 if err != nil { 156 return nil, err 157 } 158 defer session.Close() 159 160 query := bson.M{ 161 TaskLogTaskIdKey: taskId, 162 TaskLogExecutionKey: execution, 163 TaskLogTimestampKey: bson.M{ 164 "$lt": ts, 165 }, 166 } 167 168 result := []TaskLog{} 169 err = db.C(TaskLogCollection).Find(query).Sort("-" + TaskLogTimestampKey).Limit(limit).All(&result) 170 if err == mgo.ErrNotFound { 171 return nil, nil 172 } 173 return result, err 174 } 175 176 func GetRawTaskLogChannel(taskId string, execution int, severities []string, 177 msgTypes []string) (chan LogMessage, error) { 178 session, db, err := getSessionAndDB() 179 if err != nil { 180 return nil, err 181 } 182 183 logObj := TaskLog{} 184 185 // 100 is an arbitrary magic number. Unbuffered channel would be bad for 186 // performance, so just picked a buffer size out of thin air. 187 channel := make(chan LogMessage, 100) 188 189 // TODO(EVG-227) 190 var query bson.M 191 if execution == 0 { 192 query = bson.M{"$and": []bson.M{ 193 {TaskLogTaskIdKey: taskId}, 194 {"$or": []bson.M{ 195 {TaskLogExecutionKey: 0}, 196 {TaskLogExecutionKey: nil}, 197 }}}} 198 } else { 199 query = bson.M{ 200 TaskLogTaskIdKey: taskId, 201 TaskLogExecutionKey: execution, 202 } 203 } 204 iter := db.C(TaskLogCollection).Find(query).Sort(TaskLogTimestampKey).Iter() 205 206 oldMsgTypes := []string{} 207 for _, msgType := range msgTypes { 208 switch msgType { 209 case SystemLogPrefix: 210 oldMsgTypes = append(oldMsgTypes, "system") 211 case AgentLogPrefix: 212 oldMsgTypes = append(oldMsgTypes, "agent") 213 case TaskLogPrefix: 214 oldMsgTypes = append(oldMsgTypes, "task") 215 } 216 } 217 218 go func() { 219 defer session.Close() 220 defer close(channel) 221 defer iter.Close() 222 223 for iter.Next(&logObj) { 224 for _, logMsg := range logObj.Messages { 225 if len(severities) > 0 && 226 !util.SliceContains(severities, logMsg.Severity) { 227 continue 228 } 229 if len(msgTypes) > 0 { 230 if !(util.SliceContains(msgTypes, logMsg.Type) || 231 util.SliceContains(oldMsgTypes, logMsg.Type)) { 232 continue 233 } 234 } 235 channel <- logMsg 236 } 237 } 238 }() 239 240 return channel, nil 241 } 242 243 /****************************************************** 244 Functions that operate on individual log messages 245 ******************************************************/ 246 247 func (self *LogMessage) Insert(taskId string, execution int) error { 248 // get the most recent task log document 249 mostRecent, err := FindMostRecentTaskLogs(taskId, execution, 1) 250 if err != nil { 251 return err 252 } 253 254 if len(mostRecent) == 0 || mostRecent[0].MessageCount >= MessagesPerLog { 255 // create a new task log document 256 taskLog := &TaskLog{} 257 taskLog.TaskId = taskId 258 taskLog.Execution = execution 259 taskLog.Timestamp = self.Timestamp 260 taskLog.MessageCount = 1 261 taskLog.Messages = []LogMessage{*self} 262 263 return taskLog.Insert() 264 } 265 266 // update the existing task log document 267 return mostRecent[0].AddLogMessage(*self) 268 } 269 270 // note: to ignore severity or type filtering, pass in empty slices 271 func FindMostRecentLogMessages(taskId string, execution int, numMsgs int, 272 severities []string, msgTypes []string) ([]LogMessage, error) { 273 logMsgs := []LogMessage{} 274 numMsgsNeeded := numMsgs 275 lastTimeStamp := time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC) 276 277 oldMsgTypes := []string{} 278 for _, msgType := range msgTypes { 279 switch msgType { 280 case SystemLogPrefix: 281 oldMsgTypes = append(oldMsgTypes, "system") 282 case AgentLogPrefix: 283 oldMsgTypes = append(oldMsgTypes, "agent") 284 case TaskLogPrefix: 285 oldMsgTypes = append(oldMsgTypes, "task") 286 } 287 } 288 289 // keep grabbing task logs from farther back until there are enough messages 290 for numMsgsNeeded != 0 { 291 numTaskLogsToFetch := numMsgsNeeded / MessagesPerLog 292 taskLogs, err := FindTaskLogsBeforeTime(taskId, execution, lastTimeStamp, 293 numTaskLogsToFetch) 294 if err != nil { 295 return nil, err 296 } 297 // if we've exhausted the stored logs, break 298 if len(taskLogs) == 0 { 299 break 300 } 301 302 // otherwise, grab all applicable log messages out of the returned task 303 // log documents 304 for _, taskLog := range taskLogs { 305 // reverse 306 messages := make([]LogMessage, len(taskLog.Messages)) 307 for idx, msg := range taskLog.Messages { 308 messages[len(taskLog.Messages)-1-idx] = msg 309 } 310 for _, logMsg := range messages { 311 // filter by severity and type 312 if len(severities) != 0 && 313 !util.SliceContains(severities, logMsg.Severity) { 314 continue 315 } 316 if len(msgTypes) != 0 { 317 if !(util.SliceContains(msgTypes, logMsg.Type) || 318 util.SliceContains(oldMsgTypes, logMsg.Type)) { 319 continue 320 } 321 } 322 // the message is relevant, store it 323 logMsgs = append(logMsgs, logMsg) 324 numMsgsNeeded-- 325 if numMsgsNeeded == 0 { 326 return logMsgs, nil 327 } 328 } 329 } 330 // store the last timestamp 331 lastTimeStamp = taskLogs[len(taskLogs)-1].Timestamp 332 } 333 334 return logMsgs, nil 335 }