package aproxy import ( "bytes" "context" "encoding/json" "fmt" "net/http" "os" "path" "sort" "strings" "sync" "time" ) // 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 } func NewTracker() *Tracker { t := &Tracker{} t.c = make(chan Event, 100000) //t.done = make(chan bool, 1) t.wg = sync.WaitGroup{} os.MkdirAll("/var/log/aproxy", 0700) // Create func that processes events. // start tracking... t.wg.Add(1) go func() { for evt := range t.c { // Handle event. data, err := json.Marshal(evt) if err != nil { fmt.Println("Boom") continue } 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() } } t.wg.Done() }() return t } // flush flushes the buffer to disk. func (t *Tracker) flush() { dirPath := "/var/log/aproxy" dir, err := os.Open(dirPath) 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(dirPath, 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(dirPath, fmt.Sprintf("%v-%v.log", today, len(tfiles)+1))) if err != nil { panic(err) return } } else { file, err = os.OpenFile(path.Join(dirPath, tfiles[0].Name()), os.O_APPEND|os.O_WRONLY, 0666) if err != nil { panic(err) return } } } defer file.Close() file.Write(t.buffer.Bytes()) 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 }