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  }