2020-09-07 15:31:43 +02:00
|
|
|
package accesslog
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2020-09-10 18:41:30 +02:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2020-09-13 01:56:22 +02:00
|
|
|
"strings"
|
2020-09-07 15:31:43 +02:00
|
|
|
"sync"
|
2020-09-13 01:56:22 +02:00
|
|
|
"sync/atomic"
|
2020-09-07 15:31:43 +02:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/kataras/iris/v12/context"
|
|
|
|
"github.com/kataras/iris/v12/core/memstore"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestAccessLogPrint_Simple(t *testing.T) {
|
2020-09-13 01:56:22 +02:00
|
|
|
const goroutinesN = 420
|
2020-09-07 15:31:43 +02:00
|
|
|
|
|
|
|
w := new(bytes.Buffer)
|
2020-09-07 17:04:35 +02:00
|
|
|
ac := New(w)
|
2020-09-13 01:56:22 +02:00
|
|
|
ac.Async = true
|
|
|
|
ac.ResponseBody = true
|
2020-09-07 15:31:43 +02:00
|
|
|
ac.Clock = TClock(time.Time{})
|
|
|
|
|
2020-09-13 01:56:22 +02:00
|
|
|
var expected string
|
|
|
|
var expectedLines int
|
|
|
|
var mu sync.Mutex
|
2020-09-07 15:31:43 +02:00
|
|
|
for i := 0; i < goroutinesN; i++ {
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
ac.Print(
|
|
|
|
nil,
|
|
|
|
1*time.Second,
|
|
|
|
ac.TimeFormat,
|
|
|
|
200,
|
|
|
|
"GET",
|
|
|
|
"/path_value?url_query=url_query_value",
|
2020-09-11 08:38:55 +02:00
|
|
|
"::1",
|
2020-09-07 15:31:43 +02:00
|
|
|
"Incoming",
|
|
|
|
"Outcoming",
|
2020-09-08 12:44:50 +02:00
|
|
|
0,
|
|
|
|
0,
|
2020-09-12 11:34:59 +02:00
|
|
|
memstore.Store{
|
|
|
|
{Key: "path_param", ValueRaw: "path_param_value"},
|
|
|
|
},
|
|
|
|
[]memstore.StringEntry{
|
2020-09-07 15:31:43 +02:00
|
|
|
{Key: "url_query", Value: "url_query_value"},
|
2020-09-12 11:34:59 +02:00
|
|
|
},
|
|
|
|
[]memstore.Entry{
|
2020-09-07 15:31:43 +02:00
|
|
|
{Key: "custom", ValueRaw: "custom_value"},
|
|
|
|
})
|
|
|
|
}()
|
2020-09-13 01:56:22 +02:00
|
|
|
|
|
|
|
mu.Lock()
|
|
|
|
expected += "0001-01-01 00:00:00|1s|200|GET|/path_value?url_query=url_query_value|::1|path_param=path_param_value url_query=url_query_value custom=custom_value|0 B|0 B|Incoming|Outcoming|\n"
|
|
|
|
expectedLines++
|
|
|
|
mu.Unlock()
|
2020-09-07 15:31:43 +02:00
|
|
|
}
|
|
|
|
|
2020-09-13 01:56:22 +02:00
|
|
|
// time.Sleep(1 * time.Second)
|
|
|
|
// just to fire at least some routines (CI: travis).
|
|
|
|
ac.Close()
|
|
|
|
|
|
|
|
if got := atomic.LoadUint32(&ac.remaining); got > 0 { // test wait.
|
|
|
|
t.Fatalf("expected remaining: %d but got: %d", 0, got)
|
|
|
|
}
|
2020-09-07 15:31:43 +02:00
|
|
|
|
|
|
|
if got := w.String(); expected != got {
|
2020-09-13 01:56:22 +02:00
|
|
|
gotLines := strings.Count(got, "\n")
|
|
|
|
t.Logf("expected printed result to be[%d]:\n'%s'\n\nbut got[%d]:\n'%s'", expectedLines, expected, gotLines, got)
|
|
|
|
t.Fatalf("expected[%d]: %d but got: %d lines", goroutinesN, expectedLines, gotLines)
|
2020-09-07 15:31:43 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-10 18:41:30 +02:00
|
|
|
|
|
|
|
func TestAccessLogBroker(t *testing.T) {
|
|
|
|
w := new(bytes.Buffer)
|
|
|
|
ac := New(w)
|
2020-09-13 01:56:22 +02:00
|
|
|
|
2020-09-10 18:41:30 +02:00
|
|
|
ac.Clock = TClock(time.Time{})
|
|
|
|
broker := ac.Broker()
|
|
|
|
|
|
|
|
wg := new(sync.WaitGroup)
|
|
|
|
n := 4
|
|
|
|
wg.Add(4)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
i := 0
|
|
|
|
ln := broker.NewListener()
|
|
|
|
for {
|
|
|
|
select {
|
2020-09-13 01:56:22 +02:00
|
|
|
case log, ok := <-ln:
|
|
|
|
if !ok {
|
|
|
|
t.Log("Log Listener Closed")
|
|
|
|
return
|
|
|
|
}
|
2020-09-10 18:41:30 +02:00
|
|
|
lat := log.Latency
|
|
|
|
t.Log(lat.String())
|
|
|
|
wg.Done()
|
|
|
|
if expected := time.Duration(i) * time.Second; expected != lat {
|
|
|
|
panic(fmt.Sprintf("expected latency: %s but got: %s", expected, lat))
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
time.Sleep(1350 * time.Millisecond)
|
|
|
|
if i == 2 {
|
|
|
|
time.Sleep(2 * time.Second) // "random" sleep even more.
|
|
|
|
}
|
|
|
|
if log.Latency != lat {
|
|
|
|
panic("expected logger to wait for notifier before release the log")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
|
|
|
printLog := func(lat time.Duration) {
|
|
|
|
err := ac.Print(
|
|
|
|
nil,
|
|
|
|
lat,
|
|
|
|
"",
|
|
|
|
0,
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
"",
|
2020-09-11 08:38:55 +02:00
|
|
|
"",
|
2020-09-10 18:41:30 +02:00
|
|
|
0,
|
|
|
|
0,
|
2020-09-12 11:34:59 +02:00
|
|
|
nil,
|
2020-09-10 18:41:30 +02:00
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
printLog(time.Duration(i) * time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait for all listeners to finish.
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// wait for close messages.
|
|
|
|
wg.Add(1)
|
2020-09-13 01:56:22 +02:00
|
|
|
ac.Close()
|
2020-09-10 18:41:30 +02:00
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
type noOpFormatter struct{}
|
|
|
|
|
|
|
|
func (*noOpFormatter) SetOutput(io.Writer) {}
|
|
|
|
|
|
|
|
// Format prints the logs in text/template format.
|
|
|
|
func (*noOpFormatter) Format(*Log) (bool, error) {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// go test -run=^$ -bench=BenchmarkAccessLogAfter -benchmem
|
|
|
|
func BenchmarkAccessLogAfter(b *testing.B) {
|
2020-09-11 22:22:18 +02:00
|
|
|
benchmarkAccessLogAfter(b, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkAccessLogAfterPrint(b *testing.B) {
|
|
|
|
benchmarkAccessLogAfter(b, false, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func benchmarkAccessLogAfter(b *testing.B, withLogStruct, async bool) {
|
2020-09-10 18:41:30 +02:00
|
|
|
ac := New(ioutil.Discard)
|
|
|
|
ac.Clock = TClock(time.Time{})
|
|
|
|
ac.BytesReceived = false
|
2020-09-13 01:56:22 +02:00
|
|
|
ac.BytesReceivedBody = false
|
2020-09-10 18:41:30 +02:00
|
|
|
ac.BytesSent = false
|
2020-09-13 01:56:22 +02:00
|
|
|
ac.BytesSentBody = false
|
2020-09-10 18:41:30 +02:00
|
|
|
ac.BodyMinify = false
|
|
|
|
ac.RequestBody = false
|
|
|
|
ac.ResponseBody = false
|
|
|
|
ac.Async = false
|
2020-09-11 08:38:55 +02:00
|
|
|
ac.IP = false
|
2020-09-11 22:22:18 +02:00
|
|
|
if withLogStruct {
|
|
|
|
ac.SetFormatter(new(noOpFormatter)) // just to create the log structure, here we test the log creation time only.
|
|
|
|
}
|
2020-09-10 18:41:30 +02:00
|
|
|
|
|
|
|
ctx := new(context.Context)
|
|
|
|
req, err := http.NewRequest("GET", "/", nil)
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
ctx.ResetRequest(req)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
w := context.AcquireResponseWriter()
|
|
|
|
w.BeginResponse(recorder)
|
|
|
|
ctx.ResetResponseWriter(w)
|
|
|
|
|
2020-09-11 22:22:18 +02:00
|
|
|
wg := new(sync.WaitGroup)
|
|
|
|
if async {
|
|
|
|
wg.Add(b.N)
|
|
|
|
}
|
|
|
|
|
2020-09-10 18:41:30 +02:00
|
|
|
b.ResetTimer()
|
|
|
|
for n := 0; n < b.N; n++ {
|
2020-09-11 22:22:18 +02:00
|
|
|
if async {
|
|
|
|
go func() {
|
|
|
|
ac.after(ctx, time.Millisecond, "GET", "/")
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
} else {
|
|
|
|
ac.after(ctx, time.Millisecond, "GET", "/")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
if async {
|
|
|
|
wg.Wait()
|
2020-09-10 18:41:30 +02:00
|
|
|
}
|
|
|
|
w.EndResponse()
|
|
|
|
}
|