glog_file.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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. return f, fname, nil
  122. }
  123. lastErr = err
  124. }
  125. return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
  126. }
  127. // flushSyncWriter is the interface satisfied by logging destinations.
  128. type flushSyncWriter interface {
  129. Flush() error
  130. Sync() error
  131. io.Writer
  132. filenames() []string
  133. }
  134. var sinks struct {
  135. stderr stderrSink
  136. file fileSink
  137. }
  138. func init() {
  139. sinks.stderr.w = os.Stderr
  140. // Register stderr first: that way if we crash during file-writing at least
  141. // the log will have gone somewhere.
  142. logsink.TextSinks = append(logsink.TextSinks, &sinks.stderr, &sinks.file)
  143. sinks.file.flushChan = make(chan logsink.Severity, 1)
  144. go sinks.file.flushDaemon()
  145. }
  146. // stderrSink is a logsink.Text that writes log entries to stderr
  147. // if they meet certain conditions.
  148. type stderrSink struct {
  149. mu sync.Mutex
  150. w io.Writer
  151. }
  152. // Enabled implements logsink.Text.Enabled. It returns true if any of the
  153. // various stderr flags are enabled for logs of the given severity, if the log
  154. // message is from the standard "log" package, or if google.Init has not yet run
  155. // (and hence file logging is not yet initialized).
  156. func (s *stderrSink) Enabled(m *logsink.Meta) bool {
  157. return toStderr || alsoToStderr || m.Severity >= stderrThreshold.get()
  158. }
  159. // Emit implements logsink.Text.Emit.
  160. func (s *stderrSink) Emit(m *logsink.Meta, data []byte) (n int, err error) {
  161. s.mu.Lock()
  162. defer s.mu.Unlock()
  163. dn, err := s.w.Write(data)
  164. n += dn
  165. return n, err
  166. }
  167. // severityWriters is an array of flushSyncWriter with a value for each
  168. // logsink.Severity.
  169. type severityWriters [4]flushSyncWriter
  170. // fileSink is a logsink.Text that prints to a set of Google log files.
  171. type fileSink struct {
  172. mu sync.Mutex
  173. // file holds writer for each of the log types.
  174. file severityWriters
  175. flushChan chan logsink.Severity
  176. }
  177. // Enabled implements logsink.Text.Enabled. It returns true if google.Init
  178. // has run and both --disable_log_to_disk and --logtostderr are false.
  179. func (s *fileSink) Enabled(m *logsink.Meta) bool {
  180. return !toStderr
  181. }
  182. // Emit implements logsink.Text.Emit
  183. func (s *fileSink) Emit(m *logsink.Meta, data []byte) (n int, err error) {
  184. s.mu.Lock()
  185. defer s.mu.Unlock()
  186. if err = s.createMissingFiles(m.Severity); err != nil {
  187. return 0, err
  188. }
  189. for sev := m.Severity; sev >= logsink.Info; sev-- {
  190. if _, fErr := s.file[sev].Write(data); fErr != nil && err == nil {
  191. err = fErr // Take the first error.
  192. }
  193. }
  194. n = len(data)
  195. if int(m.Severity) > *logBufLevel {
  196. select {
  197. case s.flushChan <- m.Severity:
  198. default:
  199. }
  200. }
  201. return n, err
  202. }
  203. // syncBuffer joins a bufio.Writer to its underlying file, providing access to the
  204. // file's Sync method and providing a wrapper for the Write method that provides log
  205. // file rotation. There are conflicting methods, so the file cannot be embedded.
  206. // s.mu is held for all its methods.
  207. type syncBuffer struct {
  208. sink *fileSink
  209. *bufio.Writer
  210. file *os.File
  211. names []string
  212. sev logsink.Severity
  213. nbytes uint64 // The number of bytes written to this file
  214. }
  215. func (sb *syncBuffer) Sync() error {
  216. return sb.file.Sync()
  217. }
  218. func (sb *syncBuffer) Write(p []byte) (n int, err error) {
  219. if sb.nbytes+uint64(len(p)) >= MaxSize {
  220. if err := sb.rotateFile(time.Now()); err != nil {
  221. return 0, err
  222. }
  223. }
  224. n, err = sb.Writer.Write(p)
  225. sb.nbytes += uint64(n)
  226. return n, err
  227. }
  228. func (sb *syncBuffer) filenames() []string {
  229. return sb.names
  230. }
  231. const footer = "\nCONTINUED IN NEXT FILE\n"
  232. // rotateFile closes the syncBuffer's file and starts a new one.
  233. func (sb *syncBuffer) rotateFile(now time.Time) error {
  234. var err error
  235. pn := "<none>"
  236. file, name, err := create(sb.sev.String(), now)
  237. if sb.file != nil {
  238. // The current log file becomes the previous log at the end of
  239. // this block, so save its name for use in the header of the next
  240. // file.
  241. pn = sb.file.Name()
  242. sb.Flush()
  243. // If there's an existing file, write a footer with the name of
  244. // the next file in the chain, followed by the constant string
  245. // \nCONTINUED IN NEXT FILE\n to make continuation detection simple.
  246. sb.file.Write([]byte("Next log: "))
  247. sb.file.Write([]byte(name))
  248. sb.file.Write([]byte(footer))
  249. sb.file.Close()
  250. }
  251. sb.file = file
  252. sb.names = append(sb.names, name)
  253. sb.nbytes = 0
  254. if err != nil {
  255. return err
  256. }
  257. sb.Writer = bufio.NewWriterSize(sb.file, bufferSize)
  258. // Write header.
  259. var buf bytes.Buffer
  260. fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05"))
  261. fmt.Fprintf(&buf, "Running on machine: %s\n", host)
  262. fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH)
  263. fmt.Fprintf(&buf, "Previous log: %s\n", pn)
  264. fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n")
  265. n, err := sb.file.Write(buf.Bytes())
  266. sb.nbytes += uint64(n)
  267. return err
  268. }
  269. // bufferSize sizes the buffer associated with each log file. It's large
  270. // so that log records can accumulate without the logging thread blocking
  271. // on disk I/O. The flushDaemon will block instead.
  272. const bufferSize = 256 * 1024
  273. // createMissingFiles creates all the log files for severity from infoLog up to
  274. // upTo that have not already been created.
  275. // s.mu is held.
  276. func (s *fileSink) createMissingFiles(upTo logsink.Severity) error {
  277. if s.file[upTo] != nil {
  278. return nil
  279. }
  280. now := time.Now()
  281. // Files are created in increasing severity order, so we can be assured that
  282. // if a high severity logfile exists, then so do all of lower severity.
  283. for sev := logsink.Info; sev <= upTo; sev++ {
  284. if s.file[sev] != nil {
  285. continue
  286. }
  287. sb := &syncBuffer{
  288. sink: s,
  289. sev: sev,
  290. }
  291. if err := sb.rotateFile(now); err != nil {
  292. return err
  293. }
  294. s.file[sev] = sb
  295. }
  296. return nil
  297. }
  298. // flushDaemon periodically flushes the log file buffers.
  299. func (s *fileSink) flushDaemon() {
  300. tick := time.NewTicker(30 * time.Second)
  301. defer tick.Stop()
  302. for {
  303. select {
  304. case <-tick.C:
  305. s.Flush()
  306. case sev := <-s.flushChan:
  307. s.flush(sev)
  308. }
  309. }
  310. }
  311. // Flush flushes all pending log I/O.
  312. func Flush() {
  313. sinks.file.Flush()
  314. }
  315. // Flush flushes all the logs and attempts to "sync" their data to disk.
  316. func (s *fileSink) Flush() error {
  317. return s.flush(logsink.Info)
  318. }
  319. // flush flushes all logs of severity threshold or greater.
  320. func (s *fileSink) flush(threshold logsink.Severity) error {
  321. s.mu.Lock()
  322. defer s.mu.Unlock()
  323. var firstErr error
  324. updateErr := func(err error) {
  325. if err != nil && firstErr == nil {
  326. firstErr = err
  327. }
  328. }
  329. // Flush from fatal down, in case there's trouble flushing.
  330. for sev := logsink.Fatal; sev >= threshold; sev-- {
  331. file := s.file[sev]
  332. if file != nil {
  333. updateErr(file.Flush())
  334. updateErr(file.Sync())
  335. }
  336. }
  337. return firstErr
  338. }
  339. // Names returns the names of the log files holding the FATAL, ERROR,
  340. // WARNING, or INFO logs. Returns ErrNoLog if the log for the given
  341. // level doesn't exist (e.g. because no messages of that level have been
  342. // written). This may return multiple names if the log type requested
  343. // has rolled over.
  344. func Names(s string) ([]string, error) {
  345. severity, err := logsink.ParseSeverity(s)
  346. if err != nil {
  347. return nil, err
  348. }
  349. sinks.file.mu.Lock()
  350. defer sinks.file.mu.Unlock()
  351. f := sinks.file.file[severity]
  352. if f == nil {
  353. return nil, ErrNoLog
  354. }
  355. return f.filenames(), nil
  356. }