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 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. // Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package logger
  4. import (
  5. "bufio"
  6. "fmt"
  7. "os"
  8. "time"
  9. "strings"
  10. "sync"
  11. colorable "github.com/mattn/go-colorable"
  12. "github.com/mgutz/ansi"
  13. )
  14. // Level represents the level to log messages at.
  15. type Level int
  16. const (
  17. // LogDebug represents debug messages.
  18. LogDebug Level = iota
  19. // LogInfo represents informational messages.
  20. LogInfo
  21. // LogWarning represents warnings.
  22. LogWarning
  23. // LogError represents errors.
  24. LogError
  25. )
  26. var (
  27. // LogLevelNames takes a config name and gives the real log level.
  28. LogLevelNames = map[string]Level{
  29. "debug": LogDebug,
  30. "info": LogInfo,
  31. "warn": LogWarning,
  32. "warning": LogWarning,
  33. "warnings": LogWarning,
  34. "error": LogError,
  35. "errors": LogError,
  36. }
  37. // LogLevelDisplayNames gives the display name to use for our log levels.
  38. LogLevelDisplayNames = map[Level]string{
  39. LogDebug: "debug",
  40. LogInfo: "info",
  41. LogWarning: "warning",
  42. LogError: "error",
  43. }
  44. )
  45. // Manager is the main interface used to log debug/info/error messages.
  46. type Manager struct {
  47. loggers []singleLogger
  48. stdoutWriteLock sync.Mutex // use one lock for both stdout and stderr
  49. fileWriteLock sync.Mutex
  50. DumpingRawInOut bool
  51. }
  52. // Config represents the configuration of a single logger.
  53. type Config struct {
  54. // logging methods
  55. MethodStdout bool
  56. MethodStderr bool
  57. MethodFile bool
  58. Filename string
  59. // logging level
  60. Level Level
  61. // logging types
  62. Types []string
  63. ExcludedTypes []string
  64. }
  65. // NewManager returns a new log manager.
  66. func NewManager(config ...Config) (*Manager, error) {
  67. var logger Manager
  68. for _, logConfig := range config {
  69. typeMap := make(map[string]bool)
  70. for _, name := range logConfig.Types {
  71. typeMap[name] = true
  72. }
  73. excludedTypeMap := make(map[string]bool)
  74. for _, name := range logConfig.ExcludedTypes {
  75. excludedTypeMap[name] = true
  76. }
  77. sLogger := singleLogger{
  78. MethodSTDOUT: logConfig.MethodStdout,
  79. MethodSTDERR: logConfig.MethodStderr,
  80. MethodFile: fileMethod{
  81. Enabled: logConfig.MethodFile,
  82. Filename: logConfig.Filename,
  83. },
  84. Level: logConfig.Level,
  85. Types: typeMap,
  86. ExcludedTypes: excludedTypeMap,
  87. stdoutWriteLock: &logger.stdoutWriteLock,
  88. fileWriteLock: &logger.fileWriteLock,
  89. }
  90. if typeMap["userinput"] || typeMap["useroutput"] || (typeMap["*"] && !(excludedTypeMap["userinput"] && excludedTypeMap["useroutput"])) {
  91. logger.DumpingRawInOut = true
  92. }
  93. if sLogger.MethodFile.Enabled {
  94. file, err := os.OpenFile(sLogger.MethodFile.Filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
  95. if err != nil {
  96. return nil, fmt.Errorf("Could not open log file %s [%s]", sLogger.MethodFile.Filename, err.Error())
  97. }
  98. writer := bufio.NewWriter(file)
  99. sLogger.MethodFile.File = file
  100. sLogger.MethodFile.Writer = writer
  101. }
  102. logger.loggers = append(logger.loggers, sLogger)
  103. }
  104. return &logger, nil
  105. }
  106. // Log logs the given message with the given details.
  107. func (logger *Manager) Log(level Level, logType string, messageParts ...string) {
  108. for _, singleLogger := range logger.loggers {
  109. singleLogger.Log(level, logType, messageParts...)
  110. }
  111. }
  112. // Debug logs the given message as a debug message.
  113. func (logger *Manager) Debug(logType string, messageParts ...string) {
  114. for _, singleLogger := range logger.loggers {
  115. singleLogger.Log(LogDebug, logType, messageParts...)
  116. }
  117. }
  118. // Info logs the given message as an info message.
  119. func (logger *Manager) Info(logType string, messageParts ...string) {
  120. for _, singleLogger := range logger.loggers {
  121. singleLogger.Log(LogInfo, logType, messageParts...)
  122. }
  123. }
  124. // Warning logs the given message as a warning message.
  125. func (logger *Manager) Warning(logType string, messageParts ...string) {
  126. for _, singleLogger := range logger.loggers {
  127. singleLogger.Log(LogWarning, logType, messageParts...)
  128. }
  129. }
  130. // Error logs the given message as an error message.
  131. func (logger *Manager) Error(logType string, messageParts ...string) {
  132. for _, singleLogger := range logger.loggers {
  133. singleLogger.Log(LogError, logType, messageParts...)
  134. }
  135. }
  136. // Fatal logs the given message as an error message, then exits.
  137. func (logger *Manager) Fatal(logType string, messageParts ...string) {
  138. logger.Error(logType, messageParts...)
  139. logger.Error("FATAL", "Fatal error encountered, application exiting")
  140. os.Exit(1)
  141. }
  142. type fileMethod struct {
  143. Enabled bool
  144. Filename string
  145. File *os.File
  146. Writer *bufio.Writer
  147. }
  148. // singleLogger represents a single logger instance.
  149. type singleLogger struct {
  150. stdoutWriteLock *sync.Mutex
  151. fileWriteLock *sync.Mutex
  152. MethodSTDOUT bool
  153. MethodSTDERR bool
  154. MethodFile fileMethod
  155. Level Level
  156. Types map[string]bool
  157. ExcludedTypes map[string]bool
  158. }
  159. // Log logs the given message with the given details.
  160. func (logger *singleLogger) Log(level Level, logType string, messageParts ...string) {
  161. // no logging enabled
  162. if !(logger.MethodSTDOUT || logger.MethodSTDERR || logger.MethodFile.Enabled) {
  163. return
  164. }
  165. // ensure we're logging to the given level
  166. if level < logger.Level {
  167. return
  168. }
  169. // ensure we're capturing this logType
  170. logTypeCleaned := strings.ToLower(strings.TrimSpace(logType))
  171. capturing := (logger.Types["*"] || logger.Types[logTypeCleaned]) && !logger.ExcludedTypes["*"] && !logger.ExcludedTypes[logTypeCleaned]
  172. if !capturing {
  173. return
  174. }
  175. // assemble full line
  176. timeGrey := ansi.ColorFunc("243")
  177. grey := ansi.ColorFunc("8")
  178. alert := ansi.ColorFunc("232+b:red")
  179. warn := ansi.ColorFunc("black:214")
  180. info := ansi.ColorFunc("117")
  181. debug := ansi.ColorFunc("78")
  182. section := ansi.ColorFunc("229")
  183. levelDisplay := LogLevelDisplayNames[level]
  184. if level == LogError {
  185. levelDisplay = alert(levelDisplay)
  186. } else if level == LogWarning {
  187. levelDisplay = warn(levelDisplay)
  188. } else if level == LogInfo {
  189. levelDisplay = info(levelDisplay)
  190. } else if level == LogDebug {
  191. levelDisplay = debug(levelDisplay)
  192. }
  193. sep := grey(":")
  194. fullStringFormatted := fmt.Sprintf("%s %s %s %s %s %s ", timeGrey(time.Now().UTC().Format("2006-01-02T15:04:05Z")), sep, levelDisplay, sep, section(logType), sep)
  195. fullStringRaw := fmt.Sprintf("%s : %s : %s : ", time.Now().UTC().Format("2006-01-02T15:04:05Z"), LogLevelDisplayNames[level], logType)
  196. for i, p := range messageParts {
  197. fullStringFormatted += p
  198. fullStringRaw += p
  199. if i != len(messageParts)-1 {
  200. fullStringFormatted += " " + sep + " "
  201. fullStringRaw += " : "
  202. }
  203. }
  204. // output
  205. if logger.MethodSTDOUT {
  206. logger.stdoutWriteLock.Lock()
  207. fmt.Fprintln(colorable.NewColorableStdout(), fullStringFormatted)
  208. logger.stdoutWriteLock.Unlock()
  209. }
  210. if logger.MethodSTDERR {
  211. logger.stdoutWriteLock.Lock()
  212. fmt.Fprintln(colorable.NewColorableStderr(), fullStringFormatted)
  213. logger.stdoutWriteLock.Unlock()
  214. }
  215. if logger.MethodFile.Enabled {
  216. logger.fileWriteLock.Lock()
  217. logger.MethodFile.Writer.WriteString(fullStringRaw + "\n")
  218. logger.MethodFile.Writer.Flush()
  219. logger.fileWriteLock.Unlock()
  220. }
  221. }