Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for postfix running as a docker service #115

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,37 @@ the systemd journal, the Docker logs, or from a log file.

These options can be used when starting the `postfix_exporter`

| Flag | Description | Default |
|--------------------------|------------------------------------------------------|-----------------------------------|
| `--web.listen-address` | Address to listen on for web interface and telemetry | `9154` |
| `--web.telemetry-path` | Path under which to expose metrics | `/metrics` |
| `--postfix.showq_path` | Path at which Postfix places its showq socket | `/var/spool/postfix/public/showq` |
| `--postfix.logfile_path` | Path where Postfix writes log entries | `/var/log/mail.log` |
| `--log.unsupported` | Log all unsupported lines | `false` |
| `--docker.enable` | Read from the Docker logs instead of a file | `false` |
| `--docker.container.id` | The container to read Docker logs from | `postfix` |
| `--systemd.enable` | Read from the systemd journal instead of file | `false` |
| `--systemd.unit` | Name of the Postfix systemd unit | `postfix.service` |
| `--systemd.slice` | Name of the Postfix systemd slice. | `""` |
| `--systemd.journal_path` | Path to the systemd journal | `""` |
| Flag | Description | Default |
|--------------------------|-----------------------------------------------------------------|-----------------------------------|
| `--web.listen-address` | Address to listen on for web interface and telemetry | `9154` |
| `--web.telemetry-path` | Path under which to expose metrics | `/metrics` |
| `--postfix.showq_path` | Path at which Postfix places its showq socket | `/var/spool/postfix/public/showq` |
| `--postfix.logfile_path` | Path where Postfix writes log entries | `/var/log/mail.log` |
| `--log.unsupported` | Log all unsupported lines | `false` |
| `--docker.enable` | Read from the Docker logs instead of a file | `false` |
| `--docker.source.type` | Docker source type to read log from `container` or `service` | `container` |
| `--docker.source.id` | ID or name of the container or service to read Docker logs from | `postfix` |
| `--systemd.enable` | Read from the systemd journal instead of file | `false` |
| `--systemd.unit` | Name of the Postfix systemd unit | `postfix.service` |
| `--systemd.slice` | Name of the Postfix systemd slice. | `""` |
| `--systemd.journal_path` | Path to the systemd journal | `""` |

## Events from Docker

Postfix servers running in a [Docker](https://www.docker.com/)
container can be monitored using the `--docker.enable` flag. The
default container ID is `postfix`, but can be customized with the
`--docker.container.id` flag.
container or service can be monitored using the `--docker.enable` flag. The type of source can be
set with the `--docker.source.type` flag where `container` is default. The
default source ID is `postfix`, but can be customized with the
`--docker.source.id` flag set to the desired ID or name of the postfix container or service.

Example:
```
postfix_exporter --docker.enable --docker.source.type="service" --docker.source.id="stack-name_postfix"
```
or as docker entrypoint:
```
["/bin/postfix_exporter", "--docker.enable", "--docker.source.type=service", "--docker.source.id=stack-name_postfix"]
```

The default is to connect to the local Docker, but this can be
customized using [the `DOCKER_HOST` and
Expand Down
44 changes: 31 additions & 13 deletions logsource_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"log"
"strings"
"errors"

"github.com/alecthomas/kingpin"
"github.com/docker/docker/api/types"
Expand All @@ -18,7 +19,8 @@ import (
// journal.
type DockerLogSource struct {
client DockerClient
containerID string
sourceType string
sourceID string
reader *bufio.Reader
}

Expand All @@ -27,23 +29,37 @@ type DockerLogSource struct {
type DockerClient interface {
io.Closer
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
ServiceLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
}

// NewDockerLogSource returns a log source for reading Docker logs.
func NewDockerLogSource(ctx context.Context, c DockerClient, containerID string) (*DockerLogSource, error) {
r, err := c.ContainerLogs(ctx, containerID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Tail: "0",
})
func NewDockerLogSource(ctx context.Context, c DockerClient, sourceType string, sourceID string) (*DockerLogSource, error) {
var r io.ReadCloser
var err error
if sourceType == "container" {
r, err = c.ContainerLogs(ctx, sourceID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Tail: "0",
})
} else if sourceType == "service" {
r, err = c.ServiceLogs(ctx, sourceID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Tail: "0",
})
} else {
r, err = (io.ReadCloser)(nil), errors.New("docker.source.type should be set to either \"container\" or \"service\" when docker is enabled")
}
if err != nil {
return nil, err
}

logSrc := &DockerLogSource{
client: c,
containerID: containerID,
sourceID: sourceID,
reader: bufio.NewReader(r),
}

Expand All @@ -55,7 +71,7 @@ func (s *DockerLogSource) Close() error {
}

func (s *DockerLogSource) Path() string {
return "docker:" + s.containerID
return "docker:" + s.sourceID
}

func (s *DockerLogSource) Read(ctx context.Context) (string, error) {
Expand All @@ -70,12 +86,14 @@ func (s *DockerLogSource) Read(ctx context.Context) (string, error) {
// DockerLogSources from command line flags.
type dockerLogSourceFactory struct {
enable bool
containerID string
sourceType string
sourceID string
}

func (f *dockerLogSourceFactory) Init(app *kingpin.Application) {
app.Flag("docker.enable", "Read from Docker logs. Environment variable DOCKER_HOST can be used to change the address. See https://pkg.go.dev/github.com/docker/docker/client?tab=doc#NewEnvClient for more information.").Default("false").BoolVar(&f.enable)
app.Flag("docker.container.id", "ID/name of the Postfix Docker container.").Default("postfix").StringVar(&f.containerID)
app.Flag("docker.source.type", "Source type for docker logs, \"conatiner\" or \"service\"").Default("container").StringVar(&f.sourceType)
app.Flag("docker.source.id", "ID/name of the Postfix Docker container or service.").Default("postfix").StringVar(&f.sourceID)
}

func (f *dockerLogSourceFactory) New(ctx context.Context) (LogSourceCloser, error) {
Expand All @@ -88,7 +106,7 @@ func (f *dockerLogSourceFactory) New(ctx context.Context) (LogSourceCloser, erro
if err != nil {
return nil, err
}
return NewDockerLogSource(ctx, c, f.containerID)
return NewDockerLogSource(ctx, c, f.sourceType, f.sourceID)
}

func init() {
Expand Down
11 changes: 8 additions & 3 deletions logsource_docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
func TestNewDockerLogSource(t *testing.T) {
ctx := context.Background()
c := &fakeDockerClient{}
src, err := NewDockerLogSource(ctx, c, "acontainer")
src, err := NewDockerLogSource(ctx, c, "container", "acontainer")
if err != nil {
t.Fatalf("NewDockerLogSource failed: %v", err)
}
Expand All @@ -33,7 +33,7 @@ func TestNewDockerLogSource(t *testing.T) {
func TestDockerLogSource_Path(t *testing.T) {
ctx := context.Background()
c := &fakeDockerClient{}
src, err := NewDockerLogSource(ctx, c, "acontainer")
src, err := NewDockerLogSource(ctx, c, "container", "acontainer")
if err != nil {
t.Fatalf("NewDockerLogSource failed: %v", err)
}
Expand All @@ -48,7 +48,7 @@ func TestDockerLogSource_Read(t *testing.T) {
c := &fakeDockerClient{
logsReader: ioutil.NopCloser(strings.NewReader("Feb 13 23:31:30 ahost anid[123]: aline\n")),
}
src, err := NewDockerLogSource(ctx, c, "acontainer")
src, err := NewDockerLogSource(ctx, c, "container", "acontainer")
if err != nil {
t.Fatalf("NewDockerLogSource failed: %v", err)
}
Expand All @@ -73,6 +73,11 @@ func (c *fakeDockerClient) ContainerLogs(ctx context.Context, containerID string
return c.logsReader, nil
}

func (c *fakeDockerClient) ServiceLogs(ctx context.Context, serviceID string, opts types.ContainerLogsOptions) (io.ReadCloser, error) {
c.containerLogsCalls = append(c.containerLogsCalls, serviceID)
return c.logsReader, nil
}

func (c *fakeDockerClient) Close() error {
c.closeCalls++
return nil
Expand Down