Skip to content

Commit f4dc6f0

Browse files
joeybloggsjoeybloggs
authored andcommitted
Add initial email handler logic.
1 parent f23bae7 commit f4dc6f0

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed

handlers/email/email.go

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
package email
2+
3+
import (
4+
"bytes"
5+
"html/template"
6+
stdlog "log"
7+
"os"
8+
"time"
9+
10+
"gopkg.in/gomail.v2"
11+
12+
"github.com/go-playground/log"
13+
)
14+
15+
// FormatFunc is the function that the workers use to create
16+
// a new Formatter per worker allowing reusable go routine safe
17+
// variable to be used within your Formatter function.
18+
type FormatFunc func() Formatter
19+
20+
// Formatter is the function used to format the Email entry
21+
type Formatter func(e *log.Entry) *gomail.Message
22+
23+
const (
24+
space = byte(' ')
25+
equals = byte('=')
26+
colon = byte(':')
27+
base10 = 10
28+
v = "%v"
29+
gopath = "GOPATH"
30+
contentType = "text/html"
31+
defaultTemplate = `<!DOCTYPE html>
32+
<html>
33+
<body>
34+
<h2>{{ .Message }}</h2>
35+
{{ if ne .Line 0 }}
36+
{{ display_file . }}:{{ .Line }}
37+
{{ end }}
38+
{{ range $f := .Fields }}
39+
<p><b>{{ $f.Key }}</b>: {{ $f.Value }}</p>
40+
{{ end }}
41+
</body>
42+
</html>`
43+
)
44+
45+
// Email is an instance of the email logger
46+
type Email struct {
47+
buffer uint // channel buffer
48+
numWorkers uint
49+
formatFunc FormatFunc
50+
timestampFormat string
51+
gopath string
52+
fileDisplay log.FilenameDisplay
53+
template *template.Template
54+
templateHTML string
55+
host string
56+
port int
57+
username string
58+
password string
59+
from string
60+
to []string
61+
keepalive time.Duration
62+
}
63+
64+
// New returns a new instance of the email logger
65+
func New(host string, port int, username string, password string, from string, to []string) *Email {
66+
67+
e := &Email{
68+
buffer: 0,
69+
numWorkers: 1,
70+
timestampFormat: log.DefaultTimeFormat,
71+
fileDisplay: log.Lshortfile,
72+
templateHTML: defaultTemplate,
73+
host: host,
74+
port: port,
75+
username: username,
76+
password: password,
77+
from: from,
78+
to: to,
79+
keepalive: time.Second * 30,
80+
}
81+
82+
e.formatFunc = e.defaultFormatFunc
83+
84+
return e
85+
}
86+
87+
// SetKeepAliveTimout tells Email how long to keep the smtp connection
88+
// open when no messsages are being sent; it will automatically reconnect
89+
// on next message that is received.
90+
func (email *Email) SetKeepAliveTimout(keepAlive time.Duration) {
91+
email.keepalive = keepAlive
92+
}
93+
94+
// SetEmailTemplate sets Email's html tempalte to be used for email body
95+
func (email *Email) SetEmailTemplate(htmlTemplate string) {
96+
email.templateHTML = htmlTemplate
97+
}
98+
99+
// SetFilenameDisplay tells Email the filename, when present, how to display
100+
func (email *Email) SetFilenameDisplay(fd log.FilenameDisplay) {
101+
email.fileDisplay = fd
102+
}
103+
104+
// SetBuffersAndWorkers sets the channels buffer size and number of concurrent workers.
105+
// These settings should be thought about together, hence setting both in the same function.
106+
func (email *Email) SetBuffersAndWorkers(size uint, workers uint) {
107+
email.buffer = size
108+
109+
if workers == 0 {
110+
// just in case no log registered yet
111+
stdlog.Println("Invalid number of workers specified, setting to 1")
112+
log.Warn("Invalid number of workers specified, setting to 1")
113+
114+
workers = 1
115+
}
116+
117+
email.numWorkers = workers
118+
}
119+
120+
// SetTimestampFormat sets Email's timestamp output format
121+
// Default is : "2006-01-02T15:04:05.000000000Z07:00"
122+
func (email *Email) SetTimestampFormat(format string) {
123+
email.timestampFormat = format
124+
}
125+
126+
// SetFormatFunc sets FormatFunc each worker will call to get
127+
// a Formatter func
128+
func (email *Email) SetFormatFunc(fn FormatFunc) {
129+
email.formatFunc = fn
130+
}
131+
132+
// Run starts the logger consuming on the returned channed
133+
func (email *Email) Run() chan<- *log.Entry {
134+
135+
// pre-setup
136+
if email.fileDisplay == log.Llongfile {
137+
// gather $GOPATH for use in stripping off of full name
138+
// if not found still ok as will be blank
139+
email.gopath = os.Getenv(gopath)
140+
if len(email.gopath) != 0 {
141+
email.gopath += string(os.PathSeparator) + "src" + string(os.PathSeparator)
142+
}
143+
}
144+
145+
// parse email htmlTemplate, will panic if fails
146+
email.template = template.Must(template.New("email").Funcs(
147+
template.FuncMap{
148+
"display_file": func(e *log.Entry) (file string) {
149+
file = e.File
150+
151+
if email.fileDisplay == log.Lshortfile {
152+
153+
for i := len(file) - 1; i > 0; i-- {
154+
if file[i] == '/' {
155+
file = file[i+1:]
156+
break
157+
}
158+
}
159+
} else {
160+
file = file[len(email.gopath):]
161+
}
162+
return
163+
},
164+
},
165+
).Parse(email.templateHTML))
166+
167+
ch := make(chan *log.Entry, email.buffer)
168+
169+
for i := 0; i <= int(email.numWorkers); i++ {
170+
go email.handleLog(ch)
171+
}
172+
return ch
173+
}
174+
175+
func (email *Email) defaultFormatFunc() Formatter {
176+
var err error
177+
b := new(bytes.Buffer)
178+
message := gomail.NewMessage()
179+
message.SetHeader("From", email.from)
180+
message.SetHeader("To", email.to...)
181+
182+
return func(e *log.Entry) *gomail.Message {
183+
b.Reset()
184+
if err = email.template.ExecuteTemplate(b, "email", e); err != nil {
185+
log.WithFields(log.F("error", err)).Error("Error parsing Email handler template")
186+
}
187+
188+
message.SetHeader("Subject", e.Message)
189+
message.SetBody(contentType, b.String())
190+
191+
return message
192+
}
193+
}
194+
195+
func (email *Email) handleLog(entries <-chan *log.Entry) {
196+
var e *log.Entry
197+
var s gomail.SendCloser
198+
var err error
199+
var open bool
200+
var message *gomail.Message
201+
var count uint8
202+
203+
formatter := email.formatFunc()
204+
205+
d := gomail.NewDialer(email.host, email.port, email.username, email.password)
206+
207+
for {
208+
select {
209+
case e = <-entries:
210+
count = 0
211+
message = formatter(e)
212+
213+
REOPEN:
214+
// check if smtp connection open
215+
if !open {
216+
count++
217+
if s, err = d.Dial(); err != nil {
218+
log.WithFields(log.F("error", err)).Warn("ERROR connection to smtp server")
219+
220+
if count == 3 {
221+
// we tried to reconnect...
222+
e.Consumed()
223+
break
224+
}
225+
226+
goto REOPEN
227+
}
228+
count = 0
229+
open = true
230+
}
231+
232+
RESEND:
233+
count++
234+
if err = gomail.Send(s, message); err != nil {
235+
log.WithFields(log.F("error", err)).Warn("ERROR sending to smtp server, retrying")
236+
237+
if count == 3 {
238+
// maybe we got disconnected...
239+
open = false
240+
s.Close()
241+
goto REOPEN
242+
} else if count > 3 {
243+
// we reopened and tried 2 more times, can;t say we didn't try
244+
log.WithFields(log.F("error", err)).Alert("ERROR sending log via EMAIL, RETRY and REOPEN failed")
245+
e.Consumed()
246+
break
247+
}
248+
249+
goto RESEND
250+
}
251+
e.Consumed()
252+
253+
case <-time.After(email.keepalive):
254+
if open {
255+
s.Close()
256+
open = false
257+
}
258+
}
259+
}
260+
}

0 commit comments

Comments
 (0)