1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-18 18:17:03 +00:00

More metrics, better logging

This commit is contained in:
James Hillyerd
2012-10-24 12:40:17 -07:00
parent 606fb124a6
commit 81bb394755
6 changed files with 85 additions and 34 deletions

View File

@@ -60,8 +60,9 @@ func NewDataStore() *DataStore {
func (ds *DataStore) MailboxFor(emailAddress string) (*Mailbox, error) { func (ds *DataStore) MailboxFor(emailAddress string) (*Mailbox, error) {
name := ParseMailboxName(emailAddress) name := ParseMailboxName(emailAddress)
dir := HashMailboxName(name) dir := HashMailboxName(name)
section := dir[0:4] s1 := dir[0:3]
path := filepath.Join(ds.mailPath, section, dir) s2 := dir[0:6]
path := filepath.Join(ds.mailPath, s1, s2, dir)
if err := os.MkdirAll(path, 0770); err != nil { if err := os.MkdirAll(path, 0770); err != nil {
log.Error("Failed to create directory %v, %v", path, err) log.Error("Failed to create directory %v, %v", path, err)
return nil, err return nil, err
@@ -102,7 +103,7 @@ func (mb *Mailbox) GetMessages() ([]*Message, error) {
dec := gob.NewDecoder(bufio.NewReader(file)) dec := gob.NewDecoder(bufio.NewReader(file))
msg := new(Message) msg := new(Message)
if err = dec.Decode(msg); err != nil { if err = dec.Decode(msg); err != nil {
return nil, err return nil, fmt.Errorf("While decoding message: %v", err)
} }
file.Close() file.Close()
msg.mailbox = mb msg.mailbox = mb
@@ -257,7 +258,13 @@ func (m *Message) Close() error {
} }
} }
return m.createGob() err := m.createGob()
if err != nil {
log.Error("Failed to create gob: %v", err)
return err
}
return nil
} }
// Delete this Message from disk by removing both the gob and raw files // Delete this Message from disk by removing both the gob and raw files

View File

@@ -112,6 +112,7 @@ func (s *Server) startSession(id int, conn net.Conn) {
} }
if !commands[cmd] { if !commands[cmd] {
ss.send(fmt.Sprintf("500 Syntax error, %v command unrecognized", cmd)) ss.send(fmt.Sprintf("500 Syntax error, %v command unrecognized", cmd))
ss.warn("Unrecognized command: %v", cmd)
continue continue
} }
@@ -159,7 +160,7 @@ func (s *Server) startSession(id int, conn net.Conn) {
} }
} else { } else {
// readLine() returned an error // readLine() returned an error
ss.error("Connection error: %v", err) ss.warn("Connection error: %v", err)
if netErr, ok := err.(net.Error); ok { if netErr, ok := err.(net.Error); ok {
if netErr.Timeout() { if netErr.Timeout() {
ss.send("221 Idle timeout, bye bye") ss.send("221 Idle timeout, bye bye")
@@ -171,7 +172,7 @@ func (s *Server) startSession(id int, conn net.Conn) {
} }
} }
if ss.sendError != nil { if ss.sendError != nil {
ss.error("Network send error: %v", ss.sendError) ss.warn("Network send error: %v", ss.sendError)
} }
ss.info("Closing connection") ss.info("Closing connection")
} }
@@ -200,7 +201,7 @@ func (ss *Session) readyHandler(cmd string, arg string) {
m := re.FindStringSubmatch(arg) m := re.FindStringSubmatch(arg)
if m == nil { if m == nil {
ss.send("501 Was expecting MAIL arg syntax of FROM:<address>") ss.send("501 Was expecting MAIL arg syntax of FROM:<address>")
ss.warn("Bad MAIL argument: \"%v\"", arg) ss.warn("Bad MAIL argument: '%v'", arg)
return return
} }
from := m[1] from := m[1]
@@ -210,14 +211,14 @@ func (ss *Session) readyHandler(cmd string, arg string) {
args, ok := ss.parseArgs(m[2]) args, ok := ss.parseArgs(m[2])
if !ok { if !ok {
ss.send("501 Unable to parse MAIL ESMTP parameters") ss.send("501 Unable to parse MAIL ESMTP parameters")
ss.warn("Bad MAIL argument: \"%v\"", arg) ss.warn("Bad MAIL argument: '%v'", arg)
return return
} }
if args["SIZE"] != "" { if args["SIZE"] != "" {
size, err := strconv.ParseInt(args["SIZE"], 10, 32) size, err := strconv.ParseInt(args["SIZE"], 10, 32)
if err != nil { if err != nil {
ss.send("501 Unable to parse SIZE as an integer") ss.send("501 Unable to parse SIZE as an integer")
ss.error("Unable to parse SIZE '%v' as an integer", args["SIZE"]) ss.warn("Unable to parse SIZE '%v' as an integer", args["SIZE"])
return return
} }
if int(size) > ss.server.maxMessageBytes { if int(size) > ss.server.maxMessageBytes {
@@ -243,7 +244,7 @@ func (ss *Session) mailHandler(cmd string, arg string) {
case "RCPT": case "RCPT":
if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") { if (len(arg) < 4) || (strings.ToUpper(arg[0:3]) != "TO:") {
ss.send("501 Was expecting RCPT arg syntax of TO:<address>") ss.send("501 Was expecting RCPT arg syntax of TO:<address>")
ss.warn("Bad RCPT argument: \"%v\"", arg) ss.warn("Bad RCPT argument: '%v'", arg)
return return
} }
// This trim is probably too forgiving // This trim is probably too forgiving
@@ -260,7 +261,7 @@ func (ss *Session) mailHandler(cmd string, arg string) {
case "DATA": case "DATA":
if arg != "" { if arg != "" {
ss.send("501 DATA command should not have any arguments") ss.send("501 DATA command should not have any arguments")
ss.warn("Got unexpected args on DATA: \"%v\"", arg) ss.warn("Got unexpected args on DATA: '%v'", arg)
return return
} }
if ss.recipients.Len() > 0 { if ss.recipients.Len() > 0 {
@@ -309,7 +310,7 @@ func (ss *Session) dataHandler() {
ss.send("221 Idle timeout, bye bye") ss.send("221 Idle timeout, bye bye")
} }
} }
ss.error("Error: %v while reading", err) ss.warn("Error: %v while reading", err)
ss.enterState(QUIT) ss.enterState(QUIT)
return return
} }
@@ -333,7 +334,7 @@ func (ss *Session) dataHandler() {
if msgSize > ss.server.maxMessageBytes { if msgSize > ss.server.maxMessageBytes {
// Max message size exceeded // Max message size exceeded
ss.send("552 Maximum message size exceeded") ss.send("552 Maximum message size exceeded")
ss.error("Max message size exceeded while in DATA") ss.warn("Max message size exceeded while in DATA")
ss.reset() ss.reset()
// TODO: Should really cleanup the crap on filesystem... // TODO: Should really cleanup the crap on filesystem...
return return
@@ -373,10 +374,10 @@ func (ss *Session) send(msg string) {
} }
if _, err := fmt.Fprint(ss.conn, msg+"\r\n"); err != nil { if _, err := fmt.Fprint(ss.conn, msg+"\r\n"); err != nil {
ss.sendError = err ss.sendError = err
ss.error("Failed to send: \"%v\"", msg) ss.warn("Failed to send: '%v'", msg)
return return
} }
ss.trace("Sent: \"%v\"", msg) ss.trace(">> %v >>", msg)
} }
// readByteLine reads a line of input into the provided buffer. Does // readByteLine reads a line of input into the provided buffer. Does
@@ -416,7 +417,7 @@ func (ss *Session) readLine() (line string, err error) {
if err != nil { if err != nil {
return "", err return "", err
} }
ss.trace("Read: \"%v\"", strings.TrimRight(line, "\r\n")) ss.trace("<< %v <<", strings.TrimRight(line, "\r\n"))
return line, nil return line, nil
} }
@@ -427,19 +428,19 @@ func (ss *Session) parseCmd(line string) (cmd string, arg string, ok bool) {
case l == 0: case l == 0:
return "", "", true return "", "", true
case l < 4: case l < 4:
ss.error("Command too short: \"%v\"", line) ss.warn("Command too short: '%v'", line)
return "", "", false return "", "", false
case l == 4: case l == 4:
return strings.ToUpper(line), "", true return strings.ToUpper(line), "", true
case l == 5: case l == 5:
// Too long to be only command, too short to have args // Too long to be only command, too short to have args
ss.error("Mangled command: \"%v\"", line) ss.warn("Mangled command: '%v'", line)
return "", "", false return "", "", false
} }
// If we made it here, command is long enough to have args // If we made it here, command is long enough to have args
if line[4] != ' ' { if line[4] != ' ' {
// There wasn't a space after the command? // There wasn't a space after the command?
ss.error("Mangled command: \"%v\"", line) ss.warn("Mangled command: '%v'", line)
return "", "", false return "", "", false
} }
// I'm not sure if we should trim the args or not, but we will for now // I'm not sure if we should trim the args or not, but we will for now
@@ -456,7 +457,7 @@ func (ss *Session) parseArgs(arg string) (args map[string]string, ok bool) {
re := regexp.MustCompile(" (\\w+)=(\\w+)") re := regexp.MustCompile(" (\\w+)=(\\w+)")
pm := re.FindAllStringSubmatch(arg, -1) pm := re.FindAllStringSubmatch(arg, -1)
if pm == nil { if pm == nil {
ss.error("Failed to parse arg string: '%v'") ss.warn("Failed to parse arg string: '%v'")
return nil, false return nil, false
} }
for _, m := range pm { for _, m := range pm {
@@ -487,9 +488,13 @@ func (ss *Session) info(msg string, args ...interface{}) {
} }
func (ss *Session) warn(msg string, args ...interface{}) { func (ss *Session) warn(msg string, args ...interface{}) {
// Update metrics
expWarnsTotal.Add(1)
log.Warn("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...)) log.Warn("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...))
} }
func (ss *Session) error(msg string, args ...interface{}) { func (ss *Session) error(msg string, args ...interface{}) {
// Update metrics
expErrorsTotal.Add(1)
log.Error("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...)) log.Error("%v<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...))
} }

View File

@@ -19,14 +19,24 @@ type Server struct {
dataStore *DataStore dataStore *DataStore
} }
// Raw stat collectors
var expConnectsTotal = new(expvar.Int) var expConnectsTotal = new(expvar.Int)
var expConnectsCurrent = new(expvar.Int) var expConnectsCurrent = new(expvar.Int)
var expConnectsHist = new(expvar.String)
var expDeliveredTotal = new(expvar.Int) var expDeliveredTotal = new(expvar.Int)
var expDeliveredHist = new(expvar.String) var expErrorsTotal = new(expvar.Int)
var expWarnsTotal = new(expvar.Int)
// History of certain stats
var deliveredHist = list.New() var deliveredHist = list.New()
var connectsHist = list.New() var connectsHist = list.New()
var errorsHist = list.New()
var warnsHist = list.New()
// History rendered as comma delim string
var expDeliveredHist = new(expvar.String)
var expConnectsHist = new(expvar.String)
var expErrorsHist = new(expvar.String)
var expWarnsHist = new(expvar.String)
// Init a new Server object // Init a new Server object
func New() *Server { func New() *Server {
@@ -74,6 +84,8 @@ func metricsTicker(t *time.Ticker) {
_, ok = <-t.C _, ok = <-t.C
expDeliveredHist.Set(pushMetric(deliveredHist, expDeliveredTotal)) expDeliveredHist.Set(pushMetric(deliveredHist, expDeliveredTotal))
expConnectsHist.Set(pushMetric(connectsHist, expConnectsTotal)) expConnectsHist.Set(pushMetric(connectsHist, expConnectsTotal))
expErrorsHist.Set(pushMetric(errorsHist, expErrorsTotal))
expWarnsHist.Set(pushMetric(warnsHist, expWarnsTotal))
} }
} }
@@ -89,11 +101,15 @@ func pushMetric(history *list.List, ev expvar.Var) string {
func init() { func init() {
m := expvar.NewMap("smtp") m := expvar.NewMap("smtp")
m.Set("connectsTotal", expConnectsTotal) m.Set("ConnectsTotal", expConnectsTotal)
m.Set("connectsHist", expConnectsHist) m.Set("ConnectsHist", expConnectsHist)
m.Set("connectsCurrent", expConnectsCurrent) m.Set("ConnectsCurrent", expConnectsCurrent)
m.Set("deliveredTotal", expDeliveredTotal) m.Set("DeliveredTotal", expDeliveredTotal)
m.Set("deliveredHist", expDeliveredHist) m.Set("DeliveredHist", expDeliveredHist)
m.Set("ErrorsTotal", expErrorsTotal)
m.Set("ErrorsHist", expErrorsHist)
m.Set("WarnsTotal", expWarnsTotal)
m.Set("WarnsHist", expWarnsHist)
t := time.NewTicker(time.Minute) t := time.NewTicker(time.Minute)
go metricsTicker(t) go metricsTicker(t)

View File

@@ -83,16 +83,25 @@
} }
function displayMetrics(data, textStatus, jqXHR) { function displayMetrics(data, textStatus, jqXHR) {
// Non graphing
metric('uptime', data.uptime, timeFilter, false) metric('uptime', data.uptime, timeFilter, false)
// JavaScript history
metric('memstatsSys', data.memstats.Sys, sizeFilter, true) metric('memstatsSys', data.memstats.Sys, sizeFilter, true)
metric('memstatsHeapAlloc', data.memstats.HeapAlloc, sizeFilter, true) metric('memstatsHeapAlloc', data.memstats.HeapAlloc, sizeFilter, true)
metric('memstatsHeapSys', data.memstats.HeapSys, sizeFilter, true) metric('memstatsHeapSys', data.memstats.HeapSys, sizeFilter, true)
metric('memstatsHeapObjects', data.memstats.HeapObjects, numberFilter, true) metric('memstatsHeapObjects', data.memstats.HeapObjects, numberFilter, true)
metric('smtpConnectsTotal', data.smtp.connectsTotal, numberFilter, false) metric('smtpConnectsCurrent', data.smtp.ConnectsCurrent, numberFilter, true)
metric('smtpConnectsCurrent', data.smtp.connectsCurrent, numberFilter, true)
metric('smtpDeliveredTotal', data.smtp.deliveredTotal, numberFilter, false) // Server-side history
setHistory('smtpConnectsTotal', data.smtp.connectsHist) metric('smtpDeliveredTotal', data.smtp.DeliveredTotal, numberFilter, false)
setHistory('smtpDeliveredTotal', data.smtp.deliveredHist) setHistory('smtpDeliveredTotal', data.smtp.DeliveredHist)
metric('smtpConnectsTotal', data.smtp.ConnectsTotal, numberFilter, false)
setHistory('smtpConnectsTotal', data.smtp.ConnectsHist)
metric('smtpWarnsTotal', data.smtp.WarnsTotal, numberFilter, false)
setHistory('smtpWarnsTotal', data.smtp.WarnsHist)
metric('smtpErrorsTotal', data.smtp.ErrorsTotal, numberFilter, false)
setHistory('smtpErrorsTotal', data.smtp.ErrorsHist)
} }
function loadMetrics() { function loadMetrics() {
@@ -178,6 +187,18 @@ values over time.</p>
<td class="sparkline"><span id="s-smtpDeliveredTotal">.</span></td> <td class="sparkline"><span id="s-smtpDeliveredTotal">.</span></td>
<td>(60s)</td> <td>(60s)</td>
</tr> </tr>
<tr>
<th>Errors Logged:</th>
<td><span id="m-smtpErrorsTotal">.</span></td>
<td class="sparkline"><span id="s-smtpErrorsTotal"></span></td>
<td>(60s)</td>
</tr>
<tr>
<th>Warnings Logged:</th>
<td><span id="m-smtpWarnsTotal">.</span></td>
<td class="sparkline"><span id="s-smtpWarnsTotal"></span></td>
<td>(60s)</td>
</tr>
</table> </table>
<p class="last">&nbsp;</p> <p class="last">&nbsp;</p>
</div> </div>

View File

@@ -1,6 +1,7 @@
package web package web
import ( import (
"fmt"
"github.com/jhillyerd/inbucket/log" "github.com/jhillyerd/inbucket/log"
"html/template" "html/template"
"io" "io"
@@ -31,11 +32,11 @@ func MailboxList(w http.ResponseWriter, req *http.Request, ctx *Context) (err er
mb, err := ctx.DataStore.MailboxFor(name) mb, err := ctx.DataStore.MailboxFor(name)
if err != nil { if err != nil {
return err return fmt.Errorf("Failed to get mailbox for %v: %v", name, err)
} }
messages, err := mb.GetMessages() messages, err := mb.GetMessages()
if err != nil { if err != nil {
return err return fmt.Errorf("Failed to get messages for %v: %v", name, err)
} }
log.Trace("Got %v messsages", len(messages)) log.Trace("Got %v messsages", len(messages))

View File

@@ -83,6 +83,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Trace("Web: %v %v %v %v", req.RemoteAddr, req.Proto, req.Method, req.RequestURI) log.Trace("Web: %v %v %v %v", req.RemoteAddr, req.Proto, req.Method, req.RequestURI)
err = h(buf, req, ctx) err = h(buf, req, ctx)
if err != nil { if err != nil {
log.Error("Error handling %v: %v", req.RequestURI, err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }