Skip to content

Commit

Permalink
Reorganize configs to match on codes or text
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeymakinen committed Dec 30, 2023
1 parent 34dc9b9 commit 6b9e07d
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 182 deletions.
52 changes: 40 additions & 12 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,44 +14,69 @@ The other placeholders are specified separately.
See [postfix.yml](exporter/testdata/postfix.yml) for configuration examples.

```yml
host_replies:
[ - <host_reply>, ... ]
status_replies:
[ - <status_reply>, ... ]
smtp_replies:
[ - <smtp_reply>, ... ]
noqueue_reject_replies:
[ - <noqueue_reject_reply>, ... ]
```
### `<host_reply>`
### `<status_reply>`

Example log entry for `queue_status`:
Example log entry:

```
Jan 1 00:00:00 hostname postfix/smtp[12345]: 123456789AB: to=<user@example.com>, relay=example.com[123.45.67.89]:25, delay=1.23, delays=1.23/1.23/1.23/1.23, dsn=1.2.3, status=bounced (host example.com[123.45.67.89] said: 123 #1.2.3 Reasons (in reply to end of DATA command))
```

Example log entry for `other`:
In this case:

* `123` is a status code
* `1.2.3` is an enhanced status code
* `Reasons` is the text of the reply

```yml
# Only allow specific statuses.
statuses:
[ - <string>, ... ]
# The regular expression matching the reply code, enhanced code or text.
regexp: <regex>
# Match type. Accepted values: code, enhanced_code, text.
[ match: <string> | default = "text" ]
# The replacement text (may include placeholders supported by Go, see https://pkg.go.dev/regexp#Regexp.Expand).
text: <string>
```

### `<smtp_reply>`

Example log entry:

```
Jan 1 00:00:00 hostname postfix/smtp[12345]: 123456789AB: host example.com[123.45.67.89] said: 123 1.2.3 Reasons (in reply to RCPT TO command)
```

In both cases:
In this case:

* `123` is a status code
* `1.2.3` is an enhanced status code
* `Reasons` is the text of the reply

```yml
# The type of the reply. Accepted values: any, queue_status, other.
[ type: <string> | default = "any" ]
# The regular expression matching the reply text.
# The regular expression matching the reply code, enhanced code or text.
regexp: <regex>
# Match type. Accepted values: code, enhanced_code, text.
[ match: <string> | default = "text" ]
# The replacement text (may include placeholders supported by Go, see https://pkg.go.dev/regexp#Regexp.Expand).
text: <string>
```

### `<noqueue_reject_replies>`
### `<noqueue_reject_reply>`

Example log entry:

Expand All @@ -66,9 +91,12 @@ In this case:
* `Reasons` is the text of the reply

```yml
# The regular expression matching the reply text.
# The regular expression matching the reply code, enhanced code or text.
regexp: <regex>
# Match type. Accepted values: code, enhanced_code, text.
[ match: <string> | default = "text" ]
# The replacement text (may include placeholders supported by Go, see https://pkg.go.dev/regexp#Regexp.Expand).
text: <string>
```
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ make
| postfix_disconnects_total | Total number of times disconnect events were collected. | subprogram
| postfix_lost_connections_total | Total number of times lost connection events were collected. | subprogram
| postfix_not_resolved_hostnames_total | Total number of times not resolved hostname events were collected. | subprogram
| postfix_lmtp_statuses_total | Total number of times LMTP server message status change events were collected. | status
| postfix_lmtp_delay_seconds | Delay in seconds for a LMTP server to process a message. | status
| postfix_smtp_statuses_total | Total number of times SMTP server message status change events were collected. | status
| postfix_smtp_status_replies_total | Total number of times SMTP server message status change event replies were collected. Requires [configuration](CONFIGURATION.md) to be present. | status, code, enhanced_code, text
| postfix_statuses_total | Total number of times server message status change events were collected. | subprogram, status
| postfix_delay_seconds | Delay in seconds for a server to process a message. | subprogram, status
| postfix_status_replies_total | Total number of times server message status change event replies were collected. Requires [configuration](CONFIGURATION.md) to be present. | subprogram, status, code, enhanced_code, text
| postfix_smtp_replies_total | Total number of times SMTP server replies were collected. Requires [configuration](CONFIGURATION.md) to be present. | code, enhanced_code, text
| postfix_smtp_delay_seconds | Delay in seconds for a SMTP server to process a message. | status
| postfix_milter_actions_total | Total number of times milter events were collected. | subprogram, action
| postfix_login_failures_total | Total number of times login failure events were collected. | subprogram, method
| postfix_qmgr_statuses_total | Total number of times Postfix queue manager message status change events were collected. | status
Expand Down
83 changes: 43 additions & 40 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,43 @@ package config

