-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
MacBookAirM2
committed
May 9, 2023
1 parent
1771e84
commit 9acf52b
Showing
3 changed files
with
290 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
package zutils | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"sync" | ||
"time" | ||
) | ||
|
||
const ( | ||
sizeMiB = 1024 * 1024 | ||
defMaxAge = 31 | ||
defMaxSize = 64 //MiB | ||
) | ||
|
||
var _ io.WriteCloser = (*Writer)(nil) | ||
|
||
type Writer struct { | ||
maxAge int // 最大保留天数 | ||
maxSize int64 // 单个日志最大容量 默认 64MB | ||
size int64 // 累计大小 | ||
fpath string // 文件目录 完整路径 fpath=fdir+fname+fsuffix | ||
fdir string // | ||
fname string // 文件名 | ||
fsuffix string // 文件后缀名 默认 .log | ||
created time.Time // 文件创建日期 | ||
creates []byte // 文件创建日期 | ||
cons bool // 标准输出 默认 false | ||
file *os.File | ||
bw *bufio.Writer | ||
mu sync.Mutex | ||
} | ||
|
||
func New(path string) *Writer { | ||
w := &Writer{ | ||
fpath: path, //dir1/dir2/app.log | ||
mu: sync.Mutex{}, | ||
} | ||
w.fdir = filepath.Dir(w.fpath) //dir1/dir2 | ||
w.fsuffix = filepath.Ext(w.fpath) //.log | ||
w.fname = strings.TrimSuffix(filepath.Base(w.fpath), w.fsuffix) //app | ||
if w.fsuffix == "" { | ||
w.fsuffix = ".log" | ||
} | ||
w.maxSize = sizeMiB * defMaxSize | ||
w.maxAge = defMaxAge | ||
os.MkdirAll(filepath.Dir(w.fpath), 0755) | ||
go w.daemon() | ||
return w | ||
} | ||
func (w *Writer) daemon() { | ||
for range time.NewTicker(time.Second * 5).C { | ||
w.flush() | ||
} | ||
} | ||
|
||
// SetMaxAge 最大保留天数 | ||
func (w *Writer) SetMaxAge(ma int) { | ||
w.mu.Lock() | ||
w.maxAge = ma | ||
w.mu.Unlock() | ||
} | ||
|
||
// SetMaxSize 单个日志最大容量 | ||
func (w *Writer) SetMaxSize(ms int64) { | ||
if ms < 1 { | ||
return | ||
} | ||
w.mu.Lock() | ||
w.maxSize = ms | ||
w.mu.Unlock() | ||
} | ||
|
||
// SetCons 同时输出控制台 | ||
func (w *Writer) SetCons(b bool) { | ||
w.mu.Lock() | ||
w.cons = b | ||
w.mu.Unlock() | ||
} | ||
|
||
func (w *Writer) Write(p []byte) (n int, err error) { | ||
w.mu.Lock() | ||
defer w.mu.Unlock() | ||
if w.cons { | ||
os.Stderr.Write(p) | ||
} | ||
if w.file == nil { | ||
if err := w.rotate(); err != nil { | ||
os.Stderr.Write(p) | ||
return 0, err | ||
} | ||
} | ||
|
||
t := time.Now() | ||
var b []byte | ||
year, month, day := t.Date() | ||
b = appendInt(b, year, 4) | ||
b = append(b, '-') | ||
b = appendInt(b, int(month), 2) | ||
b = append(b, '-') | ||
b = appendInt(b, day, 2) | ||
|
||
// 按天切割 | ||
if !bytes.Equal(w.creates[:10], b) { //2023-04-05 | ||
go w.delete() // 每天检测一次旧文件 | ||
if err := w.rotate(); err != nil { | ||
return 0, err | ||
} | ||
} | ||
// 按大小切割 | ||
if w.size+int64(len(p)) >= w.maxSize { | ||
if err := w.rotate(); err != nil { | ||
return 0, err | ||
} | ||
} | ||
// n, err = w.file.Write(p) | ||
n, err = w.bw.Write(p) | ||
w.size += int64(n) | ||
if err != nil { | ||
return n, err | ||
} | ||
return | ||
} | ||
|
||
// rotate 切割文件 | ||
func (w *Writer) rotate() error { | ||
now := time.Now() | ||
if w.file != nil { | ||
w.bw.Flush() | ||
w.file.Sync() | ||
w.file.Close() | ||
// 保存 | ||
fbak := w.fname + w.time2name(w.created) + w.fsuffix | ||
os.Rename(w.fpath, filepath.Join(w.fdir, fbak)) | ||
w.size = 0 | ||
} | ||
finfo, err := os.Stat(w.fpath) | ||
w.created = now | ||
if err == nil { | ||
w.size = finfo.Size() | ||
w.created = finfo.ModTime() | ||
} | ||
w.creates = w.created.AppendFormat(nil, time.RFC3339) | ||
fout, err := os.OpenFile(w.fpath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) | ||
if err != nil { | ||
return err | ||
} | ||
w.file = fout | ||
w.bw = bufio.NewWriter(w.file) | ||
return nil | ||
} | ||
|
||
// 删除旧日志 | ||
func (w *Writer) delete() { | ||
if w.maxAge <= 0 { | ||
return | ||
} | ||
dir := filepath.Dir(w.fpath) | ||
fakeNow := time.Now().AddDate(0, 0, -w.maxAge) | ||
dirs, err := os.ReadDir(dir) | ||
if err != nil { | ||
return | ||
} | ||
for _, path := range dirs { | ||
name := path.Name() | ||
if path.IsDir() { | ||
continue | ||
} | ||
t, err := w.name2time(name) | ||
// 只删除满足格式的文件 | ||
if err == nil && t.Before(fakeNow) { | ||
os.Remove(filepath.Join(dir, name)) | ||
} | ||
} | ||
} | ||
func (w *Writer) name2time(name string) (time.Time, error) { | ||
name = strings.TrimPrefix(name, filepath.Base(w.fname)) | ||
name = strings.TrimSuffix(name, w.fsuffix) | ||
return time.Parse(".2006-01-02-150405", name) | ||
} | ||
func (w *Writer) time2name(t time.Time) string { | ||
return t.Format(".2006-01-02-150405") | ||
} | ||
|
||
func (w *Writer) Close() error { | ||
w.flush() | ||
return w.close() | ||
} | ||
|
||
// close closes the file if it is open. | ||
func (w *Writer) close() error { | ||
w.mu.Lock() | ||
defer w.mu.Unlock() | ||
if w.file == nil { | ||
return nil | ||
} | ||
w.file.Sync() | ||
err := w.file.Close() | ||
w.file = nil | ||
return err | ||
} | ||
|
||
func (w *Writer) flush() error { | ||
w.mu.Lock() | ||
defer w.mu.Unlock() | ||
if w.bw == nil { | ||
return nil | ||
} | ||
return w.bw.Flush() | ||
} | ||
|
||
// appendInt appends the decimal form of x to b and returns the result. | ||
// If the decimal form (excluding sign) is shorter than width, the result is padded with leading 0's. | ||
// Duplicates functionality in strconv, but avoids dependency. | ||
func appendInt(b []byte, x int, width int) []byte { | ||
u := uint(x) | ||
if x < 0 { | ||
b = append(b, '-') | ||
u = uint(-x) | ||
} | ||
|
||
// 2-digit and 4-digit fields are the most common in time formats. | ||
utod := func(u uint) byte { return '0' + byte(u) } | ||
switch { | ||
case width == 2 && u < 1e2: | ||
return append(b, utod(u/1e1), utod(u%1e1)) | ||
case width == 4 && u < 1e4: | ||
return append(b, utod(u/1e3), utod(u/1e2%1e1), utod(u/1e1%1e1), utod(u%1e1)) | ||
} | ||
|
||
// Compute the number of decimal digits. | ||
var n int | ||
if u == 0 { | ||
n = 1 | ||
} | ||
for u2 := u; u2 > 0; u2 /= 10 { | ||
n++ | ||
} | ||
|
||
// Add 0-padding. | ||
for pad := width - n; pad > 0; pad-- { | ||
b = append(b, '0') | ||
} | ||
|
||
// Ensure capacity. | ||
if len(b)+n <= cap(b) { | ||
b = b[:len(b)+n] | ||
} else { | ||
b = append(b, make([]byte, n)...) | ||
} | ||
|
||
// Assemble decimal in reverse order. | ||
i := len(b) - 1 | ||
for u >= 10 && i > 0 { | ||
q := u / 10 | ||
b[i] = utod(u - q*10) | ||
u = q | ||
i-- | ||
} | ||
b[i] = utod(u) | ||
return b | ||
} |