From 4921014c6415fdfa105c92149b3ffb9001430a2c Mon Sep 17 00:00:00 2001 From: Waleed Gadelkareem Date: Wed, 13 Sep 2017 02:03:46 +0200 Subject: [PATCH] Add JSON or Apache access log formatting option to config: AccessLogsFormat = JSON_FORMAT or APACHE_FORMAT ref #2738 --- admin_test.go | 1 + config.go | 2 ++ logs/accesslog.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++ router.go | 68 +++++++++++++++++++++++++++------------------- 4 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 logs/accesslog.go diff --git a/admin_test.go b/admin_test.go index 2348792e..a99da6fc 100644 --- a/admin_test.go +++ b/admin_test.go @@ -67,6 +67,7 @@ func oldMap() map[string]interface{} { m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs + m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum m["BConfig.Log.Outputs"] = BConfig.Log.Outputs return m diff --git a/config.go b/config.go index e6e99570..d344ec6a 100644 --- a/config.go +++ b/config.go @@ -104,6 +104,7 @@ type SessionConfig struct { // LogConfig holds Log related config type LogConfig struct { AccessLogs bool + AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string FileLineNum bool Outputs map[string]string // Store Adaptor : config } @@ -240,6 +241,7 @@ func newBConfig() *Config { }, Log: LogConfig{ AccessLogs: false, + AccessLogsFormat: "", FileLineNum: true, Outputs: map[string]string{"console": ""}, }, diff --git a/logs/accesslog.go b/logs/accesslog.go new file mode 100644 index 00000000..1245e69b --- /dev/null +++ b/logs/accesslog.go @@ -0,0 +1,69 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "bytes" + "encoding/json" + "time" + "fmt" +) + +const ( + ApacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s\n" + ApacheFormat = "APACHE_FORMAT" + JsonFormat = "JSON_FORMAT" +) + +type AccessLogRecord struct { + RemoteAddr string `json:"remote_addr"` + RequestTime time.Time `json:"request_time"` + RequestMethod string `json:"request_method"` + Request string `json:"request"` + ServerProtocol string `json:"server_protocol"` + Host string `json:"host"` + Status int `json:"status"` + BodyBytesSent int64 `json:"body_bytes_sent"` + ElapsedTime time.Duration `json:"elapsed_time"` + HttpReferrer string `json:"http_referrer"` + HttpUserAgent string `json:"http_user_agent"` + RemoteUser string `json:"remote_user"` +} + +func (r *AccessLogRecord) JSON() ([]byte, error) { + buffer := &bytes.Buffer{} + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + err := encoder.Encode(r) + return buffer.Bytes(), err +} + +func AccessLog(r *AccessLogRecord, format string) { + var msg string + + if format == ApacheFormat { + timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05") + msg = fmt.Sprintf(ApacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent, + r.ElapsedTime.Seconds(), r.HttpReferrer, r.HttpUserAgent) + } else { + jsonData, err := r.JSON() + if err != nil { + msg = fmt.Sprintf(`{"Error": "%s"}`, err) + } else { + msg = string(jsonData) + } + } + beeLogger.Debug(msg) +} diff --git a/router.go b/router.go index e5a4e80d..146e9af6 100644 --- a/router.go +++ b/router.go @@ -43,7 +43,7 @@ const ( ) const ( - routerTypeBeego = iota + routerTypeBeego = iota routerTypeRESTFul routerTypeHandler ) @@ -845,17 +845,20 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) } Admin: - //admin module record QPS +//admin module record QPS + + statusCode := context.ResponseWriter.Status + if statusCode == 0 { + statusCode = 200 + } + if BConfig.Listen.EnableAdmin { timeDur := time.Since(startTime) pattern := "" if routerInfo != nil { pattern = routerInfo.pattern } - statusCode := context.ResponseWriter.Status - if statusCode == 0 { - statusCode = 200 - } + if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) { if runRouter != nil { go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur) @@ -869,36 +872,47 @@ Admin: timeDur := time.Since(startTime) var devInfo string - statusCode := context.ResponseWriter.Status - if statusCode == 0 { - statusCode = 200 - } - iswin := (runtime.GOOS == "windows") statusColor := logs.ColorByStatus(iswin, statusCode) methodColor := logs.ColorByMethod(iswin, r.Method) resetColor := logs.ColorByMethod(iswin, "") - - if findRouter { - if routerInfo != nil { - devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode, - resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path, - routerInfo.pattern) + if BConfig.Log.AccessLogsFormat != "" { + record := &logs.AccessLogRecord{ + RemoteAddr: context.Input.IP(), + RequestTime: startTime, + RequestMethod: r.Method, + Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), + ServerProtocol: r.Proto, + Host: r.Host, + Status: statusCode, + ElapsedTime: timeDur, + HttpReferrer: r.Header.Get("Referer"), + HttpUserAgent: r.Header.Get("User-Agent"), + RemoteUser: r.Header.Get("Remote-User"), + BodyBytesSent: 0, //@todo this one is missing! + } + logs.AccessLog(record, BConfig.Log.AccessLogsFormat) + }else { + if findRouter { + if routerInfo != nil { + devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode, + resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path, + routerInfo.pattern) + } else { + devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor, + timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path) + } } else { devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor, - timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path) + timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path) + } + if iswin { + logs.W32Debug(devInfo) + } else { + logs.Debug(devInfo) } - } else { - devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor, - timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path) - } - if iswin { - logs.W32Debug(devInfo) - } else { - logs.Debug(devInfo) } } - // Call WriteHeader if status code has been set changed if context.Output.Status != 0 { context.ResponseWriter.WriteHeader(context.Output.Status)