writer.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // Copyright (c) 2021 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. // Package zapio provides tools for interacting with IO streams through Zap.
  21. package zapio
  22. import (
  23. "bytes"
  24. "io"
  25. "go.uber.org/zap"
  26. "go.uber.org/zap/zapcore"
  27. )
  28. // Writer is an io.Writer that writes to the provided Zap logger, splitting log
  29. // messages on line boundaries. The Writer will buffer writes in memory until
  30. // it encounters a newline, or the caller calls Sync or Close.
  31. //
  32. // Use the Writer with packages like os/exec where an io.Writer is required,
  33. // and you want to log the output using your existing logger configuration. For
  34. // example,
  35. //
  36. // writer := &zapio.Writer{Log: logger, Level: zap.DebugLevel}
  37. // defer writer.Close()
  38. //
  39. // cmd := exec.CommandContext(ctx, ...)
  40. // cmd.Stdout = writer
  41. // cmd.Stderr = writer
  42. // if err := cmd.Run(); err != nil {
  43. // return err
  44. // }
  45. //
  46. // Writer must be closed when finished to flush buffered data to the logger.
  47. type Writer struct {
  48. // Log specifies the logger to which the Writer will write messages.
  49. //
  50. // The Writer will panic if Log is unspecified.
  51. Log *zap.Logger
  52. // Log level for the messages written to the provided logger.
  53. //
  54. // If unspecified, defaults to Info.
  55. Level zapcore.Level
  56. buff bytes.Buffer
  57. }
  58. var (
  59. _ zapcore.WriteSyncer = (*Writer)(nil)
  60. _ io.Closer = (*Writer)(nil)
  61. )
  62. // Write writes the provided bytes to the underlying logger at the configured
  63. // log level and returns the length of the bytes.
  64. //
  65. // Write will split the input on newlines and post each line as a new log entry
  66. // to the logger.
  67. func (w *Writer) Write(bs []byte) (n int, err error) {
  68. // Skip all checks if the level isn't enabled.
  69. if !w.Log.Core().Enabled(w.Level) {
  70. return len(bs), nil
  71. }
  72. n = len(bs)
  73. for len(bs) > 0 {
  74. bs = w.writeLine(bs)
  75. }
  76. return n, nil
  77. }
  78. // writeLine writes a single line from the input, returning the remaining,
  79. // unconsumed bytes.
  80. func (w *Writer) writeLine(line []byte) (remaining []byte) {
  81. idx := bytes.IndexByte(line, '\n')
  82. if idx < 0 {
  83. // If there are no newlines, buffer the entire string.
  84. w.buff.Write(line)
  85. return nil
  86. }
  87. // Split on the newline, buffer and flush the left.
  88. line, remaining = line[:idx], line[idx+1:]
  89. // Fast path: if we don't have a partial message from a previous write
  90. // in the buffer, skip the buffer and log directly.
  91. if w.buff.Len() == 0 {
  92. w.log(line)
  93. return
  94. }
  95. w.buff.Write(line)
  96. // Log empty messages in the middle of the stream so that we don't lose
  97. // information when the user writes "foo\n\nbar".
  98. w.flush(true /* allowEmpty */)
  99. return remaining
  100. }
  101. // Close closes the writer, flushing any buffered data in the process.
  102. //
  103. // Always call Close once you're done with the Writer to ensure that it flushes
  104. // all data.
  105. func (w *Writer) Close() error {
  106. return w.Sync()
  107. }
  108. // Sync flushes buffered data to the logger as a new log entry even if it
  109. // doesn't contain a newline.
  110. func (w *Writer) Sync() error {
  111. // Don't allow empty messages on explicit Sync calls or on Close
  112. // because we don't want an extraneous empty message at the end of the
  113. // stream -- it's common for files to end with a newline.
  114. w.flush(false /* allowEmpty */)
  115. return nil
  116. }
  117. // flush flushes the buffered data to the logger, allowing empty messages only
  118. // if the bool is set.
  119. func (w *Writer) flush(allowEmpty bool) {
  120. if allowEmpty || w.buff.Len() > 0 {
  121. w.log(w.buff.Bytes())
  122. }
  123. w.buff.Reset()
  124. }
  125. func (w *Writer) log(b []byte) {
  126. if ce := w.Log.Check(w.Level, string(b)); ce != nil {
  127. ce.Write()
  128. }
  129. }