tracelog.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. // Copyright 2013 Ardan Studios. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE handle.
  4. /*
  5. Read the following blog post for more information:
  6. http://www.goinggo.net/2013/11/using-log-package-in-go.html
  7. Example Code:
  8. package main
  9. import (
  10. "fmt"
  11. "github.com/finapps/tracelog"
  12. )
  13. func main() {
  14. //tracelog.StartFile(tracelog.LevelTrace, "/Users/bill/Temp/logs", 1)
  15. tracelog.Start(tracelog.LevelTrace)
  16. tracelog.Trace("main", "main", "Hello Trace")
  17. tracelog.Info("main", "main", "Hello Info")
  18. tracelog.Warning("main", "main", "Hello Warn")
  19. tracelog.Errorf(fmt.Errorf("Exception At..."), "main", "main", "Hello Error")
  20. Example()
  21. tracelog.Stop()
  22. }
  23. func Example() {
  24. tracelog.Started("main", "Example")
  25. err := foo()
  26. if err != nil {
  27. tracelog.CompletedError(err, "main", "Example")
  28. return
  29. }
  30. tracelog.Completed("main", "Example")
  31. }
  32. Output:
  33. TRACE: 2013/11/07 08:24:32 main.go:12: main : main : Info : Hello Trace
  34. INFO: 2013/11/07 08:24:32 main.go:13: main : main : Info : Hello Info
  35. WARNING: 2013/11/07 08:24:32 main.go:14: main : main : Info : Hello Warn
  36. ERROR: 2013/11/07 08:24:32 main.go:15: main : main : Info : Hello Error : Exception At...
  37. TRACE: 2013/11/07 08:24:32 main.go:23: main : Example : Started
  38. TRACE: 2013/11/07 08:24:32 main.go:31: main : Example : Completed
  39. TRACE: 2013/11/07 08:24:32 tracelog.go:149: main : Stop : Started
  40. TRACE: 2013/11/07 08:24:32 tracelog.go:156: main : Stop : Completed
  41. */
  42. // Package tracelog implements a logging system to trace all aspect of your code. This is great for task oriented programs.
  43. // Based on the Go log standard library. It provides 4 destinations with logging levels plus you can attach a file for persistent
  44. // writes. A log clean process is provided to maintain disk space. There is also email support to send email alerts.
  45. package tracelog
  46. import (
  47. "bytes"
  48. "fmt"
  49. "io"
  50. "io/ioutil"
  51. "log"
  52. "net/smtp"
  53. "os"
  54. "runtime"
  55. "strconv"
  56. "strings"
  57. "sync/atomic"
  58. "text/template"
  59. "time"
  60. )
  61. const systemAlertSubject = "TraceLog Exception"
  62. const (
  63. // LevelTrace logs everything
  64. LevelTrace int32 = 1
  65. // LevelInfo logs Info, Warnings and Errors
  66. LevelInfo int32 = 2
  67. // LevelWarn logs Warning and Errors
  68. LevelWarn int32 = 4
  69. // LevelError logs just Errors
  70. LevelError int32 = 8
  71. )
  72. // emailConfiguration contains configuration information required by the ConfigureEmailAlerts function.
  73. type emailConfiguration struct {
  74. Host string
  75. Port int
  76. UserName string
  77. Password string
  78. To []string
  79. Auth smtp.Auth
  80. Template *template.Template
  81. }
  82. // traceLog provides support to write to log files.
  83. type traceLog struct {
  84. LogLevel int32
  85. EmailConfiguration *emailConfiguration
  86. Trace *log.Logger
  87. Info *log.Logger
  88. Warning *log.Logger
  89. Error *log.Logger
  90. File *log.Logger
  91. LogFile *os.File
  92. }
  93. // log maintains a pointer to a singleton for the logging system.
  94. var logger traceLog
  95. // Called to init the logging system.
  96. func init() {
  97. log.SetPrefix("TRACE: ")
  98. log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
  99. }
  100. // Start initializes tracelog and only displays the specified logging level.
  101. func Start(logLevel int32) {
  102. turnOnLogging(logLevel, nil)
  103. }
  104. // StartFile initializes tracelog and only displays the specified logging level
  105. // and creates a file to capture writes.
  106. func StartFile(logLevel int32, baseFilePath string, daysToKeep int) {
  107. baseFilePath = strings.TrimRight(baseFilePath, "/")
  108. currentDate := time.Now().UTC()
  109. dateDirectory := time.Now().UTC().Format("2006-01-02")
  110. dateFile := currentDate.Format("2006-01-02T15-04-05")
  111. filePath := fmt.Sprintf("%s/%s/", baseFilePath, dateDirectory)
  112. fileName := strings.Replace(fmt.Sprintf("%s.txt", dateFile), " ", "-", -1)
  113. err := os.MkdirAll(filePath, os.ModePerm)
  114. if err != nil {
  115. log.Fatalf("main : Start : Failed to Create log directory : %s : %s\n", filePath, err)
  116. }
  117. logf, err := os.Create(fmt.Sprintf("%s%s", filePath, fileName))
  118. if err != nil {
  119. log.Fatalf("main : Start : Failed to Create log file : %s : %s\n", fileName, err)
  120. }
  121. // Turn the logging on
  122. turnOnLogging(logLevel, logf)
  123. // Cleanup any existing directories
  124. logger.LogDirectoryCleanup(baseFilePath, daysToKeep)
  125. }
  126. // Stop will release resources and shutdown all processing.
  127. func Stop() error {
  128. Started("main", "Stop")
  129. var err error
  130. if logger.LogFile != nil {
  131. Trace("main", "Stop", "Closing File")
  132. err = logger.LogFile.Close()
  133. }
  134. Completed("main", "Stop")
  135. return err
  136. }
  137. // ConfigureEmail configures the email system for use.
  138. func ConfigureEmail(host string, port int, userName string, password string, to []string) {
  139. logger.EmailConfiguration = &emailConfiguration{
  140. Host: host,
  141. Port: port,
  142. UserName: userName,
  143. Password: password,
  144. To: to,
  145. Auth: smtp.PlainAuth("", userName, password, host),
  146. Template: template.Must(template.New("emailTemplate").Parse(logger.EmailScript())),
  147. }
  148. }
  149. // SendEmailException will send an email along with the exception.
  150. func SendEmailException(subject string, message string, a ...interface{}) error {
  151. var err error
  152. defer logger.CatchPanic(&err, "SendEmailException")
  153. if logger.EmailConfiguration == nil {
  154. return err
  155. }
  156. parameters := struct {
  157. From string
  158. To string
  159. Subject string
  160. Message string
  161. }{
  162. logger.EmailConfiguration.UserName,
  163. strings.Join([]string(logger.EmailConfiguration.To), ","),
  164. subject,
  165. fmt.Sprintf(message, a...),
  166. }
  167. var emailMessage bytes.Buffer
  168. logger.EmailConfiguration.Template.Execute(&emailMessage, &parameters)
  169. err = smtp.SendMail(fmt.Sprintf("%s:%d",
  170. logger.EmailConfiguration.Host, logger.EmailConfiguration.Port),
  171. logger.EmailConfiguration.Auth,
  172. logger.EmailConfiguration.UserName,
  173. logger.EmailConfiguration.To,
  174. emailMessage.Bytes())
  175. return err
  176. }
  177. // LogLevel returns the configured logging level.
  178. func LogLevel() int32 {
  179. return atomic.LoadInt32(&logger.LogLevel)
  180. }
  181. // turnOnLogging configures the logging writers.
  182. func turnOnLogging(logLevel int32, fileHandle io.Writer) {
  183. traceHandle := ioutil.Discard
  184. infoHandle := ioutil.Discard
  185. warnHandle := ioutil.Discard
  186. errorHandle := ioutil.Discard
  187. if logLevel&LevelTrace != 0 {
  188. traceHandle = os.Stdout
  189. infoHandle = os.Stdout
  190. warnHandle = os.Stdout
  191. errorHandle = os.Stderr
  192. }
  193. if logLevel&LevelInfo != 0 {
  194. infoHandle = os.Stdout
  195. warnHandle = os.Stdout
  196. errorHandle = os.Stderr
  197. }
  198. if logLevel&LevelWarn != 0 {
  199. warnHandle = os.Stdout
  200. errorHandle = os.Stderr
  201. }
  202. if logLevel&LevelError != 0 {
  203. errorHandle = os.Stderr
  204. }
  205. if fileHandle != nil {
  206. if traceHandle == os.Stdout {
  207. traceHandle = io.MultiWriter(fileHandle, traceHandle)
  208. }
  209. if infoHandle == os.Stdout {
  210. infoHandle = io.MultiWriter(fileHandle, infoHandle)
  211. }
  212. if warnHandle == os.Stdout {
  213. warnHandle = io.MultiWriter(fileHandle, warnHandle)
  214. }
  215. if errorHandle == os.Stderr {
  216. errorHandle = io.MultiWriter(fileHandle, errorHandle)
  217. }
  218. }
  219. logger.Trace = log.New(traceHandle, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile)
  220. logger.Info = log.New(infoHandle, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
  221. logger.Warning = log.New(warnHandle, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
  222. logger.Error = log.New(errorHandle, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
  223. atomic.StoreInt32(&logger.LogLevel, logLevel)
  224. }
  225. // LogDirectoryCleanup performs all the directory cleanup and maintenance.
  226. func (traceLog *traceLog) LogDirectoryCleanup(baseFilePath string, daysToKeep int) {
  227. defer traceLog.CatchPanic(nil, "LogDirectoryCleanup")
  228. Startedf("main", "LogDirectoryCleanup", "BaseFilePath[%s] DaysToKeep[%d]", baseFilePath, daysToKeep)
  229. // Get a list of existing directories.
  230. fileInfos, err := ioutil.ReadDir(baseFilePath)
  231. if err != nil {
  232. CompletedError(err, "main", "LogDirectoryCleanup")
  233. return
  234. }
  235. // Create the date to compare for directories to remove.
  236. currentDate := time.Now().UTC()
  237. compareDate := time.Date(currentDate.Year(), currentDate.Month(), currentDate.Day()-daysToKeep, 0, 0, 0, 0, time.UTC)
  238. Trace("main", "LogDirectoryCleanup", "CompareDate[%v]", compareDate)
  239. for _, fileInfo := range fileInfos {
  240. if fileInfo.IsDir() == false {
  241. continue
  242. }
  243. // The file name look like: YYYY-MM-DD
  244. parts := strings.Split(fileInfo.Name(), "-")
  245. year, err := strconv.Atoi(parts[0])
  246. if err != nil {
  247. Errorf(err, "main", "LogDirectoryCleanup", "Attempting To Convert Directory [%s]", fileInfo.Name())
  248. continue
  249. }
  250. month, err := strconv.Atoi(parts[1])
  251. if err != nil {
  252. Errorf(err, "main", "LogDirectoryCleanup", "Attempting To Convert Directory [%s]", fileInfo.Name())
  253. continue
  254. }
  255. day, err := strconv.Atoi(parts[2])
  256. if err != nil {
  257. Errorf(err, "main", "LogDirectoryCleanup", "Attempting To Convert Directory [%s]", fileInfo.Name())
  258. continue
  259. }
  260. // The directory to check.
  261. fullFileName := fmt.Sprintf("%s/%s", baseFilePath, fileInfo.Name())
  262. // Create a time type from the directory name.
  263. directoryDate := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
  264. // Compare the dates and convert to days.
  265. daysOld := int(compareDate.Sub(directoryDate).Hours() / 24)
  266. Trace("main", "LogDirectoryCleanup", "Checking Directory[%s] DaysOld[%d]", fullFileName, daysOld)
  267. if daysOld >= 0 {
  268. Trace("main", "LogDirectoryCleanup", "Removing Directory[%s]", fullFileName)
  269. err = os.RemoveAll(fullFileName)
  270. if err != nil {
  271. Trace("main", "LogDirectoryCleanup", "Attempting To Remove Directory [%s]", fullFileName)
  272. continue
  273. }
  274. Trace("main", "LogDirectoryCleanup", "Directory Removed [%s]", fullFileName)
  275. }
  276. }
  277. // We don't need the catch handler to log any errors.
  278. err = nil
  279. Completed("main", "LogDirectoryCleanup")
  280. return
  281. }
  282. // CatchPanic is used to catch any Panic and log exceptions to Stdout. It will also write the stack logger.
  283. func (traceLog *traceLog) CatchPanic(err *error, functionName string) {
  284. if r := recover(); r != nil {
  285. // Capture the stack trace
  286. buf := make([]byte, 10000)
  287. runtime.Stack(buf, false)
  288. SendEmailException(systemAlertSubject, "%s : PANIC Defered [%s] : Stack Trace : %s", functionName, r, string(buf))
  289. if err != nil {
  290. *err = fmt.Errorf("%v", r)
  291. }
  292. }
  293. }
  294. // EmailScript returns a template for the email message to be sent.
  295. func (traceLog *traceLog) EmailScript() (script string) {
  296. return `From: {{.From}}
  297. To: {{.To}}
  298. Subject: {{.Subject}}
  299. MIME-version: 1.0
  300. Content-Type: text/html; charset="UTF-8"
  301. <html><body>{{.Message}}</body></html>`
  302. }