179 lines
5.0 KiB
Go
179 lines
5.0 KiB
Go
// Copyright (C) 2014 Space Monkey, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package spacelog
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
type TextOutput interface {
|
|
Output(LogLevel, []byte)
|
|
}
|
|
|
|
// WriterOutput is an io.Writer wrapper that matches the TextOutput interface
|
|
type WriterOutput struct {
|
|
w io.Writer
|
|
}
|
|
|
|
// NewWriterOutput returns a TextOutput that writes messages to an io.Writer
|
|
func NewWriterOutput(w io.Writer) *WriterOutput {
|
|
return &WriterOutput{w: w}
|
|
}
|
|
|
|
func (o *WriterOutput) Output(_ LogLevel, message []byte) {
|
|
o.w.Write(append(bytes.TrimRight(message, "\r\n"), platformNewline...))
|
|
}
|
|
|
|
// StdlibOutput is a TextOutput that simply writes to the default Go stdlib
|
|
// logging system. It is the default. If you configure the Go stdlib to write
|
|
// to spacelog, make sure to provide a new TextOutput to your logging
|
|
// collection
|
|
type StdlibOutput struct{}
|
|
|
|
func (*StdlibOutput) Output(_ LogLevel, message []byte) {
|
|
log.Print(string(message))
|
|
}
|
|
|
|
type bufferMsg struct {
|
|
level LogLevel
|
|
message []byte
|
|
}
|
|
|
|
// BufferedOutput uses a channel to synchronize writes to a wrapped TextOutput
|
|
// and allows for buffering a limited amount of log events.
|
|
type BufferedOutput struct {
|
|
o TextOutput
|
|
c chan bufferMsg
|
|
running sync.Mutex
|
|
close_once sync.Once
|
|
}
|
|
|
|
// NewBufferedOutput returns a BufferedOutput wrapping output with a buffer
|
|
// size of buffer.
|
|
func NewBufferedOutput(output TextOutput, buffer int) *BufferedOutput {
|
|
if buffer < 0 {
|
|
buffer = 0
|
|
}
|
|
b := &BufferedOutput{
|
|
o: output,
|
|
c: make(chan bufferMsg, buffer)}
|
|
go b.process()
|
|
return b
|
|
}
|
|
|
|
// Close shuts down the BufferedOutput's processing
|
|
func (b *BufferedOutput) Close() {
|
|
b.close_once.Do(func() {
|
|
close(b.c)
|
|
})
|
|
b.running.Lock()
|
|
b.running.Unlock()
|
|
}
|
|
|
|
func (b *BufferedOutput) Output(level LogLevel, message []byte) {
|
|
b.c <- bufferMsg{level: level, message: message}
|
|
}
|
|
|
|
func (b *BufferedOutput) process() {
|
|
b.running.Lock()
|
|
defer b.running.Unlock()
|
|
for {
|
|
msg, open := <-b.c
|
|
if !open {
|
|
break
|
|
}
|
|
b.o.Output(msg.level, msg.message)
|
|
}
|
|
}
|
|
|
|
// A TextOutput object that also implements HupHandlingTextOutput may have its
|
|
// OnHup() method called when an administrative signal is sent to this process.
|
|
type HupHandlingTextOutput interface {
|
|
TextOutput
|
|
OnHup()
|
|
}
|
|
|
|
// FileWriterOutput is like WriterOutput with a plain file handle, but it
|
|
// knows how to reopen the file (or try to reopen it) if it hasn't been able
|
|
// to open the file previously, or if an appropriate signal has been received.
|
|
type FileWriterOutput struct {
|
|
*WriterOutput
|
|
path string
|
|
}
|
|
|
|
// Creates a new FileWriterOutput object. This is the only case where an
|
|
// error opening the file will be reported to the caller; if we try to
|
|
// reopen it later and the reopen fails, we'll just keep trying until it
|
|
// works.
|
|
func NewFileWriterOutput(path string) (*FileWriterOutput, error) {
|
|
fo := &FileWriterOutput{path: path}
|
|
fh, err := fo.openFile()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fo.WriterOutput = NewWriterOutput(fh)
|
|
return fo, nil
|
|
}
|
|
|
|
// Try to open the file with the path associated with this object.
|
|
func (fo *FileWriterOutput) openFile() (*os.File, error) {
|
|
return os.OpenFile(fo.path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
|
}
|
|
|
|
// Try to communicate a message without using our log file. In all likelihood,
|
|
// stderr is closed or redirected to /dev/null, but at least we can try
|
|
// writing there. In the very worst case, if an admin attaches a ptrace to
|
|
// this process, it will be more clear what the problem is.
|
|
func (fo *FileWriterOutput) fallbackLog(tmpl string, args ...interface{}) {
|
|
fmt.Fprintf(os.Stderr, tmpl, args...)
|
|
}
|
|
|
|
// Output a log line by writing it to the file. If the file has been
|
|
// released, try to open it again. If that fails, cry for a little
|
|
// while, then throw away the message and carry on.
|
|
func (fo *FileWriterOutput) Output(ll LogLevel, message []byte) {
|
|
if fo.WriterOutput == nil {
|
|
fh, err := fo.openFile()
|
|
if err != nil {
|
|
fo.fallbackLog("Could not open %#v: %s", fo.path, err)
|
|
return
|
|
}
|
|
fo.WriterOutput = NewWriterOutput(fh)
|
|
}
|
|
fo.WriterOutput.Output(ll, message)
|
|
}
|
|
|
|
// Throw away any references/handles to the output file. This probably
|
|
// means the admin wants to rotate the file out and have this process
|
|
// open a new one. Close the underlying io.Writer if that is a thing
|
|
// that it knows how to do.
|
|
func (fo *FileWriterOutput) OnHup() {
|
|
if fo.WriterOutput != nil {
|
|
wc, ok := fo.WriterOutput.w.(io.Closer)
|
|
if ok {
|
|
err := wc.Close()
|
|
if err != nil {
|
|
fo.fallbackLog("Closing %#v failed: %s", fo.path, err)
|
|
}
|
|
}
|
|
fo.WriterOutput = nil
|
|
}
|
|
}
|