v1.2.0 整个beegolog 打进去, 增加syncmap日志通道
This commit is contained in:
parent
43d3a06dac
commit
973fd817d5
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@
|
|||||||
*.iml
|
*.iml
|
||||||
out
|
out
|
||||||
gen
|
gen
|
||||||
|
runtime
|
||||||
|
36
log.go
36
log.go
@ -4,8 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/astaxie/beego"
|
"golib.gaore.com/GaoreGo/grlogs/logs"
|
||||||
"github.com/astaxie/beego/logs"
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,7 +44,7 @@ type ConnLogConfig struct {
|
|||||||
Level int `json:"level"`
|
Level int `json:"level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var loggers = make(map[string]*Logger)
|
var loggers = sync.Map{}
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
*logs.BeeLogger
|
*logs.BeeLogger
|
||||||
@ -63,18 +65,19 @@ func (l *Logger) Stop() {
|
|||||||
|
|
||||||
func GetLogger(name string) *Logger {
|
func GetLogger(name string) *Logger {
|
||||||
|
|
||||||
if l, ok := loggers[name]; ok {
|
if l, ok := loggers.Load(name); ok {
|
||||||
return l
|
return l.(*Logger)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
var level int = LEVEL_WARN
|
var level int = LEVEL_WARN
|
||||||
|
|
||||||
if beego.BConfig.RunMode == "dev" {
|
if s := os.Getenv("CENTER_RUNMODE"); s == "dev" {
|
||||||
level = LEVEL_ALL
|
level = LEVEL_ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
conf1 := FileLogConfig{
|
conf1 := FileLogConfig{
|
||||||
Filename: GetCwd(fmt.Sprintf("runtime/logs/%s.log", name)),
|
Filename: path.Join(wd, fmt.Sprintf("runtime/logs/%s.log", name)),
|
||||||
Level: LEVEL_ALL,
|
Level: LEVEL_ALL,
|
||||||
Maxlines: 0,
|
Maxlines: 0,
|
||||||
Daily: true,
|
Daily: true,
|
||||||
@ -89,13 +92,14 @@ func GetLogger(name string) *Logger {
|
|||||||
|
|
||||||
confString, _ := json.Marshal(&conf1)
|
confString, _ := json.Marshal(&conf1)
|
||||||
confString2, _ := json.Marshal(&conf2)
|
confString2, _ := json.Marshal(&conf2)
|
||||||
loggers[name] = &Logger{}
|
l := new(Logger)
|
||||||
loggers[name].BeeLogger = logs.NewLogger()
|
l.BeeLogger = logs.NewLogger()
|
||||||
loggers[name].SetLogger(logs.AdapterFile, bytes.NewBuffer(confString).String())
|
l.SetLogger(logs.AdapterFile, bytes.NewBuffer(confString).String())
|
||||||
loggers[name].SetLogger(logs.AdapterConsole, bytes.NewBuffer(confString2).String())
|
l.SetLogger(logs.AdapterConsole, bytes.NewBuffer(confString2).String())
|
||||||
loggers[name].BeeLogger.SetPrefix("_" + name)
|
l.BeeLogger.SetPrefix("_" + name)
|
||||||
loggers[name].BeeLogger.EnableFuncCallDepth(true)
|
l.BeeLogger.EnableFuncCallDepth(true)
|
||||||
loggers[name].BeeLogger.SetLogFuncCallDepth(2)
|
l.BeeLogger.SetLogFuncCallDepth(2)
|
||||||
return loggers[name]
|
loggers.Store(name, l)
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
package grlogs
|
package grlogs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego/logs"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetLogger(t *testing.T) {
|
func TestGetLogger(t *testing.T) {
|
||||||
logs.NewLogger()
|
|
||||||
logs.Info(LEVEL_ALL)
|
|
||||||
GetLogger("nds").Debug("akldalskflasfa")
|
GetLogger("nds").Debug("akldalskflasfa")
|
||||||
}
|
}
|
||||||
|
72
logs/README.md
Normal file
72
logs/README.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
## logs
|
||||||
|
logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
|
||||||
|
|
||||||
|
|
||||||
|
## How to install?
|
||||||
|
|
||||||
|
go get github.com/astaxie/beego/logs
|
||||||
|
|
||||||
|
|
||||||
|
## What adapters are supported?
|
||||||
|
|
||||||
|
As of now this logs support console, file,smtp and conn.
|
||||||
|
|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
First you must import it
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then init a Log (example with console adapter)
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := logs.NewLogger(10000)
|
||||||
|
log.SetLogger("console", "")
|
||||||
|
```
|
||||||
|
|
||||||
|
> the first params stand for how many channel
|
||||||
|
|
||||||
|
Use it like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log.Trace("trace")
|
||||||
|
log.Info("info")
|
||||||
|
log.Warn("warning")
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Critical("critical")
|
||||||
|
```
|
||||||
|
|
||||||
|
## File adapter
|
||||||
|
|
||||||
|
Configure file adapter like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conn adapter
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(1000)
|
||||||
|
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||||
|
log.Info("info")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Smtp adapter
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||||
|
log.Critical("sendmail critical")
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
```
|
83
logs/accesslog.go
Normal file
83
logs/accesslog.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||||
|
apacheFormat = "APACHE_FORMAT"
|
||||||
|
jsonFormat = "JSON_FORMAT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessLogRecord struct for holding access log data.
|
||||||
|
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)
|
||||||
|
disableEscapeHTML(encoder)
|
||||||
|
|
||||||
|
err := encoder.Encode(r)
|
||||||
|
return buffer.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableEscapeHTML(i interface{}) {
|
||||||
|
if e, ok := i.(interface {
|
||||||
|
SetEscapeHTML(bool)
|
||||||
|
}); ok {
|
||||||
|
e.SetEscapeHTML(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessLog - Format and print access log.
|
||||||
|
func AccessLog(r *AccessLogRecord, format string) {
|
||||||
|
var msg string
|
||||||
|
switch format {
|
||||||
|
case 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)
|
||||||
|
case jsonFormat:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
jsonData, err := r.json()
|
||||||
|
if err != nil {
|
||||||
|
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||||
|
} else {
|
||||||
|
msg = string(jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg))
|
||||||
|
}
|
186
logs/alils/alils.go
Normal file
186
logs/alils/alils.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CacheSize set the flush size
|
||||||
|
CacheSize int = 64
|
||||||
|
// Delimiter define the topic delimiter
|
||||||
|
Delimiter string = "##"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the Config for Ali Log
|
||||||
|
type Config struct {
|
||||||
|
Project string `json:"project"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
KeyID string `json:"key_id"`
|
||||||
|
KeySecret string `json:"key_secret"`
|
||||||
|
LogStore string `json:"log_store"`
|
||||||
|
Topics []string `json:"topics"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
FlushWhen int `json:"flush_when"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliLSWriter implements LoggerInterface.
|
||||||
|
// it writes messages in keep-live tcp connection.
|
||||||
|
type aliLSWriter struct {
|
||||||
|
store *LogStore
|
||||||
|
group []*LogGroup
|
||||||
|
withMap bool
|
||||||
|
groupMap map[string]*LogGroup
|
||||||
|
lock *sync.Mutex
|
||||||
|
Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAliLS create a new Logger
|
||||||
|
func NewAliLS() logs.Logger {
|
||||||
|
alils := new(aliLSWriter)
|
||||||
|
alils.Level = logs.LevelTrace
|
||||||
|
return alils
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init parse config and init struct
|
||||||
|
func (c *aliLSWriter) Init(jsonConfig string) (err error) {
|
||||||
|
|
||||||
|
json.Unmarshal([]byte(jsonConfig), c)
|
||||||
|
|
||||||
|
if c.FlushWhen > CacheSize {
|
||||||
|
c.FlushWhen = CacheSize
|
||||||
|
}
|
||||||
|
|
||||||
|
prj := &LogProject{
|
||||||
|
Name: c.Project,
|
||||||
|
Endpoint: c.Endpoint,
|
||||||
|
AccessKeyID: c.KeyID,
|
||||||
|
AccessKeySecret: c.KeySecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.store, err = prj.GetLogStore(c.LogStore)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create default Log Group
|
||||||
|
c.group = append(c.group, &LogGroup{
|
||||||
|
Topic: proto.String(""),
|
||||||
|
Source: proto.String(c.Source),
|
||||||
|
Logs: make([]*Log, 0, c.FlushWhen),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create other Log Group
|
||||||
|
c.groupMap = make(map[string]*LogGroup)
|
||||||
|
for _, topic := range c.Topics {
|
||||||
|
|
||||||
|
lg := &LogGroup{
|
||||||
|
Topic: proto.String(topic),
|
||||||
|
Source: proto.String(c.Source),
|
||||||
|
Logs: make([]*Log, 0, c.FlushWhen),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.group = append(c.group, lg)
|
||||||
|
c.groupMap[topic] = lg
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.group) == 1 {
|
||||||
|
c.withMap = false
|
||||||
|
} else {
|
||||||
|
c.withMap = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock = &sync.Mutex{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in connection.
|
||||||
|
// if connection is down, try to re-connect.
|
||||||
|
func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error) {
|
||||||
|
|
||||||
|
if level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic string
|
||||||
|
var content string
|
||||||
|
var lg *LogGroup
|
||||||
|
if c.withMap {
|
||||||
|
|
||||||
|
// Topic,LogGroup
|
||||||
|
strs := strings.SplitN(msg, Delimiter, 2)
|
||||||
|
if len(strs) == 2 {
|
||||||
|
pos := strings.LastIndex(strs[0], " ")
|
||||||
|
topic = strs[0][pos+1 : len(strs[0])]
|
||||||
|
content = strs[0][0:pos] + strs[1]
|
||||||
|
lg = c.groupMap[topic]
|
||||||
|
}
|
||||||
|
|
||||||
|
// send to empty Topic
|
||||||
|
if lg == nil {
|
||||||
|
content = msg
|
||||||
|
lg = c.group[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = msg
|
||||||
|
lg = c.group[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
c1 := &LogContent{
|
||||||
|
Key: proto.String("msg"),
|
||||||
|
Value: proto.String(content),
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &Log{
|
||||||
|
Time: proto.Uint32(uint32(when.Unix())),
|
||||||
|
Contents: []*LogContent{
|
||||||
|
c1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
lg.Logs = append(lg.Logs, l)
|
||||||
|
c.lock.Unlock()
|
||||||
|
|
||||||
|
if len(lg.Logs) >= c.FlushWhen {
|
||||||
|
c.flush(lg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *aliLSWriter) Flush() {
|
||||||
|
|
||||||
|
// flush all group
|
||||||
|
for _, lg := range c.group {
|
||||||
|
c.flush(lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroy connection writer and close tcp listener.
|
||||||
|
func (c *aliLSWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *aliLSWriter) flush(lg *LogGroup) {
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
err := c.store.PutLogs(lg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lg.Logs = make([]*Log, 0, c.FlushWhen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logs.Register(logs.AdapterAliLS, NewAliLS)
|
||||||
|
}
|
13
logs/alils/config.go
Executable file
13
logs/alils/config.go
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = "0.5.0" // SDK version
|
||||||
|
signatureMethod = "hmac-sha1" // Signature method
|
||||||
|
|
||||||
|
// OffsetNewest stands for the log head offset, i.e. the offset that will be
|
||||||
|
// assigned to the next message that will be produced to the shard.
|
||||||
|
OffsetNewest = "end"
|
||||||
|
// OffsetOldest stands for the oldest offset available on the logstore for a
|
||||||
|
// shard.
|
||||||
|
OffsetOldest = "begin"
|
||||||
|
)
|
1038
logs/alils/log.pb.go
Executable file
1038
logs/alils/log.pb.go
Executable file
File diff suppressed because it is too large
Load Diff
42
logs/alils/log_config.go
Executable file
42
logs/alils/log_config.go
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
// InputDetail define log detail
|
||||||
|
type InputDetail struct {
|
||||||
|
LogType string `json:"logType"`
|
||||||
|
LogPath string `json:"logPath"`
|
||||||
|
FilePattern string `json:"filePattern"`
|
||||||
|
LocalStorage bool `json:"localStorage"`
|
||||||
|
TimeFormat string `json:"timeFormat"`
|
||||||
|
LogBeginRegex string `json:"logBeginRegex"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Keys []string `json:"key"`
|
||||||
|
FilterKeys []string `json:"filterKey"`
|
||||||
|
FilterRegex []string `json:"filterRegex"`
|
||||||
|
TopicFormat string `json:"topicFormat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputDetail define the output detail
|
||||||
|
type OutputDetail struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
LogStoreName string `json:"logstoreName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogConfig define Log Config
|
||||||
|
type LogConfig struct {
|
||||||
|
Name string `json:"configName"`
|
||||||
|
InputType string `json:"inputType"`
|
||||||
|
InputDetail InputDetail `json:"inputDetail"`
|
||||||
|
OutputType string `json:"outputType"`
|
||||||
|
OutputDetail OutputDetail `json:"outputDetail"`
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedMachineGroup returns applied machine group of this config.
|
||||||
|
func (c *LogConfig) GetAppliedMachineGroup(confName string) (groupNames []string, err error) {
|
||||||
|
groupNames, err = c.project.GetAppliedMachineGroups(c.Name)
|
||||||
|
return
|
||||||
|
}
|
819
logs/alils/log_project.go
Executable file
819
logs/alils/log_project.go
Executable file
@ -0,0 +1,819 @@
|
|||||||
|
/*
|
||||||
|
Package alils implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS).
|
||||||
|
|
||||||
|
For more description about SLS, please read this article:
|
||||||
|
http://gitlab.alibaba-inc.com/sls/doc.
|
||||||
|
*/
|
||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error message in SLS HTTP response.
|
||||||
|
type errorMessage struct {
|
||||||
|
Code string `json:"errorCode"`
|
||||||
|
Message string `json:"errorMessage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogProject Define the Ali Project detail
|
||||||
|
type LogProject struct {
|
||||||
|
Name string // Project name
|
||||||
|
Endpoint string // IP or hostname of SLS endpoint
|
||||||
|
AccessKeyID string
|
||||||
|
AccessKeySecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogProject creates a new SLS project.
|
||||||
|
func NewLogProject(name, endpoint, AccessKeyID, accessKeySecret string) (p *LogProject, err error) {
|
||||||
|
p = &LogProject{
|
||||||
|
Name: name,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
AccessKeyID: AccessKeyID,
|
||||||
|
AccessKeySecret: accessKeySecret,
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLogStore returns all logstore names of project p.
|
||||||
|
func (p *LogProject) ListLogStore() (storeNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores")
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Count int
|
||||||
|
LogStores []string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storeNames = body.LogStores
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogStore returns logstore according by logstore name.
|
||||||
|
func (p *LogProject) GetLogStore(name string) (s *LogStore, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/logstores/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s = &LogStore{}
|
||||||
|
err = json.Unmarshal(buf, s)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLogStore creates a new logstore in SLS,
|
||||||
|
// where name is logstore name,
|
||||||
|
// and ttl is time-to-live(in day) of logs,
|
||||||
|
// and shardCnt is the number of shards.
|
||||||
|
func (p *LogProject) CreateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
ShardCount int `json:"shardCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &Body{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
ShardCount: shardCnt,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(store)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/logstores", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to create logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLogStore deletes a logstore according by logstore name.
|
||||||
|
func (p *LogProject) DeleteLogStore(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/logstores/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLogStore updates a logstore according by logstore name,
|
||||||
|
// obviously we can't modify the logstore name itself.
|
||||||
|
func (p *LogProject) UpdateLogStore(name string, ttl, shardCnt int) (err error) {
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
ShardCount int `json:"shardCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
store := &Body{
|
||||||
|
Name: name,
|
||||||
|
TTL: ttl,
|
||||||
|
ShardCount: shardCnt,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(store)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/logstores", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMachineGroup returns machine group name list and the total number of machine groups.
|
||||||
|
// The offset starts from 0 and the size is the max number of machine groups could be returned.
|
||||||
|
func (p *LogProject) ListMachineGroup(offset, size int) (m []string, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
size = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups?offset=%v&size=%v", offset, size)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
MachineGroups []string
|
||||||
|
Count int
|
||||||
|
Total int
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = body.MachineGroups
|
||||||
|
total = body.Total
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMachineGroup retruns machine group according by machine group name.
|
||||||
|
func (p *LogProject) GetMachineGroup(name string) (m *MachineGroup, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/machinegroups/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get machine group:%v", name)
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m = &MachineGroup{}
|
||||||
|
err = json.Unmarshal(buf, m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMachineGroup creates a new machine group in SLS.
|
||||||
|
func (p *LogProject) CreateMachineGroup(m *MachineGroup) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/machinegroups", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to create machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMachineGroup updates a machine group.
|
||||||
|
func (p *LogProject) UpdateMachineGroup(m *MachineGroup) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/machinegroups/"+m.Name, h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMachineGroup deletes machine group according machine group name.
|
||||||
|
func (p *LogProject) DeleteMachineGroup(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/machinegroups/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConfig returns config names list and the total number of configs.
|
||||||
|
// The offset starts from 0 and the size is the max number of configs could be returned.
|
||||||
|
func (p *LogProject) ListConfig(offset, size int) (cfgNames []string, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
if size <= 0 {
|
||||||
|
size = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/configs?offset=%v&size=%v", offset, size)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Total int
|
||||||
|
Configs []string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgNames = body.Configs
|
||||||
|
total = body.Total
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns config according by config name.
|
||||||
|
func (p *LogProject) GetConfig(name string) (c *LogConfig, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "GET", "/configs/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &LogConfig{}
|
||||||
|
err = json.Unmarshal(buf, c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.project = p
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig updates a config.
|
||||||
|
func (p *LogProject) UpdateConfig(c *LogConfig) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "PUT", "/configs/"+c.Name, h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConfig creates a new config in SLS.
|
||||||
|
func (p *LogProject) CreateConfig(c *LogConfig) (err error) {
|
||||||
|
|
||||||
|
body, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "deflate", // TODO: support lz4
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "POST", "/configs", h, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConfig deletes a config according by config name.
|
||||||
|
func (p *LogProject) DeleteConfig(name string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := request(p, "DELETE", "/configs/"+name, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(body, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete config")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedMachineGroups returns applied machine group names list according config name.
|
||||||
|
func (p *LogProject) GetAppliedMachineGroups(confName string) (groupNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/configs/%v/machinegroups", confName)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get applied machine groups")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Count int
|
||||||
|
Machinegroups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &Body{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groupNames = body.Machinegroups
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedConfigs returns applied config names list according machine group name groupName.
|
||||||
|
func (p *LogProject) GetAppliedConfigs(groupName string) (confNames []string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs", groupName)
|
||||||
|
r, err := request(p, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to applied configs")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cfg struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Configs []string `json:"configs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &Cfg{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
confNames = body.Configs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyConfigToMachineGroup applies config to machine group.
|
||||||
|
func (p *LogProject) ApplyConfigToMachineGroup(confName, groupName string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||||
|
r, err := request(p, "PUT", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to apply config to machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveConfigFromMachineGroup removes config from machine group.
|
||||||
|
func (p *LogProject) RemoveConfigFromMachineGroup(confName, groupName string) (err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
|
||||||
|
r, err := request(p, "DELETE", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to remove config from machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Printf("%s\n", dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
271
logs/alils/log_store.go
Executable file
271
logs/alils/log_store.go
Executable file
@ -0,0 +1,271 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
lz4 "github.com/cloudflare/golz4"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogStore Store the logs
|
||||||
|
type LogStore struct {
|
||||||
|
Name string `json:"logstoreName"`
|
||||||
|
TTL int
|
||||||
|
ShardCount int
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shard define the Log Shard
|
||||||
|
type Shard struct {
|
||||||
|
ShardID int `json:"shardID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListShards returns shard id list of this logstore.
|
||||||
|
func (s *LogStore) ListShards() (shardIDs []int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards", s.Name)
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to list logstore")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var shards []*Shard
|
||||||
|
err = json.Unmarshal(buf, &shards)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range shards {
|
||||||
|
shardIDs = append(shardIDs, v.ShardID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutLogs put logs into logstore.
|
||||||
|
// The callers should transform user logs into LogGroup.
|
||||||
|
func (s *LogStore) PutLogs(lg *LogGroup) (err error) {
|
||||||
|
body, err := proto.Marshal(lg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compresse body with lz4
|
||||||
|
out := make([]byte, lz4.CompressBound(body))
|
||||||
|
n, err := lz4.Compress(body, out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-compresstype": "lz4",
|
||||||
|
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
|
||||||
|
"Content-Type": "application/x-protobuf",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v", s.Name)
|
||||||
|
r, err := request(s.project, "POST", uri, h, out[:n])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to put logs")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCursor gets log cursor of one shard specified by shardID.
|
||||||
|
// The from can be in three form: a) unix timestamp in seccond, b) "begin", c) "end".
|
||||||
|
// For more detail please read: http://gitlab.alibaba-inc.com/sls/doc/blob/master/api/shard.md#logstore
|
||||||
|
func (s *LogStore) GetCursor(shardID int, from string) (cursor string, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=cursor&from=%v",
|
||||||
|
s.Name, shardID, from)
|
||||||
|
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get cursor")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
Cursor string
|
||||||
|
}
|
||||||
|
body := &Body{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cursor = body.Cursor
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogsBytes gets logs binary data from shard specified by shardID according cursor.
|
||||||
|
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||||
|
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||||
|
func (s *LogStore) GetLogsBytes(shardID int, cursor string,
|
||||||
|
logGroupMaxCount int) (out []byte, nextCursor string, err error) {
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
"Accept": "application/x-protobuf",
|
||||||
|
"Accept-Encoding": "lz4",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=logs&cursor=%v&count=%v",
|
||||||
|
s.Name, shardID, cursor, logGroupMaxCount)
|
||||||
|
|
||||||
|
r, err := request(s.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to get cursor")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := r.Header["X-Sls-Compresstype"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-compresstype' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != "lz4" {
|
||||||
|
err = fmt.Errorf("unexpected compress type:%v", v[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok = r.Header["X-Sls-Cursor"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-cursor' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nextCursor = v[0]
|
||||||
|
|
||||||
|
v, ok = r.Header["X-Sls-Bodyrawsize"]
|
||||||
|
if !ok || len(v) == 0 {
|
||||||
|
err = fmt.Errorf("can't find 'x-sls-bodyrawsize' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bodyRawSize, err := strconv.Atoi(v[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out = make([]byte, bodyRawSize)
|
||||||
|
err = lz4.Uncompress(buf, out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogsBytesDecode decodes logs binary data retruned by GetLogsBytes API
|
||||||
|
func LogsBytesDecode(data []byte) (gl *LogGroupList, err error) {
|
||||||
|
|
||||||
|
gl = &LogGroupList{}
|
||||||
|
err = proto.Unmarshal(data, gl)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogs gets logs from shard specified by shardID according cursor.
|
||||||
|
// The logGroupMaxCount is the max number of logGroup could be returned.
|
||||||
|
// The nextCursor is the next curosr can be used to read logs at next time.
|
||||||
|
func (s *LogStore) GetLogs(shardID int, cursor string,
|
||||||
|
logGroupMaxCount int) (gl *LogGroupList, nextCursor string, err error) {
|
||||||
|
|
||||||
|
out, nextCursor, err := s.GetLogsBytes(shardID, cursor, logGroupMaxCount)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gl, err = LogsBytesDecode(out)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
91
logs/alils/machine_group.go
Executable file
91
logs/alils/machine_group.go
Executable file
@ -0,0 +1,91 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MachineGroupAttribute define the Attribute
|
||||||
|
type MachineGroupAttribute struct {
|
||||||
|
ExternalName string `json:"externalName"`
|
||||||
|
TopicName string `json:"groupTopic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MachineGroup define the machine Group
|
||||||
|
type MachineGroup struct {
|
||||||
|
Name string `json:"groupName"`
|
||||||
|
Type string `json:"groupType"`
|
||||||
|
MachineIDType string `json:"machineIdentifyType"`
|
||||||
|
MachineIDList []string `json:"machineList"`
|
||||||
|
|
||||||
|
Attribute MachineGroupAttribute `json:"groupAttribute"`
|
||||||
|
|
||||||
|
CreateTime uint32
|
||||||
|
LastModifyTime uint32
|
||||||
|
|
||||||
|
project *LogProject
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine define the Machine
|
||||||
|
type Machine struct {
|
||||||
|
IP string
|
||||||
|
UniqueID string `json:"machine-uniqueid"`
|
||||||
|
UserdefinedID string `json:"userdefined-id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MachineList define the Machine List
|
||||||
|
type MachineList struct {
|
||||||
|
Total int
|
||||||
|
Machines []*Machine
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListMachines returns machine list of this machine group.
|
||||||
|
func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) {
|
||||||
|
h := map[string]string{
|
||||||
|
"x-sls-bodyrawsize": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("/machinegroups/%v/machines", m.Name)
|
||||||
|
r, err := request(m.project, "GET", uri, h, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
errMsg := &errorMessage{}
|
||||||
|
err = json.Unmarshal(buf, errMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to remove config from machine group")
|
||||||
|
dump, _ := httputil.DumpResponse(r, true)
|
||||||
|
fmt.Println(dump)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &MachineList{}
|
||||||
|
err = json.Unmarshal(buf, body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ms = body.Machines
|
||||||
|
total = body.Total
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppliedConfigs returns applied configs of this machine group.
|
||||||
|
func (m *MachineGroup) GetAppliedConfigs() (confNames []string, err error) {
|
||||||
|
confNames, err = m.project.GetAppliedConfigs(m.Name)
|
||||||
|
return
|
||||||
|
}
|
62
logs/alils/request.go
Executable file
62
logs/alils/request.go
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// request sends a request to SLS.
|
||||||
|
func request(project *LogProject, method, uri string, headers map[string]string,
|
||||||
|
body []byte) (resp *http.Response, err error) {
|
||||||
|
|
||||||
|
// The caller should provide 'x-sls-bodyrawsize' header
|
||||||
|
if _, ok := headers["x-sls-bodyrawsize"]; !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'x-sls-bodyrawsize' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SLS public request headers
|
||||||
|
headers["Host"] = project.Name + "." + project.Endpoint
|
||||||
|
headers["Date"] = nowRFC1123()
|
||||||
|
headers["x-sls-apiversion"] = version
|
||||||
|
headers["x-sls-signaturemethod"] = signatureMethod
|
||||||
|
if body != nil {
|
||||||
|
bodyMD5 := fmt.Sprintf("%X", md5.Sum(body))
|
||||||
|
headers["Content-MD5"] = bodyMD5
|
||||||
|
|
||||||
|
if _, ok := headers["Content-Type"]; !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'Content-Type' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc Authorization
|
||||||
|
// Authorization = "SLS <AccessKeyID>:<Signature>"
|
||||||
|
digest, err := signature(project, method, uri, headers)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyID, digest)
|
||||||
|
headers["Authorization"] = auth
|
||||||
|
|
||||||
|
// Initialize http request
|
||||||
|
reader := bytes.NewReader(body)
|
||||||
|
urlStr := fmt.Sprintf("http://%v.%v%v", project.Name, project.Endpoint, uri)
|
||||||
|
req, err := http.NewRequest(method, urlStr, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ready to do request
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
111
logs/alils/signature.go
Executable file
111
logs/alils/signature.go
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
package alils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GMT location
|
||||||
|
var gmtLoc = time.FixedZone("GMT", 0)
|
||||||
|
|
||||||
|
// NowRFC1123 returns now time in RFC1123 format with GMT timezone,
|
||||||
|
// eg. "Mon, 02 Jan 2006 15:04:05 GMT".
|
||||||
|
func nowRFC1123() string {
|
||||||
|
return time.Now().In(gmtLoc).Format(time.RFC1123)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature calculates a request's signature digest.
|
||||||
|
func signature(project *LogProject, method, uri string,
|
||||||
|
headers map[string]string) (digest string, err error) {
|
||||||
|
var contentMD5, contentType, date, canoHeaders, canoResource string
|
||||||
|
var slsHeaderKeys sort.StringSlice
|
||||||
|
|
||||||
|
// SignString = VERB + "\n"
|
||||||
|
// + CONTENT-MD5 + "\n"
|
||||||
|
// + CONTENT-TYPE + "\n"
|
||||||
|
// + DATE + "\n"
|
||||||
|
// + CanonicalizedSLSHeaders + "\n"
|
||||||
|
// + CanonicalizedResource
|
||||||
|
|
||||||
|
if val, ok := headers["Content-MD5"]; ok {
|
||||||
|
contentMD5 = val
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := headers["Content-Type"]; ok {
|
||||||
|
contentType = val
|
||||||
|
}
|
||||||
|
|
||||||
|
date, ok := headers["Date"]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("Can't find 'Date' header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc CanonicalizedSLSHeaders
|
||||||
|
slsHeaders := make(map[string]string, len(headers))
|
||||||
|
for k, v := range headers {
|
||||||
|
l := strings.TrimSpace(strings.ToLower(k))
|
||||||
|
if strings.HasPrefix(l, "x-sls-") {
|
||||||
|
slsHeaders[l] = strings.TrimSpace(v)
|
||||||
|
slsHeaderKeys = append(slsHeaderKeys, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(slsHeaderKeys)
|
||||||
|
for i, k := range slsHeaderKeys {
|
||||||
|
canoHeaders += k + ":" + slsHeaders[k]
|
||||||
|
if i+1 < len(slsHeaderKeys) {
|
||||||
|
canoHeaders += "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc CanonicalizedResource
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
canoResource += url.QueryEscape(u.Path)
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
var keys sort.StringSlice
|
||||||
|
|
||||||
|
vals := u.Query()
|
||||||
|
for k := range vals {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(keys)
|
||||||
|
canoResource += "?"
|
||||||
|
for i, k := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
canoResource += "&"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vals[k] {
|
||||||
|
canoResource += k + "=" + v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signStr := method + "\n" +
|
||||||
|
contentMD5 + "\n" +
|
||||||
|
contentType + "\n" +
|
||||||
|
date + "\n" +
|
||||||
|
canoHeaders + "\n" +
|
||||||
|
canoResource
|
||||||
|
|
||||||
|
// Signature = base64(hmac-sha1(UTF8-Encoding-Of(SignString),AccessKeySecret))
|
||||||
|
mac := hmac.New(sha1.New, []byte(project.AccessKeySecret))
|
||||||
|
_, err = mac.Write([]byte(signStr))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
digest = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||||
|
return
|
||||||
|
}
|
117
logs/conn.go
Normal file
117
logs/conn.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// connWriter implements LoggerInterface.
|
||||||
|
// it writes messages in keep-live tcp connection.
|
||||||
|
type connWriter struct {
|
||||||
|
lg *logWriter
|
||||||
|
innerWriter io.WriteCloser
|
||||||
|
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||||
|
Reconnect bool `json:"reconnect"`
|
||||||
|
Net string `json:"net"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn create new ConnWrite returning as LoggerInterface.
|
||||||
|
func NewConn() Logger {
|
||||||
|
conn := new(connWriter)
|
||||||
|
conn.Level = LevelTrace
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init init connection writer with json config.
|
||||||
|
// json config only need key "level".
|
||||||
|
func (c *connWriter) Init(jsonConfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonConfig), c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in connection.
|
||||||
|
// if connection is down, try to re-connect.
|
||||||
|
func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.needToConnectOnMsg() {
|
||||||
|
err := c.connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ReconnectOnMsg {
|
||||||
|
defer c.innerWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lg.writeln(when, msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *connWriter) Flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroy connection writer and close tcp listener.
|
||||||
|
func (c *connWriter) Destroy() {
|
||||||
|
if c.innerWriter != nil {
|
||||||
|
c.innerWriter.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) connect() error {
|
||||||
|
if c.innerWriter != nil {
|
||||||
|
c.innerWriter.Close()
|
||||||
|
c.innerWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial(c.Net, c.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.innerWriter = conn
|
||||||
|
c.lg = newLogWriter(conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) needToConnectOnMsg() bool {
|
||||||
|
if c.Reconnect {
|
||||||
|
c.Reconnect = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.innerWriter == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ReconnectOnMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterConn, NewConn)
|
||||||
|
}
|
25
logs/conn_test.go
Normal file
25
logs/conn_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConn(t *testing.T) {
|
||||||
|
log := NewLogger(1000)
|
||||||
|
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||||
|
log.Informational("informational")
|
||||||
|
}
|
99
logs/console.go
Normal file
99
logs/console.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shiena/ansicolor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// brush is a color join function
|
||||||
|
type brush func(string) string
|
||||||
|
|
||||||
|
// newBrush return a fix color Brush
|
||||||
|
func newBrush(color string) brush {
|
||||||
|
pre := "\033["
|
||||||
|
reset := "\033[0m"
|
||||||
|
return func(text string) string {
|
||||||
|
return pre + color + "m" + text + reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = []brush{
|
||||||
|
newBrush("1;37"), // Emergency white
|
||||||
|
newBrush("1;36"), // Alert cyan
|
||||||
|
newBrush("1;35"), // Critical magenta
|
||||||
|
newBrush("1;31"), // Error red
|
||||||
|
newBrush("1;33"), // Warning yellow
|
||||||
|
newBrush("1;32"), // Notice green
|
||||||
|
newBrush("1;34"), // Informational blue
|
||||||
|
newBrush("1;44"), // Debug Background blue
|
||||||
|
}
|
||||||
|
|
||||||
|
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||||
|
type consoleWriter struct {
|
||||||
|
lg *logWriter
|
||||||
|
Level int `json:"level"`
|
||||||
|
Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||||
|
func NewConsole() Logger {
|
||||||
|
cw := &consoleWriter{
|
||||||
|
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||||
|
Level: LevelDebug,
|
||||||
|
Colorful: true,
|
||||||
|
}
|
||||||
|
return cw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init init console logger.
|
||||||
|
// jsonConfig like '{"level":LevelTrace}'.
|
||||||
|
func (c *consoleWriter) Init(jsonConfig string) error {
|
||||||
|
if len(jsonConfig) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal([]byte(jsonConfig), c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in console.
|
||||||
|
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.Colorful {
|
||||||
|
msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1)
|
||||||
|
}
|
||||||
|
c.lg.writeln(when, msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (c *consoleWriter) Destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *consoleWriter) Flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterConsole, NewConsole)
|
||||||
|
}
|
51
logs/console_test.go
Normal file
51
logs/console_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Try each log level in decreasing order of priority.
|
||||||
|
func testConsoleCalls(bl *BeeLogger) {
|
||||||
|
bl.Emergency("emergency")
|
||||||
|
bl.Alert("alert")
|
||||||
|
bl.Critical("critical")
|
||||||
|
bl.Error("error")
|
||||||
|
bl.Warning("warning")
|
||||||
|
bl.Notice("notice")
|
||||||
|
bl.Informational("informational")
|
||||||
|
bl.Debug("debug")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test console logging by visually comparing the lines being output with and
|
||||||
|
// without a log level specification.
|
||||||
|
func TestConsole(t *testing.T) {
|
||||||
|
log1 := NewLogger(10000)
|
||||||
|
log1.EnableFuncCallDepth(true)
|
||||||
|
log1.SetLogger("console", "")
|
||||||
|
testConsoleCalls(log1)
|
||||||
|
|
||||||
|
log2 := NewLogger(100)
|
||||||
|
log2.SetLogger("console", `{"level":3}`)
|
||||||
|
testConsoleCalls(log2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test console without color
|
||||||
|
func TestConsoleNoColor(t *testing.T) {
|
||||||
|
log := NewLogger(100)
|
||||||
|
log.SetLogger("console", `{"color":false}`)
|
||||||
|
testConsoleCalls(log)
|
||||||
|
}
|
80
logs/es/es.go
Normal file
80
logs/es/es.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package es
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/OwnLocal/goes"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewES return a LoggerInterface
|
||||||
|
func NewES() logs.Logger {
|
||||||
|
cw := &esLogger{
|
||||||
|
Level: logs.LevelDebug,
|
||||||
|
}
|
||||||
|
return cw
|
||||||
|
}
|
||||||
|
|
||||||
|
type esLogger struct {
|
||||||
|
*goes.Client
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// {"dsn":"http://localhost:9200/","level":1}
|
||||||
|
func (el *esLogger) Init(jsonconfig string) error {
|
||||||
|
err := json.Unmarshal([]byte(jsonconfig), el)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if el.DSN == "" {
|
||||||
|
return errors.New("empty dsn")
|
||||||
|
} else if u, err := url.Parse(el.DSN); err != nil {
|
||||||
|
return err
|
||||||
|
} else if u.Path == "" {
|
||||||
|
return errors.New("missing prefix")
|
||||||
|
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
conn := goes.NewClient(host, port)
|
||||||
|
el.Client = conn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg will write the msg and level into es
|
||||||
|
func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > el.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vals := make(map[string]interface{})
|
||||||
|
vals["@timestamp"] = when.Format(time.RFC3339)
|
||||||
|
vals["@msg"] = msg
|
||||||
|
d := goes.Document{
|
||||||
|
Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()),
|
||||||
|
Type: "logs",
|
||||||
|
Fields: vals,
|
||||||
|
}
|
||||||
|
_, err := el.Index(d, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy is a empty method
|
||||||
|
func (el *esLogger) Destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush is a empty method
|
||||||
|
func (el *esLogger) Flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logs.Register(logs.AdapterEs, NewES)
|
||||||
|
}
|
405
logs/file.go
Normal file
405
logs/file.go
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
// 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"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileLogWriter implements LoggerInterface.
|
||||||
|
// It writes messages by lines limit, file size limit, or time frequency.
|
||||||
|
type fileLogWriter struct {
|
||||||
|
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||||
|
// The opened file
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
fileWriter *os.File
|
||||||
|
|
||||||
|
// Rotate at line
|
||||||
|
MaxLines int `json:"maxlines"`
|
||||||
|
maxLinesCurLines int
|
||||||
|
|
||||||
|
MaxFiles int `json:"maxfiles"`
|
||||||
|
MaxFilesCurFiles int
|
||||||
|
|
||||||
|
// Rotate at size
|
||||||
|
MaxSize int `json:"maxsize"`
|
||||||
|
maxSizeCurSize int
|
||||||
|
|
||||||
|
// Rotate daily
|
||||||
|
Daily bool `json:"daily"`
|
||||||
|
MaxDays int64 `json:"maxdays"`
|
||||||
|
dailyOpenDate int
|
||||||
|
dailyOpenTime time.Time
|
||||||
|
|
||||||
|
// Rotate hourly
|
||||||
|
Hourly bool `json:"hourly"`
|
||||||
|
MaxHours int64 `json:"maxhours"`
|
||||||
|
hourlyOpenDate int
|
||||||
|
hourlyOpenTime time.Time
|
||||||
|
|
||||||
|
Rotate bool `json:"rotate"`
|
||||||
|
|
||||||
|
Level int `json:"level"`
|
||||||
|
|
||||||
|
Perm string `json:"perm"`
|
||||||
|
|
||||||
|
RotatePerm string `json:"rotateperm"`
|
||||||
|
|
||||||
|
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||||
|
func newFileWriter() Logger {
|
||||||
|
w := &fileLogWriter{
|
||||||
|
Daily: true,
|
||||||
|
MaxDays: 7,
|
||||||
|
Hourly: false,
|
||||||
|
MaxHours: 168,
|
||||||
|
Rotate: true,
|
||||||
|
RotatePerm: "0440",
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
MaxLines: 10000000,
|
||||||
|
MaxFiles: 999,
|
||||||
|
MaxSize: 1 << 28,
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init file logger with json config.
|
||||||
|
// jsonConfig like:
|
||||||
|
// {
|
||||||
|
// "filename":"logs/beego.log",
|
||||||
|
// "maxLines":10000,
|
||||||
|
// "maxsize":1024,
|
||||||
|
// "daily":true,
|
||||||
|
// "maxDays":15,
|
||||||
|
// "rotate":true,
|
||||||
|
// "perm":"0600"
|
||||||
|
// }
|
||||||
|
func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||||
|
err := json.Unmarshal([]byte(jsonConfig), w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(w.Filename) == 0 {
|
||||||
|
return errors.New("jsonconfig must have filename")
|
||||||
|
}
|
||||||
|
w.suffix = filepath.Ext(w.Filename)
|
||||||
|
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||||
|
if w.suffix == "" {
|
||||||
|
w.suffix = ".log"
|
||||||
|
}
|
||||||
|
err = w.startLogger()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start file logger. create log file and set to locker-inside file writer.
|
||||||
|
func (w *fileLogWriter) startLogger() error {
|
||||||
|
file, err := w.createLogFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.fileWriter != nil {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
w.fileWriter = file
|
||||||
|
return w.initFd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) needRotateDaily(size int, day int) bool {
|
||||||
|
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||||
|
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||||
|
(w.Daily && day != w.dailyOpenDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) needRotateHourly(size int, hour int) bool {
|
||||||
|
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||||
|
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||||
|
(w.Hourly && hour != w.hourlyOpenDate)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write logger message into file.
|
||||||
|
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > w.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
hd, d, h := formatTimeHeader(when)
|
||||||
|
msg = string(hd) + msg + "\n"
|
||||||
|
if w.Rotate {
|
||||||
|
w.RLock()
|
||||||
|
if w.needRotateHourly(len(msg), h) {
|
||||||
|
w.RUnlock()
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(len(msg), h) {
|
||||||
|
if err := w.doRotate(when); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
} else if w.needRotateDaily(len(msg), d) {
|
||||||
|
w.RUnlock()
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateDaily(len(msg), d) {
|
||||||
|
if err := w.doRotate(when); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
} else {
|
||||||
|
w.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Lock()
|
||||||
|
_, err := w.fileWriter.Write([]byte(msg))
|
||||||
|
if err == nil {
|
||||||
|
w.maxLinesCurLines++
|
||||||
|
w.maxSizeCurSize += len(msg)
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||||
|
// Open the log file
|
||||||
|
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath := path.Dir(w.Filename)
|
||||||
|
os.MkdirAll(filepath, os.FileMode(perm))
|
||||||
|
|
||||||
|
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||||
|
if err == nil {
|
||||||
|
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||||
|
os.Chmod(w.Filename, os.FileMode(perm))
|
||||||
|
}
|
||||||
|
return fd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) initFd() error {
|
||||||
|
fd := w.fileWriter
|
||||||
|
fInfo, err := fd.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get stat err: %s", err)
|
||||||
|
}
|
||||||
|
w.maxSizeCurSize = int(fInfo.Size())
|
||||||
|
w.dailyOpenTime = time.Now()
|
||||||
|
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||||
|
w.hourlyOpenTime = time.Now()
|
||||||
|
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||||
|
w.maxLinesCurLines = 0
|
||||||
|
if w.Hourly {
|
||||||
|
go w.hourlyRotate(w.hourlyOpenTime)
|
||||||
|
} else if w.Daily {
|
||||||
|
go w.dailyRotate(w.dailyOpenTime)
|
||||||
|
}
|
||||||
|
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||||
|
count, err := w.lines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.maxLinesCurLines = count
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||||
|
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||||
|
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||||
|
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||||
|
<-tm.C
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateDaily(0, time.Now().Day()) {
|
||||||
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||||
|
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||||
|
h, _, _ := openTime.Add(1 * time.Hour).Clock()
|
||||||
|
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||||
|
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||||
|
<-tm.C
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(0, time.Now().Hour()) {
|
||||||
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) lines() (int, error) {
|
||||||
|
fd, err := os.Open(w.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 32768) // 32k
|
||||||
|
count := 0
|
||||||
|
lineSep := []byte{'\n'}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := fd.Read(buf)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count += bytes.Count(buf[:c], lineSep)
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoRotate means it need to write file in new file.
|
||||||
|
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||||
|
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||||
|
// file exists
|
||||||
|
// Find the next available number
|
||||||
|
num := w.MaxFilesCurFiles + 1
|
||||||
|
fName := ""
|
||||||
|
format := ""
|
||||||
|
var openTime time.Time
|
||||||
|
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Lstat(w.Filename)
|
||||||
|
if err != nil {
|
||||||
|
//even if the file is not exist or other ,we should RESTART the logger
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Hourly {
|
||||||
|
format = "2006010215"
|
||||||
|
openTime = w.hourlyOpenTime
|
||||||
|
} else if w.Daily {
|
||||||
|
format = "2006-01-02"
|
||||||
|
openTime = w.dailyOpenTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// only when one of them be setted, then the file would be splited
|
||||||
|
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||||
|
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||||
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
|
||||||
|
_, err = os.Lstat(fName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||||
|
_, err = os.Lstat(fName)
|
||||||
|
w.MaxFilesCurFiles = num
|
||||||
|
}
|
||||||
|
|
||||||
|
// return error if the last file checked still existed
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close fileWriter before rename
|
||||||
|
w.fileWriter.Close()
|
||||||
|
|
||||||
|
// Rename the file to its new found name
|
||||||
|
// even if occurs error,we MUST guarantee to restart new logger
|
||||||
|
err = os.Rename(w.Filename, fName)
|
||||||
|
if err != nil {
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||||
|
|
||||||
|
RESTART_LOGGER:
|
||||||
|
|
||||||
|
startLoggerErr := w.startLogger()
|
||||||
|
go w.deleteOldLog()
|
||||||
|
|
||||||
|
if startLoggerErr != nil {
|
||||||
|
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Rotate: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) deleteOldLog() {
|
||||||
|
dir := filepath.Dir(w.Filename)
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if info == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.Hourly {
|
||||||
|
if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||||
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if w.Daily {
|
||||||
|
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||||
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy close the file description, close file writer.
|
||||||
|
func (w *fileLogWriter) Destroy() {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flush file logger.
|
||||||
|
// there are no buffering messages in file logger in memory.
|
||||||
|
// flush file means sync file from disk.
|
||||||
|
func (w *fileLogWriter) Flush() {
|
||||||
|
w.fileWriter.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterFile, newFileWriter)
|
||||||
|
}
|
420
logs/file_test.go
Normal file
420
logs/file_test.go
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
// 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 (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilePerm(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
// use 0666 as test perm cause the default umask is 022
|
||||||
|
log.SetLogger("file", `{"filename":"test.log", "perm": "0666"}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Informational("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
file, err := os.Stat("test.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if file.Mode() != 0666 {
|
||||||
|
t.Fatal("unexpected log file permission")
|
||||||
|
}
|
||||||
|
os.Remove("test.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile1(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Informational("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
f, err := os.Open("test.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := bufio.NewReader(f)
|
||||||
|
lineNum := 0
|
||||||
|
for {
|
||||||
|
line, _, err := b.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
lineNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var expected = LevelDebug + 1
|
||||||
|
if lineNum != expected {
|
||||||
|
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||||
|
}
|
||||||
|
os.Remove("test.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile2(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", fmt.Sprintf(`{"filename":"test2.log","level":%d}`, LevelError))
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
f, err := os.Open("test2.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := bufio.NewReader(f)
|
||||||
|
lineNum := 0
|
||||||
|
for {
|
||||||
|
line, _, err := b.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
lineNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var expected = LevelError + 1
|
||||||
|
if lineNum != expected {
|
||||||
|
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||||
|
}
|
||||||
|
os.Remove("test2.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_01(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||||
|
b, err := exists(rotateName)
|
||||||
|
if !b || err != nil {
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate not generated")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_02(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_03(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, true, false)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_04(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDailyRotate_05(t *testing.T) {
|
||||||
|
fn1 := "rotate_day.log"
|
||||||
|
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||||
|
testFileDailyRotate(t, fn1, fn2)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
func TestFileDailyRotate_06(t *testing.T) { //test file mode
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||||
|
s, _ := os.Lstat(rotateName)
|
||||||
|
if s.Mode() != 0440 {
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate file mode error")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_01(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||||
|
b, err := exists(rotateName)
|
||||||
|
if !b || err != nil {
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate not generated")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_02(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_03(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileRotate(t, fn1, fn2, false, true)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_04(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileHourlyRotate(t, fn1, fn2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_05(t *testing.T) {
|
||||||
|
fn1 := "rotate_hour.log"
|
||||||
|
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||||
|
os.Create(fn)
|
||||||
|
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||||
|
testFileHourlyRotate(t, fn1, fn2)
|
||||||
|
os.Remove(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileHourlyRotate_06(t *testing.T) { //test file mode
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||||
|
s, _ := os.Lstat(rotateName)
|
||||||
|
if s.Mode() != 0440 {
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
t.Fatal("rotate file mode error")
|
||||||
|
}
|
||||||
|
os.Remove(rotateName)
|
||||||
|
os.Remove("test3.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
|
||||||
|
fw := &fileLogWriter{
|
||||||
|
Daily: daily,
|
||||||
|
MaxDays: 7,
|
||||||
|
Hourly: hourly,
|
||||||
|
MaxHours: 168,
|
||||||
|
Rotate: true,
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
|
}
|
||||||
|
|
||||||
|
if daily {
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||||
|
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||||
|
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||||
|
}
|
||||||
|
|
||||||
|
if hourly {
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||||
|
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||||
|
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
|
||||||
|
}
|
||||||
|
|
||||||
|
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
|
||||||
|
|
||||||
|
for _, file := range []string{fn1, fn2} {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
fw.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
||||||
|
fw := &fileLogWriter{
|
||||||
|
Daily: true,
|
||||||
|
MaxDays: 7,
|
||||||
|
Rotate: true,
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
|
}
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||||
|
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||||
|
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||||
|
today, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), fw.dailyOpenTime.Location())
|
||||||
|
today = today.Add(-1 * time.Second)
|
||||||
|
fw.dailyRotate(today)
|
||||||
|
for _, file := range []string{fn1, fn2} {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if len(content) > 0 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
fw.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
|
||||||
|
fw := &fileLogWriter{
|
||||||
|
Hourly: true,
|
||||||
|
MaxHours: 168,
|
||||||
|
Rotate: true,
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
RotatePerm: "0440",
|
||||||
|
}
|
||||||
|
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||||
|
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||||
|
fw.hourlyOpenDate = fw.hourlyOpenTime.Hour()
|
||||||
|
hour, _ := time.ParseInLocation("2006010215", time.Now().Format("2006010215"), fw.hourlyOpenTime.Location())
|
||||||
|
hour = hour.Add(-1 * time.Second)
|
||||||
|
fw.hourlyRotate(hour)
|
||||||
|
for _, file := range []string{fn1, fn2} {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if len(content) > 0 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
fw.Destroy()
|
||||||
|
}
|
||||||
|
func exists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFile(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileAsynchronous(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
log.Async()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileCallDepth(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
log.EnableFuncCallDepth(true)
|
||||||
|
log.SetLogFuncCallDepth(2)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileAsynchronousCallDepth(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
log.EnableFuncCallDepth(true)
|
||||||
|
log.SetLogFuncCallDepth(2)
|
||||||
|
log.Async()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFileOnGoroutine(b *testing.B) {
|
||||||
|
log := NewLogger(100000)
|
||||||
|
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
go log.Debug("debug")
|
||||||
|
}
|
||||||
|
os.Remove("test4.log")
|
||||||
|
}
|
72
logs/jianliao.go
Normal file
72
logs/jianliao.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type JLWriter struct {
|
||||||
|
AuthorName string `json:"authorname"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
RedirectURL string `json:"redirecturl,omitempty"`
|
||||||
|
ImageURL string `json:"imageurl,omitempty"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newJLWriter create jiaoliao writer.
|
||||||
|
func newJLWriter() Logger {
|
||||||
|
return &JLWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init JLWriter with json config string
|
||||||
|
func (s *JLWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("authorName", s.AuthorName)
|
||||||
|
form.Add("title", s.Title)
|
||||||
|
form.Add("text", text)
|
||||||
|
if s.RedirectURL != "" {
|
||||||
|
form.Add("redirectUrl", s.RedirectURL)
|
||||||
|
}
|
||||||
|
if s.ImageURL != "" {
|
||||||
|
form.Add("imageUrl", s.ImageURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *JLWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *JLWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterJianLiao, newJLWriter)
|
||||||
|
}
|
665
logs/log.go
Normal file
665
logs/log.go
Normal file
@ -0,0 +1,665 @@
|
|||||||
|
// 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 provide a general log interface
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import "github.com/astaxie/beego/logs"
|
||||||
|
//
|
||||||
|
// log := NewLogger(10000)
|
||||||
|
// log.SetLogger("console", "")
|
||||||
|
//
|
||||||
|
// > the first params stand for how many channel
|
||||||
|
//
|
||||||
|
// Use it like this:
|
||||||
|
//
|
||||||
|
// log.Trace("trace")
|
||||||
|
// log.Info("info")
|
||||||
|
// log.Warn("warning")
|
||||||
|
// log.Debug("debug")
|
||||||
|
// log.Critical("critical")
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/logs.md
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RFC5424 log message levels.
|
||||||
|
const (
|
||||||
|
LevelEmergency = iota
|
||||||
|
LevelAlert
|
||||||
|
LevelCritical
|
||||||
|
LevelError
|
||||||
|
LevelWarning
|
||||||
|
LevelNotice
|
||||||
|
LevelInformational
|
||||||
|
LevelDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
// levelLogLogger is defined to implement log.Logger
|
||||||
|
// the real log level will be LevelEmergency
|
||||||
|
const levelLoggerImpl = -1
|
||||||
|
|
||||||
|
// Name for adapter with beego official support
|
||||||
|
const (
|
||||||
|
AdapterConsole = "console"
|
||||||
|
AdapterFile = "file"
|
||||||
|
AdapterMultiFile = "multifile"
|
||||||
|
AdapterMail = "smtp"
|
||||||
|
AdapterConn = "conn"
|
||||||
|
AdapterEs = "es"
|
||||||
|
AdapterJianLiao = "jianliao"
|
||||||
|
AdapterSlack = "slack"
|
||||||
|
AdapterAliLS = "alils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Legacy log level constants to ensure backwards compatibility.
|
||||||
|
const (
|
||||||
|
LevelInfo = LevelInformational
|
||||||
|
LevelTrace = LevelDebug
|
||||||
|
LevelWarn = LevelWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
type newLoggerFunc func() Logger
|
||||||
|
|
||||||
|
// Logger defines the behavior of a log provider.
|
||||||
|
type Logger interface {
|
||||||
|
Init(config string) error
|
||||||
|
WriteMsg(when time.Time, msg string, level int) error
|
||||||
|
Destroy()
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
var adapters = make(map[string]newLoggerFunc)
|
||||||
|
var levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
|
||||||
|
|
||||||
|
// Register makes a log provide available by the provided name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, log newLoggerFunc) {
|
||||||
|
if log == nil {
|
||||||
|
panic("logs: Register provide is nil")
|
||||||
|
}
|
||||||
|
if _, dup := adapters[name]; dup {
|
||||||
|
panic("logs: Register called twice for provider " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = log
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeeLogger is default logger in beego application.
|
||||||
|
// it can contain several providers and log message into all providers.
|
||||||
|
type BeeLogger struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
level int
|
||||||
|
init bool
|
||||||
|
enableFuncCallDepth bool
|
||||||
|
loggerFuncCallDepth int
|
||||||
|
asynchronous bool
|
||||||
|
prefix string
|
||||||
|
msgChanLen int64
|
||||||
|
msgChan chan *logMsg
|
||||||
|
signalChan chan string
|
||||||
|
wg sync.WaitGroup
|
||||||
|
outputs []*nameLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAsyncMsgLen = 1e3
|
||||||
|
|
||||||
|
type nameLogger struct {
|
||||||
|
Logger
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type logMsg struct {
|
||||||
|
level int
|
||||||
|
msg string
|
||||||
|
when time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var logMsgPool *sync.Pool
|
||||||
|
|
||||||
|
// NewLogger returns a new BeeLogger.
|
||||||
|
// channelLen means the number of messages in chan(used where asynchronous is true).
|
||||||
|
// if the buffering chan is full, logger adapters write to file or other way.
|
||||||
|
func NewLogger(channelLens ...int64) *BeeLogger {
|
||||||
|
bl := new(BeeLogger)
|
||||||
|
bl.level = LevelDebug
|
||||||
|
bl.loggerFuncCallDepth = 2
|
||||||
|
bl.msgChanLen = append(channelLens, 0)[0]
|
||||||
|
if bl.msgChanLen <= 0 {
|
||||||
|
bl.msgChanLen = defaultAsyncMsgLen
|
||||||
|
}
|
||||||
|
bl.signalChan = make(chan string, 1)
|
||||||
|
bl.setLogger(AdapterConsole)
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async set the log to asynchronous and start the goroutine
|
||||||
|
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
if bl.asynchronous {
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
bl.asynchronous = true
|
||||||
|
if len(msgLen) > 0 && msgLen[0] > 0 {
|
||||||
|
bl.msgChanLen = msgLen[0]
|
||||||
|
}
|
||||||
|
bl.msgChan = make(chan *logMsg, bl.msgChanLen)
|
||||||
|
logMsgPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &logMsg{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bl.wg.Add(1)
|
||||||
|
go bl.startLogger()
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||||
|
// config need to be correct JSON as string: {"interval":360}.
|
||||||
|
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
||||||
|
config := append(configs, "{}")[0]
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
if l.name == adapterName {
|
||||||
|
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logAdapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
lg := logAdapter()
|
||||||
|
err := lg.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||||
|
// config need to be correct JSON as string: {"interval":360}.
|
||||||
|
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
if !bl.init {
|
||||||
|
bl.outputs = []*nameLogger{}
|
||||||
|
bl.init = true
|
||||||
|
}
|
||||||
|
return bl.setLogger(adapterName, configs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelLogger remove a logger adapter in BeeLogger.
|
||||||
|
func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
outputs := []*nameLogger{}
|
||||||
|
for _, lg := range bl.outputs {
|
||||||
|
if lg.name == adapterName {
|
||||||
|
lg.Destroy()
|
||||||
|
} else {
|
||||||
|
outputs = append(outputs, lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(outputs) == len(bl.outputs) {
|
||||||
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
|
}
|
||||||
|
bl.outputs = outputs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
err := l.WriteMsg(when, msg, level)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
// writeMsg will always add a '\n' character
|
||||||
|
if p[len(p)-1] == '\n' {
|
||||||
|
p = p[0 : len(p)-1]
|
||||||
|
}
|
||||||
|
// set levelLoggerImpl to ensure all log message will be write out
|
||||||
|
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||||
|
if err == nil {
|
||||||
|
return len(p), err
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
|
||||||
|
if !bl.init {
|
||||||
|
bl.lock.Lock()
|
||||||
|
bl.setLogger(AdapterConsole)
|
||||||
|
bl.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
msg = fmt.Sprintf(msg, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = bl.prefix + " " + msg
|
||||||
|
|
||||||
|
when := time.Now()
|
||||||
|
if bl.enableFuncCallDepth {
|
||||||
|
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||||
|
if !ok {
|
||||||
|
file = "???"
|
||||||
|
line = 0
|
||||||
|
}
|
||||||
|
_, filename := path.Split(file)
|
||||||
|
msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg
|
||||||
|
}
|
||||||
|
|
||||||
|
//set level info in front of filename info
|
||||||
|
if logLevel == levelLoggerImpl {
|
||||||
|
// set to emergency to ensure all log will be print out correctly
|
||||||
|
logLevel = LevelEmergency
|
||||||
|
} else {
|
||||||
|
msg = levelPrefix[logLevel] + " " + msg
|
||||||
|
}
|
||||||
|
|
||||||
|
if bl.asynchronous {
|
||||||
|
lm := logMsgPool.Get().(*logMsg)
|
||||||
|
lm.level = logLevel
|
||||||
|
lm.msg = msg
|
||||||
|
lm.when = when
|
||||||
|
bl.msgChan <- lm
|
||||||
|
} else {
|
||||||
|
bl.writeToLoggers(when, msg, logLevel)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel Set log message level.
|
||||||
|
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
|
||||||
|
// log providers will not even be sent the message.
|
||||||
|
func (bl *BeeLogger) SetLevel(l int) {
|
||||||
|
bl.level = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel Get Current log message level.
|
||||||
|
func (bl *BeeLogger) GetLevel() int {
|
||||||
|
return bl.level
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
|
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||||
|
bl.loggerFuncCallDepth = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogFuncCallDepth return log funcCallDepth for wrapper
|
||||||
|
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||||
|
return bl.loggerFuncCallDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
|
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||||
|
bl.enableFuncCallDepth = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// set prefix
|
||||||
|
func (bl *BeeLogger) SetPrefix(s string) {
|
||||||
|
bl.prefix = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// start logger chan reading.
|
||||||
|
// when chan is not empty, write logs.
|
||||||
|
func (bl *BeeLogger) startLogger() {
|
||||||
|
gameOver := false
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case bm := <-bl.msgChan:
|
||||||
|
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
case sg := <-bl.signalChan:
|
||||||
|
// Now should only send "flush" or "close" to bl.signalChan
|
||||||
|
bl.flush()
|
||||||
|
if sg == "close" {
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
gameOver = true
|
||||||
|
}
|
||||||
|
bl.wg.Done()
|
||||||
|
}
|
||||||
|
if gameOver {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency Log EMERGENCY level message.
|
||||||
|
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||||
|
if LevelEmergency > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelEmergency, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert Log ALERT level message.
|
||||||
|
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||||
|
if LevelAlert > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelAlert, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical Log CRITICAL level message.
|
||||||
|
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||||
|
if LevelCritical > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelCritical, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error Log ERROR level message.
|
||||||
|
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||||
|
if LevelError > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelError, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning Log WARNING level message.
|
||||||
|
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||||
|
if LevelWarn > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelWarn, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice Log NOTICE level message.
|
||||||
|
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||||
|
if LevelNotice > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelNotice, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informational Log INFORMATIONAL level message.
|
||||||
|
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||||
|
if LevelInfo > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelInfo, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug Log DEBUG level message.
|
||||||
|
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||||
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelDebug, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn Log WARN level message.
|
||||||
|
// compatibility alias for Warning()
|
||||||
|
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||||
|
if LevelWarn > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelWarn, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info Log INFO level message.
|
||||||
|
// compatibility alias for Informational()
|
||||||
|
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||||
|
if LevelInfo > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelInfo, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace Log TRACE level message.
|
||||||
|
// compatibility alias for Debug()
|
||||||
|
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||||
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.writeMsg(LevelDebug, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flush all chan data.
|
||||||
|
func (bl *BeeLogger) Flush() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
bl.signalChan <- "flush"
|
||||||
|
bl.wg.Wait()
|
||||||
|
bl.wg.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||||
|
func (bl *BeeLogger) Close() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
bl.signalChan <- "close"
|
||||||
|
bl.wg.Wait()
|
||||||
|
close(bl.msgChan)
|
||||||
|
} else {
|
||||||
|
bl.flush()
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
}
|
||||||
|
close(bl.signalChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset close all outputs, and set bl.outputs to nil
|
||||||
|
func (bl *BeeLogger) Reset() {
|
||||||
|
bl.Flush()
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) flush() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
for {
|
||||||
|
if len(bl.msgChan) > 0 {
|
||||||
|
bm := <-bl.msgChan
|
||||||
|
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// beeLogger references the used application logger.
|
||||||
|
var beeLogger = NewLogger()
|
||||||
|
|
||||||
|
// GetBeeLogger returns the default BeeLogger
|
||||||
|
func GetBeeLogger() *BeeLogger {
|
||||||
|
return beeLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
var beeLoggerMap = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
logs map[string]*log.Logger
|
||||||
|
}{
|
||||||
|
logs: map[string]*log.Logger{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogger returns the default BeeLogger
|
||||||
|
func GetLogger(prefixes ...string) *log.Logger {
|
||||||
|
prefix := append(prefixes, "")[0]
|
||||||
|
if prefix != "" {
|
||||||
|
prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
|
||||||
|
}
|
||||||
|
beeLoggerMap.RLock()
|
||||||
|
l, ok := beeLoggerMap.logs[prefix]
|
||||||
|
if ok {
|
||||||
|
beeLoggerMap.RUnlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
beeLoggerMap.RUnlock()
|
||||||
|
beeLoggerMap.Lock()
|
||||||
|
defer beeLoggerMap.Unlock()
|
||||||
|
l, ok = beeLoggerMap.logs[prefix]
|
||||||
|
if !ok {
|
||||||
|
l = log.New(beeLogger, prefix, 0)
|
||||||
|
beeLoggerMap.logs[prefix] = l
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset will remove all the adapter
|
||||||
|
func Reset() {
|
||||||
|
beeLogger.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async set the beelogger with Async mode and hold msglen messages
|
||||||
|
func Async(msgLen ...int64) *BeeLogger {
|
||||||
|
return beeLogger.Async(msgLen...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the global log level used by the simple logger.
|
||||||
|
func SetLevel(l int) {
|
||||||
|
beeLogger.SetLevel(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrefix sets the prefix
|
||||||
|
func SetPrefix(s string) {
|
||||||
|
beeLogger.SetPrefix(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
|
func EnableFuncCallDepth(b bool) {
|
||||||
|
beeLogger.enableFuncCallDepth = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCall set the CallDepth, default is 4
|
||||||
|
func SetLogFuncCall(b bool) {
|
||||||
|
beeLogger.EnableFuncCallDepth(b)
|
||||||
|
beeLogger.SetLogFuncCallDepth(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
|
func SetLogFuncCallDepth(d int) {
|
||||||
|
beeLogger.loggerFuncCallDepth = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets a new logger.
|
||||||
|
func SetLogger(adapter string, config ...string) error {
|
||||||
|
return beeLogger.SetLogger(adapter, config...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency logs a message at emergency level.
|
||||||
|
func Emergency(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Emergency(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert logs a message at alert level.
|
||||||
|
func Alert(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Alert(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical logs a message at critical level.
|
||||||
|
func Critical(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Critical(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at error level.
|
||||||
|
func Error(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Error(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at warning level.
|
||||||
|
func Warning(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Warn(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn compatibility alias for Warning()
|
||||||
|
func Warn(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Warn(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice logs a message at notice level.
|
||||||
|
func Notice(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Notice(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informational logs a message at info level.
|
||||||
|
func Informational(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Info(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info compatibility alias for Warning()
|
||||||
|
func Info(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Info(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at debug level.
|
||||||
|
func Debug(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Debug(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace logs a message at trace level.
|
||||||
|
// compatibility alias for Warning()
|
||||||
|
func Trace(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Trace(formatLog(f, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatLog(f interface{}, v ...interface{}) string {
|
||||||
|
var msg string
|
||||||
|
switch f.(type) {
|
||||||
|
case string:
|
||||||
|
msg = f.(string)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
|
||||||
|
//format string
|
||||||
|
} else {
|
||||||
|
//do not contain format char
|
||||||
|
msg += strings.Repeat(" %v", len(v))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
msg = fmt.Sprint(f)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
msg += strings.Repeat(" %v", len(v))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(msg, v...)
|
||||||
|
}
|
175
logs/logger.go
Normal file
175
logs/logger.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// 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 (
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logWriter struct {
|
||||||
|
sync.Mutex
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogWriter(wr io.Writer) *logWriter {
|
||||||
|
return &logWriter{writer: wr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lg *logWriter) writeln(when time.Time, msg string) {
|
||||||
|
lg.Lock()
|
||||||
|
h, _, _ := formatTimeHeader(when)
|
||||||
|
lg.writer.Write(append(append(h, msg...), '\n'))
|
||||||
|
lg.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
y1 = `0123456789`
|
||||||
|
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999`
|
||||||
|
y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
mo1 = `000000000111`
|
||||||
|
mo2 = `123456789012`
|
||||||
|
d1 = `0000000001111111111222222222233`
|
||||||
|
d2 = `1234567890123456789012345678901`
|
||||||
|
h1 = `000000000011111111112222`
|
||||||
|
h2 = `012345678901234567890123`
|
||||||
|
mi1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
|
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
|
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
ns1 = `0123456789`
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatTimeHeader(when time.Time) ([]byte, int, int) {
|
||||||
|
y, mo, d := when.Date()
|
||||||
|
h, mi, s := when.Clock()
|
||||||
|
ns := when.Nanosecond() / 1000000
|
||||||
|
//len("2006/01/02 15:04:05.123 ")==24
|
||||||
|
var buf [24]byte
|
||||||
|
|
||||||
|
buf[0] = y1[y/1000%10]
|
||||||
|
buf[1] = y2[y/100]
|
||||||
|
buf[2] = y3[y-y/100*100]
|
||||||
|
buf[3] = y4[y-y/100*100]
|
||||||
|
buf[4] = '/'
|
||||||
|
buf[5] = mo1[mo-1]
|
||||||
|
buf[6] = mo2[mo-1]
|
||||||
|
buf[7] = '/'
|
||||||
|
buf[8] = d1[d-1]
|
||||||
|
buf[9] = d2[d-1]
|
||||||
|
buf[10] = ' '
|
||||||
|
buf[11] = h1[h]
|
||||||
|
buf[12] = h2[h]
|
||||||
|
buf[13] = ':'
|
||||||
|
buf[14] = mi1[mi]
|
||||||
|
buf[15] = mi2[mi]
|
||||||
|
buf[16] = ':'
|
||||||
|
buf[17] = s1[s]
|
||||||
|
buf[18] = s2[s]
|
||||||
|
buf[19] = '.'
|
||||||
|
buf[20] = ns1[ns/100]
|
||||||
|
buf[21] = ns1[ns%100/10]
|
||||||
|
buf[22] = ns1[ns%10]
|
||||||
|
|
||||||
|
buf[23] = ' '
|
||||||
|
|
||||||
|
return buf[0:], d, h
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||||
|
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||||
|
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
|
||||||
|
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||||
|
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||||
|
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||||
|
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||||
|
|
||||||
|
w32Green = string([]byte{27, 91, 52, 50, 109})
|
||||||
|
w32White = string([]byte{27, 91, 52, 55, 109})
|
||||||
|
w32Yellow = string([]byte{27, 91, 52, 51, 109})
|
||||||
|
w32Red = string([]byte{27, 91, 52, 49, 109})
|
||||||
|
w32Blue = string([]byte{27, 91, 52, 52, 109})
|
||||||
|
w32Magenta = string([]byte{27, 91, 52, 53, 109})
|
||||||
|
w32Cyan = string([]byte{27, 91, 52, 54, 109})
|
||||||
|
|
||||||
|
reset = string([]byte{27, 91, 48, 109})
|
||||||
|
)
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
var colorMap map[string]string
|
||||||
|
|
||||||
|
func initColor() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
green = w32Green
|
||||||
|
white = w32White
|
||||||
|
yellow = w32Yellow
|
||||||
|
red = w32Red
|
||||||
|
blue = w32Blue
|
||||||
|
magenta = w32Magenta
|
||||||
|
cyan = w32Cyan
|
||||||
|
}
|
||||||
|
colorMap = map[string]string{
|
||||||
|
//by color
|
||||||
|
"green": green,
|
||||||
|
"white": white,
|
||||||
|
"yellow": yellow,
|
||||||
|
"red": red,
|
||||||
|
//by method
|
||||||
|
"GET": blue,
|
||||||
|
"POST": cyan,
|
||||||
|
"PUT": yellow,
|
||||||
|
"DELETE": red,
|
||||||
|
"PATCH": green,
|
||||||
|
"HEAD": magenta,
|
||||||
|
"OPTIONS": white,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorByStatus return color by http code
|
||||||
|
// 2xx return Green
|
||||||
|
// 3xx return White
|
||||||
|
// 4xx return Yellow
|
||||||
|
// 5xx return Red
|
||||||
|
func ColorByStatus(code int) string {
|
||||||
|
once.Do(initColor)
|
||||||
|
switch {
|
||||||
|
case code >= 200 && code < 300:
|
||||||
|
return colorMap["green"]
|
||||||
|
case code >= 300 && code < 400:
|
||||||
|
return colorMap["white"]
|
||||||
|
case code >= 400 && code < 500:
|
||||||
|
return colorMap["yellow"]
|
||||||
|
default:
|
||||||
|
return colorMap["red"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorByMethod return color by http code
|
||||||
|
func ColorByMethod(method string) string {
|
||||||
|
once.Do(initColor)
|
||||||
|
if c := colorMap[method]; c != "" {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return reset
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetColor return reset color
|
||||||
|
func ResetColor() string {
|
||||||
|
return reset
|
||||||
|
}
|
57
logs/logger_test.go
Normal file
57
logs/logger_test.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2016 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 (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatHeader_0(t *testing.T) {
|
||||||
|
tm := time.Now()
|
||||||
|
if tm.Year() >= 2100 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
dur := time.Second
|
||||||
|
for {
|
||||||
|
if tm.Year() >= 2100 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
h, _, _ := formatTimeHeader(tm)
|
||||||
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
|
t.Log(tm)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
tm = tm.Add(dur)
|
||||||
|
dur *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatHeader_1(t *testing.T) {
|
||||||
|
tm := time.Now()
|
||||||
|
year := tm.Year()
|
||||||
|
dur := time.Second
|
||||||
|
for {
|
||||||
|
if tm.Year() >= year+1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
h, _, _ := formatTimeHeader(tm)
|
||||||
|
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||||
|
t.Log(tm)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
tm = tm.Add(dur)
|
||||||
|
}
|
||||||
|
}
|
119
logs/multifile.go
Normal file
119
logs/multifile.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A filesLogWriter manages several fileLogWriter
|
||||||
|
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
|
||||||
|
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
|
||||||
|
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
|
||||||
|
// the rotate attribute also acts like fileLogWriter
|
||||||
|
type multiFileLogWriter struct {
|
||||||
|
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
|
||||||
|
fullLogWriter *fileLogWriter
|
||||||
|
Separate []string `json:"separate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
|
||||||
|
|
||||||
|
// Init file logger with json config.
|
||||||
|
// jsonConfig like:
|
||||||
|
// {
|
||||||
|
// "filename":"logs/beego.log",
|
||||||
|
// "maxLines":0,
|
||||||
|
// "maxsize":0,
|
||||||
|
// "daily":true,
|
||||||
|
// "maxDays":15,
|
||||||
|
// "rotate":true,
|
||||||
|
// "perm":0600,
|
||||||
|
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Init(config string) error {
|
||||||
|
writer := newFileWriter().(*fileLogWriter)
|
||||||
|
err := writer.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.fullLogWriter = writer
|
||||||
|
f.writers[LevelDebug+1] = writer
|
||||||
|
|
||||||
|
//unmarshal "separate" field to f.Separate
|
||||||
|
json.Unmarshal([]byte(config), f)
|
||||||
|
|
||||||
|
jsonMap := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(config), &jsonMap)
|
||||||
|
|
||||||
|
for i := LevelEmergency; i < LevelDebug+1; i++ {
|
||||||
|
for _, v := range f.Separate {
|
||||||
|
if v == levelNames[i] {
|
||||||
|
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
|
||||||
|
jsonMap["level"] = i
|
||||||
|
bs, _ := json.Marshal(jsonMap)
|
||||||
|
writer = newFileWriter().(*fileLogWriter)
|
||||||
|
err := writer.Init(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writers[i] = writer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Destroy() {
|
||||||
|
for i := 0; i < len(f.writers); i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
f.writers[i].Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if f.fullLogWriter != nil {
|
||||||
|
f.fullLogWriter.WriteMsg(when, msg, level)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(f.writers)-1; i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
if level == f.writers[i].Level {
|
||||||
|
f.writers[i].WriteMsg(when, msg, level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Flush() {
|
||||||
|
for i := 0; i < len(f.writers); i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
f.writers[i].Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
|
||||||
|
func newFilesWriter() Logger {
|
||||||
|
return &multiFileLogWriter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterMultiFile, newFilesWriter)
|
||||||
|
}
|
78
logs/multifile_test.go
Normal file
78
logs/multifile_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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 (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFiles_1(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`)
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Informational("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("error")
|
||||||
|
log.Alert("alert")
|
||||||
|
log.Critical("critical")
|
||||||
|
log.Emergency("emergency")
|
||||||
|
fns := []string{""}
|
||||||
|
fns = append(fns, levelNames[0:]...)
|
||||||
|
name := "test"
|
||||||
|
suffix := ".log"
|
||||||
|
for _, fn := range fns {
|
||||||
|
|
||||||
|
file := name + suffix
|
||||||
|
if fn != "" {
|
||||||
|
file = name + "." + fn + suffix
|
||||||
|
}
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b := bufio.NewReader(f)
|
||||||
|
lineNum := 0
|
||||||
|
lastLine := ""
|
||||||
|
for {
|
||||||
|
line, _, err := b.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
lastLine = string(line)
|
||||||
|
lineNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var expected = 1
|
||||||
|
if fn == "" {
|
||||||
|
expected = LevelDebug + 1
|
||||||
|
}
|
||||||
|
if lineNum != expected {
|
||||||
|
t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines")
|
||||||
|
}
|
||||||
|
if lineNum == 1 {
|
||||||
|
if !strings.Contains(lastLine, fn) {
|
||||||
|
t.Fatal(file + " " + lastLine + " not contains the log msg " + fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
logs/slack.go
Normal file
60
logs/slack.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type SLACKWriter struct {
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSLACKWriter create jiaoliao writer.
|
||||||
|
func newSLACKWriter() Logger {
|
||||||
|
return &SLACKWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init SLACKWriter with json config string
|
||||||
|
func (s *SLACKWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("payload", text)
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterSlack, newSLACKWriter)
|
||||||
|
}
|
149
logs/smtp.go
Normal file
149
logs/smtp.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// 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 (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server.
|
||||||
|
type SMTPWriter struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
FromAddress string `json:"fromAddress"`
|
||||||
|
RecipientAddresses []string `json:"sendTos"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMTPWriter create smtp writer.
|
||||||
|
func newSMTPWriter() Logger {
|
||||||
|
return &SMTPWriter{Level: LevelTrace}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init smtp writer with json config.
|
||||||
|
// config like:
|
||||||
|
// {
|
||||||
|
// "username":"example@gmail.com",
|
||||||
|
// "password:"password",
|
||||||
|
// "host":"smtp.gmail.com:465",
|
||||||
|
// "subject":"email title",
|
||||||
|
// "fromAddress":"from@example.com",
|
||||||
|
// "sendTos":["email1","email2"],
|
||||||
|
// "level":LevelError
|
||||||
|
// }
|
||||||
|
func (s *SMTPWriter) Init(jsonconfig string) error {
|
||||||
|
return json.Unmarshal([]byte(jsonconfig), s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
||||||
|
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return smtp.PlainAuth(
|
||||||
|
"",
|
||||||
|
s.Username,
|
||||||
|
s.Password,
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
|
||||||
|
client, err := smtp.Dial(hostAddressWithPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, _ := net.SplitHostPort(hostAddressWithPort)
|
||||||
|
tlsConn := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: host,
|
||||||
|
}
|
||||||
|
if err = client.StartTLS(tlsConn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth != nil {
|
||||||
|
if err = client.Auth(auth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.Mail(fromAddress); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range recipients {
|
||||||
|
if err = client.Rcpt(rec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(msgContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// it will send an email with subject and only this message.
|
||||||
|
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||||
|
if level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hp := strings.Split(s.Host, ":")
|
||||||
|
|
||||||
|
// Set up authentication information.
|
||||||
|
auth := s.getSMTPAuth(hp[0])
|
||||||
|
|
||||||
|
// Connect to the server, authenticate, set the sender and recipient,
|
||||||
|
// and send the email all in one step.
|
||||||
|
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||||
|
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||||
|
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg)
|
||||||
|
|
||||||
|
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *SMTPWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *SMTPWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterMail, newSMTPWriter)
|
||||||
|
}
|
27
logs/smtp_test.go
Normal file
27
logs/smtp_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSmtp(t *testing.T) {
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||||
|
log.Critical("sendmail critical")
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user