package gologger import ( "fmt" "net/http" "net/http/httputil" "os" "path/filepath" "runtime" "strings" "sync" "time" ) const ( MaxLogSize5MB int64 = 5 * 1024 * 1024 MaxLogSize1MB int64 = 1 * 1024 * 1024 MaxLogSize500KB int64 = 500 * 1024 MaxLogSize100KB int64 = 100 * 1024 MaxLogSize512B int64 = 512 logDirName = "log" ) type Logger struct { mu *sync.Mutex outputFile *os.File outputFileName string fullName string maxFileSize int64 splitCount int } func New(name string, maxFileSize int64) (logger *Logger, err error) { logger = &Logger{} logger.outputFileName = name + "-log" logger.mu = &sync.Mutex{} logger.maxFileSize = maxFileSize err = os.Mkdir(logDirName, os.ModePerm) if err != nil { if !os.IsExist(err) { fmt.Printf("logger: mkdir: couldn't create event log directory\n") return nil, err } } date := strings.Replace(time.Now().Format(time.RFC3339), ":", ".", -1) logger.outputFileName += "_" + date logger.fullName = logger.outputFileName + ".txt" path := filepath.Join(logDirName, logger.fullName) logger.outputFile, err = os.Create(path) if err != nil { fmt.Printf("logger: new: couldn't create event log file\n") return nil, err } return logger, nil } func (l *Logger) Print(format string, v ...interface{}) { msg := fmt.Sprintf(format, v...) fmt.Printf(time.Now().Format(time.RFC3339) + ": " + msg + "\n") } func (l *Logger) Log(format string, v ...interface{}) { if l.outputFile == nil { return } l.mu.Lock() defer l.mu.Unlock() msg := fmt.Sprintf(format, v...) s := time.Now().Format(time.RFC3339) + ": " + msg + "\n" if l.shouldSplit(len(s)) { l.split() } l.outputFile.WriteString(s) } func (l *Logger) RequestLog(req *http.Request, userID string) string { if l.outputFile == nil { return "" } if userID == "" { userID = "-" } var b strings.Builder b.WriteString("Request:\n") // CLF-like header ts := time.Now().Format(time.RFC3339) fmt.Fprintf(&b, "%s %s %s\n", req.RemoteAddr, userID, ts) headers := req.Header content := headers.Get("Content-Type") if content != "application/json" { b.WriteString(fmt.Sprintf("%s %s\n", req.Method, req.URL)) for k, h := range headers { b.WriteString(k + ": ") for _, h2 := range h { b.WriteString(h2 + " ") } b.WriteString("\n") } } else { body, err := httputil.DumpRequest(req, true) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) } else { b.WriteString(string(body)) } } b.WriteString("\n\n") msg := b.String() return msg } func (l *Logger) ResponseLog(status int, duration time.Duration, size int64) string { if l.outputFile == nil { return "" } var b strings.Builder fmt.Fprintf(&b, "Response:\n%d %v %dB\n", status, duration, size) b.WriteString("==============================================================\n") msg := b.String() return msg } func (l *Logger) LogHTTPTraffic(in string, out string) { if l.outputFile == nil { return } msg := in + out l.mu.Lock() defer l.mu.Unlock() if l.shouldSplit(len(msg)) { l.split() } l.outputFile.WriteString(msg) } func (l *Logger) Trace(format string, v ...interface{}) { if l.outputFile == nil { return } l.mu.Lock() defer l.mu.Unlock() _, file, line, ok := runtime.Caller(1) s := "" msg := fmt.Sprintf(format, v...) if ok { s = fmt.Sprintf("%s: %s %d: %s\n", time.Now().Format(time.RFC3339), file, line, msg) } else { s = fmt.Sprintf(time.Now().Format(time.RFC3339) + ": [can't retreive stack details]:" + msg + "\n") } if l.shouldSplit(len(s)) { l.split() } l.outputFile.WriteString(s) } func (l *Logger) Close() { if l.outputFile == nil { return } err := l.outputFile.Close() if err != nil { fmt.Printf("logger: on exit: couldn't close event log file\n") } } func (l *Logger) split() { if l.outputFile == nil { return } // close old file err := l.outputFile.Close() if err != nil { fmt.Printf("logger: split: couldn't close event file\n") return } l.splitCount++ // open new file var errnew error path := filepath.Join(logDirName, l.outputFileName+fmt.Sprintf("(%d)", l.splitCount)+".txt") l.outputFile, errnew = os.Create(path) if errnew != nil { fmt.Printf("logger: split: couldn't create event log file\n") } } func (l *Logger) shouldSplit(nextEntrySize int) bool { stats, _ := l.outputFile.Stat() return int64(nextEntrySize) >= (l.maxFileSize - stats.Size()) }