github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/instance/logger.go (about)

     1  // Copyright (c) 2018, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package instance
     7  
     8  import (
     9  	"bufio"
    10  	"bytes"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"runtime"
    15  	"strings"
    16  	"sync"
    17  	"syscall"
    18  	"time"
    19  )
    20  
    21  const (
    22  	// BasicLogFormat represents basic log format.
    23  	BasicLogFormat = "basic"
    24  	// KubernetesLogFormat represents kubernetes log format.
    25  	KubernetesLogFormat = "kubernetes"
    26  	// JSONLogFormat represents JSON log format.
    27  	JSONLogFormat = "json"
    28  )
    29  
    30  // LogFormatter implements a log formatter.
    31  type LogFormatter func(stream string, data string) string
    32  
    33  func kubernetesLogFormatter(stream, data string) string {
    34  	return fmt.Sprintf("%s %s F %s\n", time.Now().Format(time.RFC3339Nano), stream, data)
    35  }
    36  
    37  func jsonLogFormatter(stream, data string) string {
    38  	return fmt.Sprintf("{\"time\":\"%s\",\"stream\":\"%s\",\"log\":\"%s\"}\n", time.Now().Format(time.RFC3339Nano), stream, data)
    39  }
    40  
    41  func basicLogFormatter(stream, data string) string {
    42  	if stream != "" {
    43  		return fmt.Sprintf("%s %s %s\n", time.Now().Format(time.RFC3339Nano), stream, data)
    44  	}
    45  	return fmt.Sprintf("%s %s\n", time.Now().Format(time.RFC3339Nano), data)
    46  }
    47  
    48  // LogFormats contains supported log format by default.
    49  var LogFormats = map[string]LogFormatter{
    50  	BasicLogFormat:      basicLogFormatter,
    51  	KubernetesLogFormat: kubernetesLogFormatter,
    52  	JSONLogFormat:       jsonLogFormatter,
    53  }
    54  
    55  // Logger defines a file logger.
    56  type Logger struct {
    57  	file      *os.File
    58  	fileMutex sync.Mutex
    59  	formatter LogFormatter
    60  }
    61  
    62  // NewLogger instantiates a new logger with formatter and return it.
    63  func NewLogger(logPath string, formatter LogFormatter) (*Logger, error) {
    64  	logger := &Logger{
    65  		formatter: formatter,
    66  	}
    67  
    68  	if logger.formatter == nil {
    69  		logger.formatter = basicLogFormatter
    70  	}
    71  
    72  	if err := logger.openFile(logPath); err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return logger, nil
    77  }
    78  
    79  func closeFile(file *os.File) {
    80  	file.Close()
    81  }
    82  
    83  func (l *Logger) openFile(path string) (err error) {
    84  	oldmask := syscall.Umask(0)
    85  	defer syscall.Umask(oldmask)
    86  
    87  	l.file, err = os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    88  
    89  	if err == nil {
    90  		runtime.SetFinalizer(l.file, closeFile)
    91  	}
    92  
    93  	return err
    94  }
    95  
    96  func (l *Logger) scanOutput(data []byte, atEOF bool) (advance int, token []byte, err error) {
    97  	length := len(data)
    98  
    99  	if atEOF && length == 0 {
   100  		return 0, nil, nil
   101  	}
   102  
   103  	if i := bytes.IndexByte(data, '\n'); i >= 0 {
   104  		return i + 1, data[0 : i+1], nil
   105  	}
   106  
   107  	if atEOF {
   108  		return length, data[0:length], nil
   109  	}
   110  
   111  	return 0, nil, nil
   112  }
   113  
   114  // NewWriter create a new pipe pair for corresponding stream.
   115  func (l *Logger) NewWriter(stream string, dropCRNL bool) *io.PipeWriter {
   116  	reader, writer := io.Pipe()
   117  	go l.scan(stream, reader, writer, dropCRNL)
   118  	return writer
   119  }
   120  
   121  func (l *Logger) scan(stream string, pr *io.PipeReader, pw *io.PipeWriter, dropCRNL bool) {
   122  	r := strings.NewReplacer("\r", "\\r", "\n", "\\n")
   123  	scanner := bufio.NewScanner(pr)
   124  	if !dropCRNL {
   125  		scanner.Split(l.scanOutput)
   126  	}
   127  
   128  	for scanner.Scan() {
   129  		l.fileMutex.Lock()
   130  		if !dropCRNL {
   131  			fmt.Fprint(l.file, l.formatter(stream, r.Replace(scanner.Text())))
   132  		} else {
   133  			fmt.Fprint(l.file, l.formatter(stream, scanner.Text()))
   134  		}
   135  		l.fileMutex.Unlock()
   136  	}
   137  
   138  	pr.Close()
   139  	pw.Close()
   140  }
   141  
   142  // ReOpenFile closes and re-open log file (eg: log rotation).
   143  func (l *Logger) ReOpenFile() {
   144  	l.fileMutex.Lock()
   145  	defer l.fileMutex.Unlock()
   146  
   147  	path := l.file.Name()
   148  
   149  	l.file.Close()
   150  
   151  	l.openFile(path)
   152  }