- Logger file Auto-Rotation.
- Thread-Safe
- No need for any dependency.
Last active
May 2, 2022 19:36
-
-
Save Jerry0420/3da757e694837ba557f7380d1e373e9a to your computer and use it in GitHub Desktop.
An logger tool written in Golang.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"io" | |
"log" | |
"os" | |
"runtime" | |
"strings" | |
"sync" | |
"time" | |
) | |
type LoggerTool interface { | |
parseContextValue(context context.Context) []interface{} | |
generateLogContent(level string, message []interface{}) []interface{} | |
INFOf(message ...interface{}) | |
WARNf(message ...interface{}) | |
ERRORf(message ...interface{}) | |
FATALf(message ...interface{}) | |
} | |
type loggerTool struct { | |
sync.Mutex | |
logger *log.Logger | |
logFile *os.File | |
contextKeys []string | |
receiveMessage chan bool | |
} | |
func getLogFilePathOfToday(logBaseDir string) string { | |
// save log file inside "logs" dir. | |
err := os.MkdirAll(logBaseDir, 0777) | |
if err != nil { | |
log.Fatalf("Fail to getLogFilePath :%v", err) | |
} | |
return fmt.Sprintf("%s/%s.log", logBaseDir, time.Now().Format("20060102")) | |
} | |
func rotateLogFile(lt *loggerTool, logBaseDir string) { | |
for { | |
<-lt.receiveMessage | |
// daily rotate the log file. | |
today := fmt.Sprint(time.Now().Format("20060102")) | |
if !strings.Contains(lt.logFile.Name(), today) { | |
lt.Lock() | |
logFilePath := getLogFilePathOfToday(logBaseDir) | |
newlogFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
if err != nil { | |
log.Fatalf("Fail to open log file :%v", err) | |
} | |
lt.logger.SetOutput(io.MultiWriter(os.Stdout, newlogFile)) | |
err = lt.logFile.Close() | |
if err != nil { | |
log.Fatalf("Fail to close old log file :%v", err) | |
} | |
lt.logFile = newlogFile | |
defer lt.Unlock() | |
} | |
} | |
} | |
func NewLogger(contextKeys []string, logBaseDir string, disable bool) *loggerTool { | |
var lt *loggerTool | |
if disable == true { | |
lt = &loggerTool{ | |
logger: log.New(os.Stdout, "", 0), | |
receiveMessage: make(chan bool, 10000), | |
} | |
return lt | |
} | |
logFilePath := getLogFilePathOfToday(logBaseDir) | |
logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) | |
if err != nil { | |
log.Fatalf("Fail to open log file :%v", err) | |
} | |
lt = &loggerTool{ | |
logger: log.New(io.MultiWriter(os.Stdout, logFile), "", log.Ldate|log.Ltime), | |
logFile: logFile, | |
contextKeys: contextKeys, | |
receiveMessage: make(chan bool, 10000), | |
} | |
go rotateLogFile(lt, logBaseDir) | |
return lt | |
} | |
func (lt *loggerTool) parseContextValue(context context.Context) []interface{} { | |
var values []interface{} | |
for _, key := range lt.contextKeys { | |
// add pipe symbol for seperation. | |
if key == "sep" { | |
values = append(values, "|") | |
continue | |
} | |
if value := context.Value(key); value != nil { | |
values = append(values, value) | |
} | |
} | |
return values | |
} | |
func (lt *loggerTool) generateLogContent(level string, message []interface{}) []interface{} { | |
var content []interface{} | |
_, file, line, _ := runtime.Caller(2) | |
content = append(content, fmt.Sprint(level, file, ":", line, " |")) | |
if len(message) == 0 { | |
return content | |
} | |
if firstMessage, ok := message[0].(context.Context); ok { | |
contextValues := lt.parseContextValue(firstMessage) | |
content = append(content, contextValues...) | |
if len(message) > 1 { | |
message = message[1:] | |
} else { | |
return content | |
} | |
} | |
content = append(content, "|") | |
if formatString, ok := message[0].(string); ok && len(message) > 1 { | |
content = append(content, fmt.Sprintf(formatString, message[1:]...)) | |
} else { | |
content = append(content, message...) | |
} | |
return content | |
} | |
func (lt *loggerTool) INFOf(message ...interface{}) { | |
lt.receiveMessage <- true | |
content := lt.generateLogContent("[INFO] ", message) | |
lt.logger.Println(content...) | |
} | |
func (lt *loggerTool) WARNf(message ...interface{}) { | |
lt.receiveMessage <- true | |
content := lt.generateLogContent("[WARN] ", message) | |
lt.logger.Println(content...) | |
} | |
func (lt *loggerTool) ERRORf(message ...interface{}) { | |
lt.receiveMessage <- true | |
content := lt.generateLogContent("[ERROR] ", message) | |
lt.logger.Println(content...) | |
} | |
func (lt *loggerTool) FATALf(message ...interface{}) { | |
lt.receiveMessage <- true | |
content := lt.generateLogContent("[FATAL] ", message) | |
lt.logger.Fatalln(content...) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"log" | |
"net/http" | |
"path/filepath" | |
) | |
func hello(logger LoggerTool) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
ctx := context.WithValue(r.Context(), "requestID", "some-random-req-id") | |
ctx = context.WithValue(ctx, "code", 200) | |
ctx = context.WithValue(ctx, "duration", "10ms") | |
// add whatever you want into the context. | |
r = r.WithContext(ctx) | |
// 2021/11/29 20:40:21 [INFO] /path/of/this/file/main.go:22 | 200 | some-random-req-id 10ms | message inside request hello | |
logger.INFOf(ctx, "message inside request %s", "hello") | |
// 2021/11/29 20:40:21 [INFO] /path/of/this/file/main.go:22 | | im normal logger message without context. | |
logger.INFOf("im normal logger message without context.") | |
fmt.Fprintln(w, "This is hello.") | |
}) | |
} | |
func main() { | |
currentPath, _ := filepath.Abs("./") | |
// logBaseDir is the dir for all log files. | |
logBaseDir := filepath.Join(currentPath, "logs") | |
// "sep" is "|", it's a pipe symbol in a log message. | |
// The other fields: "code", "requestID" and "duration" are the fields inside context. | |
logger := NewLogger([]string{"code", "sep", "requestID", "duration"}, logBaseDir, false) | |
mux := http.NewServeMux() | |
mux.Handle("/hello", hello(logger)) | |
log.Fatal(http.ListenAndServe("0.0.0.0:8080", mux)) | |
logger.INFOf("server closed.") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment