Manager: implement endpoint for getting the full task log
Previously only the log tail was available, which is fine for many cases, but for serious debugging the entire log is needed. Manifest task: T99730
This commit is contained in:
parent
fee0717179
commit
686295090b
@ -123,6 +123,7 @@ type LogStorage interface {
|
|||||||
WriteTimestamped(logger zerolog.Logger, jobID, taskID string, logText string) error
|
WriteTimestamped(logger zerolog.Logger, jobID, taskID string, logText string) error
|
||||||
RotateFile(logger zerolog.Logger, jobID, taskID string)
|
RotateFile(logger zerolog.Logger, jobID, taskID string)
|
||||||
Tail(jobID, taskID string) (string, error)
|
Tail(jobID, taskID string) (string, error)
|
||||||
|
TaskLog(jobID, taskID string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastRendered processes the "last rendered" images.
|
// LastRendered processes the "last rendered" images.
|
||||||
|
@ -215,6 +215,45 @@ func (f *Flamenco) SetTaskStatus(e echo.Context, taskID string) error {
|
|||||||
return e.NoContent(http.StatusNoContent)
|
return e.NoContent(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Flamenco) FetchTaskLog(e echo.Context, taskID string) error {
|
||||||
|
logger := requestLogger(e)
|
||||||
|
ctx := e.Request().Context()
|
||||||
|
|
||||||
|
logger = logger.With().Str("task", taskID).Logger()
|
||||||
|
if !uuid.IsValid(taskID) {
|
||||||
|
logger.Warn().Msg("fetchTaskLog: bad task ID ")
|
||||||
|
return sendAPIError(e, http.StatusBadRequest, "bad task ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbTask, err := f.persist.FetchTask(ctx, taskID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, persistence.ErrTaskNotFound) {
|
||||||
|
return sendAPIError(e, http.StatusNotFound, "no such task")
|
||||||
|
}
|
||||||
|
logger.Error().Err(err).Msg("error fetching task")
|
||||||
|
return sendAPIError(e, http.StatusInternalServerError, "error fetching task: %v", err)
|
||||||
|
}
|
||||||
|
logger = logger.With().Str("job", dbTask.Job.UUID).Logger()
|
||||||
|
|
||||||
|
fullLog, err := f.logStorage.TaskLog(dbTask.Job.UUID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
logger.Debug().Msg("task log unavailable, task has no log on disk")
|
||||||
|
return e.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
logger.Error().Err(err).Msg("unable to fetch task log")
|
||||||
|
return sendAPIError(e, http.StatusInternalServerError, "error fetching task log: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullLog == "" {
|
||||||
|
logger.Debug().Msg("task log unavailable, on-disk task log is empty")
|
||||||
|
return e.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug().Msg("fetched task log")
|
||||||
|
return e.String(http.StatusOK, fullLog)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Flamenco) FetchTaskLogTail(e echo.Context, taskID string) error {
|
func (f *Flamenco) FetchTaskLogTail(e echo.Context, taskID string) error {
|
||||||
logger := requestLogger(e)
|
logger := requestLogger(e)
|
||||||
ctx := e.Request().Context()
|
ctx := e.Request().Context()
|
||||||
|
@ -620,6 +620,21 @@ func (mr *MockLogStorageMockRecorder) Tail(arg0, arg1 interface{}) *gomock.Call
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tail", reflect.TypeOf((*MockLogStorage)(nil).Tail), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tail", reflect.TypeOf((*MockLogStorage)(nil).Tail), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TaskLog mocks base method.
|
||||||
|
func (m *MockLogStorage) TaskLog(arg0, arg1 string) (string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "TaskLog", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskLog indicates an expected call of TaskLog.
|
||||||
|
func (mr *MockLogStorageMockRecorder) TaskLog(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TaskLog", reflect.TypeOf((*MockLogStorage)(nil).TaskLog), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// Write mocks base method.
|
// Write mocks base method.
|
||||||
func (m *MockLogStorage) Write(arg0 zerolog.Logger, arg1, arg2, arg3 string) error {
|
func (m *MockLogStorage) Write(arg0 zerolog.Logger, arg1, arg2, arg3 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -159,6 +159,20 @@ func (s *Storage) filepath(jobID, taskID string) string {
|
|||||||
return path.Join(dirpath, filename)
|
return path.Join(dirpath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TaskLog reads the entire log file.
|
||||||
|
func (s *Storage) TaskLog(jobID, taskID string) (string, error) {
|
||||||
|
filepath := s.filepath(jobID, taskID)
|
||||||
|
|
||||||
|
s.taskLock(taskID)
|
||||||
|
defer s.taskUnlock(taskID)
|
||||||
|
|
||||||
|
buffer, err := os.ReadFile(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("reading log file of job %q task %q: %w", jobID, taskID, err)
|
||||||
|
}
|
||||||
|
return string(buffer), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Tail reads the final few lines of a task log.
|
// Tail reads the final few lines of a task log.
|
||||||
func (s *Storage) Tail(jobID, taskID string) (string, error) {
|
func (s *Storage) Tail(jobID, taskID string) (string, error) {
|
||||||
filepath := s.filepath(jobID, taskID)
|
filepath := s.filepath(jobID, taskID)
|
||||||
|
@ -74,7 +74,7 @@ func TestLogRotation(t *testing.T) {
|
|||||||
assert.True(t, errors.Is(err, fs.ErrNotExist))
|
assert.True(t, errors.Is(err, fs.ErrNotExist))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogTail(t *testing.T) {
|
func TestLogTailAndFullLog(t *testing.T) {
|
||||||
s, finish, mocks := taskLogsTestFixtures(t)
|
s, finish, mocks := taskLogsTestFixtures(t)
|
||||||
defer finish()
|
defer finish()
|
||||||
|
|
||||||
@ -110,6 +110,14 @@ func TestLogTail(t *testing.T) {
|
|||||||
err = s.Write(zerolog.Nop(), jobID, taskID, bigString)
|
err = s.Write(zerolog.Nop(), jobID, taskID, bigString)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Check the full log, it should be the entire bigString plus what was written before that.
|
||||||
|
contents, err = s.TaskLog(jobID, taskID)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
expect := "Just a single line\nAnd another line!\n" + bigString
|
||||||
|
assert.Equal(t, expect, contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the tail, it should only be the few last lines of bigString.
|
||||||
contents, err = s.Tail(jobID, taskID)
|
contents, err = s.Tail(jobID, taskID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user