pflog

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 30, 2026 License: MIT Imports: 6 Imported by: 0

README

pflog

Go Reference

pflog is a Go package for parsing Postfix mail log entries. It handles the standard BSD syslog format that Postfix writes to, turning raw log lines into structured Go values.

Features

  • Parse individual log lines with Parse, or iterate over a log file with Scanner.
  • Each entry is returned as a Record containing the timestamp, hostname, Postfix daemon name, process ID, queue ID, and a typed Message.
  • Recognised message types: Connect, Disconnect, Queued, Removed, Cleanup, Delivery, Reject, BounceNotification, Warning, and Unknown.

Benchmarks

Measured on an AMD EPYC 7763 with go test -bench=. -benchmem:

Benchmark ns/op B/op allocs/op
Parse — Connect 203 128 2
Parse — Disconnect (with stats) 500 400 4
Parse — Queued 261 128 2
Parse — Delivery 328 208 2
Parse — Reject 303 176 2
Parse — Unknown 230 112 2
Scanner — 10 mixed lines 4,484 6,848 33

Installation

go get github.com/chrj/pflog

Usage

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/chrj/pflog"
)

func main() {
    f, err := os.Open("/var/log/mail.log")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    s := pflog.NewScanner(f)
    for s.Scan() {
        rec := s.Record()
        switch msg := rec.Message.(type) {
        case pflog.Delivery:
            fmt.Printf("to=%s relay=%s status=%s\n", msg.To, msg.Relay, msg.Status)
        case pflog.Reject:
            fmt.Printf("rejected at %s: %s\n", msg.Stage, msg.Detail)
        }
    }
    if err := s.Err(); err != nil {
        log.Fatal(err)
    }
}

Documentation

Overview

Package pflog parses Postfix mail log entries.

It supports the standard BSD syslog format used by Postfix:

Jan  1 00:00:00 hostname postfix/process[pid]: message

Use Parse to parse individual log lines, or Scanner to iterate over records from an io.Reader.

Each parsed entry is returned as a Record whose [Record.Message] field contains one of the concrete message types: Connect, Disconnect, Queued, Removed, Cleanup, Delivery, Reject, BounceNotification, Warning, or Unknown.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BounceNotification

type BounceNotification struct {
	// BounceQueueID is the queue ID of the bounce notification message.
	BounceQueueID string
}

BounceNotification is produced by the bounce daemon when a non-delivery report is generated.

type Cleanup

type Cleanup struct {
	// MessageID is the value of the Message-Id header (without angle brackets).
	MessageID string
}

Cleanup is produced by the cleanup daemon and carries the Message-Id header.

type Connect

type Connect struct {
	// Hostname is the connecting client's reported hostname (may be "unknown").
	Hostname string
	// IP is the connecting client's IP address.
	IP string
}

Connect is produced by smtpd when a client connects.

type Delivery

type Delivery struct {
	// To is the recipient envelope address.
	To string
	// Relay is the relay host used for delivery (e.g., "mail.example.com[1.2.3.4]:25",
	// "local", or "none").
	Relay string
	// Delay is the total message delay as reported by Postfix.
	Delay string
	// Delays is the per-stage delay breakdown (queuing/connection/setup/data).
	Delays string
	// DSN is the delivery status notification code (e.g., "2.0.0").
	DSN string
	// Status is the delivery outcome.
	Status DeliveryStatus
	// Detail is the SMTP response or error detail.
	Detail string
}

Delivery is produced by smtp, local, virtual, lmtp, or pipe for each recipient delivery attempt.

type DeliveryStatus

type DeliveryStatus string

DeliveryStatus is the final disposition of a delivery attempt.

const (
	// StatusSent indicates the message was successfully delivered.
	StatusSent DeliveryStatus = "sent"
	// StatusBounced indicates the message was returned to the sender.
	StatusBounced DeliveryStatus = "bounced"
	// StatusDeferred indicates delivery was temporarily deferred.
	StatusDeferred DeliveryStatus = "deferred"
	// StatusExpired indicates the message exceeded the maximum queue lifetime.
	StatusExpired DeliveryStatus = "expired"
)

type Disconnect

type Disconnect struct {
	// Hostname is the client's reported hostname (may be "unknown").
	Hostname string
	// IP is the client's IP address.
	IP string
	// Stats contains per-command counts reported at disconnect
	// (e.g., {"ehlo": 1, "mail": 1, "rcpt": 1, "commands": 3}).
	Stats map[string]int
}

Disconnect is produced by smtpd when a client disconnects.

type FormatError

type FormatError struct {
	// Line is the full input line that failed to parse.
	Line string
	// Reason is a short description of what part of the format was not
	// recognised (e.g., "line too short", "missing hostname",
	// "missing PID bracket", "missing message separator").
	Reason string
}

FormatError is returned by Parse when the input line does not conform to the expected BSD syslog format.

func (*FormatError) Error

func (e *FormatError) Error() string

type Message

