package gologger import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httputil" "os" "path/filepath" "runtime" "strings" "sync" "time" ) const dateTimeFormat = "2006-01-02 15:04:05" // Block ... const ( MaxLogSize5MB int64 = 5 * 1024 * 1024 MaxLogSize1MB int64 = 1 * 1024 * 1024 MaxLogSize500KB int64 = 500 * 1024 MaxLogSize100KB int64 = 100 * 1024 MaxLogSize512B int64 = 512 ) // Logger ... type Logger struct { mu *sync.Mutex outputFile *os.File outputFileName string fullName string maxFileSize int64 splitCount int directory string } // New ... func New(name, dir string, maxFileSize int64) (logger *Logger, err error) { logger = &Logger{} logger.outputFileName = name logger.mu = &sync.Mutex{} logger.maxFileSize = maxFileSize logger.directory = dir err = os.Mkdir(dir, os.ModePerm) if err != nil { if !os.IsExist(err) { return nil, err } } date := strings.Replace(time.Now().Format(dateTimeFormat), ":", ".", -1) logger.outputFileName += " " + date logger.fullName = logger.outputFileName + ".txt" path := filepath.Join(dir, logger.fullName) if logger.outputFile, err = os.Create(path); err != nil { return nil, err } return logger, nil } // Log ... 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(dateTimeFormat) + ": " + msg + "\n" if l.shouldSplit(len(s)) { l.split() } l.outputFile.WriteString(s) } // Print ... func (l *Logger) Print(format string, v ...interface{}) { msg := fmt.Sprintf(format, v...) fmt.Printf("%s: %s\n", time.Now().Format(dateTimeFormat), msg) } const MaxStackDepth = 10000 // CallStack func CallStack() (stack string) { callers := make([]uintptr, MaxStackDepth) // min 1 stackDepth := runtime.Callers(2, callers) frames := runtime.CallersFrames(callers) for i := 0; i < stackDepth; i++ { if i >= 2 { // skip runtime and startup frame, next := frames.Next() stack += fmt.Sprintf("[%s %d]\n", frame.File, frame.Line) if !next { break } } } return } // PrintTrace ... func (l *Logger) PrintTrace(format string, v ...interface{}) { stack := CallStack() msg := fmt.Sprintf(format, v...) fmt.Printf("%s:\n%s\n%s\n", time.Now().Format(dateTimeFormat), stack, msg) } // Trace ... func (l *Logger) Trace(format string, v ...interface{}) { if l.outputFile == nil { return } l.mu.Lock() defer l.mu.Unlock() stack := CallStack() msg := fmt.Sprintf(format, v...) s := fmt.Sprintf("%s:\n%s\n%s\n\n", time.Now().Format(dateTimeFormat), stack, msg) if l.shouldSplit(len(s)) { l.split() } l.outputFile.WriteString(s) } // PrintAndTrace ... func (l *Logger) PrintAndTrace(format string, v ...interface{}) { l.Print(format, v...) l.Trace(format, v...) } // LogHTTPRequest ... func (l *Logger) LogHTTPRequest(req *http.Request, userID string) string { if userID == "" { userID = "-" } var b strings.Builder b.WriteString("Request:\n") // CLF-like header fmt.Fprintf(&b, "%s %s %s\n", req.RemoteAddr, userID, time.Now().Format(dateTimeFormat)) body, err := httputil.DumpRequest(req, true) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) } const sepStr = "\r\n\r\n" sepIndex := bytes.Index(body, []byte(sepStr)) if sepIndex == -1 { b.WriteString(string(body) + "\n\n") } else { sepIndex += len(sepStr) payload, _ := printJSON(body[sepIndex:]) b.WriteString(string(body[:sepIndex]) + string(payload) + "\n\n") } return b.String() } const splitLine = "==============================================================" // LogHTTPResponse ... func (l *Logger) LogHTTPResponse(status int, duration time.Duration, size int) string { return fmt.Sprintf("Response:\n%d %v %dB\n%s\n", status, duration, size, splitLine) } // CombineHTTPLogs ... func (l *Logger) CombineHTTPLogs(in string, out string) { if l.outputFile == nil { return } l.mu.Lock() defer l.mu.Unlock() msg := in + out if l.shouldSplit(len(msg)) { l.split() } l.outputFile.WriteString(msg) } // Close ... func (l *Logger) Close() error { if l.outputFile == nil { return nil } return l.outputFile.Close() } func (l *Logger) split() error { if l.outputFile == nil { return nil } // close old file err := l.outputFile.Close() if err != nil { return err } // open new file l.splitCount++ path := filepath.Join(l.directory, l.outputFileName+fmt.Sprintf("(%d)", l.splitCount)+".txt") l.outputFile, err = os.Create(path) return err } func (l *Logger) shouldSplit(nextEntrySize int) bool { stats, _ := l.outputFile.Stat() return int64(nextEntrySize) >= (l.maxFileSize - stats.Size()) } func printJSON(in []byte) (out []byte, err error) { var buf bytes.Buffer err = json.Indent(&buf, in, "", " ") return buf.Bytes(), err }