package aproxy
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path"
"sort"
"strings"
"sync"
"time"
)
const logPath = "/var/log/aproxy"
// Event represents a tracking event
type Event struct {
TS time.Time
URL string
RemoteAddr string
UserAgent string
AcceptLanguage string
Referer string
Method string
Host string
}
// Tracker represents a tracker that logs info about http requests and logs them
type Tracker struct {
c chan Event
wg sync.WaitGroup
buffer bytes.Buffer
mux sync.Mutex
}
func NewTracker() *Tracker {
t := &Tracker{}
t.c = make(chan Event, 100000)
//t.done = make(chan bool, 1)
t.wg = sync.WaitGroup{}
os.MkdirAll(logPath, 0700)
// Create func that processes events.
// start tracking...
t.wg.Add(1)
go func() {
tick := time.Tick(60 * time.Second)
process := true
for process {
select {
case evt, ok := <-t.c:
if !ok { // Channel closed. Break out.
process = false
break
}
// Handle event.
data, err := json.Marshal(evt)
// If not if else/ then implement a function that does the stuff..
if err != nil {
log.Printf("Failed marshalling tracking json: %v", err)
} else {
t.buffer.Write(data)
t.buffer.Write([]byte("\n"))
// Roll buffer if too big... (2MB at most)
if t.buffer.Len() > 1024*1024*2 {
t.flush()
}
}
case <-tick:
// Flush buffer.
t.flush()
}
}
t.wg.Done()
}()
return t
}
// flush flushes the buffer to disk.
func (t *Tracker) flush() {
t.mux.Lock()
defer t.mux.Unlock()
data := t.buffer.Bytes()
if len(data) == 0 {
return
}
dir, err := os.Open(logPath)
if err != nil {
return
}
infos, err := dir.Readdir(0)
if err != nil {
return
}
// Compute today string.
today := time.Now().Format("2006-01-02")
tfiles := []os.FileInfo{}
// parse names. Find correct file.
for _, i := range infos {
// Handle infos...
if strings.HasPrefix(i.Name(), today) {
tfiles = append(tfiles, i)
}
}
var file *os.File
if len(tfiles) == 0 {
// Create a new file.
file, err = os.Create(path.Join(logPath, today+"-1.log"))
if err != nil {
panic(err)
return
}
} else {
sort.Slice(tfiles, func(i, j int) bool {
return tfiles[i].Name() > tfiles[j].Name()
})
if tfiles[0].Size() > 1024*1024*50 {
file, err = os.Create(path.Join(logPath, fmt.Sprintf("%v-%v.log", today, len(tfiles)+1)))
if err != nil {
panic(err)
return
}
} else {
file, err = os.OpenFile(path.Join(logPath, tfiles[0].Name()), os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
panic(err)
return
}
}
}
defer file.Close()
file.Write(data)
t.buffer.Reset()
}
func (p *Tracker) Shutdown() {
close(p.c)
p.wg.Wait()
p.flush()
}
// Perform logging...
func (p *Tracker) Track(ctx context.Context, r *http.Request) {
// Consider respecting DNT header?
h := r.Header
evt := Event{
TS: time.Now(),
URL: r.URL.String(),
RemoteAddr: r.RemoteAddr,
UserAgent: strings.Join(h["User-Agent"], "|"),
AcceptLanguage: strings.Join(h["Accept-Language"], "|"),
Referer: strings.Join(h["Referer"], "|"),
Method: r.Method,
Host: r.Host,
}
p.c <- evt
}