type Message interface {
	// contains filtered or unexported methods
}

Message is the interface implemented by all specific log message types. Use a type switch to distinguish between concrete types.

type PIDError

type PIDError struct {
	// PID is the raw PID string that failed to parse.
	PID string
	// Err is the underlying parse error.
	Err error
}

PIDError is returned by Parse when the PID field cannot be parsed as an integer. The underlying parse error is accessible via errors.Unwrap.

func (*PIDError) Error

func (e *PIDError) Error() string

func (*PIDError) Unwrap

func (e *PIDError) Unwrap() error

Unwrap returns the underlying PID parse error.

type Queued

type Queued struct {
	// From is the envelope sender address (empty for bounce messages).
	From string
	// Size is the message size in bytes.
	Size int
	// NRcpt is the number of recipients.
	NRcpt int
}

Queued is produced by qmgr when a message enters the active queue.

type Record

type Record struct {
	// Time is the log entry timestamp. Because the BSD syslog format omits the
	// year, it is set to the current UTC year at parse time. Logs that span a
	// year boundary (e.g., December entries parsed in January) will receive an
	// incorrect year.
	Time time.Time
	// Hostname is the name of the host that produced the log entry.
	Hostname string
	// Process is the Postfix daemon that produced the entry (e.g., "smtpd",
	// "smtp", "qmgr").
	Process string
	// PID is the process identifier.
	PID int
	// QueueID is the Postfix message queue ID, if present.
	QueueID string
	// Message is the parsed message payload. Use a type switch or type
	// assertion to access the concrete type.
	Message Message
}

Record is a parsed Postfix log entry.

func Parse

func Parse(line string) (*Record, error)

Parse parses a single Postfix log line and returns a Record.

The line must be in the standard BSD syslog format:

Jan  1 00:00:00 hostname postfix/process[pid]: message

An error is returned when the line does not match the expected syslog header. If the message body cannot be parsed into a specific type, the [Record.Message] field is set to Unknown and no error is returned.

type Reject

type Reject struct {
	// Stage is the SMTP stage where rejection occurred (e.g., "RCPT", "DATA").
	Stage string
	// ClientHostname is the rejecting client's hostname (may be "unknown").
	ClientHostname string
	// ClientIP is the rejecting client's IP address.
	ClientIP string
	// Code is the SMTP rejection code (e.g., 550).
	Code int
	// Detail is the full rejection reason.
	Detail string
}

Reject is produced when a message or connection is rejected.

type Removed

type Removed struct{}

Removed is produced by qmgr when a message is removed from the queue.

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner reads Postfix log [Record]s from an io.Reader one line at a time. Lines that do not match the expected syslog format are silently skipped, making it safe to use on mixed syslog files.

To be notified when a line is skipped due to a parse error, register a callback with Scanner.SetErrorHandler before calling Scanner.Scan.

Usage:

s := pflog.NewScanner(r)
for s.Scan() {
    rec := s.Record()
    // process rec…
}
if err := s.Err(); err != nil {
    log.Fatal(err)
}

func NewScanner

func NewScanner(r io.Reader) *Scanner

NewScanner returns a new Scanner that reads from r.

func (*Scanner) Err

func (s *Scanner) Err() error

Err returns the first non-EOF read error encountered by the Scanner.

func (*Scanner) Record

func (s *Scanner) Record() *Record

Record returns the most recent record parsed by Scanner.Scan. The returned pointer is valid until the next call to Scanner.Scan.

func (*Scanner) Scan

func (s *Scanner) Scan() bool

Scan advances the scanner to the next record and returns true if one is available. It returns false at the end of input or on a read error. Lines that cannot be parsed as Postfix entries are silently skipped; register an error handler with Scanner.SetErrorHandler to observe those errors.

func (*Scanner) SetErrorHandler

func (s *Scanner) SetErrorHandler(fn func(line string, err error))

SetErrorHandler registers fn to be called whenever a line is skipped because it cannot be parsed as a Postfix log entry. fn receives the raw line and the parse error. Passing nil clears a previously set handler. SetErrorHandler must be called before the first call to Scanner.Scan.

type TimestampError

type TimestampError struct {
	// Timestamp is the raw timestamp string that failed to parse.
	Timestamp string
	// Err is the underlying parse error.
	Err error
}

TimestampError is returned by Parse when the timestamp portion of the input line cannot be parsed. The underlying parse error is accessible via errors.Unwrap.

func (*TimestampError) Error

func (e *TimestampError) Error() string

func (*TimestampError) Unwrap

func (e *TimestampError) Unwrap() error

Unwrap returns the underlying timestamp parse error.

type Unknown

type Unknown struct {
	// Text is the raw message text after any queue ID prefix.
	Text string
}

Unknown represents a log message that could not be parsed into a specific type. The raw message text is preserved in Text.

type Warning

type Warning struct {
	// Text is the warning message text.
	Text string
}

Warning is a generic warning entry from any Postfix daemon.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL