You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
100 lines
2.8 KiB
100 lines
2.8 KiB
package logging |
|
|
|
import ( |
|
"os" |
|
"path/filepath" |
|
"strings" |
|
"sync" |
|
"time" |
|
) |
|
|
|
// dailyWriter는 날짜별 로그 파일(<dir>/<prefix>-YYYY-MM-DD<ext>)에 기록하고, |
|
// 날짜가 바뀌면 새 파일로 전환하며 retention일보다 오래된 파일을 삭제한다. |
|
// 외부 의존성 없이 표준 라이브러리만 사용(저사양 타깃 정책 — 회전을 위해 lumberjack 등을 쓰지 않음). |
|
type dailyWriter struct { |
|
mu sync.Mutex |
|
dir string |
|
prefix string // 예: "app" |
|
ext string // 예: ".log" |
|
retention int // 보존 일수(오늘 포함) |
|
|
|
curDate string |
|
file *os.File |
|
} |
|
|
|
// newDailyWriter는 logFile(예: logs/app.log)을 기준으로 날짜별 writer를 만든다. |
|
// → logs/app-2026-06-11.log 형태로 기록. |
|
func newDailyWriter(logFile string, retention int) (*dailyWriter, error) { |
|
if retention <= 0 { |
|
retention = 7 |
|
} |
|
base := filepath.Base(logFile) |
|
ext := filepath.Ext(base) // ".log" (없으면 "") |
|
w := &dailyWriter{ |
|
dir: filepath.Dir(logFile), |
|
prefix: strings.TrimSuffix(base, ext), |
|
ext: ext, |
|
retention: retention, |
|
} |
|
if err := w.rotate(time.Now()); err != nil { |
|
return nil, err |
|
} |
|
return w, nil |
|
} |
|
|
|
func (w *dailyWriter) filename(date string) string { |
|
return filepath.Join(w.dir, w.prefix+"-"+date+w.ext) |
|
} |
|
|
|
func (w *dailyWriter) Write(p []byte) (int, error) { |
|
w.mu.Lock() |
|
defer w.mu.Unlock() |
|
if err := w.rotate(time.Now()); err != nil { |
|
return 0, err |
|
} |
|
return w.file.Write(p) |
|
} |
|
|
|
// rotate은 now 날짜의 파일을 연다. 이미 같은 날짜 파일이 열려 있으면 아무것도 안 한다. |
|
// (newDailyWriter에서 최초 1회, 이후 Write에서 lock 보유 상태로 호출) |
|
func (w *dailyWriter) rotate(now time.Time) error { |
|
date := now.Format("2006-01-02") |
|
if w.file != nil && date == w.curDate { |
|
return nil |
|
} |
|
if w.dir != "" && w.dir != "." { |
|
if err := os.MkdirAll(w.dir, 0o755); err != nil { |
|
return err |
|
} |
|
} |
|
f, err := os.OpenFile(w.filename(date), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) |
|
if err != nil { |
|
return err |
|
} |
|
if w.file != nil { |
|
_ = w.file.Close() |
|
} |
|
w.file = f |
|
w.curDate = date |
|
w.cleanup(now) |
|
return nil |
|
} |
|
|
|
// cleanup은 retention일치(오늘 포함)만 남기고 오래된 날짜 파일을 삭제한다. |
|
func (w *dailyWriter) cleanup(now time.Time) { |
|
cutoff := now.AddDate(0, 0, -(w.retention - 1)).Format("2006-01-02") |
|
matches, err := filepath.Glob(filepath.Join(w.dir, w.prefix+"-*"+w.ext)) |
|
if err != nil { |
|
return |
|
} |
|
for _, path := range matches { |
|
name := filepath.Base(path) |
|
ds := strings.TrimSuffix(strings.TrimPrefix(name, w.prefix+"-"), w.ext) |
|
if _, err := time.Parse("2006-01-02", ds); err != nil { |
|
continue // 날짜 형식이 아니면 우리 파일이 아님 — 건드리지 않음 |
|
} |
|
if ds < cutoff { // YYYY-MM-DD는 사전식 비교 = 날짜순 비교 |
|
_ = os.Remove(path) |
|
} |
|
} |
|
}
|
|
|