Skip to content

Commit 27ee1ab

Browse files
joeybloggsjoeybloggs
authored andcommitted
Merge branch 'email-handler'
2 parents f23bae7 + d138964 commit 27ee1ab

File tree

3 files changed

+518
-1
lines changed

3 files changed

+518
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## log
22
<img align="right" src="https://raw.githubusercontent.com/go-playground/log/master/logo.png">
3-
![Project status](https://img.shields.io/badge/version-2.2-green.svg)
3+
![Project status](https://img.shields.io/badge/version-2.3-green.svg)
44
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/log/branches/master/badge.svg)](https://semaphoreci.com/joeybloggs/log)
55
[![Coverage Status](https://coveralls.io/repos/github/go-playground/log/badge.svg?branch=master)](https://coveralls.io/github/go-playground/log?branch=master)
66
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/log)](https://goreportcard.com/report/github.com/go-playground/log)

handlers/email/email.go

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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(email *Email) 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+
gopath = "GOPATH"
25+
contentType = "text/html"
26+
defaultTemplate = `<!DOCTYPE html>
27+
<html>
28+
<body>
29+
<h2>{{ .Message }}</h2>
30+
{{ if ne .ApplicationID "" }}
31+
<h4>{{ .ApplicationID }}</h4>
32+
{{ end }}
33+
<p>{{ .Level.String }}</p>
34+
<p>{{ ts . }}</p>
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+
return &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+
formatFunc: defaultFormatFunc,
81+
}
82+
}
83+
84+
// SetKeepAliveTimout tells Email how long to keep the smtp connection
85+
// open when no messsages are being sent; it will automatically reconnect
86+
// on next message that is received.
87+
func (email *Email) SetKeepAliveTimout(keepAlive time.Duration) {
88+
email.keepalive = keepAlive
89+
}
90+
91+
// SetEmailTemplate sets Email's html tempalte to be used for email body
92+
func (email *Email) SetEmailTemplate(htmlTemplate string) {
93+
email.templateHTML = htmlTemplate
94+
}
95+
96+
// SetFilenameDisplay tells Email the filename, when present, how to display
97+
func (email *Email) SetFilenameDisplay(fd log.FilenameDisplay) {
98+
email.fileDisplay = fd
99+
}
100+
101+
// SetBuffersAndWorkers sets the channels buffer size and number of concurrent workers.
102+
// These settings should be thought about together, hence setting both in the same function.
103+
func (email *Email) SetBuffersAndWorkers(size uint, workers uint) {
104+
email.buffer = size
105+
106+
if workers == 0 {
107+
// just in case no log registered yet
108+
stdlog.Println("Invalid number of workers specified, setting to 1")
109+
log.Warn("Invalid number of workers specified, setting to 1")
110+
111+
workers = 1
112+
}
113+
114+
email.numWorkers = workers
115+
}
116+
117+
// From returns the Email's From address
118+
func (email *Email) From() string {
119+
return email.from
120+
}
121+
122+
// To returns the Email's To address
123+
func (email *Email) To() []string {
124+
return email.to
125+
}
126+
127+
// SetTimestampFormat sets Email's timestamp output format
128+
// Default is : "2006-01-02T15:04:05.000000000Z07:00"
129+
func (email *Email) SetTimestampFormat(format string) {
130+
email.timestampFormat = format
131+
}
132+
133+
// SetFormatFunc sets FormatFunc each worker will call to get
134+
// a Formatter func
135+
func (email *Email) SetFormatFunc(fn FormatFunc) {
136+
email.formatFunc = fn
137+
}
138+
139+
// Run starts the logger consuming on the returned channed
140+
func (email *Email) Run() chan<- *log.Entry {
141+
142+
// pre-setup
143+
if email.fileDisplay == log.Llongfile {
144+
// gather $GOPATH for use in stripping off of full name
145+
// if not found still ok as will be blank
146+
email.gopath = os.Getenv(gopath)
147+
if len(email.gopath) != 0 {
148+
email.gopath += string(os.PathSeparator) + "src" + string(os.PathSeparator)
149+
}
150+
}
151+
152+
// parse email htmlTemplate, will panic if fails
153+
email.template = template.Must(template.New("email").Funcs(
154+
template.FuncMap{
155+
"display_file": func(e *log.Entry) (file string) {
156+
157+
file = e.File
158+
if email.fileDisplay == log.Lshortfile {
159+
160+
for i := len(file) - 1; i > 0; i-- {
161+
if file[i] == '/' {
162+
file = file[i+1:]
163+
break
164+
}
165+
}
166+
} else {
167+
file = file[len(email.gopath):]
168+
}
169+
170+
return
171+
},
172+
"ts": func(e *log.Entry) (ts string) {
173+
ts = e.Timestamp.Format(email.timestampFormat)
174+
return
175+
},
176+
},
177+
).Parse(email.templateHTML))
178+
179+
ch := make(chan *log.Entry, email.buffer)
180+
181+
for i := 0; i <= int(email.numWorkers); i++ {
182+
go email.handleLog(ch)
183+
}
184+
return ch
185+
}
186+
187+
func defaultFormatFunc(email *Email) Formatter {
188+
var err error
189+
b := new(bytes.Buffer)
190+
message := gomail.NewMessage()
191+
message.SetHeader("From", email.from)
192+
message.SetHeader("To", email.to...)
193+
194+
return func(e *log.Entry) *gomail.Message {
195+
b.Reset()
196+
if err = email.template.ExecuteTemplate(b, "email", e); err != nil {
197+
log.WithFields(log.F("error", err)).Error("Error parsing Email handler template")
198+
}
199+
200+
message.SetHeader("Subject", e.Message)
201+
message.SetBody(contentType, b.String())
202+
203+
return message
204+
}
205+
}
206+
207+
func (email *Email) handleLog(entries <-chan *log.Entry) {
208+
var e *log.Entry
209+
var s gomail.SendCloser
210+
var err error
211+
var open bool
212+
var alreadyTriedSending bool
213+
var message *gomail.Message
214+
var count uint8
215+
216+
formatter := email.formatFunc(email)
217+
218+
d := gomail.NewDialer(email.host, email.port, email.username, email.password)
219+
220+
for {
221+
select {
222+
case e = <-entries:
223+
count = 0
224+
alreadyTriedSending = false
225+
message = formatter(e)
226+
227+
REOPEN:
228+
// check if smtp connection open
229+
if !open {
230+
count++
231+
if s, err = d.Dial(); err != nil {
232+
log.WithFields(log.F("error", err)).Warn("ERROR connection to smtp server")
233+
234+
if count == 3 {
235+
// we tried to reconnect...
236+
e.Consumed()
237+
break
238+
}
239+
240+
goto REOPEN
241+
}
242+
count = 0
243+
open = true
244+
}
245+
246+
RESEND:
247+
count++
248+
if err = gomail.Send(s, message); err != nil {
249+
250+
log.WithFields(log.F("error", err)).Warn("ERROR sending to smtp server, retrying")
251+
252+
if count == 3 && !alreadyTriedSending {
253+
// maybe we got disconnected...
254+
alreadyTriedSending = true
255+
open = false
256+
s.Close()
257+
goto REOPEN
258+
} else if alreadyTriedSending {
259+
// we reopened and tried 2 more times, can;t say we didn't try
260+
log.WithFields(log.F("error", err)).Alert("ERROR sending log via EMAIL, RETRY and REOPEN failed")
261+
e.Consumed()
262+
break
263+
}
264+
265+
goto RESEND
266+
}
267+
268+
e.Consumed()
269+
270+
case <-time.After(email.keepalive):
271+
if open {
272+
s.Close()
273+
open = false
274+
}
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)