main.go 4.33 KB
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\n", req.RemoteAddr, 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())
}