Modified JSON tags in the Notify struct of ACL config and the ConfigBase and Retention structs in access log config to include 'omitempty'
Access Logging
Provides HTTP access logging with file rotation, log filtering, and multiple output formats for request and ACL event logging.
Overview
The accesslog package captures HTTP request/response information and writes it to files or stdout. It includes configurable log formats, filtering rules, and automatic log rotation with retention policies.
Primary Consumers
internal/route- Middleware for logging proxied requestsinternal/acl- ACL decision logginginternal/api- Request audit trails
Non-goals
- Does not provide log parsing or analysis
- Does not implement log aggregation across services
- Does not provide log shipping to external systems
- Does not implement access control (use
internal/acl)
Stability
Internal package. Public interfaces are stable.
Public API
Exported Types
AccessLogger Interface
type AccessLogger interface {
// Log records an HTTP request and response
Log(req *http.Request, res *http.Response)
// LogError logs a request with an error status code
LogError(req *http.Request, err error)
// LogACL logs an ACL block/allow event
LogACL(info *maxmind.IPInfo, blocked bool)
// Config returns the logger configuration
Config() *Config
// Flush forces any buffered log data to be written
Flush()
// Close closes the logger and releases resources
Close() error
}
Main interface for logging HTTP requests and ACL events.
Writer Interface
type Writer interface {
io.WriteCloser
ShouldBeBuffered() bool
Name() string // file name or path
}
Output destination interface.
Format Type
type Format string
const (
FormatCommon Format = "common"
FormatCombined Format = "combined"
FormatJSON Format = "json"
)
Log format constants.
Configuration Types
RequestLoggerConfig
type RequestLoggerConfig struct {
ConfigBase
Format Format `json:"format" validate:"oneof=common combined json"`
Filters Filters `json:"filters"`
Fields Fields `json:"fields"`
}
Configuration for request/response logging.
ACLLoggerConfig
type ACLLoggerConfig struct {
ConfigBase
LogAllowed bool `json:"log_allowed"`
}
Configuration for ACL event logging.
ConfigBase
type ConfigBase struct {
B int `json:"buffer_size"` // Deprecated: buffer size is adjusted dynamically
Path string `json:"path"`
Stdout bool `json:"stdout"`
Retention *Retention `json:"retention" aliases:"keep"`
RotateInterval time.Duration `json:"rotate_interval,omitempty" swaggertype:"primitive,integer"`
}
Common configuration for all loggers.
Filters
type Filters struct {
StatusCodes LogFilter[*StatusCodeRange] `json:"status_codes"`
Method LogFilter[HTTPMethod] `json:"method"`
Host LogFilter[Host] `json:"host"`
Headers LogFilter[*HTTPHeader] `json:"headers"`
CIDR LogFilter[*CIDR] `json:"cidr"`
}
Filtering rules for what to log.
Fields
type Fields struct {
Headers FieldConfig `json:"headers" aliases:"header"`
Query FieldConfig `json:"query" aliases:"queries"`
Cookies FieldConfig `json:"cookies" aliases:"cookie"`
}
Field configuration for what data to include.
Exported Functions
Constructor
func NewAccessLogger(parent task.Parent, cfg AnyConfig) (AccessLogger, error)
func NewMockAccessLogger(parent task.Parent, cfg *RequestLoggerConfig) AccessLogger
func NewAccessLoggerWithIO(parent task.Parent, writer Writer, anyCfg AnyConfig) AccessLogger
Create access loggers from configurations.
Default Configurations
func DefaultRequestLoggerConfig() *RequestLoggerConfig
func DefaultACLLoggerConfig() *ACLLoggerConfig
Returns default configurations.
Architecture
Core Components
graph TD
subgraph Request Flow
Req[HTTP Request] -->|Passed to| Log[AccessLogger.Log]
Res[HTTP Response] -->|Passed to| Log
Log -->|Formats| Fmt[RequestFormatter]
Fmt -->|Writes to| Writer[BufferedWriter]
Writer -->|Outputs to| Output[File/Stdout]
end
subgraph Background Tasks
Rotator[Rotation Task] -->|Triggers| Rotate[ShouldRotate]
Adjuster[Buffer Adjuster] -->|Adjusts| Buffer[Buffer Size]
end
| Component | Responsibility |
|---|---|
AccessLogger |
Main logging interface |
RequestFormatter |
Formats request/response logs |
ACLFormatter |
Formats ACL decision logs |
Writer |
Output destination (file/stdout) |
BufferedWriter |
Efficient I/O with dynamic buffering |
Log Flow
sequenceDiagram
participant Request
participant AccessLogger
participant Formatter
participant BufferedWriter
participant File
Request->>AccessLogger: Log(req, res)
AccessLogger->>AccessLogger: shouldLog() filter check
alt Passes filters
AccessLogger->>Formatter: AppendRequestLog(line, req, res)
Formatter->>AccessLogger: Formatted line
AccessLogger->>BufferedWriter: Write(line)
BufferedWriter->>BufferedWriter: Buffer if needed
BufferedWriter->>File: Flush when full/rotating
else Fails filters
AccessLogger->>Request: Skip logging
end
Buffer Management
The logger dynamically adjusts buffer size based on write throughput:
| Parameter | Value |
|---|---|
| Initial Buffer Size | 4 KB |
| Maximum Buffer Size | 8 MB |
| Adjustment Interval | 5 seconds |
Buffer size adjustment formula:
newBufSize = origBufSize +/- step
step = max(|wps - origBufSize|/2, wps/2)
Rotation Logic
stateDiagram-v2
[*] --> Logging
Logging --> Logging: Normal writes
Logging --> Rotating: Interval reached
Rotating --> Logging: New file created
Rotating --> [*]: Logger closed
Rotation checks:
- Is rotation enabled (supportRotate + valid retention)?
- Is retention period valid?
- Create new file with timestamp suffix
- Delete old files beyond retention
Log Formats
Common Format
127.0.0.1 - - [10/Jan/2024:12:00:00 +0000] "GET /api HTTP/1.1" 200 1234
Combined Format
127.0.0.1 - - [10/Jan/2024:12:00:00 +0000] "GET /api HTTP/1.1" 200 1234 "https://example.com" "Mozilla/5.0"
JSON Format
{
"level": "info",
"time": "10/Jan/2024:12:00:00 +0000",
"ip": "127.0.0.1",
"method": "GET",
"scheme": "http",
"host": "example.com",
"path": "/api",
"protocol": "HTTP/1.1",
"status": 200,
"type": "application/json",
"size": 1234,
"referer": "https://example.com",
"useragent": "Mozilla/5.0"
}
Configuration Surface
YAML Configuration
access_log:
path: /var/log/godoxy/access.log
stdout: false
rotate_interval: 1h
retention:
days: 30
format: combined
filters:
status_codes:
keep:
- min: 200
max: 599
method:
keep:
- GET
- POST
headers:
keep:
- name: Authorization
Configuration Fields
| Field | Type | Default | Description |
|---|---|---|---|
path |
string | - | Log file path |
stdout |
bool | false | Also log to stdout |
rotate_interval |
duration | 1h | Rotation interval |
retention.days |
int | 30 | Days to retain logs |
format |
string | combined | Log format |
filters.status_codes |
range[] | all | Status code filter |
filters.method |
string[] | all | HTTP method filter |
filters.cidr |
CIDR[] | none | IP range filter |
Reloading
Configuration is fixed at construction time. Create a new logger to apply changes.
Dependency and Integration Map
Internal Dependencies
| Package | Purpose |
|---|---|
internal/maxmind/types |
IP geolocation for ACL logs |
internal/serialization |
Default value factory registration |
External Dependencies
| Dependency | Purpose |
|---|---|
github.com/rs/zerolog |
JSON formatting and logging |
github.com/yusing/goutils/task |
Lifetime management |
github.com/puzpuzpuz/xsync/v4 |
Concurrent map operations |
golang.org/x/time/rate |
Error rate limiting |
Observability
Logs
| Level | When |
|---|---|
| Debug | Buffer size adjustments |
| Info | Log file rotation |
| Error | Write failures (rate limited) |
Metrics
None exposed directly. Write throughput tracked internally.
Security Considerations
- Log files should have appropriate permissions (644)
- Sensitive headers can be filtered via
Filters.Headers - Query parameters and cookies are configurable via
Fields - Rate limiting prevents error log flooding
Failure Modes and Recovery
| Failure | Detection | Recovery |
|---|---|---|
| Write error | Write() returns error |
Rate-limited logging, then task finish |
| File deleted while open | Write failure | Logger continues with error |
| Disk full | Write failure | Error logged, may terminate |
| Rotation error | Rotate() returns error |
Continue with current file |
Error Rate Limiting
const (
errRateLimit = 200 * time.Millisecond
errBurst = 5
)
Errors are rate-limited to prevent log flooding. After burst exceeded, task is finished.
Usage Examples
Basic Request Logger
import "github.com/yusing/godoxy/internal/logging/accesslog"
cfg := accesslog.DefaultRequestLoggerConfig()
cfg.Path = "/var/log/godoxy/access.log"
cfg.RotateInterval = time.Hour
cfg.Retention = &accesslog.Retention{Days: 30}
logger, err := accesslog.NewAccessLogger(parent, cfg)
if err != nil {
log.Fatal(err)
}
defer logger.Close()
// Log a request
logger.Log(req, res)
JSON Format with Filters
cfg := accesslog.RequestLoggerConfig{
ConfigBase: accesslog.ConfigBase{
Path: "/var/log/godoxy/requests.json.log",
Retention: &accesslog.Retention{Days: 7},
},
Format: accesslog.FormatJSON,
Filters: accesslog.Filters{
StatusCodes: accesslog.LogFilter[*accesslog.StatusCodeRange]{
Keep: []accesslog.StatusCodeRange{{Min: 400, Max: 599}},
},
},
}
logger := accesslog.NewAccessLogger(parent, &cfg)
ACL Logger
aclCfg := accesslog.DefaultACLLoggerConfig()
aclCfg.Path = "/var/log/godoxy/acl.log"
aclCfg.LogAllowed = false // Only log blocked requests
aclLogger, err := accesslog.NewAccessLogger(parent, aclCfg)
if err != nil {
log.Fatal(err)
}
// Log ACL decision
aclLogger.LogACL(ipInfo, true) // blocked
aclLogger.LogACL(ipInfo, false) // allowed (if LogAllowed is true)
Custom Writer
type customWriter struct {
*os.File
}
func (w *customWriter) ShouldBeBuffered() bool { return true }
func (w *customWriter) Name() string { return "custom" }
writer := &customWriter{File: myFile}
logger := accesslog.NewAccessLoggerWithIO(parent, writer, cfg)
Integration with Route Middleware
func accessLogMiddleware(logger accesslog.AccessLogger) gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
logger.Log(c.Request, c.Writer.Result())
}
}
Performance Characteristics
- Buffered writes reduce I/O operations
- Dynamic buffer sizing adapts to throughput
- Per-writer locks allow parallel writes to different files
- Byte pools reduce GC pressure
- Efficient log rotation with back scanning
Testing Notes
NewMockAccessLoggerfor testing without file I/O- Mock file implementation via
NewMockFile - Filter tests verify predicate logic
- Rotation tests verify retention cleanup
Related Packages
internal/route- Route middleware integrationinternal/acl- ACL decision logginginternal/maxmind- IP geolocation for ACL logs