filelog.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
  2. package log4go
  3. import (
  4. "fmt"
  5. "os"
  6. "time"
  7. )
  8. // This log writer sends output to a file
  9. type FileLogWriter struct {
  10. rec chan *LogRecord
  11. rot chan bool
  12. // The opened file
  13. filename string
  14. file *os.File
  15. // The logging format
  16. format string
  17. // File header/trailer
  18. header, trailer string
  19. // Rotate at linecount
  20. maxlines int
  21. maxlines_curlines int
  22. // Rotate at size
  23. maxsize int
  24. maxsize_cursize int
  25. // Rotate daily
  26. daily bool
  27. daily_opendate int
  28. // Keep old logfiles (.001, .002, etc)
  29. rotate bool
  30. maxbackup int
  31. }
  32. // This is the FileLogWriter's output method
  33. func (w *FileLogWriter) LogWrite(rec *LogRecord) {
  34. w.rec <- rec
  35. }
  36. func (w *FileLogWriter) Close() {
  37. close(w.rec)
  38. w.file.Sync()
  39. }
  40. // NewFileLogWriter creates a new LogWriter which writes to the given file and
  41. // has rotation enabled if rotate is true.
  42. //
  43. // If rotate is true, any time a new log file is opened, the old one is renamed
  44. // with a .### extension to preserve it. The various Set* methods can be used
  45. // to configure log rotation based on lines, size, and daily.
  46. //
  47. // The standard log-line format is:
  48. // [%D %T] [%L] (%S) %M
  49. func NewFileLogWriter(fname string, rotate bool) *FileLogWriter {
  50. w := &FileLogWriter{
  51. rec: make(chan *LogRecord, LogBufferLength),
  52. rot: make(chan bool),
  53. filename: fname,
  54. format: "[%D %T] [%L] (%S) %M",
  55. rotate: rotate,
  56. maxbackup: 999,
  57. }
  58. // open the file for the first time
  59. if err := w.intRotate(); err != nil {
  60. fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
  61. return nil
  62. }
  63. go func() {
  64. defer func() {
  65. if w.file != nil {
  66. fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
  67. w.file.Close()
  68. }
  69. }()
  70. for {
  71. select {
  72. case <-w.rot:
  73. if err := w.intRotate(); err != nil {
  74. fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
  75. return
  76. }
  77. case rec, ok := <-w.rec:
  78. if !ok {
  79. return
  80. }
  81. now := time.Now()
  82. if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
  83. (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
  84. (w.daily && now.Day() != w.daily_opendate) {
  85. if err := w.intRotate(); err != nil {
  86. fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
  87. return
  88. }
  89. }
  90. // Perform the write
  91. n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec))
  92. if err != nil {
  93. fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
  94. return
  95. }
  96. // Update the counts
  97. w.maxlines_curlines++
  98. w.maxsize_cursize += n
  99. }
  100. }
  101. }()
  102. return w
  103. }
  104. // Request that the logs rotate
  105. func (w *FileLogWriter) Rotate() {
  106. w.rot <- true
  107. }
  108. // If this is called in a threaded context, it MUST be synchronized
  109. func (w *FileLogWriter) intRotate() error {
  110. // Close any log file that may be open
  111. if w.file != nil {
  112. fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
  113. w.file.Close()
  114. }
  115. // If we are keeping log files, move it to the next available number
  116. if w.rotate {
  117. _, err := os.Lstat(w.filename)
  118. if err == nil { // file exists
  119. // Find the next available number
  120. num := 1
  121. fname := ""
  122. if w.daily && time.Now().Day() != w.daily_opendate {
  123. yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
  124. for ; err == nil && num <= w.maxbackup; num++ {
  125. fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num)
  126. _, err = os.Lstat(fname)
  127. }
  128. // return error if the last file checked still existed
  129. if err == nil {
  130. return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
  131. }
  132. } else {
  133. num = w.maxbackup - 1
  134. for ; num >= 1; num-- {
  135. fname = w.filename + fmt.Sprintf(".%d", num)
  136. nfname := w.filename + fmt.Sprintf(".%d", num+1)
  137. _, err = os.Lstat(fname)
  138. if err == nil {
  139. os.Rename(fname, nfname)
  140. }
  141. }
  142. }
  143. w.file.Close()
  144. // Rename the file to its newfound home
  145. err = os.Rename(w.filename, fname)
  146. if err != nil {
  147. return fmt.Errorf("Rotate: %s\n", err)
  148. }
  149. }
  150. }
  151. // Open the log file
  152. fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
  153. if err != nil {
  154. return err
  155. }
  156. w.file = fd
  157. now := time.Now()
  158. fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now}))
  159. // Set the daily open date to the current date
  160. w.daily_opendate = now.Day()
  161. // initialize rotation values
  162. w.maxlines_curlines = 0
  163. w.maxsize_cursize = 0
  164. return nil
  165. }
  166. // Set the logging format (chainable). Must be called before the first log
  167. // message is written.
  168. func (w *FileLogWriter) SetFormat(format string) *FileLogWriter {
  169. w.format = format
  170. return w
  171. }
  172. // Set the logfile header and footer (chainable). Must be called before the first log
  173. // message is written. These are formatted similar to the FormatLogRecord (e.g.
  174. // you can use %D and %T in your header/footer for date and time).
  175. func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter {
  176. w.header, w.trailer = head, foot
  177. if w.maxlines_curlines == 0 {
  178. fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()}))
  179. }
  180. return w
  181. }
  182. // Set rotate at linecount (chainable). Must be called before the first log
  183. // message is written.
  184. func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter {
  185. //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines)
  186. w.maxlines = maxlines
  187. return w
  188. }
  189. // Set rotate at size (chainable). Must be called before the first log message
  190. // is written.
  191. func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter {
  192. //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize)
  193. w.maxsize = maxsize
  194. return w
  195. }
  196. // Set rotate daily (chainable). Must be called before the first log message is
  197. // written.
  198. func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
  199. //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily)
  200. w.daily = daily
  201. return w
  202. }
  203. // Set max backup files. Must be called before the first log message
  204. // is written.
  205. func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter {
  206. w.maxbackup = maxbackup
  207. return w
  208. }
  209. // SetRotate changes whether or not the old logs are kept. (chainable) Must be
  210. // called before the first log message is written. If rotate is false, the
  211. // files are overwritten; otherwise, they are rotated to another file before the
  212. // new log is opened.
  213. func (w *FileLogWriter) SetRotate(rotate bool) *FileLogWriter {
  214. //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotate: %v\n", rotate)
  215. w.rotate = rotate
  216. return w
  217. }
  218. // NewXMLLogWriter is a utility method for creating a FileLogWriter set up to
  219. // output XML record log messages instead of line-based ones.
  220. func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter {
  221. return NewFileLogWriter(fname, rotate).SetFormat(
  222. ` <record level="%L">
  223. <timestamp>%D %T</timestamp>
  224. <source>%S</source>
  225. <message>%M</message>
  226. </record>`).SetHeadFoot("<log created=\"%D %T\">", "</log>")
  227. }