output.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright (C) 2014 Space Monkey, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package spacelog
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io"
  19. "log"
  20. "os"
  21. "sync"
  22. )
  23. type TextOutput interface {
  24. Output(LogLevel, []byte)
  25. }
  26. // WriterOutput is an io.Writer wrapper that matches the TextOutput interface
  27. type WriterOutput struct {
  28. w io.Writer
  29. }
  30. // NewWriterOutput returns a TextOutput that writes messages to an io.Writer
  31. func NewWriterOutput(w io.Writer) *WriterOutput {
  32. return &WriterOutput{w: w}
  33. }
  34. func (o *WriterOutput) Output(_ LogLevel, message []byte) {
  35. o.w.Write(append(bytes.TrimRight(message, "\r\n"), platformNewline...))
  36. }
  37. // StdlibOutput is a TextOutput that simply writes to the default Go stdlib
  38. // logging system. It is the default. If you configure the Go stdlib to write
  39. // to spacelog, make sure to provide a new TextOutput to your logging
  40. // collection
  41. type StdlibOutput struct{}
  42. func (*StdlibOutput) Output(_ LogLevel, message []byte) {
  43. log.Print(string(message))
  44. }
  45. type bufferMsg struct {
  46. level LogLevel
  47. message []byte
  48. }
  49. // BufferedOutput uses a channel to synchronize writes to a wrapped TextOutput
  50. // and allows for buffering a limited amount of log events.
  51. type BufferedOutput struct {
  52. o TextOutput
  53. c chan bufferMsg
  54. running sync.Mutex
  55. close_once sync.Once
  56. }
  57. // NewBufferedOutput returns a BufferedOutput wrapping output with a buffer
  58. // size of buffer.
  59. func NewBufferedOutput(output TextOutput, buffer int) *BufferedOutput {
  60. if buffer < 0 {
  61. buffer = 0
  62. }
  63. b := &BufferedOutput{
  64. o: output,
  65. c: make(chan bufferMsg, buffer)}
  66. go b.process()
  67. return b
  68. }
  69. // Close shuts down the BufferedOutput's processing
  70. func (b *BufferedOutput) Close() {
  71. b.close_once.Do(func() {
  72. close(b.c)
  73. })
  74. b.running.Lock()
  75. b.running.Unlock()
  76. }
  77. func (b *BufferedOutput) Output(level LogLevel, message []byte) {
  78. b.c <- bufferMsg{level: level, message: message}
  79. }
  80. func (b *BufferedOutput) process() {
  81. b.running.Lock()
  82. defer b.running.Unlock()
  83. for {
  84. msg, open := <-b.c
  85. if !open {
  86. break
  87. }
  88. b.o.Output(msg.level, msg.message)
  89. }
  90. }
  91. // A TextOutput object that also implements HupHandlingTextOutput may have its
  92. // OnHup() method called when an administrative signal is sent to this process.
  93. type HupHandlingTextOutput interface {
  94. TextOutput
  95. OnHup()
  96. }
  97. // FileWriterOutput is like WriterOutput with a plain file handle, but it
  98. // knows how to reopen the file (or try to reopen it) if it hasn't been able
  99. // to open the file previously, or if an appropriate signal has been received.
  100. type FileWriterOutput struct {
  101. *WriterOutput
  102. path string
  103. }
  104. // Creates a new FileWriterOutput object. This is the only case where an
  105. // error opening the file will be reported to the caller; if we try to
  106. // reopen it later and the reopen fails, we'll just keep trying until it
  107. // works.
  108. func NewFileWriterOutput(path string) (*FileWriterOutput, error) {
  109. fo := &FileWriterOutput{path: path}
  110. fh, err := fo.openFile()
  111. if err != nil {
  112. return nil, err
  113. }
  114. fo.WriterOutput = NewWriterOutput(fh)
  115. return fo, nil
  116. }
  117. // Try to open the file with the path associated with this object.
  118. func (fo *FileWriterOutput) openFile() (*os.File, error) {
  119. return os.OpenFile(fo.path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
  120. }
  121. // Try to communicate a message without using our log file. In all likelihood,
  122. // stderr is closed or redirected to /dev/null, but at least we can try
  123. // writing there. In the very worst case, if an admin attaches a ptrace to
  124. // this process, it will be more clear what the problem is.
  125. func (fo *FileWriterOutput) fallbackLog(tmpl string, args ...interface{}) {
  126. fmt.Fprintf(os.Stderr, tmpl, args...)
  127. }
  128. // Output a log line by writing it to the file. If the file has been
  129. // released, try to open it again. If that fails, cry for a little
  130. // while, then throw away the message and carry on.
  131. func (fo *FileWriterOutput) Output(ll LogLevel, message []byte) {
  132. if fo.WriterOutput == nil {
  133. fh, err := fo.openFile()
  134. if err != nil {
  135. fo.fallbackLog("Could not open %#v: %s", fo.path, err)
  136. return
  137. }
  138. fo.WriterOutput = NewWriterOutput(fh)
  139. }
  140. fo.WriterOutput.Output(ll, message)
  141. }
  142. // Throw away any references/handles to the output file. This probably
  143. // means the admin wants to rotate the file out and have this process
  144. // open a new one. Close the underlying io.Writer if that is a thing
  145. // that it knows how to do.
  146. func (fo *FileWriterOutput) OnHup() {
  147. if fo.WriterOutput != nil {
  148. wc, ok := fo.WriterOutput.w.(io.Closer)
  149. if ok {
  150. err := wc.Close()
  151. if err != nil {
  152. fo.fallbackLog("Closing %#v failed: %s", fo.path, err)
  153. }
  154. }
  155. fo.WriterOutput = nil
  156. }
  157. }