Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch access logging to logrus #1647

Merged
merged 1 commit into from
May 24, 2017

Conversation

rjshep
Copy link
Contributor

@rjshep rjshep commented May 21, 2017

Building on some initial work to introduce a new access logging framework, this PR uses logrus as the log handler to write access logs using information collected in the log data table.

A logrus Formatter implements Traefik Common Log Format producing logs which are backward compatible with the current access logs.

Also includes applicable changes from #1507

@ldez ldez added area/logs kind/enhancement a new or improved feature. labels May 21, 2017
@ldez ldez added this to the 1.4 milestone May 21, 2017
@@ -544,7 +541,6 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
redirectHandlers := make(map[string]negroni.Handler)
backends := map[string]http.Handler{}
backendsHealthcheck := map[string]*healthcheck.BackendHealthCheck{}
backend2FrontendMap := map[string]string{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why you have remove this line?

Copy link
Contributor Author

@rjshep rjshep May 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the backend to frontend map was only used by the old logger. The new framework, added during a previous PR, uses accesslog.NewSaveBackend and accesslog.NewSaveFrontend to achieve the same thing.

Copy link
Member

@emilevauge emilevauge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @rjshep !

Thanks a lot for this new (smaller) PR :)

I would like to suggest a design change.
In order to remove all if l.logger != nil from logger.go: NewLogHandler should return an error in case fileName is empty. And we should not add this middleware into the chain in this use case.

Could you also remove saveBackend & saveFrontend middlewares from server.go when we don't have access logs enabled?

I also made few comments/suggestions in the code :)


timestamp := entry.Data[StartUTC].(time.Time).Format(commonLogTimeFormat)
elapsedMillis := entry.Data[Duration].(time.Duration).Nanoseconds() / 1000000
referer := entry.Data["request_Referer"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is a constant in standard lib or in lorgus ?

if referer == nil {
referer = ""
}
userAgent := entry.Data["request_User-Agent"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is a constant in standard lib or in lorgus ?

}

return "-"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT of this kind of refactoring?

func toLogString(v interface{}) string {
	defaultValue := "-"
	if v == nil {
		return defaultValue
	}

	switch s := v.(type) {
	case string:
		return quoted(s, defaultValue)

	case fmt.Stringer:
		return quoted(s.String(), defaultValue)

	default:
		return defaultValue
	}

}

func quoted(s string, defaultValue string) string {
	if len(s) == 0 {
		return defaultValue
	}
	return `"` + s + `"`
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM I've added it in. It has meant that the log format is not fully backward compatible. Referer and User Agent fields are now specified as a hyphen if not present, previously they were just missing. I believe the new format is actually correct, however.

@rjshep rjshep force-pushed the logrus-access-logging branch 2 times, most recently from 8d4399e to 05ab100 Compare May 22, 2017 20:27
@rjshep
Copy link
Contributor Author

rjshep commented May 22, 2017

Hi @emilevauge I've made the requested changes. Thanks for the design change, that is a better solution :)

Copy link
Contributor

@timoreimann timoreimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I left a few comments.

c.Assert(regexp.MustCompile(`^\d+ms$`).MatchString(tokens[12]), checker.True)
c.Assert(tokens[10], checker.Equals, fmt.Sprintf("%d", i+1))
c.Assert(strings.HasPrefix(tokens[11], "frontend"), checker.True)
c.Assert(strings.HasPrefix(tokens[12], "http://127.0.0.1:808"), checker.True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comprehension question: why do we have to change the indices?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Referer and User Agent fields are now specified as a hyphen if not present, previously they were just missing. I believe the new format is actually correct, however.

@@ -23,11 +29,34 @@ const (
// Note: Current implementation collects log data but does not have the facility to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment still valid?

}

// NewLogHandler creates a new LogHandler
func NewLogHandler() *LogHandler {
return &LogHandler{}
func NewLogHandler(fileName string) (*LogHandler, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit-pick: I'd probably call the variable filePath.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done :-)

dir := filepath.Dir(fileName)

err := os.MkdirAll(dir, 0755)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be merged with previous line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


err := os.MkdirAll(dir, 0755)
if err != nil {
log.Errorf("Failed to create log path %s: %s", dir, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we return an error here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes


file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
if err != nil {
log.Error("Error opening file", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we return an error instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

return &LogHandler{logger: logger, file: file}, nil
}

return nil, errors.New("Empty file name specified for accessLogsFile")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this return at the very top of the file, right after a check for fileName == "". That way, we can save one level of indentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -47,16 +42,15 @@ func TestLogger(t *testing.T) {

logfilePath := filepath.Join(tmp, logfileNameSuffix)

logger = NewLogger(logfilePath)
logger, err = NewLogHandler(logfilePath)
assert.NoError(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have added testify.require by now. Please use it here and wherever continuation of a test does not make sense anymore in case of a failed assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Copy link
Contributor

@ldez ldez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!!! 🎉

LGTM

Copy link
Contributor

@timoreimann timoreimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One tiny thing left. :-)

dir := filepath.Dir(filePath)

if err := os.MkdirAll(dir, 0755); err != nil {
log.Errorf("Failed to create log path %s: %s", dir, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging an error and returning an error normally do not go together; instead, we should return an error containing sufficient context and let the caller log.

Could you please pass the (de-capitalized) log message content to an fmt.Errorf call and just return that?

Same for lines 48:49.

@rjshep
Copy link
Contributor Author

rjshep commented May 23, 2017

@timoreimann change made and commits squashed.

Copy link
Contributor

@timoreimann timoreimann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks a lot!

Copy link
Member

@emilevauge emilevauge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rjshep Just 2 minor comments :)
Other than that looks good!

@@ -85,6 +110,7 @@ func (l *LogHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next h

// Close closes the Logger (i.e. the file etc).
func (l *LogHandler) Close() error {
l.file.Close()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be return l.file.Close() ;)

server/server.go Outdated
server.loggerMiddleware.Close()
server.accessLoggerMiddleware.Close()
if server.accessLoggerMiddleware != nil {
server.accessLoggerMiddleware.Close()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check the return error here.

@rjshep
Copy link
Contributor Author

rjshep commented May 24, 2017

Thanks @emilevauge. Changes made :)

Copy link
Member

@emilevauge emilevauge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @rjshep for this great PR!
We really appreciate your exemplary attitude 👏 👏 👏

@altovedo altovedo deleted the logrus-access-logging branch September 12, 2018 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/logs kind/enhancement a new or improved feature.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants