173 lines
3.8 KiB
Go
173 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"github.com/golang/glog"
|
|
)
|
|
|
|
var (
|
|
logPath string
|
|
bindAddress string
|
|
maxBackString string
|
|
maxBack time.Duration
|
|
|
|
reLine = regexp.MustCompile(`([^ ]+) - - \[([^\]]+)\] "([^"]+)" ([0-9]+) ([0-9]+) "([^"]+)" "([^"]+)"`)
|
|
)
|
|
|
|
type logEntry struct {
|
|
address net.IP
|
|
timestamp time.Time
|
|
request string
|
|
referrer string
|
|
agent string
|
|
}
|
|
|
|
func parseNginxLine(line []byte) (logEntry, error) {
|
|
entry := logEntry{}
|
|
matches := reLine.FindSubmatch(line)
|
|
if matches == nil {
|
|
return entry, errors.New("Could not parse line.")
|
|
}
|
|
entry.address = net.ParseIP(string(matches[1]))
|
|
t, err := time.Parse("2/Jan/2006:15:04:05 -0700", string(matches[2]))
|
|
if err != nil {
|
|
return entry, err
|
|
}
|
|
entry.timestamp = t
|
|
entry.request = string(matches[3])
|
|
entry.referrer = string(matches[6])
|
|
entry.agent = string(matches[7])
|
|
return entry, nil
|
|
}
|
|
|
|
func getBackground(address net.IP) (string, error) {
|
|
f, err := os.Open(logPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// Seek to end of file.
|
|
if _, err = f.Seek(0, os.SEEK_END); err != nil {
|
|
return "", err
|
|
}
|
|
if _, err = f.Seek(-1, os.SEEK_CUR); err != nil {
|
|
return "", err
|
|
}
|
|
now := time.Now()
|
|
for {
|
|
// Seek backwards until next newline.
|
|
line := []byte{}
|
|
for {
|
|
d := make([]byte, 1)
|
|
if _, err = io.ReadFull(f, d); err != nil {
|
|
return "", err
|
|
}
|
|
if _, err = f.Seek(-2, os.SEEK_CUR); err != nil {
|
|
return "", err
|
|
}
|
|
if string(d) == "\n" {
|
|
break
|
|
}
|
|
line = append(d, line...)
|
|
}
|
|
if string(line) == "" {
|
|
continue
|
|
}
|
|
entry, err := parseNginxLine(line)
|
|
if err != nil {
|
|
glog.Errorf("Could not parse line %s: %v", line, err)
|
|
continue
|
|
}
|
|
if now.Sub(entry.timestamp) > maxBack {
|
|
break
|
|
}
|
|
if !entry.address.Equal(address) {
|
|
continue
|
|
}
|
|
if entry.referrer == "-" {
|
|
continue
|
|
}
|
|
u, err := url.Parse(entry.referrer)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if !strings.Contains(u.Host, "google") || u.Path != "/search" {
|
|
continue
|
|
}
|
|
glog.Infof("Entry: %s, %s", entry.address.String(), entry.referrer)
|
|
query := u.Query().Get("q")
|
|
if query != "" {
|
|
t := humanize.Time(entry.timestamp)
|
|
return fmt.Sprintf("Address %s searched %s for '%s' %s", address.String(), u.Host, query, t), nil
|
|
}
|
|
}
|
|
|
|
return "", errors.New("No background.")
|
|
}
|
|
|
|
type apiResponse struct {
|
|
Error string `json:error`
|
|
Data string `json:data`
|
|
}
|
|
|
|
func handleCheck(w http.ResponseWriter, r *http.Request) {
|
|
response := apiResponse{}
|
|
defer func() {
|
|
data, err := json.Marshal(response)
|
|
if err != nil {
|
|
glog.Error("While marshalling response: %v", err)
|
|
w.WriteHeader(500)
|
|
w.Write([]byte("Internal server error."))
|
|
}
|
|
w.WriteHeader(200)
|
|
w.Write(data)
|
|
}()
|
|
addressString := r.URL.Query().Get("address")
|
|
if addressString == "" {
|
|
response.Error = "No address specified."
|
|
return
|
|
}
|
|
address := net.ParseIP(addressString)
|
|
if address == nil {
|
|
response.Error = "Invalid address."
|
|
return
|
|
}
|
|
|
|
background, err := getBackground(address)
|
|
if err != nil {
|
|
response.Error = "No background."
|
|
glog.Errorf("While getting background for %s: %v", addressString, err)
|
|
return
|
|
}
|
|
response.Data = background
|
|
}
|
|
|
|
func main() {
|
|
flag.StringVar(&logPath, "nginx_log_path", "/var/log/nginx/access_log", "Path to NGINX access log.")
|
|
flag.StringVar(&bindAddress, "bind_address", ":8080", "Port to bind HTTP/JSON interface to.")
|
|
flag.StringVar(&maxBackString, "max_back", "5m", "How far back in time to look for records.")
|
|
flag.Parse()
|
|
|
|
d, err := time.ParseDuration(maxBackString)
|
|
if err != nil {
|
|
glog.Exit(err)
|
|
}
|
|
maxBack = d
|
|
|
|
glog.Infof("Starting on %s, watching %s.", bindAddress, logPath)
|
|
http.HandleFunc("/api/1/background", handleCheck)
|
|
glog.Exit(http.ListenAndServe(bindAddress, nil))
|
|
}
|