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

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