logger.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package logger
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "os"
  7. "path/filepath"
  8. "strconv"
  9. "time"
  10. )
  11. /*
  12. ArozOS System Logger
  13. This script is designed to make a managed log for the ArozOS system
  14. and replace the ton of log.Println in the system core
  15. */
  16. // globalPrintJSON is set once at startup via SetGlobalJSONOutput.
  17. var globalPrintJSON bool
  18. func SetGlobalJSONOutput(enabled bool) {
  19. globalPrintJSON = enabled
  20. }
  21. // defaultLogger is the single system-wide logger used by all packages.
  22. // It starts as a tmp (stdout-only) logger and is replaced by SetDefaultLogger
  23. // once the real logger is ready in main().
  24. var defaultLogger, _ = NewTmpLogger()
  25. // SetDefaultLogger replaces the default logger used by all packages.
  26. // Call this once in main() after the persistent logger is created.
  27. func SetDefaultLogger(l *Logger) {
  28. defaultLogger = l
  29. }
  30. // PrintAndLog is a package-level convenience function that delegates to the
  31. // default logger. All modules should call this instead of keeping their own
  32. // logger instances.
  33. func PrintAndLog(title string, message string, originalError error) {
  34. defaultLogger.PrintAndLog(title, message, originalError)
  35. }
  36. type Logger struct {
  37. LogToFile bool //Set enable write to file
  38. PrintJSON bool //Print console output as JSON lines instead of plain text
  39. Prefix string //Prefix for log files
  40. LogFolder string //Folder to store the log file
  41. CurrentLogFile string //Current writing filename
  42. file *os.File //File, empty if LogToFile is false
  43. }
  44. type jsonLogEntry struct {
  45. Time string `json:"time"`
  46. Level string `json:"level"`
  47. Title string `json:"title"`
  48. Message string `json:"message"`
  49. }
  50. // Create a default logger
  51. func NewLogger(logFilePrefix string, logFolder string, logToFile bool) (*Logger, error) {
  52. if logToFile {
  53. err := os.MkdirAll(logFolder, 0775)
  54. if err != nil {
  55. return nil, err
  56. }
  57. }
  58. thisLogger := Logger{
  59. LogToFile: logToFile,
  60. Prefix: logFilePrefix,
  61. LogFolder: logFolder,
  62. }
  63. if logToFile {
  64. logFilePath := thisLogger.getLogFilepath()
  65. f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
  66. if err != nil {
  67. return nil, err
  68. }
  69. thisLogger.CurrentLogFile = logFilePath
  70. thisLogger.file = f
  71. }
  72. return &thisLogger, nil
  73. }
  74. // Create a non-persistent logger for one-time uses
  75. func NewTmpLogger() (*Logger, error) {
  76. return NewLogger("", "", false)
  77. }
  78. func (l *Logger) getLogFilepath() string {
  79. year, month, _ := time.Now().Date()
  80. return filepath.Join(l.LogFolder, l.Prefix+"_"+strconv.Itoa(year)+"-"+strconv.Itoa(int(month))+".log")
  81. }
  82. // PrintAndLog will log the message to file and print the log to STDOUT
  83. func (l *Logger) PrintAndLog(title string, message string, originalError error) {
  84. if l == nil {
  85. // Not initiated yet, just print to console
  86. log.Println("[" + title + "] " + message)
  87. return
  88. }
  89. go func() {
  90. l.Log(title, message, originalError)
  91. }()
  92. if l.PrintJSON || globalPrintJSON {
  93. level := "info"
  94. if originalError != nil {
  95. level = "error"
  96. }
  97. entry := jsonLogEntry{
  98. Time: time.Now().Format("2006-01-02T15:04:05.000000Z07:00"),
  99. Level: level,
  100. Title: title,
  101. Message: message,
  102. }
  103. b, err := json.Marshal(entry)
  104. if err != nil {
  105. log.Println("[" + title + "] " + message)
  106. return
  107. }
  108. fmt.Println(string(b))
  109. } else {
  110. log.Println("[" + title + "] " + message)
  111. }
  112. }
  113. func (l *Logger) Log(title string, errorMessage string, originalError error) {
  114. if l != nil && l.LogToFile {
  115. l.ValidateAndUpdateLogFilepath()
  116. if originalError == nil {
  117. l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [INFO]" + errorMessage + "\n")
  118. } else {
  119. l.file.WriteString(time.Now().Format("2006-01-02 15:04:05.000000") + "|" + fmt.Sprintf("%-16s", title) + " [ERROR]" + errorMessage + " " + originalError.Error() + "\n")
  120. }
  121. }
  122. }
  123. // Validate if the logging target is still valid (detect any months change)
  124. func (l *Logger) ValidateAndUpdateLogFilepath() {
  125. expectedCurrentLogFilepath := l.getLogFilepath()
  126. if l.CurrentLogFile != expectedCurrentLogFilepath {
  127. //Change of month. Update to a new log file
  128. l.file.Close()
  129. f, err := os.OpenFile(expectedCurrentLogFilepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
  130. if err != nil {
  131. log.Println("[Logger] Unable to create new log. Logging to file disabled.")
  132. l.LogToFile = false
  133. return
  134. }
  135. l.CurrentLogFile = expectedCurrentLogFilepath
  136. l.file = f
  137. }
  138. }
  139. func (l *Logger) Close() {
  140. l.file.Close()
  141. }