| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- // 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
- }
- }
|