vault/helper/logging/logfile.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

151 lines
3.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package logging
import (
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/hashicorp/go-multierror"
)
var now = time.Now
type LogFile struct {
// Name of the log file
fileName string
// Path to the log file
logPath string
// duration between each file rotation operation
duration time.Duration
// lastCreated represents the creation time of the latest log
lastCreated time.Time
// fileInfo is the pointer to the current file being written to
fileInfo *os.File
// maxBytes is the maximum number of desired bytes for a log file
maxBytes int
// bytesWritten is the number of bytes written in the current log file
bytesWritten int64
// Max rotated files to keep before removing them.
maxArchivedFiles int
// acquire is the mutex utilized to ensure we have no concurrency issues
acquire sync.Mutex
}
// Write is used to implement io.Writer
func (l *LogFile) Write(b []byte) (n int, err error) {
l.acquire.Lock()
defer l.acquire.Unlock()
// Create a new file if we have no file to write to
if l.fileInfo == nil {
if err := l.openNew(); err != nil {
return 0, err
}
} else if err := l.rotate(); err != nil { // Check for the last contact and rotate if necessary
return 0, err
}
bytesWritten, err := l.fileInfo.Write(b)
if bytesWritten > 0 {
l.bytesWritten += int64(bytesWritten)
}
return bytesWritten, err
}
func (l *LogFile) fileNamePattern() string {
// Extract the file extension
fileExt := filepath.Ext(l.fileName)
// If we have no file extension we append .log
if fileExt == "" {
fileExt = ".log"
}
// Remove the file extension from the filename
return strings.TrimSuffix(l.fileName, fileExt) + "-%s" + fileExt
}
func (l *LogFile) openNew() error {
fileNamePattern := l.fileNamePattern()
createTime := now()
newFileName := fmt.Sprintf(fileNamePattern, strconv.FormatInt(createTime.UnixNano(), 10))
newFilePath := filepath.Join(l.logPath, newFileName)
// Try creating a file. We truncate the file because we are the only authority to write the logs
filePointer, err := os.OpenFile(newFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o640)
if err != nil {
return err
}
// New file, new 'bytes' tracker, new creation time :) :)
l.fileInfo = filePointer
l.lastCreated = createTime
l.bytesWritten = 0
return nil
}
func (l *LogFile) rotate() error {
// Get the time from the last point of contact
timeElapsed := time.Since(l.lastCreated)
// Rotate if we hit the byte file limit or the time limit
if (l.bytesWritten >= int64(l.maxBytes) && (l.maxBytes > 0)) || timeElapsed >= l.duration {
if err := l.fileInfo.Close(); err != nil {
return err
}
if err := l.pruneFiles(); err != nil {
return err
}
return l.openNew()
}
return nil
}
func (l *LogFile) pruneFiles() error {
if l.maxArchivedFiles == 0 {
return nil
}
pattern := filepath.Join(l.logPath, fmt.Sprintf(l.fileNamePattern(), "*"))
matches, err := filepath.Glob(pattern)
if err != nil {
return err
}
switch {
case l.maxArchivedFiles < 0:
return removeFiles(matches)
case len(matches) < l.maxArchivedFiles:
return nil
}
sort.Strings(matches)
last := len(matches) - l.maxArchivedFiles
return removeFiles(matches[:last])
}
func removeFiles(files []string) (err error) {
for _, file := range files {
if fileError := os.Remove(file); fileError != nil {
err = multierror.Append(err, fmt.Errorf("error removing file %s: %v", file, fileError))
}
}
return err
}