You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

logger.go 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. // Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package logger
  4. import (
  5. "bufio"
  6. "bytes"
  7. "fmt"
  8. "os"
  9. "time"
  10. "strings"
  11. "sync"
  12. "sync/atomic"
  13. colorable "github.com/mattn/go-colorable"
  14. "github.com/mgutz/ansi"
  15. )
  16. // Level represents the level to log messages at.
  17. type Level int
  18. const (
  19. // LogDebug represents debug messages.
  20. LogDebug Level = iota
  21. // LogInfo represents informational messages.
  22. LogInfo
  23. // LogWarning represents warnings.
  24. LogWarning
  25. // LogError represents errors.
  26. LogError
  27. )
  28. var (
  29. colorTimeGrey = ansi.ColorFunc("243")
  30. colorGrey = ansi.ColorFunc("8")
  31. colorAlert = ansi.ColorFunc("232+b:red")
  32. colorWarn = ansi.ColorFunc("black:214")
  33. colorInfo = ansi.ColorFunc("117")
  34. colorDebug = ansi.ColorFunc("78")
  35. colorSection = ansi.ColorFunc("229")
  36. separator = colorGrey(":")
  37. colorableStdout = colorable.NewColorableStdout()
  38. colorableStderr = colorable.NewColorableStderr()
  39. )
  40. var (
  41. // LogLevelNames takes a config name and gives the real log level.
  42. LogLevelNames = map[string]Level{
  43. "debug": LogDebug,
  44. "info": LogInfo,
  45. "warn": LogWarning,
  46. "warning": LogWarning,
  47. "warnings": LogWarning,
  48. "error": LogError,
  49. "errors": LogError,
  50. }
  51. // LogLevelDisplayNames gives the display name to use for our log levels.
  52. LogLevelDisplayNames = map[Level]string{
  53. LogDebug: "debug",
  54. LogInfo: "info",
  55. LogWarning: "warning",
  56. LogError: "error",
  57. }
  58. )
  59. // Manager is the main interface used to log debug/info/error messages.
  60. type Manager struct {
  61. configMutex sync.RWMutex
  62. loggers []singleLogger
  63. stdoutWriteLock sync.Mutex // use one lock for both stdout and stderr
  64. fileWriteLock sync.Mutex
  65. loggingRawIO uint32
  66. }
  67. // LoggingConfig represents the configuration of a single logger.
  68. type LoggingConfig struct {
  69. Method string
  70. MethodStdout bool
  71. MethodStderr bool
  72. MethodFile bool
  73. Filename string
  74. TypeString string `yaml:"type"`
  75. Types []string `yaml:"real-types"`
  76. ExcludedTypes []string `yaml:"real-excluded-types"`
  77. LevelString string `yaml:"level"`
  78. Level Level `yaml:"level-real"`
  79. }
  80. // NewManager returns a new log manager.
  81. func NewManager(config []LoggingConfig) (*Manager, error) {
  82. var logger Manager
  83. if err := logger.ApplyConfig(config); err != nil {
  84. return nil, err
  85. }
  86. return &logger, nil
  87. }
  88. // ApplyConfig applies the given config to this logger (rehashes the config, in other words).
  89. func (logger *Manager) ApplyConfig(config []LoggingConfig) error {
  90. logger.configMutex.Lock()
  91. defer logger.configMutex.Unlock()
  92. for _, logger := range logger.loggers {
  93. logger.Close()
  94. }
  95. logger.loggers = nil
  96. atomic.StoreUint32(&logger.loggingRawIO, 0)
  97. // for safety, this deep-copies all mutable data in `config`
  98. // XXX let's keep it that way
  99. var lastErr error
  100. for _, logConfig := range config {
  101. typeMap := make(map[string]bool)
  102. for _, name := range logConfig.Types {
  103. typeMap[name] = true
  104. }
  105. excludedTypeMap := make(map[string]bool)
  106. for _, name := range logConfig.ExcludedTypes {
  107. excludedTypeMap[name] = true
  108. }
  109. sLogger := singleLogger{
  110. MethodSTDOUT: logConfig.MethodStdout,
  111. MethodSTDERR: logConfig.MethodStderr,
  112. MethodFile: fileMethod{
  113. Enabled: logConfig.MethodFile,
  114. Filename: logConfig.Filename,
  115. },
  116. Level: logConfig.Level,
  117. Types: typeMap,
  118. ExcludedTypes: excludedTypeMap,
  119. stdoutWriteLock: &logger.stdoutWriteLock,
  120. fileWriteLock: &logger.fileWriteLock,
  121. }
  122. ioEnabled := typeMap["userinput"] || typeMap["useroutput"] || (typeMap["*"] && !(excludedTypeMap["userinput"] && excludedTypeMap["useroutput"]))
  123. // raw I/O is only logged at level debug;
  124. if ioEnabled && logConfig.Level == LogDebug {
  125. atomic.StoreUint32(&logger.loggingRawIO, 1)
  126. }
  127. if sLogger.MethodFile.Enabled {
  128. file, err := os.OpenFile(sLogger.MethodFile.Filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
  129. if err != nil {
  130. lastErr = fmt.Errorf("Could not open log file %s [%s]", sLogger.MethodFile.Filename, err.Error())
  131. }
  132. writer := bufio.NewWriter(file)
  133. sLogger.MethodFile.File = file
  134. sLogger.MethodFile.Writer = writer
  135. }
  136. logger.loggers = append(logger.loggers, sLogger)
  137. }
  138. return lastErr
  139. }
  140. // IsLoggingRawIO returns true if raw user input and output is being logged.
  141. func (logger *Manager) IsLoggingRawIO() bool {
  142. return atomic.LoadUint32(&logger.loggingRawIO) == 1
  143. }
  144. // Log logs the given message with the given details.
  145. func (logger *Manager) Log(level Level, logType string, messageParts ...string) {
  146. logger.configMutex.RLock()
  147. defer logger.configMutex.RUnlock()
  148. for _, singleLogger := range logger.loggers {
  149. singleLogger.Log(level, logType, messageParts...)
  150. }
  151. }
  152. // Debug logs the given message as a debug message.
  153. func (logger *Manager) Debug(logType string, messageParts ...string) {
  154. logger.Log(LogDebug, logType, messageParts...)
  155. }
  156. // Info logs the given message as an info message.
  157. func (logger *Manager) Info(logType string, messageParts ...string) {
  158. logger.Log(LogInfo, logType, messageParts...)
  159. }
  160. // Warning logs the given message as a warning message.
  161. func (logger *Manager) Warning(logType string, messageParts ...string) {
  162. logger.Log(LogWarning, logType, messageParts...)
  163. }
  164. // Error logs the given message as an error message.
  165. func (logger *Manager) Error(logType string, messageParts ...string) {
  166. logger.Log(LogError, logType, messageParts...)
  167. }
  168. type fileMethod struct {
  169. Enabled bool
  170. Filename string
  171. File *os.File
  172. Writer *bufio.Writer
  173. }
  174. // singleLogger represents a single logger instance.
  175. type singleLogger struct {
  176. stdoutWriteLock *sync.Mutex
  177. fileWriteLock *sync.Mutex
  178. MethodSTDOUT bool
  179. MethodSTDERR bool
  180. MethodFile fileMethod
  181. Level Level
  182. Types map[string]bool
  183. ExcludedTypes map[string]bool
  184. }
  185. func (logger *singleLogger) Close() error {
  186. if logger.MethodFile.Enabled {
  187. flushErr := logger.MethodFile.Writer.Flush()
  188. closeErr := logger.MethodFile.File.Close()
  189. if flushErr != nil {
  190. return flushErr
  191. }
  192. return closeErr
  193. }
  194. return nil
  195. }
  196. // Log logs the given message with the given details.
  197. func (logger *singleLogger) Log(level Level, logType string, messageParts ...string) {
  198. // no logging enabled
  199. if !(logger.MethodSTDOUT || logger.MethodSTDERR || logger.MethodFile.Enabled) {
  200. return
  201. }
  202. // ensure we're logging to the given level
  203. if level < logger.Level {
  204. return
  205. }
  206. // ensure we're capturing this logType
  207. logTypeCleaned := strings.ToLower(strings.TrimSpace(logType))
  208. capturing := (logger.Types["*"] || logger.Types[logTypeCleaned]) && !logger.ExcludedTypes["*"] && !logger.ExcludedTypes[logTypeCleaned]
  209. if !capturing {
  210. return
  211. }
  212. // assemble full line
  213. levelDisplay := LogLevelDisplayNames[level]
  214. if level == LogError {
  215. levelDisplay = colorAlert(levelDisplay)
  216. } else if level == LogWarning {
  217. levelDisplay = colorWarn(levelDisplay)
  218. } else if level == LogInfo {
  219. levelDisplay = colorInfo(levelDisplay)
  220. } else if level == LogDebug {
  221. levelDisplay = colorDebug(levelDisplay)
  222. }
  223. var formattedBuf, rawBuf bytes.Buffer
  224. fmt.Fprintf(&formattedBuf, "%s %s %s %s %s %s ", colorTimeGrey(time.Now().UTC().Format("2006-01-02T15:04:05.000Z")), separator, levelDisplay, separator, colorSection(logType), separator)
  225. if logger.MethodFile.Enabled {
  226. fmt.Fprintf(&rawBuf, "%s : %s : %s : ", time.Now().UTC().Format("2006-01-02T15:04:05Z"), LogLevelDisplayNames[level], logType)
  227. }
  228. for i, p := range messageParts {
  229. formattedBuf.WriteString(p)
  230. if logger.MethodFile.Enabled {
  231. rawBuf.WriteString(p)
  232. }
  233. if i != len(messageParts)-1 {
  234. formattedBuf.WriteRune(' ')
  235. formattedBuf.WriteString(separator)
  236. formattedBuf.WriteRune(' ')
  237. if logger.MethodFile.Enabled {
  238. rawBuf.WriteString(" : ")
  239. }
  240. }
  241. }
  242. formattedBuf.WriteRune('\n')
  243. if logger.MethodFile.Enabled {
  244. rawBuf.WriteRune('\n')
  245. }
  246. // output
  247. if logger.MethodSTDOUT {
  248. logger.stdoutWriteLock.Lock()
  249. colorableStdout.Write(formattedBuf.Bytes())
  250. logger.stdoutWriteLock.Unlock()
  251. }
  252. if logger.MethodSTDERR {
  253. logger.stdoutWriteLock.Lock()
  254. colorableStderr.Write(formattedBuf.Bytes())
  255. logger.stdoutWriteLock.Unlock()
  256. }
  257. if logger.MethodFile.Enabled {
  258. logger.fileWriteLock.Lock()
  259. logger.MethodFile.Writer.Write(rawBuf.Bytes())
  260. logger.MethodFile.Writer.Flush()
  261. logger.fileWriteLock.Unlock()
  262. }
  263. }