mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-08-04 08:20:01 +00:00
Also simplify the code by directly storing the path to the log file in the LogFile structure. Signed-off-by: Rolf Neugebauer <rolf.neugebauer@docker.com>
202 lines
4.7 KiB
Go
202 lines
4.7 KiB
Go
package main
|
|
|
|
// Write logs to files and perform rotation.
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// These must be kept in sync with memlogd:
|
|
const (
|
|
logDump byte = iota
|
|
logFollow
|
|
logDumpFollow
|
|
)
|
|
|
|
const mb = 1024 * 1024
|
|
|
|
// LogMessage is a message received from memlogd.
|
|
type LogMessage struct {
|
|
Time time.Time // time message was received by memlogd
|
|
Name string // name of the service that wrote the message
|
|
Message string // body of the message
|
|
}
|
|
|
|
func (m *LogMessage) String() string {
|
|
return m.Time.Format(time.RFC3339) + " " + m.Name + " " + m.Message
|
|
}
|
|
|
|
// ParseLogMessage reconstructs a LogMessage from a line of text which looks like:
|
|
// <timestamp>,<origin>;<body>
|
|
func ParseLogMessage(line string) (*LogMessage, error) {
|
|
bits := strings.SplitN(line, ";", 2)
|
|
if len(bits) != 2 {
|
|
return nil, errors.New("Failed to parse log message: " + line)
|
|
}
|
|
bits2 := strings.Split(bits[0], ",")
|
|
if len(bits2) < 2 {
|
|
// There could be more parameters in future
|
|
return nil, errors.New("Failed to parse log message: " + line)
|
|
}
|
|
Time, err := time.Parse(time.RFC3339, bits2[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &LogMessage{
|
|
Time: Time,
|
|
Name: bits2[1],
|
|
Message: bits[1],
|
|
}, nil
|
|
}
|
|
|
|
// LogFile is where we write LogMessages to
|
|
type LogFile struct {
|
|
File *os.File // active file handle
|
|
Path string // Path to the logfile
|
|
BytesWritten int // total number of bytes written so far
|
|
}
|
|
|
|
// NewLogFile creates a new LogFile.
|
|
func NewLogFile(dir, name string) (*LogFile, error) {
|
|
// If the log exists already we want to append to it.
|
|
p := filepath.Join(dir, name+".log")
|
|
f, err := os.OpenFile(p, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &LogFile{
|
|
File: f,
|
|
Path: p,
|
|
BytesWritten: int(fi.Size()),
|
|
}, nil
|
|
}
|
|
|
|
// Write appends a message to the log file
|
|
func (l *LogFile) Write(m *LogMessage) error {
|
|
s := m.String()
|
|
_, err := io.WriteString(l.File, s)
|
|
if err == nil {
|
|
l.BytesWritten += len(s)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Close a log file
|
|
func (l *LogFile) Close() error {
|
|
return l.File.Close()
|
|
}
|
|
|
|
// Rotate closes the current log file, rotates the files and creates an empty log file.
|
|
func (l *LogFile) Rotate(maxLogFiles int) error {
|
|
if err := l.File.Close(); err != nil {
|
|
return err
|
|
}
|
|
for i := maxLogFiles - 1; i >= 0; i-- {
|
|
newerFile := fmt.Sprintf("%s.%d", l.Path, i-1)
|
|
// special case: if index is 0 we omit the suffix i.e. we expect
|
|
// foo foo.1 foo.2 up to foo.<maxLogFiles-1>
|
|
if i == 0 {
|
|
newerFile = l.Path
|
|
}
|
|
olderFile := fmt.Sprintf("%s.%d", l.Path, i)
|
|
// overwrite the olderFile with the newerFile
|
|
err := os.Rename(newerFile, olderFile)
|
|
if os.IsNotExist(err) {
|
|
// the newerFile does not exist
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
f, err := os.Create(l.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l.File = f
|
|
l.BytesWritten = 0
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
socketPath := flag.String("socket", "/var/run/memlogdq.sock", "memlogd log query socket")
|
|
logDir := flag.String("log-dir", "/var/log", "Directory containing log files")
|
|
maxLogFiles := flag.Int("max-log-files", 10, "Maximum number of rotated log files before deletion")
|
|
maxLogSize := flag.Int("max-log-size", mb, "Maximum size of a log file before rotation")
|
|
flag.Parse()
|
|
|
|
addr := net.UnixAddr{
|
|
Name: *socketPath,
|
|
Net: "unix",
|
|
}
|
|
conn, err := net.DialUnix("unix", nil, &addr)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
n, err := conn.Write([]byte{logDumpFollow})
|
|
if err != nil || n < 1 {
|
|
log.Fatalf("Failed to write request to memlogd socket: %v", err)
|
|
}
|
|
|
|
// map of service name to active log file
|
|
logs := make(map[string]*LogFile)
|
|
|
|
r := bufio.NewReader(conn)
|
|
for {
|
|
line, err := r.ReadString('\n')
|
|
if err == io.EOF {
|
|
return
|
|
}
|
|
if err != nil {
|
|
log.Fatalf("Failed to read from memlogd: %v", err)
|
|
}
|
|
msg, err := ParseLogMessage(line)
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
if strings.HasPrefix(msg.Name, "logwrite") {
|
|
// don't log our own output in a loop
|
|
continue
|
|
}
|
|
|
|
var logF *LogFile
|
|
var ok bool
|
|
if logF, ok = logs[msg.Name]; !ok {
|
|
logF, err = NewLogFile(*logDir, msg.Name)
|
|
if err != nil {
|
|
log.Printf("Failed to create log file %s: %v", msg.Name, err)
|
|
continue
|
|
}
|
|
logs[msg.Name] = logF
|
|
}
|
|
if err = logF.Write(msg); err != nil {
|
|
log.Printf("Failed to write to log file %s: %v", msg.Name, err)
|
|
if err := logF.Close(); err != nil {
|
|
log.Printf("Failed to close log file %s: %v", msg.Name, err)
|
|
}
|
|
delete(logs, msg.Name)
|
|
continue
|
|
}
|
|
if logF.BytesWritten > *maxLogSize {
|
|
logF.Rotate(*maxLogFiles)
|
|
}
|
|
}
|
|
}
|