Skip to content

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 requests
  • internal/acl - ACL decision logging
  • internal/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 ​

go
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 ​

go
type Writer interface {
    io.WriteCloser
    ShouldBeBuffered() bool
    Name() string // file name or path
}

Output destination interface.

Format Type ​

go
type Format string

const (
    FormatCommon   Format = "common"
    FormatCombined Format = "combined"
    FormatJSON     Format = "json"
)

Log format constants.

Configuration Types ​

RequestLoggerConfig ​

go
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 ​

go
type ACLLoggerConfig struct {
    ConfigBase
    LogAllowed bool `json:"log_allowed"`
}

Configuration for ACL event logging.

ConfigBase ​

go
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 ​

go
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 ​

go
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 ​

go
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 ​

go
func DefaultRequestLoggerConfig() *RequestLoggerConfig
func DefaultACLLoggerConfig() *ACLLoggerConfig

Returns default configurations.

Architecture ​

Core Components ​

ComponentResponsibility
AccessLoggerMain logging interface
RequestFormatterFormats request/response logs
ACLFormatterFormats ACL decision logs
WriterOutput destination (file/stdout)
BufferedWriterEfficient I/O with dynamic buffering

Log Flow ​

Buffer Management ​

The logger dynamically adjusts buffer size based on write throughput:

ParameterValue
Initial Buffer Size4 KB
Maximum Buffer Size8 MB
Adjustment Interval5 seconds

Buffer size adjustment formula:

go
newBufSize = origBufSize +/- step
step = max(|wps - origBufSize|/2, wps/2)

Rotation Logic ​

Rotation checks:

  1. Is rotation enabled (supportRotate + valid retention)?
  2. Is retention period valid?
  3. Create new file with timestamp suffix
  4. 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 ​

json
{
  "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 ​

yaml
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 ​

FieldTypeDefaultDescription
pathstring-Log file path
stdoutboolfalseAlso log to stdout
rotate_intervalduration1hRotation interval
retention.daysint30Days to retain logs
formatstringcombinedLog format
filters.status_codesrange[]allStatus code filter
filters.methodstring[]allHTTP method filter
filters.cidrCIDR[]noneIP range filter

Reloading ​

Configuration is fixed at construction time. Create a new logger to apply changes.

Dependency and Integration Map ​

Internal Dependencies ​

PackagePurpose
internal/maxmind/typesIP geolocation for ACL logs
internal/serializationDefault value factory registration

External Dependencies ​

DependencyPurpose
github.com/rs/zerologJSON formatting and logging
github.com/yusing/goutils/taskLifetime management
github.com/puzpuzpuz/xsync/v4Concurrent map operations
golang.org/x/time/rateError rate limiting

Observability ​

Logs ​

LevelWhen
DebugBuffer size adjustments
InfoLog file rotation
ErrorWrite 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 ​

FailureDetectionRecovery
Write errorWrite() returns errorRate-limited logging, then task finish
File deleted while openWrite failureLogger continues with error
Disk fullWrite failureError logged, may terminate
Rotation errorRotate() returns errorContinue with current file

Error Rate Limiting ​

go
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 ​

go
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 ​

go
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 ​

go
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 ​

go
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 ​

go
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 ​

  • NewMockAccessLogger for testing without file I/O
  • Mock file implementation via NewMockFile
  • Filter tests verify predicate logic
  • Rotation tests verify retention cleanup
  • internal/route - Route middleware integration
  • internal/acl - ACL decision logging
  • internal/maxmind - IP geolocation for ACL logs

Released under the MIT License.