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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "bufio"
  6. "fmt"
  7. "os"
  8. "time"
  9. "strings"
  10. "github.com/mgutz/ansi"
  11. )
  12. // LogLevel represents the level to log messages at.
  13. type LogLevel int
  14. const (
  15. // LogDebug represents debug messages.
  16. LogDebug LogLevel = iota
  17. // LogInfo represents informational messages.
  18. LogInfo
  19. // LogWarn represents warnings.
  20. LogWarn
  21. // LogError represents errors.
  22. LogError
  23. )
  24. var (
  25. logLevelNames = map[string]LogLevel{
  26. "debug": LogDebug,
  27. "info": LogInfo,
  28. "warn": LogWarn,
  29. "warning": LogWarn,
  30. "warnings": LogWarn,
  31. "error": LogError,
  32. "errors": LogError,
  33. }
  34. logLevelDisplayNames = map[LogLevel]string{
  35. LogDebug: "debug",
  36. LogInfo: "info",
  37. LogWarn: "warning",
  38. LogError: "error",
  39. }
  40. )
  41. // Logger is the main interface used to log debug/info/error messages.
  42. type Logger struct {
  43. loggers []SingleLogger
  44. DumpingRawInOut bool
  45. }
  46. // NewLogger returns a new Logger.
  47. func NewLogger(config []LoggingConfig) (*Logger, error) {
  48. var logger Logger
  49. for _, logConfig := range config {
  50. sLogger := SingleLogger{
  51. MethodSTDERR: logConfig.Methods["stderr"],
  52. MethodFile: fileMethod{
  53. Enabled: logConfig.Methods["file"],
  54. Filename: logConfig.Filename,
  55. },
  56. Level: logConfig.Level,
  57. Types: logConfig.Types,
  58. ExcludedTypes: logConfig.ExcludedTypes,
  59. }
  60. if logConfig.Types["userinput"] || logConfig.Types["useroutput"] || (logConfig.Types["*"] && !(logConfig.ExcludedTypes["userinput"] && logConfig.ExcludedTypes["useroutput"])) {
  61. logger.DumpingRawInOut = true
  62. }
  63. if sLogger.MethodFile.Enabled {
  64. file, err := os.OpenFile(sLogger.MethodFile.Filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
  65. if err != nil {
  66. return nil, fmt.Errorf("Could not open log file %s [%s]", sLogger.MethodFile.Filename, err.Error())
  67. }
  68. writer := bufio.NewWriter(file)
  69. sLogger.MethodFile.File = file
  70. sLogger.MethodFile.Writer = writer
  71. }
  72. logger.loggers = append(logger.loggers, sLogger)
  73. }
  74. return &logger, nil
  75. }
  76. // Log logs the given message with the given details.
  77. func (logger *Logger) Log(level LogLevel, logType string, messageParts ...string) {
  78. for _, singleLogger := range logger.loggers {
  79. singleLogger.Log(level, logType, messageParts...)
  80. }
  81. }
  82. type fileMethod struct {
  83. Enabled bool
  84. Filename string
  85. File *os.File
  86. Writer *bufio.Writer
  87. }
  88. // SingleLogger represents a single logger instance.
  89. type SingleLogger struct {
  90. MethodSTDERR bool
  91. MethodFile fileMethod
  92. Level LogLevel
  93. Types map[string]bool
  94. ExcludedTypes map[string]bool
  95. }
  96. // Log logs the given message with the given details.
  97. func (logger *SingleLogger) Log(level LogLevel, logType string, messageParts ...string) {
  98. // no logging enabled
  99. if !(logger.MethodSTDERR || logger.MethodFile.Enabled) {
  100. return
  101. }
  102. // ensure we're logging to the given level
  103. if level < logger.Level {
  104. return
  105. }
  106. // ensure we're capturing this logType
  107. logTypeCleaned := strings.ToLower(strings.TrimSpace(logType))
  108. capturing := (logger.Types["*"] || logger.Types[logTypeCleaned]) && !logger.ExcludedTypes["*"] && !logger.ExcludedTypes[logTypeCleaned]
  109. if !capturing {
  110. return
  111. }
  112. // assemble full line
  113. timeGrey := ansi.ColorFunc("243")
  114. grey := ansi.ColorFunc("8")
  115. alert := ansi.ColorFunc("232+b:red")
  116. warn := ansi.ColorFunc("black:214")
  117. info := ansi.ColorFunc("117")
  118. debug := ansi.ColorFunc("78")
  119. section := ansi.ColorFunc("229")
  120. levelDisplay := logLevelDisplayNames[level]
  121. if level == LogError {
  122. levelDisplay = alert(levelDisplay)
  123. } else if level == LogWarn {
  124. levelDisplay = warn(levelDisplay)
  125. } else if level == LogInfo {
  126. levelDisplay = info(levelDisplay)
  127. } else if level == LogDebug {
  128. levelDisplay = debug(levelDisplay)
  129. }
  130. sep := grey(":")
  131. 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)
  132. fullStringRaw := fmt.Sprintf("%s : %s : %s : ", time.Now().UTC().Format("2006-01-02T15:04:05Z"), logLevelDisplayNames[level], section(logType))
  133. for i, p := range messageParts {
  134. fullStringFormatted += p
  135. fullStringRaw += p
  136. if i != len(messageParts)-1 {
  137. fullStringFormatted += " " + sep + " "
  138. fullStringRaw += " : "
  139. }
  140. }
  141. // output
  142. if logger.MethodSTDERR {
  143. fmt.Fprintln(os.Stderr, fullStringFormatted)
  144. }
  145. if logger.MethodFile.Enabled {
  146. logger.MethodFile.Writer.WriteString(fullStringRaw + "\n")
  147. }
  148. }