├── .gitignore ├── go.mod ├── postfix-log-parser ├── cmd │ ├── execute.go │ └── root.go └── main.go ├── go.sum ├── Makefile ├── LICENSE ├── .circleci └── config.yml ├── test ├── test.log └── test-iso8601.log ├── postfix-log-parser.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | __* 3 | postfix-log-parser/postfix-log-parser 4 | pkg/ 5 | *.zip 6 | *.tgz 7 | *.tar.gz 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/youyo/postfix-log-parser 2 | 3 | require ( 4 | github.com/spf13/cobra v0.0.3 5 | github.com/spf13/pflag v1.0.3 // indirect 6 | ) 7 | 8 | go 1.13 9 | -------------------------------------------------------------------------------- /postfix-log-parser/cmd/execute.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "os" 4 | 5 | func initConfig() {} 6 | 7 | func Execute() { 8 | cmd := NewCmdRoot() 9 | cmd.SetOutput(os.Stdout) 10 | if err := cmd.Execute(); err != nil { 11 | cmd.SetOutput(os.Stderr) 12 | cmd.Println(err) 13 | os.Exit(1) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 2 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 3 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 4 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | Owner := youyo 3 | Name := postfix-log-parser 4 | Repository := "github.com/$(Owner)/$(Name)" 5 | GithubToken := ${GITHUB_TOKEN} 6 | Version := $(shell git describe --tags --abbrev=0) 7 | 8 | ## Setup 9 | setup: 10 | GO111MODULE=off go get -v github.com/Songmu/goxz/cmd/goxz 11 | GO111MODULE=off go get -v github.com/tcnksm/ghr 12 | GO111MODULE=off go get -v github.com/jstemmer/go-junit-report 13 | 14 | ## Run tests 15 | test: 16 | go test -v -cover \ 17 | $(shell go list ./...) 18 | 19 | ## Execute `go run` 20 | run: 21 | go run \ 22 | $(Name)/main.go ${OPTION} 23 | 24 | ## Vendoring 25 | vendoring: 26 | go mod download 27 | 28 | ## Build 29 | build: 30 | go get 31 | goxz -os=darwin,linux -arch=amd64 -d=pkg ./$(Name) 32 | 33 | ## Release 34 | release: 35 | ghr -t ${GithubToken} -u $(Owner) -r $(Name) --replace $(Version) pkg/ 36 | 37 | ## Remove packages 38 | clean: 39 | rm -rf pkg/ 40 | 41 | ## Show help 42 | help: 43 | @make2help $(MAKEFILE_LIST) 44 | 45 | .PHONY: help 46 | .SILENT: 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2018 youyo <1003ni2@gmail.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /postfix-log-parser/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 youyo <1003ni2@gmail.com> 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package main 22 | 23 | import "github.com/youyo/postfix-log-parser/postfix-log-parser/cmd" 24 | 25 | func main() { 26 | cmd.Execute() 27 | } 28 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | workflows: 2 | version: 2 3 | main: 4 | jobs: 5 | - test 6 | - release: 7 | requires: 8 | - test 9 | filters: 10 | branches: 11 | only: master 12 | 13 | defaults: &defaults 14 | working_directory: /go/src/github.com/youyo/postfix-log-parser 15 | docker: 16 | - image: circleci/golang:1 17 | environment: 18 | GO111MODULE: "on" 19 | 20 | version: 2 21 | jobs: 22 | test: 23 | <<: *defaults 24 | steps: 25 | - checkout 26 | - restore_cache: &restore_cache 27 | name: Restore go modules cache 28 | keys: 29 | - mod-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "go.mod" }} 30 | - run: &vendoring 31 | name: Vendoring 32 | command: make vendoring 33 | - save_cache: &save_cache 34 | name: Save go modules cache 35 | key: mod-{{ .Environment.COMMON_CACHE_KEY }}-{{ checksum "go.mod" }} 36 | paths: 37 | - /go/pkg/mod/cache 38 | - run: &setup 39 | name: Dependency 40 | command: | 41 | make setup 42 | - run: mkdir -p /tmp/test-results 43 | - run: 44 | name: Test 45 | command: | 46 | trap "go-junit-report /tmp/test-results/go-test-report.xml" EXIT 47 | make test | tee /tmp/test-results/go-test.out 48 | - run: &build 49 | name: Build 50 | command: | 51 | make build 52 | - store_test_results: 53 | path: /tmp/test-results 54 | release: 55 | <<: *defaults 56 | steps: 57 | - checkout 58 | - restore_cache: *restore_cache 59 | - run: *vendoring 60 | - save_cache: *save_cache 61 | - run: *setup 62 | - run: *build 63 | - run: 64 | name: Release 65 | command: | 66 | make release 67 | -------------------------------------------------------------------------------- /test/test.log: -------------------------------------------------------------------------------- 1 | Oct 10 15:59:28 mail postfix/smtpd[1830]: connect from example.com[127.0.0.1] 2 | Oct 10 15:59:28 mail postfix/smtpd[1830]: C6E0DDB74006: client=example.com[127.0.0.1] 3 | Oct 10 15:59:28 mail postfix/cleanup[1894]: C6E0DDB74006: message-id= 4 | Oct 10 15:59:28 mail postfix/qmgr[18719]: C6E0DDB74006: from=, size=309891, nrcpt=1 (queue active) 5 | Oct 10 15:59:28 mail postfix/smtpd[1830]: disconnect from example.com[127.0.0.1] 6 | Oct 10 15:59:29 mail postfix/smtpd[1827]: connect from example.com[127.0.0.1] 7 | Oct 10 15:59:29 mail postfix/smtpd[1827]: 3D74ADB7400B: client=example.com[127.0.0.1] 8 | Oct 10 15:59:29 mail postfix/cleanup[1695]: 3D74ADB7400B: message-id= 9 | Oct 10 15:59:29 mail postfix/smtpd[1827]: disconnect from example.com[127.0.0.1] 10 | Oct 10 15:59:29 mail postfix/qmgr[18719]: 3D74ADB7400B: from=, size=2140, nrcpt=1 (queue active) 11 | Oct 10 15:59:30 mail postfix/smtp[1810]: 3D74ADB7400B: to=, relay=example.to[192.168.0.20]:25, delay=1.7, delays=0.02/0/1.7/0.06, dsn=2.0.0, status=sent (250 [Sniper] OK 1539154772 snipe-queue 10549) 12 | Oct 10 15:59:30 mail postfix/smtp[1810]: 3D74ADB7400B: to=, relay=example.to[192.168.0.20]:25, delay=1.7, delays=0.02/0/1.7/0.06, dsn=2.0.0, status=sent (250 [Sniper] OK 1539154772 snipe-queue 10549) 13 | Oct 10 15:59:30 mail postfix/qmgr[18719]: 3D74ADB7400B: removed 14 | Oct 10 15:59:31 mail postfix/smtpd[1830]: connect from example.com[127.0.0.1] 15 | Oct 10 15:59:31 mail postfix/smtpd[1830]: 6526CDB7400B: client=example.com[127.0.0.1] 16 | Oct 10 15:59:31 mail postfix/cleanup[1894]: 6526CDB7400B: message-id=<153915476795520900002087@example.aaa> 17 | Oct 10 15:59:31 mail postfix/smtpd[1830]: disconnect from example.com[127.0.0.1] 18 | Oct 10 15:59:31 mail postfix/qmgr[18719]: 6526CDB7400B: from=, size=4118, nrcpt=1 (queue active) 19 | Oct 10 15:59:31 mail postfix/smtp[1417]: 6526CDB7400B: to=, relay=example.ccc[192.168.0.10]:25, delay=0.1, delays=0.02/0/0.03/0.05, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 71111424269E) 20 | Oct 10 15:59:31 mail postfix/qmgr[18719]: 6526CDB7400B: removed 21 | Oct 10 15:59:32 mail postfix/smtp[1874]: C6E0DDB74006: to=, relay=example.ddd[192.168.0.30]:25, delay=3.4, delays=0.11/0/0.38/2.9, dsn=2.0.0, status=sent (250 2.0.0 OK 1539154772 az9-v6si5976496plb.190 - gsmtp) 22 | Oct 10 15:59:32 mail postfix/qmgr[18719]: C6E0DDB74006: removed 23 | -------------------------------------------------------------------------------- /postfix-log-parser.go: -------------------------------------------------------------------------------- 1 | package postfixlog 2 | 3 | import ( 4 | "regexp" 5 | "time" 6 | ) 7 | 8 | const ( 9 | TimeFormat = "Jan 2 15:04:05" 10 | TimeFormatISO8601 = "2006-01-02T15:04:05.999999-07:00" 11 | TimeRegexpFormat = `([A-Za-z]{3}\s*[0-9]{1,2} [0-9]{2}:[0-9]{2}:[0-9]{2}|^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z))` 12 | HostRegexpFormat = `([0-9A-Za-z\.]*)` 13 | ProcessRegexpFormat = `(postfix/[a-z]*\[[0-9]{1,5}\])?` 14 | QueueIdRegexpFormat = `([0-9A-Z]*)` 15 | MessageDetailsRegexpFormat = `((?:client=(.+)\[(.+)\])?(?:message-id=<(.+)>)?(?:from=<(.+@.+)>)?(?:to=<(.+@.+)>.*status=([a-z]+))?.*)` 16 | RegexpFormat = TimeRegexpFormat + ` ` + HostRegexpFormat + ` ` + ProcessRegexpFormat + `: ` + QueueIdRegexpFormat + `(?:\: )?` + MessageDetailsRegexpFormat 17 | ) 18 | 19 | type ( 20 | PostfixLog struct { 21 | LogFormat LogFormat 22 | Regexp *regexp.Regexp 23 | } 24 | 25 | LogFormat struct { 26 | Time *time.Time `json:"time"` 27 | Hostname string `json:"hostname"` 28 | Process string `json:"process"` 29 | QueueId string `json:"queue_id"` 30 | Messages string `json:"messages"` 31 | ClientHostname string `json:"client_hostname"` 32 | ClinetIp string `json:"client_ip"` 33 | MessageId string `json:"message_id"` 34 | From string `json:"from"` 35 | To string `json:"to"` 36 | Status string `json:"status"` 37 | } 38 | ) 39 | 40 | func NewPostfixLog() *PostfixLog { 41 | return &PostfixLog{ 42 | Regexp: regexp.MustCompile(RegexpFormat), 43 | } 44 | } 45 | 46 | func (p *PostfixLog) Parse(text []byte) (LogFormat, error) { 47 | re := p.Regexp.Copy() 48 | group := re.FindSubmatch(text) 49 | var t time.Time 50 | t, err := time.ParseInLocation(TimeFormat, string(group[1]), time.Local) 51 | if err != nil { 52 | t, err = time.ParseInLocation(TimeFormatISO8601, string(group[1]), time.Local) 53 | if err != nil { 54 | return LogFormat{}, err 55 | } 56 | } 57 | 58 | logFormat := LogFormat{ 59 | Time: &t, 60 | Hostname: string(group[2]), 61 | Process: string(group[3]), 62 | QueueId: string(group[4]), 63 | Messages: string(group[5]), 64 | ClientHostname: string(group[6]), 65 | ClinetIp: string(group[7]), 66 | MessageId: string(group[8]), 67 | From: string(group[9]), 68 | To: string(group[10]), 69 | Status: string(group[11]), 70 | } 71 | 72 | return logFormat, nil 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postfix-log-parser 2 | 3 | Parse postfix log, and output json format 4 | 5 | ## Install 6 | 7 | Place a `postfix-log-parser` command to your PATH and set an executable flag. 8 | Download the latest release from github. https://github.com/youyo/postfix-log-parser/releases/latest 9 | 10 | ## Usage 11 | 12 | Input postfix logs as os stdin. 13 | 14 | ``` console 15 | # cat /var/log/maillog | ./postfix-log-parser 16 | { 17 | "time": "0000-10-10T15:59:29+09:00", 18 | "hostname": "mail", 19 | "process": "postfix/smtpd[1827]", 20 | "queue_id": "3D74ADB7400B", 21 | "client_hostname": "example.com", 22 | "client_ip": "127.0.0.1", 23 | "message_id": "f93388828093534f92d85ffe21b2a719@example.info", 24 | "from": "test2@example.info", 25 | "messages": [ 26 | { 27 | "time": "0000-10-10T15:59:30+09:00", 28 | "to": "test@example.to", 29 | "status": "sent", 30 | "message": "to=, relay=example.to[192.168.0.20]:25, delay=1.7, delays=0.02/0/1.7/0.06, dsn=2.0.0, status=sent (250 [Sniper] OK 1539154772 snipe-queue 10549)" 31 | }, 32 | { 33 | "time": "0000-10-10T15:59:30+09:00", 34 | "to": "test2@example.to", 35 | "status": "sent", 36 | "message": "to=, relay=example.to[192.168.0.20]:25, delay=1.7, delays=0.02/0/1.7/0.06, dsn=2.0.0, status=sent (250 [Sniper] OK 1539154772 snipe-queue 10549)" 37 | } 38 | ] 39 | } 40 | . 41 | . 42 | . 43 | ``` 44 | 45 | ## Library usage 46 | 47 | ``` 48 | $ go get github.com/youyo/postfix-log-parser 49 | ``` 50 | 51 | ``` main.go 52 | package main 53 | 54 | import ( 55 | "github.com/k0kubun/pp" 56 | postfixlog "github.com/youyo/postfix-log-parser" 57 | ) 58 | 59 | func main() { 60 | textByte := []byte("Oct 10 04:02:08 mail.example.com postfix/smtp[22928]: DFBEFDBF00C5: to=, relay=mail.example-to.com[192.168.0.10]:25, delay=5.3, delays=0.26/0/0.31/4.7, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as C598F1B0002D)") 61 | 62 | p := postfixlog.NewPostfixLog() 63 | logFormat, _ := p.Parse(textByte) 64 | pp.Println(logFormat) 65 | } 66 | ``` 67 | 68 | ``` 69 | $ go run main.go 70 | postfixlog.LogFormat{ 71 | Time: &0-10-10 04:02:08 Local, 72 | Hostname: "mail.example.com", 73 | Process: "postfix/smtp[22928]", 74 | QueueId: "DFBEFDBF00C5", 75 | Messages: "to=, relay=mail.example-to.com[192.168.0.10]:25, delay=5.3, delays=0.26/0/0.31/4.7, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as C598F1B0002D)", 76 | ClientHostname: "", 77 | ClinetIp: "", 78 | MessageId: "", 79 | From: "", 80 | To: "test@example-to.com", 81 | Status: "sent", 82 | } 83 | ``` 84 | -------------------------------------------------------------------------------- /test/test-iso8601.log: -------------------------------------------------------------------------------- 1 | 2020-01-30T06:26:05.404714+09:00 mail postfix/smtpd[1830]: connect from example.com[127.0.0.1] 2 | 2020-01-30T06:26:05.404714+09:00 mail postfix/smtpd[1830]: C6E0DDB74006: client=example.com[127.0.0.1] 3 | 2020-01-30T06:26:05.404714+09:00 mail postfix/cleanup[1894]: C6E0DDB74006: message-id= 4 | 2020-01-30T06:26:05.404714+09:00 mail postfix/qmgr[18719]: C6E0DDB74006: from=, size=309891, nrcpt=1 (queue active) 5 | 2020-01-30T06:26:05.404714+09:00 mail postfix/smtpd[1830]: disconnect from example.com[127.0.0.1] 6 | 2020-01-30T06:26:06.404714+09:00 mail postfix/smtpd[1827]: connect from example.com[127.0.0.1] 7 | 2020-01-30T06:26:06.404714+09:00 mail postfix/smtpd[1827]: 3D74ADB7400B: client=example.com[127.0.0.1] 8 | 2020-01-30T06:26:06.404714+09:00 mail postfix/cleanup[1695]: 3D74ADB7400B: message-id= 9 | 2020-01-30T06:26:06.404714+09:00 mail postfix/smtpd[1827]: disconnect from example.com[127.0.0.1] 10 | 2020-01-30T06:26:06.404714+09:00 mail postfix/qmgr[18719]: 3D74ADB7400B: from=, size=2140, nrcpt=1 (queue active) 11 | 2020-01-30T06:26:07.404714+09:00 mail postfix/smtp[1810]: 3D74ADB7400B: to=, relay=example.to[192.168.0.20]:25, delay=1.7, delays=0.02/0/1.7/0.06, dsn=2.0.0, status=sent (250 [Sniper] OK 1539154772 snipe-queue 10549) 12 | 2020-01-30T06:26:07.404714+09:00 mail postfix/smtp[1810]: 3D74ADB7400B: to=, relay=example.to[192.168.0.20]:25, delay=1.7, delays=0.02/0/1.7/0.06, dsn=2.0.0, status=sent (250 [Sniper] OK 1539154772 snipe-queue 10549) 13 | 2020-01-30T06:26:07.404714+09:00 mail postfix/qmgr[18719]: 3D74ADB7400B: removed 14 | 2020-01-30T06:26:08.404714+09:00 mail postfix/smtpd[1830]: connect from example.com[127.0.0.1] 15 | 2020-01-30T06:26:08.404714+09:00 mail postfix/smtpd[1830]: 6526CDB7400B: client=example.com[127.0.0.1] 16 | 2020-01-30T06:26:08.404714+09:00 mail postfix/cleanup[1894]: 6526CDB7400B: message-id=<153915476795520900002087@example.aaa> 17 | 2020-01-30T06:26:08.404714+09:00 mail postfix/smtpd[1830]: disconnect from example.com[127.0.0.1] 18 | 2020-01-30T06:26:08.404714+09:00 mail postfix/qmgr[18719]: 6526CDB7400B: from=, size=4118, nrcpt=1 (queue active) 19 | 2020-01-30T06:26:08.404714+09:00 mail postfix/smtp[1417]: 6526CDB7400B: to=, relay=example.ccc[192.168.0.10]:25, delay=0.1, delays=0.02/0/0.03/0.05, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 71111424269E) 20 | 2020-01-30T06:26:08.404714+09:00 mail postfix/qmgr[18719]: 6526CDB7400B: removed 21 | 2020-01-30T06:26:09.404714+09:00 mail postfix/smtp[1874]: C6E0DDB74006: to=, relay=example.ddd[192.168.0.30]:25, delay=3.4, delays=0.11/0/0.38/2.9, dsn=2.0.0, status=sent (250 2.0.0 OK 1539154772 az9-v6si5976496plb.190 - gsmtp) 22 | 2020-01-30T06:26:09.404714+09:00 mail postfix/qmgr[18719]: C6E0DDB74006: removed 23 | -------------------------------------------------------------------------------- /postfix-log-parser/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "os" 9 | "time" 10 | 11 | "github.com/spf13/cobra" 12 | postfixlog "github.com/youyo/postfix-log-parser" 13 | ) 14 | 15 | func init() {} 16 | 17 | type ( 18 | PostfixLogParser struct { 19 | Time *time.Time `json:"time"` 20 | Hostname string `json:"hostname"` 21 | Process string `json:"process"` 22 | QueueId string `json:"queue_id"` 23 | ClientHostname string `json:"client_hostname"` 24 | ClinetIp string `json:"client_ip"` 25 | MessageId string `json:"message_id"` 26 | From string `json:"from"` 27 | Messages []Message `json:"messages"` 28 | } 29 | 30 | Message struct { 31 | Time *time.Time `json:"time"` 32 | To string `json:"to"` 33 | Status string `json:"status"` 34 | Message string `json:"message"` 35 | } 36 | ) 37 | 38 | func NewCmdRoot() *cobra.Command { 39 | cmd := &cobra.Command{ 40 | Use: "postfix-log-parser", 41 | Short: "Parse postfix log, and output json format", 42 | //Long: ``, 43 | Run: func(cmd *cobra.Command, args []string) { 44 | 45 | // create queue 46 | m := make(map[string]*PostfixLogParser) 47 | 48 | // initialize 49 | p := postfixlog.NewPostfixLog() 50 | 51 | // writer 52 | wtr := bufio.NewWriter(os.Stdout) 53 | 54 | // input stdin 55 | scanner := bufio.NewScanner(os.Stdin) 56 | for scanner.Scan() { 57 | 58 | // parse log 59 | logFormat, err := p.Parse(scanner.Bytes()) 60 | if err != nil { 61 | cmd.SetOutput(os.Stderr) 62 | cmd.Println(err) 63 | os.Exit(1) 64 | } 65 | 66 | /* 67 | Oct 10 04:02:02 mail.example.com postfix/smtpd[22941]: DFBEFDBF00C5: client=example.net[127.0.0.1] 68 | */ 69 | if logFormat.ClientHostname != "" { 70 | m[logFormat.QueueId] = &PostfixLogParser{ 71 | Time: logFormat.Time, 72 | Hostname: logFormat.Hostname, 73 | Process: logFormat.Process, 74 | QueueId: logFormat.QueueId, 75 | ClientHostname: logFormat.ClientHostname, 76 | ClinetIp: logFormat.ClinetIp, 77 | } 78 | } 79 | 80 | /* 81 | Oct 10 04:02:02 mail.example.com postfix/cleanup[22923]: DFBEFDBF00C5: message-id=<20181009190202.81363306015D@example.com> 82 | */ 83 | if logFormat.MessageId != "" { 84 | if plp, ok := m[logFormat.QueueId]; ok { 85 | plp.MessageId = logFormat.MessageId 86 | } 87 | } 88 | 89 | /* 90 | Oct 10 04:02:03 mail.example.com postfix/qmgr[18719]: DFBEFDBF00C5: from=, size=3578, nrcpt=1 (queue active) 91 | */ 92 | if logFormat.From != "" { 93 | if plp, ok := m[logFormat.QueueId]; ok { 94 | plp.From = logFormat.From 95 | } 96 | } 97 | 98 | /* 99 | Oct 10 04:02:08 mail.example.com postfix/smtp[22928]: DFBEFDBF00C5: to=, relay=mail.example-to.com[192.168.0.10]:25, delay=5.3, delays=0.26/0/0.31/4.7, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as C598F1B0002D) 100 | */ 101 | if logFormat.To != "" { 102 | if plp, ok := m[logFormat.QueueId]; ok { 103 | message := Message{ 104 | Time: logFormat.Time, 105 | To: logFormat.To, 106 | Status: logFormat.Status, 107 | Message: logFormat.Messages, 108 | } 109 | 110 | plp.Messages = append(plp.Messages, message) 111 | } 112 | } 113 | 114 | /* 115 | Oct 10 04:02:08 mail.example.com postfix/qmgr[18719]: DFBEFDBF00C5: removed 116 | */ 117 | // "removed" message is end of logs. then flush. 118 | if logFormat.Messages == "removed" { 119 | if plp, ok := m[logFormat.QueueId]; ok { 120 | jsonBytes, err := json.Marshal(plp) 121 | if err != nil { 122 | log.Fatal(err) 123 | } 124 | 125 | fmt.Fprintln(wtr, string(jsonBytes)) 126 | wtr.Flush() 127 | } 128 | } 129 | 130 | } 131 | }, 132 | } 133 | 134 | cobra.OnInitialize(initConfig) 135 | return cmd 136 | } 137 | --------------------------------------------------------------------------------