mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-08-22 12:04:54 -04:00
109 lines
2.6 KiB
Go
109 lines
2.6 KiB
Go
// Package accesslog writes proxy activity as JSON Lines.
|
|
package accesslog
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
accessLogFileMode os.FileMode = 0o600
|
|
|
|
// EventRequest identifies the response sent by the proxy to a client.
|
|
EventRequest = "request"
|
|
// EventUpstream identifies one HTTP exchange with an upstream service.
|
|
EventUpstream = "upstream"
|
|
)
|
|
|
|
type requestIDKey struct{}
|
|
|
|
// Entry is one proxy activity record.
|
|
type Entry struct {
|
|
Time time.Time `json:"time"`
|
|
Event string `json:"event"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
Method string `json:"method"`
|
|
Path string `json:"path,omitempty"`
|
|
URL string `json:"url,omitempty"`
|
|
StatusCode int `json:"status_code,omitempty"`
|
|
DurationMS int64 `json:"duration_ms"`
|
|
RemoteAddr string `json:"remote_addr,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// Logger appends complete JSON objects to a file, one per line.
|
|
type Logger struct {
|
|
mu sync.Mutex
|
|
file *os.File
|
|
encoder *json.Encoder
|
|
}
|
|
|
|
// Open opens path for append, creating it with owner-only permissions when needed.
|
|
func Open(path string) (*Logger, error) {
|
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, accessLogFileMode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening access log: %w", err)
|
|
}
|
|
|
|
return &Logger{
|
|
file: file,
|
|
encoder: json.NewEncoder(file),
|
|
}, nil
|
|
}
|
|
|
|
// Write appends an entry to the log.
|
|
func (l *Logger) Write(entry Entry) error {
|
|
if entry.Time.IsZero() {
|
|
entry.Time = time.Now().UTC()
|
|
}
|
|
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
if err := l.encoder.Encode(entry); err != nil {
|
|
return fmt.Errorf("writing access log: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close closes the log file after any active writer finishes.
|
|
func (l *Logger) Close() error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
if err := l.file.Close(); err != nil {
|
|
return fmt.Errorf("closing access log: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WithRequestID stores a proxy request ID in ctx.
|
|
func WithRequestID(ctx context.Context, requestID string) context.Context {
|
|
return context.WithValue(ctx, requestIDKey{}, requestID)
|
|
}
|
|
|
|
// RequestID returns the proxy request ID stored in ctx.
|
|
func RequestID(ctx context.Context) string {
|
|
requestID, _ := ctx.Value(requestIDKey{}).(string)
|
|
return requestID
|
|
}
|
|
|
|
// URLWithoutSecrets returns a URL without user information, query values, or fragments.
|
|
func URLWithoutSecrets(value *url.URL) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
|
|
clean := *value
|
|
clean.User = nil
|
|
clean.RawQuery = ""
|
|
clean.ForceQuery = false
|
|
clean.Fragment = ""
|
|
clean.RawFragment = ""
|
|
return clean.String()
|
|
}
|