main.go 4.56 KB
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)
}

// CallerFilenameAndLineNumber ...
func CallerFilenameAndLineNumber() (string, int) {
	_, path, line, _ := runtime.Caller(2)
	file := filepath.Base(path)
	return file, line
}

// PrintTrace ...
func (l *Logger) PrintTrace(format string, v ...interface{}) {
	file, line := CallerFilenameAndLineNumber()

	msg := fmt.Sprintf(format, v...)
	fmt.Printf("%s: %s %d: %s\n", time.Now().Format(dateTimeFormat), file, line, msg)
}

// Trace ...
func (l *Logger) Trace(format string, v ...interface{}) {
	if l.outputFile == nil {
		return
	}

	l.mu.Lock()
	defer l.mu.Unlock()

	file, line := CallerFilenameAndLineNumber()

	msg := fmt.Sprintf(format, v...)
	s := fmt.Sprintf("%s: %s %d: %s\n", time.Now().Format(dateTimeFormat), file, line, 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
}