From 07c93aafb7aaa23988c40bcce493d8ef17ef21b7 Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Sun, 17 Jan 2016 10:48:37 +0000
Subject: [PATCH] use strings for message timestamps

---
 api.go           | 12 +++---------
 api_test.go      |  2 +-
 server/db.go     | 10 +++++++---
 server/server.go | 29 ++++++++++++++++++++++-------
 unique_id.go     | 12 ++++++------
 5 files changed, 39 insertions(+), 26 deletions(-)

diff --git a/api.go b/api.go
index 21bdf45..e782c30 100644
--- a/api.go
+++ b/api.go
@@ -11,7 +11,7 @@ import (
 type Message map[string]interface{}
 
 var mandatoryAttributes = []string{
-	"user", "message", "stamp",
+	"user", "message", "timestamp",
 }
 
 func (m Message) IsValid() bool {
@@ -30,15 +30,9 @@ func (m Message) GetString(key string) string {
 	return ""
 }
 
-func (m Message) GetInt(key string) int64 {
-	if value, ok := m[key]; ok {
-		return int64(value.(float64))
-	}
-	return 0
-}
-
 func (m Message) Stamp() time.Time {
-	return time.Unix(m.GetInt("stamp"), 0)
+	t, _ := time.Parse(m.GetString("timestamp"), time.RFC3339)
+	return t
 }
 
 func (m Message) ToJSON() []byte {
diff --git a/api_test.go b/api_test.go
index b9d379f..63c4463 100644
--- a/api_test.go
+++ b/api_test.go
@@ -6,7 +6,7 @@ import (
 
 var okTestData = `
 {
-  "stamp": 1394393566,
+  "timestamp": "2009-11-10T23:00:00Z",
   "user": "test",
   "message": "sample message"
 }
diff --git a/server/db.go b/server/db.go
index 4601799..ad80a93 100644
--- a/server/db.go
+++ b/server/db.go
@@ -31,6 +31,9 @@ import (
 // and store the message id under index/<key>/<value>/<id> (yes, the
 // id appears twice, it's part of the key just to ensure uniqueness).
 //
+// Keys and values can be anything as long as they don't contain a
+// null byte (which is used internally as the primary key separator).
+//
 type DB struct {
 	leveldb      *levigo.DB
 	cache        *levigo.Cache
@@ -80,7 +83,8 @@ func NewDB(path string, dbopts *DBOptions) *DB {
 
 	// Exclude some keys by default.
 	db.excludedKeys["message"] = struct{}{}
-	db.excludedKeys["stamp"] = struct{}{}
+	db.excludedKeys["stamp"] = struct{}{}     // old field for timestamp
+	db.excludedKeys["timestamp"] = struct{}{}
 	for _, key := range dbopts.ExcludedKeys {
 		db.excludedKeys[key] = struct{}{}
 	}
@@ -99,7 +103,7 @@ func (db *DB) isExcluded(key string) bool {
 	return ok
 }
 
-var keySeparator = byte('|')
+var keySeparator = byte(0)
 
 func makeKey(prefix string, parts ...[]byte) []byte {
 	allParts := make([][]byte, 0, len(parts)+1)
@@ -305,7 +309,7 @@ func (db *DB) Query(q map[string]string) ([]audit.Message, error) {
 		}
 	}
 	if len(errors) > 0 {
-		return nil, fmt.Errorf("Query errors: %s", strings.Join(errors, "; "))
+		return nil, fmt.Errorf("query errors: %s", strings.Join(errors, "; "))
 	}
 
 	// Retrieve rows from the key set.
diff --git a/server/server.go b/server/server.go
index 057c19d..a607838 100644
--- a/server/server.go
+++ b/server/server.go
@@ -19,6 +19,17 @@ func NewHttpServer(db *DB) *HttpServer {
 	return &HttpServer{database: db}
 }
 
+func remoteHost(r *http.Request) string {
+	if host, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
+		return host
+	}
+	return r.RemoteAddr
+}
+
+func dumpMsg(msg audit.Message) string {
+	return strings.TrimRight(string(msg.ToJSON()), "\n")
+}
+
 // Write handler: the input data is already a JSON message.
 func (h *HttpServer) writeHandler(w http.ResponseWriter, r *http.Request) {
 	if r.Header.Get("Content-Type") != "application/json" {
@@ -32,18 +43,22 @@ func (h *HttpServer) writeHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	// Log all received messages, even if there is an error later.
+	log.Printf("%s: received message: %s", remoteHost(r), dumpMsg(msg))
+
+	if !msg.IsValid() {
+		log.Printf("%s: invalid message", remoteHost(r))
+		http.Error(w, "Invalid Message", http.StatusBadRequest)
+		return
+	}
+
 	if err := h.database.Write(msg); err != nil {
+		log.Printf("%s: error writing to database: %v", remoteHost(r), err)
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
 
-	// Request was successful, log it and return empty 200 OK reply.
-	var hostStr string
-	if host, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
-		hostStr = host
-	}
-	logText := strings.TrimRight(string(msg.ToJSON()), "\n")
-	log.Printf("%s: received message: %s", hostStr, logText)
+	// Request was successful, return an empty 200 OK reply.
 	w.WriteHeader(200)
 }
 
diff --git a/unique_id.go b/unique_id.go
index 686e17c..ebe3ce1 100644
--- a/unique_id.go
+++ b/unique_id.go
@@ -1,7 +1,6 @@
 package audit
 
 import (
-	"bytes"
 	"encoding/binary"
 	"math/rand"
 	"time"
@@ -17,12 +16,13 @@ func init() {
 // resulting IDs can be sorted lexicographically preserving the time
 // ordering.
 func NewUniqueId(t time.Time) []byte {
-	var b bytes.Buffer
+	var b [16]byte
 
 	// The key consists of serialized time + sufficiently large
-	// random number. This should allow a high insertion rate.
-	binary.Write(&b, binary.BigEndian, t.UnixNano())
-	binary.Write(&b, binary.BigEndian, rand.Int63())
+	// random number. This should allow a high insertion rate, and
+	// hopefully prevent conflicts considering the rand global lock.
+	binary.BigEndian.PutUint64(b[:8], uint64(t.UnixNano()))
+	binary.BigEndian.PutUint64(b[8:16], uint64(rand.Int63()))
 
-	return b.Bytes()
+	return b[:]
 }
-- 
GitLab