import (
"errors"
"fmt"
"os"
"regexp"
"strconv"

"gopkg.in/yaml.v3"
)

type Config struct {
HostReplies []HostReplyConfig `yaml:"host_replies,omitempty"`
NoqueueRejectReplies []NoqueueRejectReplyConfig `yaml:"noqueue_reject_replies,omitempty"`
StatusReplies []StatusReplyMatchConfig `yaml:"status_replies,omitempty"`
SmtpReplies []ReplyMatchConfig `yaml:"smtp_replies,omitempty"`
NoqueueRejectReplies []ReplyMatchConfig `yaml:"noqueue_reject_replies,omitempty"`
}

func Load(name string) (*Config, error) {
f, err := os.Open(name)
if err != nil {
return nil, fmt.Errorf("error reading config file: %v", err)
return nil, errors.New("error reading config file: " + err.Error())
}
defer f.Close()
d := yaml.NewDecoder(f)
d.KnownFields(true)
var cfg Config
if err = d.Decode(&cfg); err != nil {
return nil, fmt.Errorf("error parsing config file: %v", err)
return nil, errors.New("error parsing config file: " + err.Error())
}
return &cfg, nil
}

type HostReplyConfig struct {
Type HostReplyType `yaml:"type,omitempty"`
Regexp *Regexp `yaml:"regexp"`
Text string `yaml:"text"`
type StatusReplyMatchConfig struct {
Statuses []string `yaml:"statuses,omitempty"`
Regexp *Regexp `yaml:"regexp"`
Match MatchType `yaml:"match,omitempty"`
Text string `yaml:"text"`
}

func (cfg *HostReplyConfig) UnmarshalYAML(value *yaml.Node) error {
type plain HostReplyConfig
func (cfg *StatusReplyMatchConfig) UnmarshalYAML(value *yaml.Node) error {
type plain StatusReplyMatchConfig
if err := value.Decode((*plain)(cfg)); err != nil {
return err
}
Expand All @@ -46,49 +48,50 @@ func (cfg *HostReplyConfig) UnmarshalYAML(value *yaml.Node) error {
return nil
}

type HostReplyType int
type ReplyMatchConfig struct {
Regexp *Regexp `yaml:"regexp"`
Match MatchType `yaml:"match,omitempty"`
Text string `yaml:"text"`
}

func (t *HostReplyType) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
func (cfg *ReplyMatchConfig) UnmarshalYAML(value *yaml.Node) error {
type plain ReplyMatchConfig
if err := value.Decode((*plain)(cfg)); err != nil {
return err
}
switch s {
case "", "any":
*t = HostReplyAny
case "queue_status":
*t = HostReplyQueueStatus
case "other":
*t = HostReplyOther
default:
return fmt.Errorf("unsupported host reply type %q", s)
if cfg.Text == "" {
return errors.New("empty text replacement")
}
return nil
}

// HostReplyType types.
const (
HostReplyAny HostReplyType = iota
HostReplyQueueStatus
HostReplyOther
)

type NoqueueRejectReplyConfig struct {
Regexp *Regexp `yaml:"regexp"`
Text string `yaml:"text"`
}
type MatchType int

func (cfg *NoqueueRejectReplyConfig) UnmarshalYAML(value *yaml.Node) error {
type plain NoqueueRejectReplyConfig
if err := value.Decode((*plain)(cfg)); err != nil {
func (t *MatchType) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
if cfg.Text == "" {
return errors.New("empty text replacement")
switch s {
case "", "text":
*t = MatchTypeText
case "code":
*t = MatchTypeCode
case "enhanced_code":
*t = MatchTypeEnhancedCode
default:
return errors.New("unsupported match type " + strconv.Quote(s))
}
return nil
}

// MatchType types.
const (
MatchTypeText MatchType = iota
MatchTypeCode
MatchTypeEnhancedCode
)

type Regexp struct {
*regexp.Regexp
}
Expand Down
Loading

0 comments on commit 6b9e07d

Please sign in to comment.