diff --git a/browser.go b/browser.go
index 09e506b10ec4b8945ef1a6687ea406e208a5e677..e4c546d5a7a32ec2cb882a6c4513696b5c7887a3 100644
--- a/browser.go
+++ b/browser.go
@@ -21,6 +21,8 @@ type report struct {
 
 type ReportHandler struct{}
 
+func (h *ReportHandler) Name() string { return "report-api" }
+
 func (h *ReportHandler) Parse(contentType string, req *http.Request) ([]Event, error) {
 	if contentType != "application/reports+json" {
 		return nil, ErrNoMatch
diff --git a/collector.go b/collector.go
index 24e3a9a29f50aa8fe30fa85973651f5b58169ae7..aefe291229aea6db26fdbb1a61c91e035f50d18c 100644
--- a/collector.go
+++ b/collector.go
@@ -16,6 +16,7 @@ import (
 var ErrNoMatch = errors.New("no match")
 
 type Handler interface {
+	Name() string
 	Parse(string, *http.Request) ([]Event, error)
 	ParseMIME(*enmime.Part) ([]Event, error)
 }
@@ -60,19 +61,22 @@ func (c *Collector) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 
 	// Find a handler that can successfully parse the request, and
 	// get a list of Events.
-	var events []Event
-	matched := false
-hloop:
 	for _, h := range c.handlers {
-		var err error
-		events, err = h.Parse(ct, req)
+		events, err := h.Parse(ct, req)
 		switch err {
-		case ErrNoMatch:
-			continue
 		case nil:
-			matched = true
+			// Send the parsed events to the Sink.
+			for _, e := range events {
+				c.sink.Send(e)
+				reportsByType.WithLabelValues(
+					e.GetString("type"), e.GetString("domain")).Inc()
+			}
+
+			log.Printf("http: %s: received %d events from %s", h.Name(), len(events), getRemoteIP(req))
 			w.WriteHeader(http.StatusOK)
-			break hloop
+			return
+		case ErrNoMatch:
+			continue
 		default:
 			log.Printf("error parsing report (%s): %v", ct, err)
 			http.Error(w, err.Error(), http.StatusBadRequest)
@@ -80,19 +84,8 @@ hloop:
 		}
 	}
 
-	if !matched {
-		log.Printf("no matching handlers for \"%s\"", ct)
-		http.Error(w, "No matching handlers", http.StatusBadRequest)
-		return
-	}
-
-	// Augment the Events with additional information obtained
-	// from the HTTP request, and send them to the forwarder.
-	for _, e := range events {
-		c.sink.Send(e)
-		reportsByType.WithLabelValues(
-			e.GetString("type"), e.GetString("domain")).Inc()
-	}
+	log.Printf("no matching handlers for \"%s\"", ct)
+	http.Error(w, "No matching handlers", http.StatusBadRequest)
 }
 
 func (c *Collector) ServeSMTP(peer smtpd.Peer, env smtpd.Envelope) error {
@@ -104,18 +97,20 @@ func (c *Collector) ServeSMTP(peer smtpd.Peer, env smtpd.Envelope) error {
 
 	// Find a handler that can successfully parse the request, and
 	// get a list of Events.
-	var events []Event
-	matched := false
-hloop:
 	for _, h := range c.handlers {
-		var err error
-		events, err = h.ParseMIME(msgParts)
+		events, err := h.ParseMIME(msgParts)
 		switch err {
+		case nil:
+			// Send the parsed events to the Sink.
+			for _, e := range events {
+				c.sink.Send(e)
+				reportsByType.WithLabelValues(
+					e.GetString("type"), e.GetString("domain")).Inc()
+			}
+			log.Printf("smtp: %s: received %d events from %s", h.Name(), len(events), env.Sender)
+			return nil
 		case ErrNoMatch:
 			continue
-		case nil:
-			matched = true
-			break hloop
 		default:
 			log.Printf("smtp: error handling report: %v", err)
 			// Discard the message.
@@ -123,19 +118,8 @@ hloop:
 		}
 	}
 
-	if !matched {
-		log.Printf("smtp: no matching handlers")
-		// Discard the message.
-		return nil
-	}
-
-	// Augment the Events with additional information obtained
-	// from the HTTP request, and send them to the forwarder.
-	for _, e := range events {
-		c.sink.Send(e)
-		reportsByType.WithLabelValues(
-			e.GetString("type"), e.GetString("domain")).Inc()
-	}
+	log.Printf("smtp: no matching handlers")
+	// Discard the message.
 	return nil
 }
 
diff --git a/csp.go b/csp.go
index 698c38eaf93b0131e912071e3711356f6b3071c8..a69445784f40150d2467973e7334aa1254dd37a9 100644
--- a/csp.go
+++ b/csp.go
@@ -27,6 +27,8 @@ type legacyCSPReportContainer struct {
 
 type LegacyCSPHandler struct{}
 
+func (h *LegacyCSPHandler) Name() string { return "legacy-csp" }
+
 func (h *LegacyCSPHandler) Parse(contentType string, req *http.Request) ([]Event, error) {
 	if contentType != "application/csp-report" {
 		return nil, ErrNoMatch
diff --git a/dmarc.go b/dmarc.go
index 064036cb13f895a9c238cf23a9591d6a76656b0a..152a1107a7339691a1e49c1f17fdc87c38d357a4 100644
--- a/dmarc.go
+++ b/dmarc.go
@@ -75,6 +75,8 @@ type dmarcReport struct {
 
 type DMARCHandler struct{}
 
+func (h *DMARCHandler) Name() string { return "dmarc" }
+
 func (h *DMARCHandler) parseDMARC(r io.Reader) ([]Event, error) {
 	var report dmarcReport
 	if err := xml.NewDecoder(r).Decode(&report); err != nil {
diff --git a/tlsrpt.go b/tlsrpt.go
index 3f169e73673e6e54c1df64f0d9d8ae7fbda33421..d293b0d7fbabebcba9924789c9a1f9d5c2c931ba 100644
--- a/tlsrpt.go
+++ b/tlsrpt.go
@@ -51,6 +51,8 @@ type tlsrpt struct {
 
 type TLSRPTHandler struct{}
 
+func (h *TLSRPTHandler) Name() string { return "tls-rpt" }
+
 func (h *TLSRPTHandler) Parse(contentType string, req *http.Request) ([]Event, error) {
 	var r io.Reader
 	switch contentType {