2022-02-18 18:19:35 +01:00

112 lines
3.4 KiB
Go

package task_logs
/* ***** BEGIN GPL LICENSE BLOCK *****
*
* Original Code Copyright (C) 2022 Blender Foundation.
*
* This file is part of Flamenco.
*
* Flamenco is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
*
* ***** END GPL LICENSE BLOCK ***** */
import (
"fmt"
"os"
"path"
"github.com/rs/zerolog"
)
// Storage can write data to task logs, rotate logs, etc.
type Storage struct {
BasePath string // Directory where task logs are stored.
}
// NewStorage creates a new log storage rooted at `basePath`.
func NewStorage(basePath string) *Storage {
return &Storage{
BasePath: basePath,
}
}
func (s *Storage) Write(logger zerolog.Logger, jobID, taskID string, logText string) error {
// Shortcut to avoid creating an empty log file. It also solves an
// index out of bounds error further down when we check the last character.
if logText == "" {
return nil
}
filepath := s.filepath(jobID, taskID)
logger = logger.With().Str("filepath", filepath).Logger()
if err := os.MkdirAll(path.Dir(filepath), 0755); err != nil {
logger.Error().Err(err).Msg("unable to create directory for log file")
return fmt.Errorf("error creating directory: %w", err)
}
file, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
logger.Error().Err(err).Msg("unable to open log file for append/create/write")
return fmt.Errorf("unable to open log file for append/create/write: %w", err)
}
if n, err := file.WriteString(logText); n < len(logText) || err != nil {
logger.Error().
Int("written", n).
Int("totalLength", len(logText)).
Err(err).
Msg("could only write partial log file")
file.Close()
return fmt.Errorf("could only write partial log file: %w", err)
}
if logText[len(logText)-1] != '\n' {
if n, err := file.WriteString("\n"); n < 1 || err != nil {
logger.Error().Err(err).Msg("could not append line end")
file.Close()
return err
}
}
if err := file.Close(); err != nil {
logger.Error().Err(err).Msg("error closing log file")
return err
}
return nil
}
// RotateFile rotates the task's log file, ignoring (but logging) any errors that occur.
func (s *Storage) RotateFile(logger zerolog.Logger, jobID, taskID string) {
logpath := s.filepath(jobID, taskID)
logger = logger.With().Str("logpath", logpath).Logger()
err := rotateLogFile(logger, logpath)
if err != nil {
// rotateLogFile() has already logged something, so we can ignore `err`.
logger.Trace().Err(err).Msg("ignoring error from log rotation")
}
}
// filepath returns the file path suitable to write a log file.
func (s *Storage) filepath(jobID, taskID string) string {
var dirpath string
if jobID == "" {
dirpath = path.Join(s.BasePath, "jobless")
} else {
dirpath = path.Join(s.BasePath, "job-"+jobID[:4], jobID)
}
filename := fmt.Sprintf("task-%v.txt", taskID)
return path.Join(dirpath, filename)
}