file.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // Copyright 2017 Unknwon
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // 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, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. package clog
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "log"
  21. "os"
  22. "path/filepath"
  23. "strings"
  24. "time"
  25. )
  26. const (
  27. FILE MODE = "file"
  28. SIMPLE_DATE_FORMAT = "2006-01-02"
  29. LOG_PREFIX_LENGTH = len("2017/02/06 21:20:08 ")
  30. )
  31. // FileRotationConfig represents rotation related configurations for file mode logger.
  32. // All the settings can take effect at the same time, remain zero values to disable them.
  33. type FileRotationConfig struct {
  34. // Do rotation for output files.
  35. Rotate bool
  36. // Rotate on daily basis.
  37. Daily bool
  38. // Maximum size in bytes of file for a rotation.
  39. MaxSize int64
  40. // Maximum number of lines for a rotation.
  41. MaxLines int64
  42. // Maximum lifetime of a output file in days.
  43. MaxDays int64
  44. }
  45. type FileConfig struct {
  46. // Minimum level of messages to be processed.
  47. Level LEVEL
  48. // Buffer size defines how many messages can be queued before hangs.
  49. BufferSize int64
  50. // File name to outout messages.
  51. Filename string
  52. // Rotation related configurations.
  53. FileRotationConfig
  54. }
  55. type file struct {
  56. // Indicates whether object is been used in standalone mode.
  57. standalone bool
  58. *log.Logger
  59. Adapter
  60. file *os.File
  61. filename string
  62. openDay int
  63. currentSize int64
  64. currentLines int64
  65. rotate FileRotationConfig
  66. }
  67. func newFile() Logger {
  68. return &file{
  69. Adapter: Adapter{
  70. quitChan: make(chan struct{}),
  71. },
  72. }
  73. }
  74. // NewFileWriter returns an io.Writer for synchronized file logger in standalone mode.
  75. func NewFileWriter(filename string, cfg FileRotationConfig) (io.Writer, error) {
  76. f := &file{
  77. standalone: true,
  78. }
  79. if err := f.Init(FileConfig{
  80. Filename: filename,
  81. FileRotationConfig: cfg,
  82. }); err != nil {
  83. return nil, err
  84. }
  85. return f, nil
  86. }
  87. func (f *file) Level() LEVEL { return f.level }
  88. var newLineBytes = []byte("\n")
  89. func (f *file) initFile() (err error) {
  90. f.file, err = os.OpenFile(f.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
  91. if err != nil {
  92. return fmt.Errorf("OpenFile '%s': %v", f.filename, err)
  93. }
  94. f.Logger = log.New(f.file, "", log.Ldate|log.Ltime)
  95. return nil
  96. }
  97. // isExist checks whether a file or directory exists.
  98. // It returns false when the file or directory does not exist.
  99. func isExist(path string) bool {
  100. _, err := os.Stat(path)
  101. return err == nil || os.IsExist(err)
  102. }
  103. // rotateFilename returns next available rotate filename with given date.
  104. func (f *file) rotateFilename(date string) string {
  105. filename := fmt.Sprintf("%s.%s", f.filename, date)
  106. if !isExist(filename) {
  107. return filename
  108. }
  109. format := filename + ".%03d"
  110. for i := 1; i < 1000; i++ {
  111. filename := fmt.Sprintf(format, i)
  112. if !isExist(filename) {
  113. return filename
  114. }
  115. }
  116. panic("too many log files for yesterday")
  117. }
  118. func (f *file) deleteOutdatedFiles() {
  119. filepath.Walk(filepath.Dir(f.filename), func(path string, info os.FileInfo, err error) error {
  120. if !info.IsDir() &&
  121. info.ModTime().Before(time.Now().Add(-24*time.Hour*time.Duration(f.rotate.MaxDays))) &&
  122. strings.HasPrefix(filepath.Base(path), filepath.Base(f.filename)) {
  123. os.Remove(path)
  124. }
  125. return nil
  126. })
  127. }
  128. func (f *file) initRotate() error {
  129. // Gather basic file info for rotation.
  130. fi, err := f.file.Stat()
  131. if err != nil {
  132. return fmt.Errorf("Stat: %v", err)
  133. }
  134. f.currentSize = fi.Size()
  135. // If there is any content in the file, count the number of lines.
  136. if f.rotate.MaxLines > 0 && f.currentSize > 0 {
  137. data, err := ioutil.ReadFile(f.filename)
  138. if err != nil {
  139. return fmt.Errorf("ReadFile '%s': %v", f.filename, err)
  140. }
  141. f.currentLines = int64(bytes.Count(data, newLineBytes)) + 1
  142. }
  143. if f.rotate.Daily {
  144. now := time.Now()
  145. f.openDay = now.Day()
  146. lastWriteTime := fi.ModTime()
  147. if lastWriteTime.Year() != now.Year() ||
  148. lastWriteTime.Month() != now.Month() ||
  149. lastWriteTime.Day() != now.Day() {
  150. if err = f.file.Close(); err != nil {
  151. return fmt.Errorf("Close: %v", err)
  152. }
  153. if err = os.Rename(f.filename, f.rotateFilename(lastWriteTime.Format(SIMPLE_DATE_FORMAT))); err != nil {
  154. return fmt.Errorf("Rename: %v", err)
  155. }
  156. if err = f.initFile(); err != nil {
  157. return fmt.Errorf("initFile: %v", err)
  158. }
  159. }
  160. }
  161. if f.rotate.MaxDays > 0 {
  162. f.deleteOutdatedFiles()
  163. }
  164. return nil
  165. }
  166. func (f *file) Init(v interface{}) (err error) {
  167. cfg, ok := v.(FileConfig)
  168. if !ok {
  169. return ErrConfigObject{"FileConfig", v}
  170. }
  171. if !isValidLevel(cfg.Level) {
  172. return ErrInvalidLevel{}
  173. }
  174. f.level = cfg.Level
  175. f.filename = cfg.Filename
  176. os.MkdirAll(filepath.Dir(f.filename), os.ModePerm)
  177. if err = f.initFile(); err != nil {
  178. return fmt.Errorf("initFile: %v", err)
  179. }
  180. f.rotate = cfg.FileRotationConfig
  181. if f.rotate.Rotate {
  182. f.initRotate()
  183. }
  184. if !f.standalone {
  185. f.msgChan = make(chan *Message, cfg.BufferSize)
  186. }
  187. return nil
  188. }
  189. func (f *file) ExchangeChans(errorChan chan<- error) chan *Message {
  190. f.errorChan = errorChan
  191. return f.msgChan
  192. }
  193. func (f *file) write(msg *Message) int {
  194. f.Logger.Print(msg.Body)
  195. bytesWrote := len(msg.Body)
  196. if !f.standalone {
  197. bytesWrote += LOG_PREFIX_LENGTH
  198. }
  199. if f.rotate.Rotate {
  200. f.currentSize += int64(bytesWrote)
  201. f.currentLines++ // TODO: should I care if log message itself contains new lines?
  202. var (
  203. needsRotate = false
  204. rotateDate time.Time
  205. )
  206. now := time.Now()
  207. if f.rotate.Daily && now.Day() != f.openDay {
  208. needsRotate = true
  209. rotateDate = now.Add(-24 * time.Hour)
  210. } else if (f.rotate.MaxSize > 0 && f.currentSize >= f.rotate.MaxSize) ||
  211. (f.rotate.MaxLines > 0 && f.currentLines >= f.rotate.MaxLines) {
  212. needsRotate = true
  213. rotateDate = now
  214. }
  215. if needsRotate {
  216. f.file.Close()
  217. if err := os.Rename(f.filename, f.rotateFilename(rotateDate.Format(SIMPLE_DATE_FORMAT))); err != nil {
  218. f.errorChan <- fmt.Errorf("fail to rename rotate file '%s': %v", f.filename, err)
  219. }
  220. if err := f.initFile(); err != nil {
  221. f.errorChan <- fmt.Errorf("fail to init log file '%s': %v", f.filename, err)
  222. }
  223. f.openDay = now.Day()
  224. f.currentSize = 0
  225. f.currentLines = 0
  226. }
  227. }
  228. return bytesWrote
  229. }
  230. var _ io.Writer = new(file)
  231. // Write implements method of io.Writer interface.
  232. func (f *file) Write(p []byte) (int, error) {
  233. return f.write(&Message{
  234. Body: string(p),
  235. }), nil
  236. }
  237. func (f *file) Start() {
  238. LOOP:
  239. for {
  240. select {
  241. case msg := <-f.msgChan:
  242. f.write(msg)
  243. case <-f.quitChan:
  244. break LOOP
  245. }
  246. }
  247. for {
  248. if len(f.msgChan) == 0 {
  249. break
  250. }
  251. f.write(<-f.msgChan)
  252. }
  253. f.quitChan <- struct{}{} // Notify the cleanup is done.
  254. }
  255. func (f *file) Destroy() {
  256. f.quitChan <- struct{}{}
  257. <-f.quitChan
  258. close(f.msgChan)
  259. close(f.quitChan)
  260. f.file.Close()
  261. }
  262. func init() {
  263. Register(FILE, newFile)
  264. }