blob: bf7e90687ec60cee12197822aacae43ab85d5c5f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package log
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
)
var pathReplacer *strings.Replacer
func EscapPath(msg string) string {
return pathReplacer.Replace(msg)
}
func removeFile(path string) error {
fileInfo, err := os.Stat(path)
if err != nil {
return err
}
if fileInfo.IsDir() {
return nil
}
err = os.Remove(path)
if err != nil {
return err
}
return nil
}
func removeExceededFiles(path string, baseFileName string,
maxKeptCount int, rotateStage string) {
if maxKeptCount < 0 {
return
}
fileList := make([]string, 0, 2*maxKeptCount)
var pat string
if rotateStage == "rollover" {
//rotated file, svc.log.20060102150405000
pat = fmt.Sprintf(`%s\.[0-9]{1,17}$`, baseFileName)
} else if rotateStage == "backup" {
//backup compressed file, svc.log.20060102150405000.zip
pat = fmt.Sprintf(`%s\.[0-9]{17}\.zip$`, baseFileName)
} else {
return
}
fileList, err := FilterFileList(path, pat, 0777)
if err != nil {
Error("filepath.Walk() "+EscapPath(path)+" failed", err)
return
}
sort.Strings(fileList)
if len(fileList) <= maxKeptCount {
return
}
//remove exceeded files, keep file count below maxBackupCount
for len(fileList) > maxKeptCount {
filePath := fileList[0]
Warn("remove " + EscapPath(filePath))
err := removeFile(filePath)
if err != nil {
Error("remove "+EscapPath(filePath)+" failed", err)
break
}
//remove the first element of a list
fileList = append(fileList[:0], fileList[1:]...)
}
}
//filePath: file full path, like ${_APP_LOG_DIR}/svc.log.1
//fileBaseName: rollover file base name, like svc.log
//replaceTimestamp: whether or not to replace the num. of a rolled file
func compressFile(filePath, fileBaseName string, replaceTimestamp bool) error {
ifp, err := os.Open(filePath)
if err != nil {
return err
}
defer ifp.Close()
var zipFilePath string
if replaceTimestamp {
//svc.log.1 -> svc.log.20060102150405000.zip
zipFileBase := fileBaseName + "." + getTimeStamp() + "." + "zip"
zipFilePath = filepath.Dir(filePath) + "/" + zipFileBase
} else {
zipFilePath = filePath + ".zip"
}
zipFile, err := os.OpenFile(zipFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0440)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
ofp, err := zipWriter.Create(filepath.Base(filePath))
if err != nil {
return err
}
_, err = io.Copy(ofp, ifp)
if err != nil {
return err
}
return nil
}
func shouldRollover(fPath string, MaxFileSize int) bool {
if MaxFileSize <= 0 {
return false
}
fileInfo, err := os.Stat(fPath)
if err != nil {
Error("state "+EscapPath(fPath)+" failed", err)
return false
}
if fileInfo.Size() > int64(MaxFileSize*1024*1024) {
return true
}
return false
}
func doRollover(fPath string, MaxFileSize int, MaxBackupCount int) {
if !shouldRollover(fPath, MaxFileSize) {
return
}
timeStamp := getTimeStamp()
//absolute path
rotateFile := fPath + "." + timeStamp
err := CopyFile(fPath, rotateFile)
if err != nil {
Error("copy "+EscapPath(fPath)+" failed", err)
}
//truncate the file
f, err := os.OpenFile(fPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
Error("truncate "+EscapPath(fPath)+" failed", err)
return
}
f.Close()
//remove exceeded rotate files
removeExceededFiles(filepath.Dir(fPath), filepath.Base(fPath), MaxBackupCount, "rollover")
}
func doBackup(fPath string, MaxBackupCount int) {
if MaxBackupCount <= 0 {
return
}
pat := fmt.Sprintf(`%s\.[0-9]{1,17}$`, filepath.Base(fPath))
rotateFileList, err := FilterFileList(filepath.Dir(fPath), pat, 0777)
if err != nil {
Error("walk"+EscapPath(fPath)+" failed", err)
return
}
for _, file := range rotateFileList {
var err error
p := fmt.Sprintf(`%s\.[0-9]{17}$`, filepath.Base(fPath))
if ret, _ := regexp.MatchString(p, file); ret {
//svc.log.20060102150405000, not replace Timestamp
err = compressFile(file, filepath.Base(fPath), false)
} else {
//svc.log.1, replace Timestamp
err = compressFile(file, filepath.Base(fPath), true)
}
if err != nil {
Error("compress"+EscapPath(file)+" failed", err)
continue
}
err = removeFile(file)
if err != nil {
Error("remove"+EscapPath(file)+" failed", err)
}
}
//remove exceeded backup files
removeExceededFiles(filepath.Dir(fPath), filepath.Base(fPath), MaxBackupCount, "backup")
}
func LogRotateFile(file string, MaxFileSize int, MaxBackupCount int) {
defer func() {
if e := recover(); e != nil {
Errorf(nil, "LogRotate file %s catch an exception, err: %v.", EscapPath(file), e)
}
}()
doRollover(file, MaxFileSize, MaxBackupCount)
doBackup(file, MaxBackupCount)
}
//path: where log files need rollover
//MaxFileSize: MaxSize of a file before rotate. By M Bytes.
//MaxBackupCount: Max counts to keep of a log's backup files.
func LogRotate(path string, MaxFileSize int, MaxBackupCount int) {
//filter .log .trace files
defer func() {
if e := recover(); e != nil {
Errorf(nil, "LogRotate catch an exception, err: %v.", e)
}
}()
pat := `.(\.log|\.trace|\.out)$`
fileList, err := FilterFileList(path, pat, 0200)
if err != nil {
Error("filepath.Walk() "+EscapPath(path)+" failed", err)
return
}
for _, file := range fileList {
LogRotateFile(file, MaxFileSize, MaxBackupCount)
}
}
func isSkip(f os.FileInfo, permits os.FileMode) bool {
//dir or permission deny
return f.IsDir() || (f.Mode()&permits == 0000)
}
//path : where the file will be filtered
//pat : regexp pattern to filter the matched file
//permit : check the file whether match any of the permits or not
func FilterFileList(path, pat string, permits os.FileMode) ([]string, error) {
capacity := 10
//initialize a fileName slice, len=0, cap=10
fileList := make([]string, 0, capacity)
err := filepath.Walk(path,
func(pathName string, f os.FileInfo, e error) error {
if f == nil {
return e
}
if isSkip(f, permits) {
return nil
}
if pat != "" {
ret, _ := regexp.MatchString(pat, f.Name())
if !ret {
return nil
}
}
fileList = append(fileList, pathName)
return nil
})
return fileList, err
}
func getTimeStamp() string {
now := time.Now().Format("2006.01.02.15.04.05.000")
timeSlot := strings.Replace(now, ".", "", -1)
return timeSlot
}
func CopyFile(srcFile, destFile string) error {
file, err := os.Open(srcFile)
if err != nil {
return err
}
defer file.Close()
dest, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer dest.Close()
_, err = io.Copy(dest, file)
return err
}
func init() {
var s []string
if e := os.Getenv("INSTALL_ROOT"); len(e) > 0 {
s = append(s, e, "INSTALL_ROOT")
}
if e := os.Getenv("SSL_ROOT"); len(e) > 0 {
s = append(s, e, "SSL_ROOT")
}
if e := os.Getenv("CIPHER_ROOT"); len(e) > 0 {
s = append(s, e, "CIPHER_ROOT")
}
if e := os.Getenv("APP_ROOT"); len(e) > 0 {
s = append(s, e, "APP_ROOT")
}
if e := os.Getenv("_APP_LOG_DIR"); len(e) > 0 {
s = append(s, e, "_APP_LOG_DIR")
}
if e := os.Getenv("_APP_SHARE_DIR"); len(e) > 0 {
s = append(s, e, "_APP_SHARE_DIR")
}
if e := os.Getenv("_APP_TMP_DIR"); len(e) > 0 {
s = append(s, e, "_APP_TMP_DIR")
}
pathReplacer = strings.NewReplacer(s...)
}