rotate.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /*
  2. * Copyright (c) 2016, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. // Package `rotate` provides an io.Writer interface for files that will detect when the open
  20. // file has been rotated away (due to log rotation, or manual move/deletion) and re-open it.
  21. // This allows the standard log.Logger to continue working as expected after log rotation
  22. // happens (without needing to specify the `copytruncate` or equivalient options).
  23. //
  24. // This package is safe to use concurrently from multiple goroutines
  25. package rotate
  26. import (
  27. "os"
  28. "sync"
  29. )
  30. // RotatableFileWriter implementation that knows when the file has been rotated and re-opens it
  31. type RotatableFileWriter struct {
  32. sync.Mutex
  33. file *os.File
  34. fileInfo *os.FileInfo
  35. mode os.FileMode
  36. name string
  37. }
  38. // Close closes the underlying file
  39. func (f *RotatableFileWriter) Close() error {
  40. f.Lock()
  41. err := f.file.Close()
  42. f.Unlock()
  43. return err
  44. }
  45. // reopen provides the (not exported, not concurrency safe) implementation of re-opening the file and updates the struct's fileInfo
  46. func (f *RotatableFileWriter) reopen() error {
  47. if f.file != nil {
  48. f.file.Close()
  49. f.file = nil
  50. f.fileInfo = nil
  51. }
  52. reopened, err := os.OpenFile(f.name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, f.mode)
  53. if err != nil {
  54. return err
  55. }
  56. f.file = reopened
  57. fileInfo, err := os.Stat(f.name)
  58. if err != nil {
  59. return err
  60. }
  61. f.fileInfo = &fileInfo
  62. return nil
  63. }
  64. // Reopen provides the concurrency safe implementation of re-opening the file, and updating the struct's fileInfo
  65. func (f *RotatableFileWriter) Reopen() error {
  66. f.Lock()
  67. err := f.reopen()
  68. f.Unlock()
  69. return err
  70. }
  71. // Write implements the standard io.Writer interface, but checks whether or not the file
  72. // has changed prior to writing. If it has, it will reopen the file first, then write
  73. func (f *RotatableFileWriter) Write(p []byte) (int, error) {
  74. f.Lock()
  75. defer f.Unlock() // Defer unlock due to the possibility of early return
  76. // If a call to Write fails while attempting to re-open the file, f.fileInfo
  77. // could be left nil, causing subsequent writes to panic. This will attempt
  78. // to re-open the file handle prior to writing in that case
  79. if f.file == nil || f.fileInfo == nil {
  80. err := f.reopen()
  81. if err != nil {
  82. return 0, err
  83. }
  84. }
  85. currentFileInfo, err := os.Stat(f.name)
  86. if err != nil || !os.SameFile(*f.fileInfo, currentFileInfo) {
  87. err := f.reopen()
  88. if err != nil {
  89. return 0, err
  90. }
  91. }
  92. bytesWritten, err := f.file.Write(p)
  93. // If the write fails with nothing written, attempt to re-open the file and retry the write
  94. if bytesWritten == 0 && err != nil {
  95. err = f.reopen()
  96. if err != nil {
  97. return 0, err
  98. }
  99. bytesWritten, err = f.file.Write(p)
  100. }
  101. return bytesWritten, err
  102. }
  103. // NewRotatableFileWriter opens a file for appending and writing that can be safely rotated
  104. func NewRotatableFileWriter(name string, mode os.FileMode) (*RotatableFileWriter, error) {
  105. rotatableFileWriter := RotatableFileWriter{
  106. file: nil,
  107. name: name,
  108. mode: mode,
  109. fileInfo: nil,
  110. }
  111. err := rotatableFileWriter.reopen()
  112. if err != nil {
  113. return nil, err
  114. }
  115. return &rotatableFileWriter, nil
  116. }