Make accesslogs.logTheRoundTrip async to get lost performance
This commit is contained in:
parent
5b3bba8f6e
commit
c09febfffc
4 changed files with 70 additions and 9 deletions
|
@ -124,6 +124,20 @@ filePath = "/path/to/access.log"
|
||||||
format = "json"
|
format = "json"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To write the logs in async, specify `bufferingSize` as the format (must be >0):
|
||||||
|
```toml
|
||||||
|
[accessLog]
|
||||||
|
filePath = "/path/to/access.log"
|
||||||
|
# Buffering Size
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 0
|
||||||
|
#
|
||||||
|
# Number of access log lines to process in a buffered way.
|
||||||
|
#
|
||||||
|
bufferingSize = 100
|
||||||
|
```
|
||||||
|
|
||||||
To filter logs you can specify a set of filters which are logically "OR-connected". Thus, specifying multiple filters will keep more access logs than specifying only one:
|
To filter logs you can specify a set of filters which are logically "OR-connected". Thus, specifying multiple filters will keep more access logs than specifying only one:
|
||||||
```toml
|
```toml
|
||||||
[accessLog]
|
[accessLog]
|
||||||
|
|
|
@ -31,6 +31,12 @@ const (
|
||||||
JSONFormat = "json"
|
JSONFormat = "json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type logHandlerParams struct {
|
||||||
|
logDataTable *LogData
|
||||||
|
crr *captureRequestReader
|
||||||
|
crw *captureResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
// LogHandler will write each request and its response to the access log.
|
// LogHandler will write each request and its response to the access log.
|
||||||
type LogHandler struct {
|
type LogHandler struct {
|
||||||
config *types.AccessLog
|
config *types.AccessLog
|
||||||
|
@ -38,6 +44,8 @@ type LogHandler struct {
|
||||||
file *os.File
|
file *os.File
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
httpCodeRanges types.HTTPCodeRanges
|
httpCodeRanges types.HTTPCodeRanges
|
||||||
|
logHandlerChan chan logHandlerParams
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLogHandler creates a new LogHandler
|
// NewLogHandler creates a new LogHandler
|
||||||
|
@ -50,6 +58,7 @@ func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
|
||||||
}
|
}
|
||||||
file = f
|
file = f
|
||||||
}
|
}
|
||||||
|
logHandlerChan := make(chan logHandlerParams, config.BufferingSize)
|
||||||
|
|
||||||
var formatter logrus.Formatter
|
var formatter logrus.Formatter
|
||||||
|
|
||||||
|
@ -70,9 +79,10 @@ func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
logHandler := &LogHandler{
|
logHandler := &LogHandler{
|
||||||
config: config,
|
config: config,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
file: file,
|
file: file,
|
||||||
|
logHandlerChan: logHandlerChan,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Filters != nil {
|
if config.Filters != nil {
|
||||||
|
@ -83,6 +93,16 @@ func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.BufferingSize > 0 {
|
||||||
|
logHandler.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer logHandler.wg.Done()
|
||||||
|
for handlerParams := range logHandler.logHandlerChan {
|
||||||
|
logHandler.logTheRoundTrip(handlerParams.logDataTable, handlerParams.crr, handlerParams.crw)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
return logHandler, nil
|
return logHandler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,11 +182,22 @@ func (l *LogHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next h
|
||||||
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
|
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
|
||||||
|
|
||||||
logDataTable.DownstreamResponse = crw.Header()
|
logDataTable.DownstreamResponse = crw.Header()
|
||||||
l.logTheRoundTrip(logDataTable, crr, crw)
|
|
||||||
|
if l.config.BufferingSize > 0 {
|
||||||
|
l.logHandlerChan <- logHandlerParams{
|
||||||
|
logDataTable: logDataTable,
|
||||||
|
crr: crr,
|
||||||
|
crw: crw,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l.logTheRoundTrip(logDataTable, crr, crw)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the Logger (i.e. the file etc).
|
// Close closes the Logger (i.e. the file, drain logHandlerChan, etc).
|
||||||
func (l *LogHandler) Close() error {
|
func (l *LogHandler) Close() error {
|
||||||
|
close(l.logHandlerChan)
|
||||||
|
l.wg.Wait()
|
||||||
return l.file.Close()
|
return l.file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,21 @@ func TestLoggerCLF(t *testing.T) {
|
||||||
assertValidLogData(t, expectedLog, logData)
|
assertValidLogData(t, expectedLog, logData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAsyncLoggerCLF(t *testing.T) {
|
||||||
|
tmpDir := createTempDir(t, CommonFormat)
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
|
||||||
|
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024}
|
||||||
|
doLogging(t, config)
|
||||||
|
|
||||||
|
logData, err := ioutil.ReadFile(logFilePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expectedLog := ` TestHost - TestUser [13/Apr/2016:07:14:19 -0700] "POST testpath HTTP/0.0" 123 12 "testReferer" "testUserAgent" 1 "testFrontend" "http://127.0.0.1/testBackend" 1ms`
|
||||||
|
assertValidLogData(t, expectedLog, logData)
|
||||||
|
}
|
||||||
|
|
||||||
func assertString(exp string) func(t *testing.T, actual interface{}) {
|
func assertString(exp string) func(t *testing.T, actual interface{}) {
|
||||||
return func(t *testing.T, actual interface{}) {
|
return func(t *testing.T, actual interface{}) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
|
@ -22,10 +22,11 @@ type TraefikLog struct {
|
||||||
|
|
||||||
// AccessLog holds the configuration settings for the access logger (middlewares/accesslog).
|
// AccessLog holds the configuration settings for the access logger (middlewares/accesslog).
|
||||||
type AccessLog struct {
|
type AccessLog struct {
|
||||||
FilePath string `json:"file,omitempty" description:"Access log file path. Stdout is used when omitted or empty" export:"true"`
|
FilePath string `json:"file,omitempty" description:"Access log file path. Stdout is used when omitted or empty" export:"true"`
|
||||||
Format string `json:"format,omitempty" description:"Access log format: json | common" export:"true"`
|
Format string `json:"format,omitempty" description:"Access log format: json | common" export:"true"`
|
||||||
Filters *AccessLogFilters `json:"filters,omitempty" description:"Access log filters, used to keep only specific access logs" export:"true"`
|
Filters *AccessLogFilters `json:"filters,omitempty" description:"Access log filters, used to keep only specific access logs" export:"true"`
|
||||||
Fields *AccessLogFields `json:"fields,omitempty" description:"AccessLogFields" export:"true"`
|
Fields *AccessLogFields `json:"fields,omitempty" description:"AccessLogFields" export:"true"`
|
||||||
|
BufferingSize int64 `json:"bufferingSize,omitempty" description:"Number of access log lines to process in a buffered way. Default 0." export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessLogFilters holds filters configuration
|
// AccessLogFilters holds filters configuration
|
||||||
|
|
Loading…
Reference in a new issue