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 }