glog_file.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. // Go support for leveled logs, analogous to https://github.com/google/glog.
  2. //
  3. // Copyright 2023 Google Inc. All Rights Reserved.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. // File I/O for logs.
  17. package glog
  18. import (
  19. "bufio"
  20. "bytes"
  21. "errors"
  22. "flag"
  23. "fmt"
  24. "io"
  25. "os"
  26. "os/user"
  27. "path/filepath"
  28. "runtime"
  29. "strings"
  30. "sync"
  31. "time"
  32. "github.com/golang/glog/internal/logsink"
  33. )
  34. // logDirs lists the candidate directories for new log files.
  35. var logDirs []string
  36. var (
  37. // If non-empty, overrides the choice of directory in which to write logs.
  38. // See createLogDirs for the full list of possible destinations.
  39. logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory")
  40. logLink = flag.String("log_link", "", "If non-empty, add symbolic links in this directory to the log files")
  41. logBufLevel = flag.Int("logbuflevel", int(logsink.Info), "Buffer log messages logged at this level or lower"+
  42. " (-1 means don't buffer; 0 means buffer INFO only; ...). Has limited applicability on non-prod platforms.")
  43. )
  44. func createLogDirs() {
  45. if *logDir != "" {
  46. logDirs = append(logDirs, *logDir)
  47. }
  48. logDirs = append(logDirs, os.TempDir())
  49. }
  50. var (
  51. pid = os.Getpid()
  52. program = filepath.Base(os.Args[0])
  53. host = "unknownhost"
  54. userName = "unknownuser"
  55. )
  56. func init() {
  57. h, err := os.Hostname()
  58. if err == nil {
  59. host = shortHostname(h)
  60. }
  61. current, err := user.Current()
  62. if err == nil {
  63. userName = current.Username
  64. }
  65. // Sanitize userName since it is used to construct file paths.
  66. userName = strings.Map(func(r rune) rune {
  67. switch {
  68. case r >= 'a' && r <= 'z':
  69. case r >= 'A' && r <= 'Z':
  70. case r >= '0' && r <= '9':
  71. default:
  72. return '_'
  73. }
  74. return r
  75. }, userName)
  76. }
  77. // shortHostname returns its argument, truncating at the first period.
  78. // For instance, given "www.google.com" it returns "www".
  79. func shortHostname(hostname string) string {
  80. if i := strings.Index(hostname, "."); i >= 0 {
  81. return hostname[:i]
  82. }
  83. return hostname
  84. }
  85. // logName returns a new log file name containing tag, with start time t, and
  86. // the name for the symlink for tag.
  87. func logName(tag string, t time.Time) (name, link string) {
  88. name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
  89. program,
  90. host,
  91. userName,
  92. tag,
  93. t.Year(),
  94. t.Month(),
  95. t.Day(),
  96. t.Hour(),
  97. t.Minute(),
  98. t.Second(),
  99. pid)
  100. return name, program + "." + tag
  101. }
  102. var onceLogDirs sync.Once
  103. // create creates a new log file and returns the file and its filename, which
  104. // contains tag ("INFO", "FATAL", etc.) and t. If the file is created
  105. // successfully, create also attempts to update the symlink for that tag, ignoring
  106. // errors.
  107. func create(tag string, t time.Time) (f *os.File, filename string, err error) {
  108. onceLogDirs.Do(createLogDirs)
  109. if len(logDirs) == 0 {
  110. return nil, "", errors.New("log: no log dirs")
  111. }
  112. name, link := logName(tag, t)
  113. var lastErr error
  114. for _, dir := range logDirs {
  115. fname := filepath.Join(dir, name)
  116. f, err := os.Create(fname)
  117. if err == nil {
  118. symlink := filepath.Join(dir, link)
  119. os.Remove(symlink) // ignore err
  120. os.Symlink(name, symlink) // ignore err
  121. if *logLink != "" {
  122. lsymlink := filepath.Join(*logLink, link)
  123. os.Remove(lsymlink) // ignore err
  124. os.Symlink(fname, lsymlink) // ignore err
  125. }
  126. return f, fname, nil
  127. }
  128. lastErr = err
  129. }
  130. return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
  131. }
  132. // flushSyncWriter is the interface satisfied by logging destinations.
  133. type flushSyncWriter interface {
  134. Flush() error
  135. Sync() error
  136. io.Writer
  137. filenames() []string
  138. }
  139. var sinks struct {
  140. stderr stderrSink
  141. file fileSink
  142. }
  143. func init() {
  144. // Register stderr first: that way if we crash during file-writing at least
  145. // the log will have gone somewhere.
  146. logsink.TextSinks = append(logsink.TextSinks, &sinks.stderr, &sinks.file)
  147. sinks.file.flushChan = make(chan logsink.Severity, 1)
  148. go sinks.file.flushDaemon()
  149. }
  150. // stderrSink is a logsink.Text that writes log entries to stderr
  151. // if they meet certain conditions.
  152. type stderrSink struct {
  153. mu sync.Mutex
  154. w io.Writer // if nil Emit uses os.Stderr directly
  155. }
  156. // Enabled implements logsink.Text.Enabled. It returns true if any of the
  157. // various stderr flags are enabled for logs of the given severity, if the log
  158. // message is from the standard "log" package, or if google.Init has not yet run
  159. // (and hence file logging is not yet initialized).
  160. func (s *stderrSink) Enabled(m *logsink.Meta) bool {
  161. return toStderr || alsoToStderr || m.Severity >= stderrThreshold.get()
  162. }
  163. // Emit implements logsink.Text.Emit.
  164. func (s *stderrSink) Emit(m *logsink.Meta, data []byte) (n int, err error) {
  165. s.mu.Lock()
  166. defer s.mu.Unlock()
  167. w := s.w
  168. if w == nil {
  169. w = os.Stderr
  170. }
  171. dn, err := w.Write(data)
  172. n += dn
  173. return n, err
  174. }
  175. // severityWriters is an array of flushSyncWriter with a value for each
  176. // logsink.Severity.
  177. type severityWriters [4]flushSyncWriter
  178. // fileSink is a logsink.Text that prints to a set of Google log files.
  179. type fileSink struct {
  180. mu sync.Mutex
  181. // file holds writer for each of the log types.
  182. file severityWriters
  183. flushChan chan logsink.Severity
  184. }
  185. // Enabled implements logsink.Text.Enabled. It returns true if google.Init
  186. // has run and both --disable_log_to_disk and --logtostderr are false.
  187. func (s *fileSink) Enabled(m *logsink.Meta) bool {
  188. return !toStderr
  189. }
  190. // Emit implements logsink.Text.Emit
  191. func (s *fileSink) Emit(m *logsink.Meta, data []byte) (n int, err error) {
  192. s.mu.Lock()
  193. defer s.mu.Unlock()
  194. if err = s.createMissingFiles(m.Severity); err != nil {
  195. return 0, err
  196. }
  197. for sev := m.Severity; sev >= logsink.Info; sev-- {
  198. if _, fErr := s.file[sev].Write(data); fErr != nil && err == nil {
  199. err = fErr // Take the first error.
  200. }
  201. }
  202. n = len(data)
  203. if int(m.Severity) > *logBufLevel {
  204. select {
  205. case s.flushChan <- m.Severity:
  206. default:
  207. }
  208. }
  209. return n, err
  210. }
  211. // syncBuffer joins a bufio.Writer to its underlying file, providing access to the
  212. // file's Sync method and providing a wrapper for the Write method that provides log
  213. // file rotation. There are conflicting methods, so the file cannot be embedded.
  214. // s.mu is held for all its methods.
  215. type syncBuffer struct {
  216. sink *fileSink
  217. *bufio.Writer
  218. file *os.File
  219. names []string
  220. sev logsink.Severity
  221. nbytes uint64 // The number of bytes written to this file
  222. }
  223. func (sb *syncBuffer) Sync() error {
  224. return sb.file.Sync()
  225. }
  226. func (sb *syncBuffer) Write(p []byte) (n int, err error) {
  227. if sb.nbytes+uint64(len(p)) >= MaxSize {
  228. if err := sb.rotateFile(time.Now()); err != nil {
  229. return 0, err
  230. }
  231. }
  232. n, err = sb.Writer.Write(p)
  233. sb.nbytes += uint64(n)
  234. return n, err
  235. }
  236. func (sb *syncBuffer) filenames() []string {
  237. return sb.names
  238. }
  239. const footer = "\nCONTINUED IN NEXT FILE\n"
  240. // rotateFile closes the syncBuffer's file and starts a new one.
  241. func (sb *syncBuffer) rotateFile(now time.Time) error {
  242. var err error
  243. pn := "<none>"
  244. file, name, err := create(sb.sev.String(), now)
  245. if sb.file != nil {
  246. // The current log file becomes the previous log at the end of
  247. // this block, so save its name for use in the header of the next
  248. // file.
  249. pn = sb.file.Name()
  250. sb.Flush()
  251. // If there's an existing file, write a footer with the name of
  252. // the next file in the chain, followed by the constant string
  253. // \nCONTINUED IN NEXT FILE\n to make continuation detection simple.
  254. sb.file.Write([]byte("Next log: "))
  255. sb.file.Write([]byte(name))
  256. sb.file.Write([]byte(footer))
  257. sb.file.Close()
  258. }
  259. sb.file = file
  260. sb.names = append(sb.names, name)
  261. sb.nbytes = 0
  262. if err != nil {
  263. return err
  264. }
  265. sb.Writer = bufio.NewWriterSize(sb.file, bufferSize)
  266. // Write header.
  267. var buf bytes.Buffer
  268. fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05"))
  269. fmt.Fprintf(&buf, "Running on machine: %s\n", host)
  270. fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH)
  271. fmt.Fprintf(&buf, "Previous log: %s\n", pn)
  272. fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n")
  273. n, err := sb.file.Write(buf.Bytes())
  274. sb.nbytes += uint64(n)
  275. return err
  276. }
  277. // bufferSize sizes the buffer associated with each log file. It's large
  278. // so that log records can accumulate without the logging thread blocking
  279. // on disk I/O. The flushDaemon will block instead.
  280. const bufferSize = 256 * 1024
  281. // createMissingFiles creates all the log files for severity from infoLog up to
  282. // upTo that have not already been created.
  283. // s.mu is held.
  284. func (s *fileSink) createMissingFiles(upTo logsink.Severity) error {
  285. if s.file[upTo] != nil {
  286. return nil
  287. }
  288. now := time.Now()
  289. // Files are created in increasing severity order, so we can be assured that
  290. // if a high severity logfile exists, then so do all of lower severity.
  291. for sev := logsink.Info; sev <= upTo; sev++ {
  292. if s.file[sev] != nil {
  293. continue
  294. }
  295. sb := &syncBuffer{
  296. sink: s,
  297. sev: sev,
  298. }
  299. if err := sb.rotateFile(now); err != nil {
  300. return err
  301. }
  302. s.file[sev] = sb
  303. }
  304. return nil
  305. }
  306. // flushDaemon periodically flushes the log file buffers.
  307. func (s *fileSink) flushDaemon() {
  308. tick := time.NewTicker(30 * time.Second)
  309. defer tick.Stop()
  310. for {
  311. select {
  312. case <-tick.C:
  313. s.Flush()
  314. case sev := <-s.flushChan:
  315. s.flush(sev)
  316. }
  317. }
  318. }
  319. // Flush flushes all pending log I/O.
  320. func Flush() {
  321. sinks.file.Flush()
  322. }
  323. // Flush flushes all the logs and attempts to "sync" their data to disk.
  324. func (s *fileSink) Flush() error {
  325. return s.flush(logsink.Info)
  326. }
  327. // flush flushes all logs of severity threshold or greater.
  328. func (s *fileSink) flush(threshold logsink.Severity) error {
  329. s.mu.Lock()
  330. defer s.mu.Unlock()
  331. var firstErr error
  332. updateErr := func(err error) {
  333. if err != nil && firstErr == nil {
  334. firstErr = err
  335. }
  336. }
  337. // Flush from fatal down, in case there's trouble flushing.
  338. for sev := logsink.Fatal; sev >= threshold; sev-- {
  339. file := s.file[sev]
  340. if file != nil {
  341. updateErr(file.Flush())
  342. updateErr(file.Sync())
  343. }
  344. }
  345. return firstErr
  346. }
  347. // Names returns the names of the log files holding the FATAL, ERROR,
  348. // WARNING, or INFO logs. Returns ErrNoLog if the log for the given
  349. // level doesn't exist (e.g. because no messages of that level have been
  350. // written). This may return multiple names if the log type requested
  351. // has rolled over.
  352. func Names(s string) ([]string, error) {
  353. severity, err := logsink.ParseSeverity(s)
  354. if err != nil {
  355. return nil, err
  356. }
  357. sinks.file.mu.Lock()
  358. defer sinks.file.mu.Unlock()
  359. f := sinks.file.file[severity]
  360. if f == nil {
  361. return nil, ErrNoLog
  362. }
  363. return f.filenames(), nil
  364. }