Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
256
backend/internal/logging/request_logger_body_source.go
Normal file
256
backend/internal/logging/request_logger_body_source.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// FileBodySource stores large log sections as ordered temp-file parts.
|
||||
type FileBodySource struct {
|
||||
mu sync.Mutex
|
||||
dir string
|
||||
paths []string
|
||||
cleaned bool
|
||||
}
|
||||
|
||||
// NewFileBodySourceInDir creates a temp-backed source under baseDir.
|
||||
func NewFileBodySourceInDir(baseDir string, prefix string) (*FileBodySource, error) {
|
||||
prefix = sanitizeTempPrefix(prefix)
|
||||
baseDir = strings.TrimSpace(baseDir)
|
||||
if baseDir == "" {
|
||||
return nil, fmt.Errorf("base directory is required")
|
||||
}
|
||||
if errMkdir := os.MkdirAll(baseDir, 0755); errMkdir != nil {
|
||||
return nil, errMkdir
|
||||
}
|
||||
dir, errCreate := os.MkdirTemp(baseDir, "request-log-parts-"+prefix+"-*")
|
||||
if errCreate != nil {
|
||||
return nil, errCreate
|
||||
}
|
||||
return &FileBodySource{dir: dir}, nil
|
||||
}
|
||||
|
||||
func sanitizeTempPrefix(prefix string) string {
|
||||
prefix = strings.TrimSpace(prefix)
|
||||
if prefix == "" {
|
||||
return "log"
|
||||
}
|
||||
var builder strings.Builder
|
||||
for _, r := range prefix {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
builder.WriteRune(r)
|
||||
case r >= 'A' && r <= 'Z':
|
||||
builder.WriteRune(r)
|
||||
case r >= '0' && r <= '9':
|
||||
builder.WriteRune(r)
|
||||
case r == '-' || r == '_':
|
||||
builder.WriteRune(r)
|
||||
default:
|
||||
builder.WriteByte('-')
|
||||
}
|
||||
}
|
||||
out := strings.Trim(builder.String(), "-_")
|
||||
if out == "" {
|
||||
return "log"
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// CreatePart creates one ordered detail log part.
|
||||
func (s *FileBodySource) CreatePart(prefix string) (*os.File, error) {
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("file body source is nil")
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.cleaned {
|
||||
return nil, fmt.Errorf("file body source has been cleaned")
|
||||
}
|
||||
prefix = sanitizeTempPrefix(prefix)
|
||||
if errMkdir := os.MkdirAll(s.dir, 0755); errMkdir != nil {
|
||||
return nil, errMkdir
|
||||
}
|
||||
file, errCreate := os.CreateTemp(s.dir, prefix+"-*.tmp")
|
||||
if errCreate != nil {
|
||||
return nil, errCreate
|
||||
}
|
||||
s.paths = append(s.paths, file.Name())
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// AppendPart appends one complete ordered part to the source.
|
||||
func (s *FileBodySource) AppendPart(data []byte) error {
|
||||
data = bytes.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
file, errCreate := s.CreatePart("part")
|
||||
if errCreate != nil {
|
||||
return errCreate
|
||||
}
|
||||
writeErr := writeLogPart(file, data, false)
|
||||
if errClose := file.Close(); errClose != nil {
|
||||
if writeErr == nil {
|
||||
writeErr = errClose
|
||||
}
|
||||
}
|
||||
return writeErr
|
||||
}
|
||||
|
||||
// AppendBytes appends raw bytes to a single ordered part.
|
||||
func (s *FileBodySource) AppendBytes(data []byte) error {
|
||||
if s == nil {
|
||||
return fmt.Errorf("file body source is nil")
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.cleaned {
|
||||
return fmt.Errorf("file body source has been cleaned")
|
||||
}
|
||||
if errMkdir := os.MkdirAll(s.dir, 0755); errMkdir != nil {
|
||||
return errMkdir
|
||||
}
|
||||
|
||||
var file *os.File
|
||||
var errOpen error
|
||||
if len(s.paths) == 0 {
|
||||
file, errOpen = os.CreateTemp(s.dir, "part-*.tmp")
|
||||
if errOpen == nil {
|
||||
s.paths = append(s.paths, file.Name())
|
||||
}
|
||||
} else {
|
||||
file, errOpen = os.OpenFile(s.paths[len(s.paths)-1], os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
}
|
||||
if errOpen != nil {
|
||||
return errOpen
|
||||
}
|
||||
|
||||
_, writeErr := file.Write(data)
|
||||
if errClose := file.Close(); errClose != nil {
|
||||
if writeErr == nil {
|
||||
writeErr = errClose
|
||||
}
|
||||
}
|
||||
return writeErr
|
||||
}
|
||||
|
||||
// HasPayload reports whether any detail parts were recorded.
|
||||
func (s *FileBodySource) HasPayload() bool {
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return len(s.paths) > 0 && !s.cleaned
|
||||
}
|
||||
|
||||
// Paths returns a copy of the ordered part paths.
|
||||
func (s *FileBodySource) Paths() []string {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
out := make([]string, len(s.paths))
|
||||
copy(out, s.paths)
|
||||
return out
|
||||
}
|
||||
|
||||
// WriteTo merges all ordered parts into w.
|
||||
func (s *FileBodySource) WriteTo(w io.Writer) error {
|
||||
if s == nil || w == nil {
|
||||
return nil
|
||||
}
|
||||
paths := s.Paths()
|
||||
wrote := false
|
||||
for _, path := range paths {
|
||||
file, errOpen := os.Open(path)
|
||||
if errOpen != nil {
|
||||
if os.IsNotExist(errOpen) {
|
||||
continue
|
||||
}
|
||||
return errOpen
|
||||
}
|
||||
if wrote {
|
||||
if _, errWrite := io.WriteString(w, "\n"); errWrite != nil {
|
||||
if errClose := file.Close(); errClose != nil {
|
||||
log.WithError(errClose).Warn("failed to close log part file")
|
||||
}
|
||||
return errWrite
|
||||
}
|
||||
}
|
||||
_, errCopy := io.Copy(w, file)
|
||||
if errClose := file.Close(); errClose != nil {
|
||||
log.WithError(errClose).Warn("failed to close log part file")
|
||||
if errCopy == nil {
|
||||
errCopy = errClose
|
||||
}
|
||||
}
|
||||
if errCopy != nil {
|
||||
return errCopy
|
||||
}
|
||||
wrote = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bytes merges all ordered parts into memory.
|
||||
func (s *FileBodySource) Bytes() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if errWrite := s.WriteTo(&buf); errWrite != nil {
|
||||
return nil, errWrite
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Cleanup removes all temp detail parts and their directory.
|
||||
func (s *FileBodySource) Cleanup() error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.cleaned {
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
paths := make([]string, len(s.paths))
|
||||
copy(paths, s.paths)
|
||||
dir := s.dir
|
||||
s.paths = nil
|
||||
s.cleaned = true
|
||||
s.mu.Unlock()
|
||||
|
||||
var firstErr error
|
||||
for _, path := range paths {
|
||||
if errRemove := os.Remove(path); errRemove != nil && !os.IsNotExist(errRemove) && firstErr == nil {
|
||||
firstErr = errRemove
|
||||
}
|
||||
}
|
||||
if dir != "" {
|
||||
if errRemove := os.RemoveAll(dir); errRemove != nil && firstErr == nil {
|
||||
firstErr = errRemove
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func cleanupFileBodySources(sources ...*FileBodySource) {
|
||||
for _, source := range sources {
|
||||
if source == nil {
|
||||
continue
|
||||
}
|
||||
if errCleanup := source.Cleanup(); errCleanup != nil {
|
||||
log.WithError(errCleanup).Warn("failed to clean up log part files")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue