1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-25 23:41:28 +00:00

Merge branch 'develop' of https://github.com/astaxie/beego into develop

This commit is contained in:
astaxie 2018-07-20 15:25:04 +08:00
commit 0d0d87f600
12 changed files with 156 additions and 90 deletions

View File

@ -67,6 +67,7 @@ func oldMap() map[string]interface{} {
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs
m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs m["BConfig.Log.Outputs"] = BConfig.Log.Outputs

View File

@ -106,6 +106,7 @@ type SessionConfig struct {
// LogConfig holds Log related config // LogConfig holds Log related config
type LogConfig struct { type LogConfig struct {
AccessLogs bool AccessLogs bool
EnableStaticLogs bool //log static files requests default: false
AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string
FileLineNum bool FileLineNum bool
Outputs map[string]string // Store Adaptor : config Outputs map[string]string // Store Adaptor : config
@ -182,6 +183,11 @@ func recoverPanic(ctx *context.Context) {
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender { if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
showErr(err, ctx, stack) showErr(err, ctx, stack)
} }
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
} }
} }
@ -247,6 +253,7 @@ func newBConfig() *Config {
}, },
Log: LogConfig{ Log: LogConfig{
AccessLogs: false, AccessLogs: false,
EnableStaticLogs: false,
AccessLogsFormat: "APACHE_FORMAT", AccessLogsFormat: "APACHE_FORMAT",
FileLineNum: true, FileLineNum: true,
Outputs: map[string]string{"console": ""}, Outputs: map[string]string{"console": ""},

View File

@ -272,7 +272,22 @@ func (c *Controller) viewPath() string {
// Redirect sends the redirection response to url with status code. // Redirect sends the redirection response to url with status code.
func (c *Controller) Redirect(url string, code int) { func (c *Controller) Redirect(url string, code int) {
logAccess(c.Ctx, nil, code)
c.Ctx.Redirect(code, url) c.Ctx.Redirect(code, url)
panic(ErrAbort)
}
// Set the data depending on the accepted
func (c *Controller) SetData(data interface{}) {
accept := c.Ctx.Input.Header("Accept")
switch accept {
case applicationJSON:
c.Data["json"] = data
case applicationXML, textXML:
c.Data["xml"] = data
default:
c.Data["json"] = data
}
} }
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string. // Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.

View File

@ -93,11 +93,6 @@ func showErr(err interface{}, ctx *context.Context, stack string) {
"BeegoVersion": VERSION, "BeegoVersion": VERSION,
"GoVersion": runtime.Version(), "GoVersion": runtime.Version(),
} }
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
t.Execute(ctx.ResponseWriter, data) t.Execute(ctx.ResponseWriter, data)
} }
@ -439,6 +434,9 @@ func exception(errCode string, ctx *context.Context) {
} }
func executeError(err *errorInfo, ctx *context.Context, code int) { func executeError(err *errorInfo, ctx *context.Context, code int) {
//make sure to log the error in the access log
logAccess(ctx, nil, code)
if err.errorType == errorTypeHandler { if err.errorType == errorTypeHandler {
ctx.ResponseWriter.WriteHeader(code) ctx.ResponseWriter.WriteHeader(code)
err.handler(ctx.ResponseWriter, ctx.Request) err.handler(ctx.ResponseWriter, ctx.Request)

View File

@ -16,48 +16,57 @@ As of now this logs support console, file,smtp and conn.
First you must import it First you must import it
import ( ```golang
import (
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
) )
```
Then init a Log (example with console adapter) Then init a Log (example with console adapter)
log := NewLogger(10000) ```golang
log.SetLogger("console", "") log := logs.NewLogger(10000)
log.SetLogger("console", "")
```
> the first params stand for how many channel > the first params stand for how many channel
Use it like this: Use it like this:
log.Trace("trace") ```golang
log.Info("info") log.Trace("trace")
log.Warn("warning") log.Info("info")
log.Debug("debug") log.Warn("warning")
log.Critical("critical") log.Debug("debug")
log.Critical("critical")
```
## File adapter ## File adapter
Configure file adapter like this: Configure file adapter like this:
log := NewLogger(10000) ```golang
log.SetLogger("file", `{"filename":"test.log"}`) log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test.log"}`)
```
## Conn adapter ## Conn adapter
Configure like this: Configure like this:
log := NewLogger(1000) ```golang
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) log := NewLogger(1000)
log.Info("info") log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
log.Info("info")
```
## Smtp adapter ## Smtp adapter
Configure like this: Configure like this:
log := NewLogger(10000) ```golang
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) log := NewLogger(10000)
log.Critical("sendmail critical") log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
time.Sleep(time.Second * 30) log.Critical("sendmail critical")
time.Sleep(time.Second * 30)
```

View File

@ -16,6 +16,7 @@ package logs
import ( import (
"bytes" "bytes"
"strings"
"encoding/json" "encoding/json"
"fmt" "fmt"
"time" "time"
@ -63,9 +64,7 @@ func disableEscapeHTML(i interface{}) {
// AccessLog - Format and print access log. // AccessLog - Format and print access log.
func AccessLog(r *AccessLogRecord, format string) { func AccessLog(r *AccessLogRecord, format string) {
var msg string var msg string
switch format { switch format {
case apacheFormat: case apacheFormat:
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05") timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent, msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
@ -80,6 +79,5 @@ func AccessLog(r *AccessLogRecord, format string) {
msg = string(jsonData) msg = string(jsonData)
} }
} }
beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg))
beeLogger.Debug(msg)
} }

View File

@ -198,7 +198,11 @@ func (o *querySet) PrepareInsert() (Inserter, error) {
// query all data and map to containers. // query all data and map to containers.
// cols means the columns when querying. // cols means the columns when querying.
func (o *querySet) All(container interface{}, cols ...string) (int64, error) { func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols) num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
if num == 0 {
return 0, ErrNoRows
}
return num, err
} }
// query one row data and map to containers. // query one row data and map to containers.

View File

@ -877,13 +877,15 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
} }
Admin: Admin:
//admin module record QPS //admin module record QPS
statusCode := context.ResponseWriter.Status statusCode := context.ResponseWriter.Status
if statusCode == 0 { if statusCode == 0 {
statusCode = 200 statusCode = 200
} }
logAccess(context, &startTime, statusCode)
if BConfig.Listen.EnableAdmin { if BConfig.Listen.EnableAdmin {
timeDur := time.Since(startTime) timeDur := time.Since(startTime)
pattern := "" pattern := ""
@ -900,31 +902,13 @@ Admin:
} }
} }
if BConfig.RunMode == DEV || BConfig.Log.AccessLogs { if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
timeDur := time.Since(startTime)
var devInfo string var devInfo string
timeDur := time.Since(startTime)
iswin := (runtime.GOOS == "windows") iswin := (runtime.GOOS == "windows")
statusColor := logs.ColorByStatus(iswin, statusCode) statusColor := logs.ColorByStatus(iswin, statusCode)
methodColor := logs.ColorByMethod(iswin, r.Method) methodColor := logs.ColorByMethod(iswin, r.Method)
resetColor := logs.ColorByMethod(iswin, "") resetColor := logs.ColorByMethod(iswin, "")
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 findRouter {
if routerInfo != nil { if routerInfo != nil {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode, devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
@ -944,7 +928,6 @@ Admin:
logs.Debug(devInfo) logs.Debug(devInfo)
} }
} }
}
// Call WriteHeader if status code has been set changed // Call WriteHeader if status code has been set changed
if context.Output.Status != 0 { if context.Output.Status != 0 {
context.ResponseWriter.WriteHeader(context.Output.Status) context.ResponseWriter.WriteHeader(context.Output.Status)
@ -991,3 +974,38 @@ func toURL(params map[string]string) string {
} }
return strings.TrimRight(u, "&") return strings.TrimRight(u, "&")
} }
func logAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
//Skip logging if AccessLogs config is false
if !BConfig.Log.AccessLogs {
return
}
//Skip logging static requests unless EnableStaticLogs config is true
if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) {
return
}
var (
requestTime time.Time
elapsedTime time.Duration
r = ctx.Request
)
if startTime != nil {
requestTime = *startTime
elapsedTime = time.Since(*startTime)
}
record := &logs.AccessLogRecord{
RemoteAddr: ctx.Input.IP(),
RequestTime: requestTime,
RequestMethod: r.Method,
Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto),
ServerProtocol: r.Proto,
Host: r.Host,
Status: statusCode,
ElapsedTime: elapsedTime,
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)
}

View File

@ -37,6 +37,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
@ -118,8 +119,8 @@ type Provider struct {
} }
// SessionInit init redis session // SessionInit init redis session
// savepath like redis server addr,pool size,password,dbnum // savepath like redis server addr,pool size,password,dbnum,IdleTimeout second
// e.g. 127.0.0.1:6379,100,astaxie,0 // e.g. 127.0.0.1:6379,100,astaxie,0,30
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",") configs := strings.Split(savePath, ",")
@ -149,6 +150,13 @@ func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
} else { } else {
rp.dbNum = 0 rp.dbNum = 0
} }
var idleTimeout time.Duration = 0
if len(configs) > 4 {
timeout, err := strconv.Atoi(configs[4])
if err == nil && timeout > 0 {
idleTimeout = time.Duration(timeout) * time.Second
}
}
rp.poollist = &redis.Pool{ rp.poollist = &redis.Pool{
Dial: func() (redis.Conn, error) { Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", rp.savePath) c, err := redis.Dial("tcp", rp.savePath)
@ -174,6 +182,8 @@ func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
MaxIdle: rp.poolsize, MaxIdle: rp.poolsize,
} }
rp.poollist.IdleTimeout = idleTimeout
return rp.poollist.Get().Err() return rp.poollist.Get().Err()
} }

View File

@ -74,7 +74,7 @@ func serverStaticRouter(ctx *context.Context) {
if enableCompress { if enableCompress {
acceptEncoding = context.ParseEncoding(ctx.Request) acceptEncoding = context.ParseEncoding(ctx.Request)
} }
b, n, sch, err := openFile(filePath, fileInfo, acceptEncoding) b, n, sch, reader, err := openFile(filePath, fileInfo, acceptEncoding)
if err != nil { if err != nil {
if BConfig.RunMode == DEV { if BConfig.RunMode == DEV {
logs.Warn("Can't compress the file:", filePath, err) logs.Warn("Can't compress the file:", filePath, err)
@ -89,47 +89,53 @@ func serverStaticRouter(ctx *context.Context) {
ctx.Output.Header("Content-Length", strconv.FormatInt(sch.size, 10)) ctx.Output.Header("Content-Length", strconv.FormatInt(sch.size, 10))
} }
http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, sch) http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, reader)
} }
type serveContentHolder struct { type serveContentHolder struct {
*bytes.Reader data []byte
modTime time.Time modTime time.Time
size int64 size int64
encoding string encoding string
} }
type serveContentReader struct {
*bytes.Reader
}
var ( var (
staticFileMap = make(map[string]*serveContentHolder) staticFileMap = make(map[string]*serveContentHolder)
mapLock sync.RWMutex mapLock sync.RWMutex
) )
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) { func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, *serveContentReader, error) {
mapKey := acceptEncoding + ":" + filePath mapKey := acceptEncoding + ":" + filePath
mapLock.RLock() mapLock.RLock()
mapFile := staticFileMap[mapKey] mapFile := staticFileMap[mapKey]
mapLock.RUnlock() mapLock.RUnlock()
if isOk(mapFile, fi) { if isOk(mapFile, fi) {
return mapFile.encoding != "", mapFile.encoding, mapFile, nil reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
} }
mapLock.Lock() mapLock.Lock()
defer mapLock.Unlock() defer mapLock.Unlock()
if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) { if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, nil, err
} }
defer file.Close() defer file.Close()
var bufferWriter bytes.Buffer var bufferWriter bytes.Buffer
_, n, err := context.WriteFile(acceptEncoding, &bufferWriter, file) _, n, err := context.WriteFile(acceptEncoding, &bufferWriter, file)
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, nil, err
} }
mapFile = &serveContentHolder{Reader: bytes.NewReader(bufferWriter.Bytes()), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n} mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n}
staticFileMap[mapKey] = mapFile staticFileMap[mapKey] = mapFile
} }
return mapFile.encoding != "", mapFile.encoding, mapFile, nil reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
} }
func isOk(s *serveContentHolder, fi os.FileInfo) bool { func isOk(s *serveContentHolder, fi os.FileInfo) bool {

View File

@ -16,7 +16,7 @@ var licenseFile = filepath.Join(currentWorkDir, "LICENSE")
func testOpenFile(encoding string, content []byte, t *testing.T) { func testOpenFile(encoding string, content []byte, t *testing.T) {
fi, _ := os.Stat(licenseFile) fi, _ := os.Stat(licenseFile)
b, n, sch, err := openFile(licenseFile, fi, encoding) b, n, sch, reader, err := openFile(licenseFile, fi, encoding)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.Fail() t.Fail()
@ -24,7 +24,7 @@ func testOpenFile(encoding string, content []byte, t *testing.T) {
t.Log("open static file encoding "+n, b) t.Log("open static file encoding "+n, b)
assetOpenFileAndContent(sch, content, t) assetOpenFileAndContent(sch, reader, content, t)
} }
func TestOpenStaticFile_1(t *testing.T) { func TestOpenStaticFile_1(t *testing.T) {
file, _ := os.Open(licenseFile) file, _ := os.Open(licenseFile)
@ -53,13 +53,13 @@ func TestOpenStaticFileDeflate_1(t *testing.T) {
testOpenFile("deflate", content, t) testOpenFile("deflate", content, t)
} }
func assetOpenFileAndContent(sch *serveContentHolder, content []byte, t *testing.T) { func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader, content []byte, t *testing.T) {
t.Log(sch.size, len(content)) t.Log(sch.size, len(content))
if sch.size != int64(len(content)) { if sch.size != int64(len(content)) {
t.Log("static content file size not same") t.Log("static content file size not same")
t.Fail() t.Fail()
} }
bs, _ := ioutil.ReadAll(sch) bs, _ := ioutil.ReadAll(reader)
for i, v := range content { for i, v := range content {
if v != bs[i] { if v != bs[i] {
t.Log("content not same") t.Log("content not same")

View File

@ -365,10 +365,10 @@ func (v *Validation) Valid(obj interface{}) (b bool, err error) {
return return
} }
var hasReuired bool var hasRequired bool
for _, vf := range vfs { for _, vf := range vfs {
if vf.Name == "Required" { if vf.Name == "Required" {
hasReuired = true hasRequired = true
} }
currentField := objV.Field(i).Interface() currentField := objV.Field(i).Interface()
@ -382,7 +382,7 @@ func (v *Validation) Valid(obj interface{}) (b bool, err error) {
chk := Required{""}.IsSatisfied(currentField) chk := Required{""}.IsSatisfied(currentField)
if !hasReuired && v.RequiredFirst && !chk { if !hasRequired && v.RequiredFirst && !chk {
if _, ok := CanSkipFuncs[vf.Name]; ok { if _, ok := CanSkipFuncs[vf.Name]; ok {
continue continue
} }