234 lines
5.8 KiB
Go
Executable File
234 lines
5.8 KiB
Go
Executable File
package files
|
|
|
|
import (
|
|
"encoding/json"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"bufio"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strconv"
|
|
|
|
"path/filepath"
|
|
|
|
"github.com/snail007/mini-logger"
|
|
)
|
|
|
|
const (
|
|
T_JSON = iota
|
|
T_TEXT
|
|
)
|
|
|
|
type FileConfig struct {
|
|
IsRotate bool
|
|
MaxBytes int64
|
|
MaxCount int
|
|
LogPath string
|
|
FileNameSet map[string]uint8
|
|
IsCompress bool
|
|
Type int
|
|
Format string
|
|
}
|
|
type bufWriter struct {
|
|
file *os.File
|
|
writer *bufio.Writer
|
|
}
|
|
type FileWriter struct {
|
|
filePtrMap map[string]*bufWriter
|
|
c FileConfig
|
|
}
|
|
|
|
func New(config FileConfig) *FileWriter {
|
|
return &FileWriter{
|
|
c: config,
|
|
filePtrMap: map[string]*bufWriter{},
|
|
}
|
|
}
|
|
|
|
func NewDefault() *FileWriter {
|
|
return &FileWriter{
|
|
filePtrMap: map[string]*bufWriter{},
|
|
c: GetDefaultFileConfig(),
|
|
}
|
|
}
|
|
|
|
//GetDefaultFileConfig config for new FileWriter
|
|
//IsRotate : whether to rotate log file.
|
|
//MaxBytes : when RotateType is file.T_SIZE ,this need to be set.
|
|
//MaxCount : how many log files can be remain.
|
|
//LogPath : the folder which store log files,must be exists.
|
|
//FileNameSet : key is filename of log file,no extesion.value is levels
|
|
//IsCompress : whether to compress rotate log file.
|
|
//Type:output format,should be files.T_JSON or files.T_TEXT
|
|
//Format:when Type is files.T_TEXT,this can setting output text format,
|
|
// default is : [{level}] [{date} {time}.{mili}] {text} {fields}
|
|
func GetDefaultFileConfig() FileConfig {
|
|
return FileConfig{
|
|
IsRotate: true,
|
|
MaxBytes: 100 * 1024 * 1024,
|
|
MaxCount: 10,
|
|
LogPath: "log",
|
|
FileNameSet: map[string]uint8{
|
|
"info": logger.InfoLevel,
|
|
"error": logger.WarnLevel | logger.ErrorLevel | logger.FatalLevel,
|
|
},
|
|
IsCompress: true,
|
|
Type: T_JSON,
|
|
Format: "[{level}] [{date} {time}.{mili}] {text} {fields}",
|
|
}
|
|
}
|
|
func getFilePath(logPath, filename string) string {
|
|
|
|
return path.Join(logPath, filename+".log")
|
|
|
|
}
|
|
func (w *FileWriter) Init() (err error) {
|
|
if _, err := os.Stat(w.c.LogPath); os.IsNotExist(err) {
|
|
os.Mkdir(w.c.LogPath, 0700)
|
|
}
|
|
for filename := range w.c.FileNameSet {
|
|
f, err := os.OpenFile(getFilePath(w.c.LogPath, filename), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bf := bufio.NewWriter(f)
|
|
w.filePtrMap[filename] = &bufWriter{
|
|
file: f,
|
|
writer: bf,
|
|
}
|
|
}
|
|
return
|
|
}
|
|
func (w *FileWriter) Write(e logger.Entry) {
|
|
for filename, levels := range w.c.FileNameSet {
|
|
if e.Level&levels == e.Level {
|
|
date := time.Unix(e.Timestamp, 0).Format("2006/01/02")
|
|
time := time.Unix(e.Timestamp, 0).Format("15:04:05")
|
|
var c string
|
|
if w.c.Type == T_TEXT {
|
|
c = strings.Replace(w.c.Format, "{level}", fmt.Sprintf("%-5s", e.LevelString), -1)
|
|
c = strings.Replace(c, "{date}", date, -1)
|
|
c = strings.Replace(c, "{time}", time, -1)
|
|
c = strings.Replace(c, "{fields}", e.FieldsString, -1)
|
|
c = strings.Replace(c, "{mili}", fmt.Sprintf("%03d", e.Milliseconds), -1)
|
|
c = strings.Replace(c, "{text}", e.Content, -1)
|
|
} else if w.c.Type == T_JSON {
|
|
e.LevelString = strings.TrimRight(e.LevelString, " ")
|
|
e.FieldsString = ""
|
|
v, _ := json.Marshal(e)
|
|
c = string(v)
|
|
} else {
|
|
return
|
|
}
|
|
w.filePtrMap[filename].writer.WriteString(fmt.Sprintln(c))
|
|
w.filePtrMap[filename].writer.Flush()
|
|
if w.c.IsRotate {
|
|
var filepath = getFilePath(w.c.LogPath, filename)
|
|
if stat, err := os.Stat(filepath); err == nil {
|
|
if stat.Size() > w.c.MaxBytes {
|
|
w.filePtrMap[filename].file.Close()
|
|
var i = findI(w, filename)
|
|
var p = ""
|
|
if w.c.IsCompress {
|
|
p = path.Join(w.c.LogPath, fmt.Sprintf("%s.%d", filename, i)+".log.gz")
|
|
} else {
|
|
p = path.Join(w.c.LogPath, fmt.Sprintf("%s.%d", filename, i)+".log")
|
|
}
|
|
if ok, err := exists(p); (err == nil && !ok) || err != nil {
|
|
if err != nil {
|
|
fmt.Printf("ERROR fail to get stat of log file,%s", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
for k := i - w.c.MaxCount; k >= 0; k-- {
|
|
p1 := getFilePath(w.c.LogPath, fmt.Sprintf("%s.%d", filename, k))
|
|
p2 := p1 + ".gz"
|
|
p1e, _ := exists(p1)
|
|
p2e, _ := exists(p2)
|
|
os.Remove(p1)
|
|
os.Remove(p2)
|
|
if !p1e && !p2e {
|
|
break
|
|
}
|
|
}
|
|
|
|
newfilepath := getFilePath(w.c.LogPath, fmt.Sprintf("%s.%d", filename, i))
|
|
e := os.Rename(filepath, newfilepath)
|
|
if e != nil {
|
|
fmt.Printf("ERROR fail to Rename log file,%s", e)
|
|
return
|
|
}
|
|
if w.c.IsCompress {
|
|
go func() {
|
|
f, _ := os.OpenFile(newfilepath+".gz", os.O_CREATE|os.O_WRONLY, 0600)
|
|
rf, _ := os.OpenFile(newfilepath, os.O_RDONLY, 0600)
|
|
gz, err := gzip.NewWriterLevel(f, gzip.BestCompression)
|
|
b, _ := ioutil.ReadAll(rf)
|
|
if err == nil {
|
|
gz.Write(b)
|
|
gz.Flush()
|
|
gz.Close()
|
|
f.Close()
|
|
rf.Close()
|
|
os.Remove(newfilepath)
|
|
} else {
|
|
fmt.Printf("ERROR fail to compress log file,%s", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
f, e := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
|
if e != nil {
|
|
fmt.Println("ERROR fail to create log file")
|
|
return
|
|
}
|
|
bf := bufio.NewWriter(f)
|
|
w.filePtrMap[filename] = &bufWriter{
|
|
file: f,
|
|
writer: bf,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
func exists(path string) (bool, error) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return true, err
|
|
}
|
|
func findI(w *FileWriter, filename string) int {
|
|
subfix := ""
|
|
if w.c.IsCompress {
|
|
subfix = ".gz"
|
|
}
|
|
p := w.c.LogPath + "/" + filename + ".*.log" + subfix
|
|
files, _ := filepath.Glob(p)
|
|
l := len(files)
|
|
if l == 0 {
|
|
return 0
|
|
}
|
|
re := regexp.MustCompile(filename + "\\.(\\d+)\\.log" + subfix)
|
|
matched := re.FindStringSubmatch(files[l-1])
|
|
if len(matched) == 2 {
|
|
i, _ := strconv.Atoi(matched[1])
|
|
return i + 1
|
|
}
|
|
return 0
|
|
}
|