mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* 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>
151 lines
3.5 KiB
Go
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
|
|
}
|