Initial commit
This commit is contained in:
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
.DS_Store
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.dSYM
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# IDE project
|
||||||
|
*.project
|
||||||
|
*.buildpath
|
||||||
|
.idea/*
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Xcode
|
||||||
|
## gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||||
|
## Build generated
|
||||||
|
build/
|
||||||
|
DerivedData
|
||||||
|
## Various settings
|
||||||
|
*.pbxuser
|
||||||
|
!default.pbxuser
|
||||||
|
*.mode1v3
|
||||||
|
!default.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
!default.mode2v3
|
||||||
|
*.perspectivev3
|
||||||
|
!default.perspectivev3
|
||||||
|
xcuserdata
|
||||||
|
## Other
|
||||||
|
*.xccheckout
|
||||||
|
*.moved-aside
|
||||||
|
*.xcuserstate
|
||||||
|
*.xcscmblueprint
|
||||||
|
## Playgrounds
|
||||||
|
timeline.xctimeline
|
||||||
|
playground.xcworkspace
|
||||||
|
.build/
|
||||||
24
Readme.md
Normal file
24
Readme.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#Go lib
|
||||||
|
|
||||||
|
## tlog
|
||||||
|
|
||||||
|
## trace
|
||||||
|
|
||||||
|
## metrics
|
||||||
|
|
||||||
|
## cache
|
||||||
|
|
||||||
|
## lrucache
|
||||||
|
|
||||||
|
## db
|
||||||
|
mysql
|
||||||
|
## kv
|
||||||
|
redis
|
||||||
|
## kafka
|
||||||
|
|
||||||
|
## aws
|
||||||
|
|
||||||
|
## geo
|
||||||
|
maxmind geoip2
|
||||||
|
## qgrpc
|
||||||
|
|
||||||
126
aws/aws.go
Normal file
126
aws/aws.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AwsAccessKeyId string `toml:"access_key_id" json:"access_key_id"`
|
||||||
|
AwsSecretAccessKey string `toml:"secret_access_key" json:"secret_access_id"`
|
||||||
|
AwsBuckets string `toml:"buckets" json:"buckets"`
|
||||||
|
AwsRegion string `toml:"region" json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS3Session(c *Config) *session.Session {
|
||||||
|
cfg := aws.NewConfig().WithRegion(c.AwsRegion).WithCredentials(
|
||||||
|
credentials.NewStaticCredentials(
|
||||||
|
c.AwsAccessKeyId,
|
||||||
|
c.AwsSecretAccessKey,
|
||||||
|
""),
|
||||||
|
)
|
||||||
|
s, _ := session.NewSession(cfg)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func Put(c Config, key, contentType string, input io.ReadSeeker) error {
|
||||||
|
s := NewS3Session(&c)
|
||||||
|
client := s3.New(s)
|
||||||
|
_, err := client.PutObject(
|
||||||
|
&s3.PutObjectInput{
|
||||||
|
Bucket: aws.String(c.AwsBuckets),
|
||||||
|
Key: aws.String(key),
|
||||||
|
ContentType: aws.String(contentType),
|
||||||
|
Body: input,
|
||||||
|
CacheControl: aws.String("max-age=2592000"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Del(c Config, key string) error {
|
||||||
|
s := NewS3Session(&c)
|
||||||
|
client := s3.New(s)
|
||||||
|
|
||||||
|
arg := &s3.DeleteObjectInput{
|
||||||
|
Bucket: aws.String(c.AwsBuckets),
|
||||||
|
Key: aws.String(key),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.DeleteObject(arg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(c Config, key string) (string, []byte, error) {
|
||||||
|
s := NewS3Session(&c)
|
||||||
|
client := s3.New(s)
|
||||||
|
|
||||||
|
arg := &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(c.AwsBuckets),
|
||||||
|
Key: aws.String(key),
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := client.GetObject(arg)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret != nil {
|
||||||
|
defer ret.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := ""
|
||||||
|
if ret != nil && ret.ContentType != nil {
|
||||||
|
contentType = *ret.ContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(ret.Body)
|
||||||
|
|
||||||
|
return contentType, b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func List(c Config, prefix string, file string, count int64) ([]string, error) {
|
||||||
|
s := NewS3Session(&c)
|
||||||
|
client := s3.New(s)
|
||||||
|
|
||||||
|
input := &s3.ListObjectsInput{
|
||||||
|
Bucket: aws.String(c.AwsBuckets),
|
||||||
|
Prefix: aws.String(prefix),
|
||||||
|
Marker: aws.String(file),
|
||||||
|
MaxKeys: aws.Int64(count),
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := client.ListObjects(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := []string{}
|
||||||
|
|
||||||
|
for _, obj := range ret.Contents {
|
||||||
|
if strings.HasSuffix(*obj.Key, "/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys = append(keys, *obj.Key)
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Copy(c Config, source, key string) error {
|
||||||
|
s := NewS3Session(&c)
|
||||||
|
client := s3.New(s)
|
||||||
|
|
||||||
|
_, err := client.CopyObject(&s3.CopyObjectInput{
|
||||||
|
Bucket: aws.String(c.AwsBuckets),
|
||||||
|
CopySource: aws.String(source),
|
||||||
|
Key: aws.String(key),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
122
cache/cache.go
vendored
Normal file
122
cache/cache.go
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/creachadair/cityhash"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//API
|
||||||
|
var Nil error = errors.New("cache: nil")
|
||||||
|
|
||||||
|
func Set(key string, value interface{}) error {
|
||||||
|
return SetEx(key, value, 86400)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetEx(key string, value interface{}, exp int) error {
|
||||||
|
return setEx(key, value, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(key string) (interface{}, error) {
|
||||||
|
return get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Del(key string) error {
|
||||||
|
return del(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
//internal
|
||||||
|
var dbs map[byte]*db
|
||||||
|
|
||||||
|
type db struct {
|
||||||
|
m map[string]*item
|
||||||
|
t time.Duration
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type item struct {
|
||||||
|
t int
|
||||||
|
d interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if dbs == nil {
|
||||||
|
dbs = make(map[byte]*db, 256)
|
||||||
|
for i := 0; i <= 255; i++ {
|
||||||
|
dbs[byte(i)] = newdb(time.Duration(3600+i) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newdb(t time.Duration) *db {
|
||||||
|
ret := &db{
|
||||||
|
t: t,
|
||||||
|
m: make(map[string]*item),
|
||||||
|
}
|
||||||
|
go ret.flush()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
//定期扫描过期的key
|
||||||
|
func (this *db) flush() {
|
||||||
|
for range time.NewTicker(this.t).C {
|
||||||
|
this.doflush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *db) doflush() {
|
||||||
|
start := time.Now()
|
||||||
|
tt := int(start.Unix())
|
||||||
|
var i, j int
|
||||||
|
this.Lock()
|
||||||
|
for key, item := range this.m {
|
||||||
|
i++
|
||||||
|
if item.t != 0 && item.t < tt {
|
||||||
|
j++
|
||||||
|
delete(this.m, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Unlock()
|
||||||
|
tlog.Infof("FlushCacheKey||Totoal=%d||Expired=%d||t=%s", i, j, time.Since(start))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setEx(key string, value interface{}, exp int) error {
|
||||||
|
if exp <= 0 {
|
||||||
|
return errors.New("args error, expTime must be > 0")
|
||||||
|
}
|
||||||
|
db, _ := dbs[hash(key)]
|
||||||
|
db.Lock()
|
||||||
|
db.m[key] = &item{
|
||||||
|
d: value,
|
||||||
|
t: int(time.Now().Unix()) + exp}
|
||||||
|
db.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(key string) (interface{}, error) {
|
||||||
|
db, _ := dbs[hash(key)]
|
||||||
|
db.RLock()
|
||||||
|
item, ok := db.m[key]
|
||||||
|
db.RUnlock()
|
||||||
|
if ok {
|
||||||
|
if item.t >= int(time.Now().Unix()) {
|
||||||
|
return item.d, nil
|
||||||
|
}
|
||||||
|
del(key)
|
||||||
|
}
|
||||||
|
return nil, Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func del(key string) error {
|
||||||
|
db, _ := dbs[hash(key)]
|
||||||
|
db.Lock()
|
||||||
|
delete(db.m, key)
|
||||||
|
db.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(key string) byte {
|
||||||
|
return byte(cityhash.Hash32([]byte(key)))
|
||||||
|
}
|
||||||
88
cache/cache_test.go
vendored
Normal file
88
cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet(t *testing.T) {
|
||||||
|
err := Set("test", "test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value, err := Get("test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(value.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStruct(t *testing.T) {
|
||||||
|
err := Set("struct", T{A: 1, B: "testtest"})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value1, err := Get("struct")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v\n", value1.(T))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStruct1(t *testing.T) {
|
||||||
|
err := Set("struct", &T{A: 1, B: "testtest"})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value1, err := Get("struct")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v\n", value1.(*T))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDel(t *testing.T) {
|
||||||
|
err := Del("test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value, err := Get("test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(value.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetEx(t *testing.T) {
|
||||||
|
err := SetEx("testex", []byte("testex"), 2)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
valueEx, err := Get("testex")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(valueEx.([]byte))
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
valueEx, err = Get("testex")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(valueEx.([]byte))
|
||||||
|
}
|
||||||
67
db/db.go
Normal file
67
db/db.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
var x map[string]*sql.DB = make(map[string]*sql.DB)
|
||||||
|
|
||||||
|
var ErrNoDb = errors.New("no db")
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Addr string `toml:"addr" json:"addr"`
|
||||||
|
User string `toml:"user" json:"user"`
|
||||||
|
Pwd string `toml:"pwd" json:"pwd"`
|
||||||
|
Db string `toml:"db" json:"db"`
|
||||||
|
Options string `toml:"options" json:"options"`
|
||||||
|
MaxOpenConns int `toml:"max_open_conns" json:"max_open_conns"`
|
||||||
|
MaxIdleConns int `toml:"max_idle_conns" json:"max_idle_conns"`
|
||||||
|
MaxConnLifeTime int `toml:"max_conn_life_time" json:"max_conn_life_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) Dsn() string {
|
||||||
|
if c.Options == "" {
|
||||||
|
format := "%s:%s@tcp(%s)/%s?charset=utf8mb4&timeout=5s"
|
||||||
|
return fmt.Sprintf(format, c.User, c.Pwd, c.Addr, c.Db)
|
||||||
|
} else {
|
||||||
|
format := "%s:%s@tcp(%s)/%s?charset=utf8mb4&timeout=5s&%s"
|
||||||
|
return fmt.Sprintf(format, c.User, c.Pwd, c.Addr, c.Db, c.Options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Add(name string, c Config) error {
|
||||||
|
db, err := sql.Open("mysql", c.Dsn())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db.SetMaxIdleConns(c.MaxIdleConns)
|
||||||
|
db.SetMaxOpenConns(c.MaxOpenConns)
|
||||||
|
|
||||||
|
if c.MaxConnLifeTime <= 0 {
|
||||||
|
c.MaxConnLifeTime = 600 // Default 10 minutes
|
||||||
|
}
|
||||||
|
db.SetConnMaxLifetime(time.Duration(c.MaxConnLifeTime) * time.Second)
|
||||||
|
|
||||||
|
_, ok := x[name]
|
||||||
|
if ok {
|
||||||
|
return errors.New("db exists")
|
||||||
|
}
|
||||||
|
x[name] = db
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDB(name string) (*sql.DB, error) {
|
||||||
|
db, ok := x[name]
|
||||||
|
if ok {
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
return nil, ErrNoDb
|
||||||
|
}
|
||||||
92
geo/geo.go
Normal file
92
geo/geo.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package geo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
iplib "github.com/oschwald/geoip2-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *iplib.Reader
|
||||||
|
|
||||||
|
func Init(file string) (err error) {
|
||||||
|
db, err = iplib.Open(file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
if db != nil {
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func City(ip string) (*iplib.City, error) {
|
||||||
|
return db.City(net.ParseIP(ip))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCity(ip string) (string, error) {
|
||||||
|
r, err := db.City(net.ParseIP(ip))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return r.City.Names["en"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCountry(ip string) (string, error) {
|
||||||
|
r, err := db.City(net.ParseIP(ip))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return r.Country.Names["en"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProvince(ip string) (string, error) {
|
||||||
|
defer func() { recover() }()
|
||||||
|
r, err := db.City(net.ParseIP(ip))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return r.Subdivisions[0].Names["en"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//同时获取国家,省份,简写, 城市名称
|
||||||
|
func GetLoc(ip string) (string, string, string, error) {
|
||||||
|
defer func() { recover() }()
|
||||||
|
r, err := db.City(net.ParseIP(ip))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Country.IsoCode, r.Subdivisions[0].IsoCode, r.City.Names["en"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 iso_code, name
|
||||||
|
func GetContinent(ip string) (string, string, error) {
|
||||||
|
defer func() { recover() }()
|
||||||
|
r, err := db.City(net.ParseIP(ip))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Continent.Code, r.Continent.Names["en"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return latitude and longitude
|
||||||
|
func GetCoordinate(ip string) (float64, float64, error) {
|
||||||
|
defer func() { recover() }()
|
||||||
|
r, err := db.City(net.ParseIP(ip))
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Location.Latitude, r.Location.Longitude, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTimezone(ip string) (string, error) {
|
||||||
|
defer func() { recover() }()
|
||||||
|
r, err := db.City(net.ParseIP(ip))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Location.TimeZone, nil
|
||||||
|
}
|
||||||
79
gfetcd/README.md
Normal file
79
gfetcd/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
## 代码结构
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
├── README.md
|
||||||
|
├── client.go # grpc客户端调用
|
||||||
|
├── config.go # 配置结构
|
||||||
|
├── resolver.go # 地址解析
|
||||||
|
└── server.go # grpc服务端调用
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据结构
|
||||||
|
|
||||||
|
### 基本结构
|
||||||
|
|
||||||
|
``` golang
|
||||||
|
type BasicConfig struct {
|
||||||
|
DialTimeout int `toml:"dial_timeout"` // 连接超时时间,默认5秒
|
||||||
|
EndPoints []string `toml:"endpoints"` // etcd的地址
|
||||||
|
Prefix string `toml:"prefix"` // etcd服务的路径前缀,区分环境用
|
||||||
|
Debug bool `toml:"debug"` // 使用debug模式,打印详细的etcd交互日志,很多,非调试不建议打开
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### grpc服务端用的配置
|
||||||
|
|
||||||
|
``` golang
|
||||||
|
type NameConfig struct {
|
||||||
|
BasicConfig
|
||||||
|
Servers []string `toml:"servers"` // 注册的服务名称,可以是多个
|
||||||
|
Addr string `toml:"addr"` // 服务地址,可以是IP:PORT或者:PORT格式
|
||||||
|
TTL int64 `toml:"ttl"` // 租约超时时间,默认5秒
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### grpc客户端用的配置
|
||||||
|
|
||||||
|
``` golang
|
||||||
|
type ResolverConfig struct {
|
||||||
|
BasicConfig
|
||||||
|
WatchServers []string `toml:"watch_servers"` // 依赖的服务名称
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### grpc服务端
|
||||||
|
|
||||||
|
``` golang
|
||||||
|
// demo
|
||||||
|
&config.EtcdName = {
|
||||||
|
EndPoints:[]string{"127.0.0.1:2379"},
|
||||||
|
Prefix: "/ng/test/okash",
|
||||||
|
Servers: []string{"orders.Orders", "product.Product"},
|
||||||
|
Addr: ":8701",
|
||||||
|
}
|
||||||
|
etcdName, err := gfetcd.NameRegist(&config.EtcdName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer etcdName.Unregister()
|
||||||
|
```
|
||||||
|
|
||||||
|
### grpc客户端
|
||||||
|
|
||||||
|
```golang
|
||||||
|
// demo
|
||||||
|
&config.EtcdReslv{
|
||||||
|
EndPoints:[]string{"127.0.0.1:2379"},
|
||||||
|
Prefix: "/ng/test/okash",
|
||||||
|
WatchServers: []string{"product.Product"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接etcd
|
||||||
|
EtcdReslv, err = gfetcd.NewResolver(&config.EtcdReslv)
|
||||||
|
|
||||||
|
// 调用方法
|
||||||
|
err := EtcdReslv.Call(context.Background(), &XXXReq{}, &XXXResp{})
|
||||||
|
```
|
||||||
|
|
||||||
91
gfetcd/client.go
Normal file
91
gfetcd/client.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package gfetcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
"google.golang.org/grpc/keepalive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 客户端配置
|
||||||
|
type ResolverConfig struct {
|
||||||
|
BasicConfig
|
||||||
|
WatchServers []string `toml:"watch_servers"` // 依赖的服务名称
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
userCfg *ResolverConfig
|
||||||
|
etcdCli *clientv3.Client
|
||||||
|
rpcServers map[string]*grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolver(cfg *ResolverConfig) (resolver *Resolver, err error) {
|
||||||
|
|
||||||
|
if cfg.Debug {
|
||||||
|
clientv3.SetLogger(grpclog.NewLoggerV2WithVerbosity(os.Stderr, os.Stderr, os.Stderr, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
cli, err := etcdDial(&cfg.BasicConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver = &Resolver{
|
||||||
|
userCfg: cfg,
|
||||||
|
etcdCli: cli,
|
||||||
|
rpcServers: make(map[string]*grpc.ClientConn),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, serviceName := range cfg.WatchServers {
|
||||||
|
|
||||||
|
service := ServiceKey(cfg.Prefix, serviceName)
|
||||||
|
tlog.Debugf("ETCD RESOLVER||service: %s", service)
|
||||||
|
|
||||||
|
//r := &etcdnaming.GRPCResolver{Client: cli} // todo: 全部更新为新版后,改为这种方式
|
||||||
|
r := &GFgrpcResolver{Client: cli}
|
||||||
|
b := grpc.RoundRobin(r)
|
||||||
|
|
||||||
|
resolver.rpcServers[service], err = grpc.Dial(service, grpc.WithInsecure(),
|
||||||
|
grpc.WithTimeout(time.Second*5),
|
||||||
|
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: time.Duration(2) * time.Second}),
|
||||||
|
grpc.WithBalancer(b),
|
||||||
|
grpc.WithBlock())
|
||||||
|
if err != nil {
|
||||||
|
tlog.Errorf("ETCD RESOLVER||service: %s ||grpc dial err:%s", service, err)
|
||||||
|
return nil, fmt.Errorf("grpc dial err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Resolver) Call(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
|
||||||
|
// method format: /<serviceName>/<methodName>
|
||||||
|
// eg. "/case_expire.CaseExpire/ExpireCase"
|
||||||
|
|
||||||
|
t := strings.Split(method, "/")
|
||||||
|
if len(t) < 3 {
|
||||||
|
tlog.Errorf("ETCD RESOLVER||method name: %s incorrect format", method)
|
||||||
|
return fmt.Errorf("invalid method format")
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceKey := ServiceKey(self.userCfg.Prefix, t[1], "")
|
||||||
|
|
||||||
|
tlog.Debugf("ETCD RESOLVER||Call(serviceKey: %s, method: %s)",
|
||||||
|
serviceKey, method)
|
||||||
|
|
||||||
|
conn, ok := self.rpcServers[serviceKey]
|
||||||
|
if !ok {
|
||||||
|
tlog.Errorf("ETCD RESOLVER||method: %s||no grpc client available", method)
|
||||||
|
return fmt.Errorf("no grpc client available")
|
||||||
|
}
|
||||||
|
|
||||||
|
return grpc.Invoke(ctx, method, args, reply, conn, opts...)
|
||||||
|
}
|
||||||
39
gfetcd/config.go
Normal file
39
gfetcd/config.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package gfetcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_DIAL_TIMEOUT = time.Second * 5
|
||||||
|
DEFAULT_LEASE_TTL = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// 基础配置,公共字段
|
||||||
|
type BasicConfig struct {
|
||||||
|
DialTimeout int `toml:"dial_timeout"` // 连接超时时间
|
||||||
|
EndPoints []string `toml:"endpoints"` // etcd的地址
|
||||||
|
Prefix string `toml:"prefix"` // etcd服务的路径前缀,区分环境用
|
||||||
|
Debug bool `toml:"debug"` // 使用debug模式
|
||||||
|
}
|
||||||
|
|
||||||
|
func etcdDial(cfg *BasicConfig) (*clientv3.Client, error) {
|
||||||
|
dialTimeout := DEFAULT_DIAL_TIMEOUT
|
||||||
|
if cfg.DialTimeout > 0 {
|
||||||
|
dialTimeout = time.Duration(cfg.DialTimeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientv3.New(clientv3.Config{
|
||||||
|
Endpoints: cfg.EndPoints,
|
||||||
|
DialTimeout: dialTimeout,
|
||||||
|
DialKeepAliveTime: 2 * time.Second,
|
||||||
|
DialKeepAliveTimeout: 2 * time.Second,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServiceKey(opts ...string) string {
|
||||||
|
return path.Join(opts...)
|
||||||
|
}
|
||||||
159
gfetcd/resolver.go
Normal file
159
gfetcd/resolver.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// 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 gfetcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
etcd "github.com/coreos/etcd/clientv3"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/naming"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrWatcherClosed = fmt.Errorf("naming: watch closed")
|
||||||
|
|
||||||
|
// GRPCResolver creates a grpc.Watcher for a target to track its resolution changes.
|
||||||
|
type GFgrpcResolver struct {
|
||||||
|
// Client is an initialized etcd client.
|
||||||
|
Client *etcd.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (gr *GRPCResolver) Update(ctx context.Context, target string, nm naming.Update, opts ...etcd.OpOption) (err error) {
|
||||||
|
switch nm.Op {
|
||||||
|
case naming.Add:
|
||||||
|
var v []byte
|
||||||
|
if v, err = json.Marshal(nm); err != nil {
|
||||||
|
return status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
_, err = gr.Client.KV.Put(ctx, target+"/"+nm.Addr, string(v), opts...)
|
||||||
|
case naming.Delete:
|
||||||
|
_, err = gr.Client.Delete(ctx, target+"/"+nm.Addr, opts...)
|
||||||
|
default:
|
||||||
|
return status.Error(codes.InvalidArgument, "naming: bad naming op")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (gr *GFgrpcResolver) Resolve(target string) (naming.Watcher, error) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
w := &GFgRPCWatcher{c: gr.Client, target: target + "/", ctx: ctx, cancel: cancel}
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GFgRPCWatcher struct {
|
||||||
|
c *etcd.Client
|
||||||
|
target string
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
wch etcd.WatchChan
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next gets the next set of updates from the etcd resolver.
|
||||||
|
// Calls to Next should be serialized; concurrent calls are not safe since
|
||||||
|
// there is no way to reconcile the update ordering.
|
||||||
|
func (gw *GFgRPCWatcher) Next() ([]*naming.Update, error) {
|
||||||
|
if gw.wch == nil {
|
||||||
|
// first Next() returns all addresses
|
||||||
|
return gw.firstNext()
|
||||||
|
}
|
||||||
|
if gw.err != nil {
|
||||||
|
return nil, gw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// process new events on target/*
|
||||||
|
wr, ok := <-gw.wch
|
||||||
|
if !ok {
|
||||||
|
gw.err = status.Error(codes.Unavailable, ErrWatcherClosed.Error())
|
||||||
|
return nil, gw.err
|
||||||
|
}
|
||||||
|
if gw.err = wr.Err(); gw.err != nil {
|
||||||
|
return nil, gw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
updates := make([]*naming.Update, 0, len(wr.Events))
|
||||||
|
for _, e := range wr.Events {
|
||||||
|
var jupdate naming.Update
|
||||||
|
var err error
|
||||||
|
var jsonVal bool
|
||||||
|
|
||||||
|
switch e.Type {
|
||||||
|
case etcd.EventTypePut:
|
||||||
|
if strings.HasPrefix(string(e.Kv.Value), "{") {
|
||||||
|
err = json.Unmarshal(e.Kv.Value, &jupdate)
|
||||||
|
jsonVal = true
|
||||||
|
} else {
|
||||||
|
jupdate.Addr = string(e.Kv.Value)
|
||||||
|
}
|
||||||
|
jupdate.Op = naming.Add
|
||||||
|
|
||||||
|
case etcd.EventTypeDelete:
|
||||||
|
if strings.HasPrefix(string(e.PrevKv.Value), "{") {
|
||||||
|
err = json.Unmarshal(e.PrevKv.Value, &jupdate)
|
||||||
|
} else {
|
||||||
|
jupdate.Addr = string(e.PrevKv.Value)
|
||||||
|
}
|
||||||
|
jupdate.Op = naming.Delete
|
||||||
|
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonVal {
|
||||||
|
if err == nil {
|
||||||
|
updates = append(updates, &jupdate)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updates = append(updates, &jupdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gw *GFgRPCWatcher) firstNext() ([]*naming.Update, error) {
|
||||||
|
// Use serialized request so resolution still works if the target etcd
|
||||||
|
// server is partitioned away from the quorum.
|
||||||
|
resp, err := gw.c.Get(gw.ctx, gw.target, etcd.WithPrefix(), etcd.WithSerializable())
|
||||||
|
if gw.err = err; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updates := make([]*naming.Update, 0, len(resp.Kvs))
|
||||||
|
for _, kv := range resp.Kvs {
|
||||||
|
var jupdate naming.Update
|
||||||
|
|
||||||
|
if strings.HasPrefix(string(kv.Value), "{") {
|
||||||
|
if err := json.Unmarshal(kv.Value, &jupdate); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jupdate.Addr = string(kv.Value)
|
||||||
|
}
|
||||||
|
updates = append(updates, &jupdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []etcd.OpOption{etcd.WithRev(resp.Header.Revision + 1), etcd.WithPrefix(), etcd.WithPrevKV()}
|
||||||
|
gw.wch = gw.c.Watch(gw.ctx, gw.target, opts...)
|
||||||
|
return updates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gw *GFgRPCWatcher) Close() { gw.cancel() }
|
||||||
142
gfetcd/server.go
Normal file
142
gfetcd/server.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package gfetcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
etcdnaming "github.com/coreos/etcd/clientv3/naming"
|
||||||
|
"github.com/siddontang/go/log"
|
||||||
|
"google.golang.org/grpc/naming"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 服务端配置
|
||||||
|
type NameConfig struct {
|
||||||
|
BasicConfig
|
||||||
|
Servers []string `toml:"servers"` // 注册的服务名称,可以是多个
|
||||||
|
Addr string `toml:"addr"` // 服务地址
|
||||||
|
TTL int64 `toml:"ttl"` // lease, default 5
|
||||||
|
UseNew bool `toml:"use_new"` // 使用新版地址格式
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *NameConfig) GetAddr() string {
|
||||||
|
if strings.HasPrefix(self.Addr, ":") {
|
||||||
|
return fmt.Sprintf("%s%s", getLocalIP(), self.Addr)
|
||||||
|
}
|
||||||
|
return self.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取本机的内网Ip, 如果发现对方的ip 和自己的ip 相同,用127.0.0.1 替代
|
||||||
|
func getLocalIP() string {
|
||||||
|
ifaces, _ := net.Interfaces()
|
||||||
|
for _, i := range ifaces {
|
||||||
|
addrs, _ := i.Addrs()
|
||||||
|
for _, addr := range addrs {
|
||||||
|
var ip net.IP
|
||||||
|
switch v := addr.(type) {
|
||||||
|
case *net.IPNet:
|
||||||
|
ip = v.IP
|
||||||
|
case *net.IPAddr:
|
||||||
|
ip = v.IP
|
||||||
|
}
|
||||||
|
ipAddr := ip.String()
|
||||||
|
if strings.HasPrefix(ipAddr, "172.") || strings.HasPrefix(ipAddr, "192.") || strings.HasPrefix(ipAddr, "10.") {
|
||||||
|
return ipAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Name struct {
|
||||||
|
userCfg *NameConfig
|
||||||
|
etcdCli *clientv3.Client
|
||||||
|
stopSignal chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NameRegist(cfg *NameConfig) (name *Name, err error) {
|
||||||
|
|
||||||
|
cli, err := etcdDial(&cfg.BasicConfig)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Errorf("ETCD NAME||endpoints: %#v||err: %s", cfg.EndPoints, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name = &Name{
|
||||||
|
userCfg: cfg,
|
||||||
|
etcdCli: cli,
|
||||||
|
stopSignal: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TTL == 0 {
|
||||||
|
cfg.TTL = DEFAULT_LEASE_TTL
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Second * time.Duration(cfg.TTL/2))
|
||||||
|
serviceAddr := cfg.GetAddr()
|
||||||
|
resolver := &etcdnaming.GRPCResolver{Client: cli}
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
lease, err := cli.Grant(context.Background(), cfg.TTL)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Errorf("ETCD NAME||endpoints: %#v||grant err: %s",
|
||||||
|
cfg.EndPoints, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, serviceName := range cfg.Servers {
|
||||||
|
|
||||||
|
serviceKey := ServiceKey("/new", cfg.Prefix, serviceName)
|
||||||
|
|
||||||
|
if err = resolver.Update(context.Background(), serviceKey,
|
||||||
|
naming.Update{Op: naming.Add, Addr: serviceAddr},
|
||||||
|
clientv3.WithLease(lease.ID)); err != nil {
|
||||||
|
|
||||||
|
tlog.Errorf("ETCD NAME||endpoints: %#v service: %s addr: %s||update err: %s",
|
||||||
|
cfg.EndPoints, serviceKey, serviceAddr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.UseNew {
|
||||||
|
serviceKey = ServiceKey(cfg.Prefix, serviceName, serviceAddr)
|
||||||
|
if _, err = cli.Put(context.Background(), serviceKey, serviceAddr, clientv3.WithLease(lease.ID)); err != nil {
|
||||||
|
tlog.Errorf("ETCD NAME||endpoints: %#v put key: %s value: %s||err: %s",
|
||||||
|
cfg.EndPoints, serviceKey, serviceAddr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-name.stopSignal:
|
||||||
|
tlog.Info("ETCD NAME||received stop signal")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Name) Unregister() {
|
||||||
|
self.stopSignal <- true
|
||||||
|
//self.stopSignal = make(chan bool, 1) // just a hack to avoid multi unregister deadlock
|
||||||
|
|
||||||
|
serviceAddr := self.userCfg.GetAddr()
|
||||||
|
for _, serviceName := range self.userCfg.Servers {
|
||||||
|
serviceKey := ServiceKey(self.userCfg.Prefix, serviceName, serviceAddr)
|
||||||
|
|
||||||
|
if _, err := self.etcdCli.Delete(context.Background(), serviceKey); err != nil {
|
||||||
|
tlog.Errorf("ETCD NAME||unregister key: %s||err: %s", serviceKey, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("ETCD SERVER||unregister done")
|
||||||
|
}
|
||||||
52
go.mod
Normal file
52
go.mod
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
module globalfintech/golib
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Shopify/sarama v1.22.0
|
||||||
|
github.com/aws/aws-sdk-go v1.19.15
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
|
||||||
|
github.com/bsm/sarama-cluster v2.1.15+incompatible
|
||||||
|
github.com/coreos/bbolt v1.3.2 // indirect
|
||||||
|
github.com/coreos/etcd v3.3.15+incompatible
|
||||||
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
|
||||||
|
github.com/creachadair/cityhash v0.1.0
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1
|
||||||
|
github.com/gogo/protobuf v1.2.1 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
|
||||||
|
github.com/golang/protobuf v1.3.1
|
||||||
|
github.com/google/btree v1.0.0 // indirect
|
||||||
|
github.com/google/uuid v1.1.1 // indirect
|
||||||
|
github.com/gorilla/websocket v1.4.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
|
||||||
|
github.com/hashicorp/consul/api v1.2.0
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1
|
||||||
|
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.7 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||||
|
github.com/onsi/gomega v1.5.0 // indirect
|
||||||
|
github.com/oschwald/geoip2-golang v1.2.1
|
||||||
|
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v0.9.2
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
|
||||||
|
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
|
||||||
|
github.com/sirupsen/logrus v1.4.1 // indirect
|
||||||
|
github.com/soheilhy/cmux v0.1.4 // indirect
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.2 // indirect
|
||||||
|
go.uber.org/atomic v1.3.2 // indirect
|
||||||
|
go.uber.org/multierr v1.1.0 // indirect
|
||||||
|
go.uber.org/zap v1.9.1 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||||
|
google.golang.org/grpc v1.20.1
|
||||||
|
sigs.k8s.io/yaml v1.1.0 // indirect
|
||||||
|
)
|
||||||
265
go.sum
Normal file
265
go.sum
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14=
|
||||||
|
github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
|
github.com/Shopify/sarama v1.22.0 h1:rtiODsvY4jW6nUV6n3K+0gx/8WlAwVt+Ixt6RIvpYyo=
|
||||||
|
github.com/Shopify/sarama v1.22.0/go.mod h1:lm3THZ8reqBDBQKQyb5HB3sY1lKp3grEbQ81aWSgPp4=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/aws/aws-sdk-go v1.19.15 h1:Ov8zxPunHxZFxrXBarqzkZBSZugJRzPgb4mfq/SyVgA=
|
||||||
|
github.com/aws/aws-sdk-go v1.19.15/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
|
github.com/bsm/sarama-cluster v2.1.15+incompatible h1:RkV6WiNRnqEEbp81druK8zYhmnIgdOjqSVi0+9Cnl2A=
|
||||||
|
github.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
|
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
|
github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE=
|
||||||
|
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||||
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/creachadair/cityhash v0.1.0 h1:rzSfISre5xT/EgbVvXBmsUJ58Hsrp8ccYMZVvc5uBmk=
|
||||||
|
github.com/creachadair/cityhash v0.1.0/go.mod h1:bYNmpea/cMZgL9Rjt+ZfDfjaT0hScMO6SSGV1DN5lEM=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
|
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hashicorp/consul/api v1.2.0 h1:oPsuzLp2uk7I7rojPKuncWbZ+m5TMoD4Ivs+2Rkeh4Y=
|
||||||
|
github.com/hashicorp/consul/api v1.2.0/go.mod h1:1SIkFYi2ZTXUE5Kgt179+4hH33djo11+0Eo2XgTAtkw=
|
||||||
|
github.com/hashicorp/consul/sdk v0.2.0 h1:GWFYFmry/k4b1hEoy7kSkmU8e30GAyI4VZHk0fRxeL4=
|
||||||
|
github.com/hashicorp/consul/sdk v0.2.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
|
||||||
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||||
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
||||||
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||||
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||||
|
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
|
||||||
|
github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE=
|
||||||
|
github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE=
|
||||||
|
github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41 h1:GeinFsrjWz97fAxVUEd748aV0cYL+I6k44gFJTCVvpU=
|
||||||
|
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||||
|
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||||
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
|
||||||
|
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||||
|
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||||
|
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||||
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||||
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
|
||||||
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||||
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
55
kafka/README.md
Normal file
55
kafka/README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
# kafka cluster封装
|
||||||
|
|
||||||
|
> 目前只封装了consumer,单个consumer支持同时消费一个topic
|
||||||
|
> 如果指定了group,且group名称开头不为'test_consumer_'则认为是正式订阅,会在处理完成后标记消息已经被消费
|
||||||
|
> 否则会生成随机的group名称,用于测试
|
||||||
|
|
||||||
|
## 示例代码如下
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golib/kafka"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 回调的消息处理函数,用户自己实现的消息处理逻辑
|
||||||
|
func processor(msg []byte) {
|
||||||
|
fmt.Println("recv msg:", string(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// 配置,可以从文件加载
|
||||||
|
cfg := kafka.Config{
|
||||||
|
Brokers: []string{"test.kafka.o-kash.com:9092"},
|
||||||
|
Topic: "charge_success",
|
||||||
|
Workers: 5, // 并发的回调函数数量,不是partition的数量
|
||||||
|
}
|
||||||
|
|
||||||
|
if consumer := kafka.NewConsumer(&cfg, processor); consumer != nil {
|
||||||
|
consumer.Run()
|
||||||
|
|
||||||
|
// 监控退出信号通知consumer退出
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-ch
|
||||||
|
|
||||||
|
consumer.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 产生测试用的消息
|
||||||
|
|
||||||
|
使用官方工具
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./kafka-console-producer.sh --broker-list test.kafka.o-kash.com:9092 --topic charge_success
|
||||||
|
```
|
||||||
123
kafka/kafka.go
Normal file
123
kafka/kafka.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package kafka
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
|
||||||
|
"github.com/Shopify/sarama"
|
||||||
|
cluster "github.com/bsm/sarama-cluster"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Brokers []string `toml:"brokers"`
|
||||||
|
Topic string `toml:"topic"`
|
||||||
|
Group string `toml:"group"`
|
||||||
|
Workers int `toml:"workers"`
|
||||||
|
Oldest bool `toml:"oldest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Consumer struct {
|
||||||
|
test bool
|
||||||
|
consumer *cluster.Consumer
|
||||||
|
workers int
|
||||||
|
handler func([]byte)
|
||||||
|
stop chan bool
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TEST_PREFIX = "test_consumer_"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewConsumer(cfg *Config, hdl func([]byte)) *Consumer {
|
||||||
|
|
||||||
|
var (
|
||||||
|
csm *cluster.Consumer
|
||||||
|
err error
|
||||||
|
group string
|
||||||
|
)
|
||||||
|
|
||||||
|
config := cluster.NewConfig()
|
||||||
|
config.Consumer.Return.Errors = true
|
||||||
|
config.Group.Return.Notifications = true
|
||||||
|
|
||||||
|
if cfg.Oldest {
|
||||||
|
config.Consumer.Offsets.Initial = sarama.OffsetOldest
|
||||||
|
} else {
|
||||||
|
config.Consumer.Offsets.Initial = sarama.OffsetNewest
|
||||||
|
}
|
||||||
|
|
||||||
|
group = cfg.Group
|
||||||
|
if group == "" {
|
||||||
|
group = fmt.Sprintf("%s%.8x", TEST_PREFIX, rand.Int63())
|
||||||
|
}
|
||||||
|
|
||||||
|
tlog.Infof("NewConsumer() brokers=%#v, topic=%s, workers=%d, group=%s", cfg.Brokers, cfg.Topic, cfg.Workers, group)
|
||||||
|
|
||||||
|
if csm, err = cluster.NewConsumer(cfg.Brokers, group, []string{cfg.Topic}, config); err != nil {
|
||||||
|
tlog.Errorf("NewConsumer() create fail: %s", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tlog.Infof("NewConsumer() Created OK! %#v", csm)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for err := range csm.Errors() {
|
||||||
|
tlog.Errorf("KafkaConsumer<brokers=%#v, group=%s> Error: %s", cfg.Brokers, group, err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for ntf := range csm.Notifications() {
|
||||||
|
tlog.Infof("KafkaConsumer<brokers=%#v, group=%s> Notice: %#v", cfg.Brokers, group, ntf)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &Consumer{
|
||||||
|
test: strings.HasPrefix(group, TEST_PREFIX),
|
||||||
|
consumer: csm,
|
||||||
|
workers: cfg.Workers,
|
||||||
|
handler: hdl,
|
||||||
|
stop: make(chan bool),
|
||||||
|
wg: &sync.WaitGroup{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Consumer) Run() {
|
||||||
|
|
||||||
|
for i := 0; i < self.workers; i++ {
|
||||||
|
go self.Worker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Consumer) Stop() {
|
||||||
|
close(self.stop)
|
||||||
|
self.wg.Wait()
|
||||||
|
self.consumer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Consumer) Worker() {
|
||||||
|
self.wg.Add(1)
|
||||||
|
defer self.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg, ok := <-self.consumer.Messages():
|
||||||
|
if ok {
|
||||||
|
//tlog.Debugf("topic: %s, partition: %d, offset: %d, msg: %s", msg.Topic, msg.Partition, msg.Offset, string(msg.Value))
|
||||||
|
self.handler(msg.Value)
|
||||||
|
|
||||||
|
// 测试用的group不标记处理完毕,防止破坏集群数据
|
||||||
|
if !self.test {
|
||||||
|
self.consumer.MarkOffset(msg, "done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-self.stop:
|
||||||
|
tlog.Info("KafkaConsumer stop signal received!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
kv/kv.go
Normal file
98
kv/kv.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package kv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var x map[string]*redis.Client = make(map[string]*redis.Client)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Addrs []string `toml:"addrs" json:"addrs"`
|
||||||
|
Pwd string `toml:"pwd" json:"pwd"`
|
||||||
|
PoolSize int `toml:"pool_size" json:"pool_size"`
|
||||||
|
ReadTimeout int `toml:"read_timeout" json:"read_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnixConfig struct {
|
||||||
|
Addr string `toml:"addr" json:"addr"`
|
||||||
|
PoolSize int `toml:"pool_size" json:"pool_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddByUnix(name string, c *UnixConfig) error {
|
||||||
|
p := redis.NewClient(&redis.Options{
|
||||||
|
Network: "unix",
|
||||||
|
Addr: c.Addr,
|
||||||
|
PoolSize: c.PoolSize,
|
||||||
|
})
|
||||||
|
|
||||||
|
x[name] = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Add(name string, c *Config) error {
|
||||||
|
p, err := NewRedisClientWithConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x[name] = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRedisConn(name string) (*redis.Client, error) {
|
||||||
|
conn, ok := x[name]
|
||||||
|
if ok {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("no redis conn")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Parse() (*redis.Options, error) {
|
||||||
|
redisNum := len(c.Addrs)
|
||||||
|
if redisNum < 1 {
|
||||||
|
return nil, errors.New("redis addrs is empty")
|
||||||
|
}
|
||||||
|
ch := make(chan []string, redisNum)
|
||||||
|
for i := 0; i < redisNum; i++ {
|
||||||
|
list := make([]string, redisNum)
|
||||||
|
for j := 0; j < redisNum; j++ {
|
||||||
|
list[j] = c.Addrs[(i+j)%redisNum]
|
||||||
|
}
|
||||||
|
ch <- list
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ReadTimeout < 1 {
|
||||||
|
c.ReadTimeout = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
options := &redis.Options{
|
||||||
|
Password: c.Pwd,
|
||||||
|
PoolSize: c.PoolSize,
|
||||||
|
ReadTimeout: time.Duration(c.ReadTimeout) * time.Second,
|
||||||
|
WriteTimeout: time.Second,
|
||||||
|
PoolTimeout: time.Second,
|
||||||
|
IdleTimeout: 60 * time.Second,
|
||||||
|
Dialer: func() (net.Conn, error) {
|
||||||
|
list := <-ch
|
||||||
|
ch <- list
|
||||||
|
for _, addr := range list {
|
||||||
|
c, err := net.DialTimeout("tcp", addr, 1000*time.Millisecond)
|
||||||
|
if err == nil {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("all redis down")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedisClientWithConfig(c *Config) (*redis.Client, error) {
|
||||||
|
options, err := c.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return redis.NewClient(options), nil
|
||||||
|
}
|
||||||
61
kv/mkv.go
Normal file
61
kv/mkv.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package kv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNoClient error = errors.New("no memcache client")
|
||||||
|
var mClient *memcache.Client
|
||||||
|
|
||||||
|
//memcache 的配置
|
||||||
|
type MConfig struct {
|
||||||
|
Addrs []string `toml:"addrs" json:"addrs"`
|
||||||
|
PoolSize int `toml:"pool_size" json:"pool_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MConfig) GetAddrs() []string {
|
||||||
|
if c != nil {
|
||||||
|
return c.Addrs
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MConfig) GetPoolSize() int {
|
||||||
|
if c != nil {
|
||||||
|
return c.PoolSize
|
||||||
|
}
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitMcClient(c *MConfig) {
|
||||||
|
mClient = memcache.New(c.GetAddrs()...)
|
||||||
|
mClient.MaxIdleConns = c.GetPoolSize()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMcClient() *memcache.Client {
|
||||||
|
return mClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(key string) ([]byte, error) {
|
||||||
|
client := GetMcClient()
|
||||||
|
if client == nil {
|
||||||
|
return nil, ErrNoClient
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := client.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return item.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Del(key string) error {
|
||||||
|
if client := GetMcClient(); client == nil {
|
||||||
|
return ErrNoClient
|
||||||
|
} else {
|
||||||
|
return client.Delete(key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
108
lrucache/lrucache.go
Normal file
108
lrucache/lrucache.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package lrucache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/creachadair/cityhash"
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API
|
||||||
|
var (
|
||||||
|
Nil error = errors.New("cache: nil")
|
||||||
|
ErrExpTime error = errors.New("args error, expTime must be > 0")
|
||||||
|
dbs map[int]*lru.Cache
|
||||||
|
)
|
||||||
|
|
||||||
|
func Set(key string, value interface{}) error {
|
||||||
|
return SetEx(key, value, 86400)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetEx(key string, value interface{}, exp int) error {
|
||||||
|
return setEx(key, value, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(key string) (interface{}, error) {
|
||||||
|
return get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Del(key string) error {
|
||||||
|
return del(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if dbs == nil {
|
||||||
|
dbs = make(map[int]*lru.Cache, 1024)
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
dbs[i], _ = lru.New(3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func flush() {
|
||||||
|
i := 0
|
||||||
|
for range time.NewTicker(time.Minute).C {
|
||||||
|
start := time.Now()
|
||||||
|
shard := i % 1024
|
||||||
|
db, _ := dbs[shard]
|
||||||
|
keys := db.Keys()
|
||||||
|
for _, key := range keys {
|
||||||
|
if keyS, ok := key.(string); ok {
|
||||||
|
getByDb(keyS, db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
tlog.Infof("FlushKey||time=%s||shard=%d||old=%d||new=%d", time.Since(start), shard, len(keys), db.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type item struct {
|
||||||
|
d interface{}
|
||||||
|
t int
|
||||||
|
}
|
||||||
|
|
||||||
|
func setEx(key string, value interface{}, exp int) error {
|
||||||
|
if exp < 1 {
|
||||||
|
return ErrExpTime
|
||||||
|
}
|
||||||
|
|
||||||
|
db, _ := dbs[hash(key)]
|
||||||
|
db.Add(key, &item{d: value, t: int(time.Now().Unix()) + exp})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(key string) (interface{}, error) {
|
||||||
|
db, _ := dbs[hash(key)]
|
||||||
|
return getByDb(key, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getByDb(key string, db *lru.Cache) (interface{}, error) {
|
||||||
|
if value, ok := db.Get(key); ok {
|
||||||
|
if item, ok := value.(*item); ok {
|
||||||
|
if item.t >= int(time.Now().Unix()) {
|
||||||
|
return item.d, nil
|
||||||
|
} else {
|
||||||
|
delByDb(key, db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func del(key string) error {
|
||||||
|
db, _ := dbs[hash(key)]
|
||||||
|
return delByDb(key, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func delByDb(key string, db *lru.Cache) error {
|
||||||
|
db.Remove(key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(key string) int {
|
||||||
|
return int(cityhash.Hash32([]byte(key)) % 1024)
|
||||||
|
}
|
||||||
14
metric/alert.go
Normal file
14
metric/alert.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var singletonCounter prometheus.Counter
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
func SingletonCounter() {
|
||||||
|
|
||||||
|
}
|
||||||
25
metric/examples/exporter_demo.go
Normal file
25
metric/examples/exporter_demo.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"globalfintech/golib/metric"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := metric.ExporterCfg{
|
||||||
|
Listener: nil,
|
||||||
|
RegisterConsul: false,
|
||||||
|
ProductName: "ProductName_001",
|
||||||
|
ServiceRegion: "ServiceRegion_001",
|
||||||
|
AppName: "AppName_001",
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := metric.InitExporter(cfg); err != nil {
|
||||||
|
fmt.Println("prometheus exporter stopped,err: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fmt.Println("prometheus exporter started")
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
||||||
46
metric/examples/main.go
Normal file
46
metric/examples/main.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"globalfintech/golib/metric"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var callCounter = metric.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Namespace: "crm",
|
||||||
|
Name: "call_total_count2",
|
||||||
|
Help: "Total Request",
|
||||||
|
},
|
||||||
|
[]string{"privilege", "uri"},
|
||||||
|
)
|
||||||
|
|
||||||
|
func test2() {
|
||||||
|
pusher := metric.InitPusher("http://ops-dev.a.mobimagic.com:9091", "MetricsExample3", prometheus.Labels{})
|
||||||
|
|
||||||
|
pusher.Collector(callCounter).Add()
|
||||||
|
pusher.Push()
|
||||||
|
//pusher.Push(callCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
//test2()
|
||||||
|
|
||||||
|
var labels = prometheus.Labels{"app": "app2",
|
||||||
|
"country": "INDIA",
|
||||||
|
"service": "test2",
|
||||||
|
}
|
||||||
|
|
||||||
|
var pushGateway, job = "http://ops-dev.a.mobimagic.com:9091", "app2"
|
||||||
|
|
||||||
|
//fmt.Println("labels:", labels)
|
||||||
|
metric.InitDefaultPusher(pushGateway, job, labels)
|
||||||
|
|
||||||
|
for {
|
||||||
|
callCounter.With(prometheus.Labels{"privilege": "test", "uri": "xxxxx/sswwe"}).Inc()
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
fmt.Println("Inc 1")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
113
metric/exporter.go
Normal file
113
metric/exporter.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultListenerAddr = "0.0.0.0:9166"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExporterCfg struct {
|
||||||
|
Listener net.Listener
|
||||||
|
RegisterConsul bool
|
||||||
|
ProductName string
|
||||||
|
ServiceRegion string
|
||||||
|
AppName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exporter init function must be executed asynchronously
|
||||||
|
func InitExporter(cfg ExporterCfg) (err error) {
|
||||||
|
lis := cfg.Listener
|
||||||
|
if cfg.Listener == nil {
|
||||||
|
if lis, err = net.Listen("tcp", DefaultListenerAddr); err != nil {
|
||||||
|
tlog.Error("fail to create listener with default address")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, port, err := net.SplitHostPort(lis.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
tlog.Error("fail to get port from listener, err: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := http.NewServeMux()
|
||||||
|
h.Handle("/metrics", promhttp.Handler())
|
||||||
|
h.HandleFunc("/health_checker", healthChecker)
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: h,
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
ReadHeaderTimeout: 30 * time.Second,
|
||||||
|
IdleTimeout: 30 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.RegisterConsul {
|
||||||
|
var tags []string
|
||||||
|
tags = append(tags, fmt.Sprintf("app=%s", cfg.ProductName))
|
||||||
|
tags = append(tags, fmt.Sprintf("country=%s", cfg.ServiceRegion))
|
||||||
|
err = consulRegister(port, "", tags, cfg.AppName)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Error("consul register failed, err: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = server.Serve(lis); err != nil {
|
||||||
|
tlog.Error("fail to init prometheus exporter, err: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func consulRegister(checkPort string, regAddr string, tags []string, name string) (err error) {
|
||||||
|
config := consulapi.DefaultConfig()
|
||||||
|
if regAddr != "" {
|
||||||
|
config.Address = regAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := consulapi.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Error("fail to new consul client, err: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
intPort, _ := strconv.Atoi(checkPort)
|
||||||
|
hostName, _ := os.Hostname()
|
||||||
|
|
||||||
|
registration := &consulapi.AgentServiceRegistration{
|
||||||
|
Port: intPort,
|
||||||
|
Name: name,
|
||||||
|
ID: name + ":" + hostName,
|
||||||
|
Tags: append(tags, "lang=go"),
|
||||||
|
Check: &consulapi.AgentServiceCheck{
|
||||||
|
HTTP: fmt.Sprintf("http://%s:%s%s", "127.0.0.1", checkPort, "/health_checker"),
|
||||||
|
Interval: "10s",
|
||||||
|
Timeout: "10s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Agent().ServiceRegister(registration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func healthChecker(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"code": 0,
|
||||||
|
"message": "ok",
|
||||||
|
"server_time": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
res, _ := json.Marshal(data)
|
||||||
|
resp.Write(res)
|
||||||
|
}
|
||||||
160
metric/metric.go
Normal file
160
metric/metric.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const missVal = "MissLabel"
|
||||||
|
|
||||||
|
// CounterVecDecorator Add safe decorator
|
||||||
|
type CounterVecDecorator struct {
|
||||||
|
*prometheus.CounterVec
|
||||||
|
secureLabels []string
|
||||||
|
once *sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *CounterVecDecorator {
|
||||||
|
v := &CounterVecDecorator{prometheus.NewCounterVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// With is secure wrap for with of prometheus.CounterVec
|
||||||
|
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||||
|
// It's secure
|
||||||
|
func (v *CounterVecDecorator) With(labels prometheus.Labels) prometheus.Counter {
|
||||||
|
// To avoid initialization sequence, so must delay register to with func
|
||||||
|
v.once.Do(func() {
|
||||||
|
Register(v)
|
||||||
|
})
|
||||||
|
|
||||||
|
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||||
|
|
||||||
|
//
|
||||||
|
c, err := v.GetMetricWith(safeLabels)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// should never happen , but should still add handler to record alert
|
||||||
|
// TODO singleton alert Gauge and return an alert counter
|
||||||
|
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// GaugeVecDecorator Add safe decorator
|
||||||
|
type GaugeVecDecorator struct {
|
||||||
|
*prometheus.GaugeVec
|
||||||
|
secureLabels []string
|
||||||
|
once *sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *GaugeVecDecorator {
|
||||||
|
v := &GaugeVecDecorator{prometheus.NewGaugeVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// With is secure wrap for with of prometheus.GaugeVec
|
||||||
|
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||||
|
// It's secure
|
||||||
|
func (v *GaugeVecDecorator) With(labels prometheus.Labels) prometheus.Gauge {
|
||||||
|
// To avoid initialization sequence, so must delay register to with func
|
||||||
|
v.once.Do(func() {
|
||||||
|
Register(v)
|
||||||
|
})
|
||||||
|
|
||||||
|
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||||
|
//
|
||||||
|
c, err := v.GetMetricWith(safeLabels)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// should never happen , but should still add handler to record alert
|
||||||
|
// TODO singleton alert Gauge and return
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistogramVecDecorator Add safe
|
||||||
|
type HistogramVecDecorator struct {
|
||||||
|
*prometheus.HistogramVec
|
||||||
|
secureLabels []string
|
||||||
|
once *sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *HistogramVecDecorator {
|
||||||
|
v := &HistogramVecDecorator{prometheus.NewHistogramVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// With is secure wrap for with of prometheus.HistogramVec
|
||||||
|
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||||
|
// It's secure
|
||||||
|
func (v *HistogramVecDecorator) With(labels prometheus.Labels) prometheus.Observer {
|
||||||
|
// To avoid initialization sequence, so must delay register to with func
|
||||||
|
v.once.Do(func() {
|
||||||
|
Register(v)
|
||||||
|
})
|
||||||
|
|
||||||
|
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||||
|
//
|
||||||
|
c, err := v.GetMetricWith(safeLabels)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// should never happen , but should still add handler to record alert
|
||||||
|
// TODO singleton alert Histogram and return
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SummaryVecDecorator Add safe
|
||||||
|
type SummaryVecDecorator struct {
|
||||||
|
*prometheus.SummaryVec
|
||||||
|
secureLabels []string
|
||||||
|
once *sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSummaryVec(opts prometheus.SummaryOpts, labelNames []string) *SummaryVecDecorator {
|
||||||
|
v := &SummaryVecDecorator{prometheus.NewSummaryVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||||
|
//if len(autoReg) == 0 || autoReg[0] == true {
|
||||||
|
// Register(v)
|
||||||
|
//}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// With is secure wrap for with of prometheus.SummaryVec
|
||||||
|
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||||
|
// It's secure
|
||||||
|
func (v *SummaryVecDecorator) With(labels prometheus.Labels) prometheus.Observer {
|
||||||
|
// To avoid initialization sequence, so must delay register to with func
|
||||||
|
v.once.Do(func() {
|
||||||
|
Register(v)
|
||||||
|
})
|
||||||
|
|
||||||
|
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||||
|
//
|
||||||
|
c, err := v.GetMetricWith(safeLabels)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// should never happen , but should still add handler to record alert
|
||||||
|
// TODO singleton alert Gauge and return
|
||||||
|
}
|
||||||
|
//prometheus.NewCounter(v.SummaryVec.)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签对齐
|
||||||
|
func labelAlignment(labelNames []string, labels prometheus.Labels) prometheus.Labels {
|
||||||
|
|
||||||
|
newLabels := make(prometheus.Labels, len(labelNames))
|
||||||
|
|
||||||
|
for _, val := range labelNames {
|
||||||
|
if v, ok := labels[val]; ok {
|
||||||
|
newLabels[val] = v
|
||||||
|
} else {
|
||||||
|
newLabels[val] = missVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newLabels
|
||||||
|
}
|
||||||
48
metric/metric_test.go
Normal file
48
metric/metric_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_labelAlignment(t *testing.T) {
|
||||||
|
tds := []struct {
|
||||||
|
in1 []string
|
||||||
|
in2 prometheus.Labels
|
||||||
|
in2New prometheus.Labels
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]string{"label1", "label2"},
|
||||||
|
map[string]string{"label1": "1"},
|
||||||
|
map[string]string{"label1": "1", "label2": missVal},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"label1", "label3"},
|
||||||
|
map[string]string{"label1": "1", "label2": "2"},
|
||||||
|
map[string]string{"label1": "1", "label3": missVal},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{},
|
||||||
|
map[string]string{"label1": "1", "label2": "2"},
|
||||||
|
map[string]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"label1", "label3", "label4"},
|
||||||
|
map[string]string{"label1": "1", "label2": "2"},
|
||||||
|
map[string]string{"label1": "1", "label3": missVal, "label4": missVal},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, d := range tds {
|
||||||
|
//var org map[string]string
|
||||||
|
//copy(org, d.in2)
|
||||||
|
result := labelAlignment(d.in1, d.in2)
|
||||||
|
t.Log(result, d.in2New)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(result, d.in2New) {
|
||||||
|
|
||||||
|
t.Errorf("Input: %#v, Expected is not same as , get ", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
69
metric/push.go
Normal file
69
metric/push.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/push"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultPusher *push.Pusher
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPushIntervalSecond = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitDefaultPusher(gateway string, job string, labels prometheus.Labels, pushIntervalSecond ...int) {
|
||||||
|
defaultPusher = InitPusher(gateway, job, labels)
|
||||||
|
|
||||||
|
// for default pusher , we should add process collector and go collector
|
||||||
|
// then we can monitor all base monitor on our job
|
||||||
|
defaultPusher.
|
||||||
|
Collector(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})).
|
||||||
|
Collector(prometheus.NewGoCollector()).
|
||||||
|
Add()
|
||||||
|
|
||||||
|
// TODO implement init
|
||||||
|
// and push
|
||||||
|
//defaultPusher = InitPusher("")
|
||||||
|
go func() {
|
||||||
|
var pushInterval = defaultPushIntervalSecond * time.Second
|
||||||
|
if len(pushIntervalSecond) > 0 {
|
||||||
|
pushInterval = time.Second * time.Duration(pushIntervalSecond[0])
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
defaultPusher.Push()
|
||||||
|
time.Sleep(pushInterval)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitTriggerLastPush
|
||||||
|
// TODO
|
||||||
|
func ExitTriggerLastPush() {
|
||||||
|
|
||||||
|
defaultPusher.Push()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//var MustLabel prometheus.Labels
|
||||||
|
//
|
||||||
|
//type RequireLabelConf struct {
|
||||||
|
// Service string `json:"service";yaml:`
|
||||||
|
// App string ``
|
||||||
|
// Country string
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Init Pusher
|
||||||
|
// return prometheus pusher
|
||||||
|
// TODO add labels
|
||||||
|
// need lan ,app,country, service labels to implement
|
||||||
|
func InitPusher(gateway string, job string, labels prometheus.Labels) *push.Pusher {
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
|
||||||
|
p := push.New(gateway, job).Grouping("instance", host).Grouping("lang","go")
|
||||||
|
for name, value := range labels {
|
||||||
|
p.Grouping(name, value)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
27
metric/register.go
Normal file
27
metric/register.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register Here, we must decide pusher/exporter which we use
|
||||||
|
// it's very important, note: var/init/func init sort
|
||||||
|
// keep, when a git rep, not only a service or job
|
||||||
|
// collector will mixed with Push And Registered for pull
|
||||||
|
// So register with pull and add for push collector must control in one place
|
||||||
|
// then we can use conf to switch
|
||||||
|
// avoid collector duplicate
|
||||||
|
// we don't use must register, to avoid panic
|
||||||
|
func Register(cs ...prometheus.Collector) {
|
||||||
|
if defaultPusher == nil {
|
||||||
|
// register by exporter
|
||||||
|
for _, mv := range cs {
|
||||||
|
prometheus.Register(mv)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// register by pusher
|
||||||
|
for _, mv := range cs {
|
||||||
|
defaultPusher.Collector(mv).Add()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
197
metrics/example1/example1.go
Normal file
197
metrics/example1/example1.go
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"globalfintech/golib/metrics"
|
||||||
|
"globalfintech/golib/metrics/prometheus"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// parse the flags
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
processMetrics, err := metrics.NewProcessMetrics("test", "INDIA", "testProject")
|
||||||
|
if err != nil {
|
||||||
|
tlog.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
workPort := processMetrics.GetMetricsPort()
|
||||||
|
// init Exporter
|
||||||
|
tlog.Info("Beginning to serve on port :", workPort)
|
||||||
|
processMetrics.InitExporter()
|
||||||
|
|
||||||
|
// You can registry metrics any where as you want
|
||||||
|
// Now we registry a local gauge in function main
|
||||||
|
workersGauge := prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
|
||||||
|
Namespace: "worker",
|
||||||
|
Subsystem: "jobs",
|
||||||
|
Name: "worker_number",
|
||||||
|
Help: "Number of workers.",
|
||||||
|
}, []string{"server"})
|
||||||
|
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
workersGauge.With("server", host).Set(float64(workers))
|
||||||
|
|
||||||
|
// create a channel with a 1000 Job buffer
|
||||||
|
jobChannel := make(chan *Job, 1000)
|
||||||
|
// start the job processor
|
||||||
|
go startJobProcessor(jobChannel)
|
||||||
|
// start a Goroutine to create some mock jobs
|
||||||
|
go createJobs(jobChannel)
|
||||||
|
|
||||||
|
exit := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
// kill (no param) default send syscanll.SIGTERM
|
||||||
|
// kill -2 is syscall.SIGINT
|
||||||
|
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
|
||||||
|
signal.Notify(exit, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
<-exit
|
||||||
|
|
||||||
|
tlog.Info("Graceful shutdown Server in 10 seconds...")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// set workers to 0 , then stop all workers and job creator
|
||||||
|
workers = 0
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
tlog.Info("timeout of 10 seconds.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// init pusher
|
||||||
|
// pusher usually uses at DAG/Cron job processor
|
||||||
|
pusher := metrics.InitPusher("http://ops-dev.a.mobimagic.com:9091", "MetricsExample1")
|
||||||
|
if err := pusher.Collector(workerTotalCounter.Cv).Add(); err != nil {
|
||||||
|
tlog.Error("Could not push to Pushgateway:", err)
|
||||||
|
}
|
||||||
|
// time is up
|
||||||
|
close(jobChannel)
|
||||||
|
tlog.Infof("Jobs left: %d", len(jobChannel))
|
||||||
|
for leftJob := range jobChannel {
|
||||||
|
tlog.Info(leftJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlog.Info("Server exit")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modes = []string{"SMS", "WeChat", "WhatsApp", "GooglePush", "ApplePush"}
|
||||||
|
workers = 0
|
||||||
|
|
||||||
|
// Two ways to define name
|
||||||
|
// Name : fullname
|
||||||
|
// or Namespace / Subsystem / Name to combine a full name
|
||||||
|
workerTotalCounter = prometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||||
|
Name: "worker_jobs_processed_total",
|
||||||
|
Help: "Total number of jobs processed by the workers.",
|
||||||
|
}, []string{"worker_id", "mode"})
|
||||||
|
|
||||||
|
inqueueGauge = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
|
||||||
|
Namespace: "worker",
|
||||||
|
Subsystem: "jobs",
|
||||||
|
Name: "inqueue",
|
||||||
|
Help: "Number of jobs in queue.",
|
||||||
|
}, []string{"mode"})
|
||||||
|
|
||||||
|
processingTimeHistogram = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{
|
||||||
|
Name: "worker_jobs_process_time_seconds",
|
||||||
|
Help: "Amount of time spent processing jobs",
|
||||||
|
Buckets: []float64{1, 2, 3, 4, 5}, //if no set, then use DefBuckets : []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
|
||||||
|
}, []string{"worker_id", "mode"})
|
||||||
|
)
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
Mode string
|
||||||
|
Sleep time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.IntVar(&workers, "workers", 10, "Number of workers to use")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMode() string {
|
||||||
|
return modes[rand.Int()%len(modes)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeJob creates a new job with a random sleep time between 0.5s and 5s
|
||||||
|
func makeJob() *Job {
|
||||||
|
return &Job{
|
||||||
|
Mode: getMode(),
|
||||||
|
Sleep: time.Duration(rand.Int()%4500+500) * time.Millisecond,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createJobs(jobs chan<- *Job) {
|
||||||
|
for {
|
||||||
|
if workers == 0 {
|
||||||
|
tlog.Info("Stop create jobs.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a random job
|
||||||
|
job := makeJob()
|
||||||
|
// track the job in the inqueue tracker
|
||||||
|
inqueueGauge.With("mode", job.Mode).Add(1)
|
||||||
|
// send the job down the channel
|
||||||
|
jobs <- job
|
||||||
|
// don't pile up too quickly
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a worker that pulls jobs from the job channel
|
||||||
|
func startWorker(workerID int, jobs <-chan *Job) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// read from the job channel
|
||||||
|
case job := <-jobs:
|
||||||
|
startTime := time.Now()
|
||||||
|
// decrement the in queue tracker
|
||||||
|
inqueueGauge.With("mode", job.Mode).Sub(1)
|
||||||
|
|
||||||
|
// fake processing the request
|
||||||
|
time.Sleep(job.Sleep)
|
||||||
|
tlog.Infof("[%d][%s] Processed job in %0.3f seconds", workerID, job.Mode, time.Now().Sub(startTime).Seconds())
|
||||||
|
// track the total number of jobs processed by the worker
|
||||||
|
workerTotalCounter.With("worker_id", strconv.FormatInt(int64(workerID), 10), "mode", job.Mode).Inc()
|
||||||
|
processingTimeHistogram.With("worker_id", strconv.FormatInt(int64(workerID), 10), "mode", job.Mode).Observe(time.Now().Sub(startTime).Seconds())
|
||||||
|
|
||||||
|
if workers == 0 {
|
||||||
|
tlog.Infof("worker[%d] quit", workerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startJobProcessor(jobs <-chan *Job) {
|
||||||
|
tlog.Infof("[INFO] starting %d workers\n", workers)
|
||||||
|
wait := sync.WaitGroup{}
|
||||||
|
// notify the sync group we need to wait for 10 goroutines
|
||||||
|
wait.Add(workers)
|
||||||
|
|
||||||
|
// start 10 works
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
go func(workerID int) {
|
||||||
|
// start the worker
|
||||||
|
startWorker(workerID, jobs)
|
||||||
|
wait.Done()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wait.Wait()
|
||||||
|
}
|
||||||
190
metrics/metrics.go
Normal file
190
metrics/metrics.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/push"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultAddr string = "9166"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Counter describes a metric that accumulates values monotonically.
|
||||||
|
// An example of a counter is the number of received HTTP requests.
|
||||||
|
type Counter interface {
|
||||||
|
With(labelValues ...string) Counter
|
||||||
|
Add(delta float64)
|
||||||
|
Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gauge describes a metric that takes specific values over time.
|
||||||
|
// An example of a gauge is the current depth of a job queue.
|
||||||
|
type Gauge interface {
|
||||||
|
With(labelValues ...string) Gauge
|
||||||
|
Set(value float64)
|
||||||
|
Add(delta float64)
|
||||||
|
Sub(delta float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Histogram describes a metric that takes repeated observations of the same
|
||||||
|
// kind of thing, and produces a statistical summary of those observations,
|
||||||
|
// typically expressed as quantiles or buckets. An example of a histogram is
|
||||||
|
// HTTP request latencies.
|
||||||
|
type Histogram interface {
|
||||||
|
With(labelValues ...string) Histogram
|
||||||
|
Observe(value float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelValues is a type alias that provides validation on its With method.
|
||||||
|
// Metrics may include it as a member to help them satisfy With semantics and
|
||||||
|
// save some code duplication.
|
||||||
|
type LabelValues []string
|
||||||
|
|
||||||
|
// With validates the input, and returns a new aggregate labelValues.
|
||||||
|
func (lvs LabelValues) With(labelValues ...string) LabelValues {
|
||||||
|
if len(labelValues)%2 != 0 {
|
||||||
|
labelValues = append(labelValues, "unknown")
|
||||||
|
}
|
||||||
|
return append(lvs, labelValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// new a *ProcessMetrics
|
||||||
|
// productName,serviceRegion,appName all those params must be passed
|
||||||
|
// port if not be pass,will use defaultAddr 9166
|
||||||
|
func NewProcessMetrics(productName, serviceRegion, appName string, port ...string) (*ProcessMetrics, error) {
|
||||||
|
if len(productName) == 0 || len(serviceRegion) == 0 || len(appName) == 0 {
|
||||||
|
err := errors.New("productName,serviceRegion,appName all those params must be passed")
|
||||||
|
tlog.Error(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addr := defaultAddr
|
||||||
|
if len(port) > 0 {
|
||||||
|
addr = port[0]
|
||||||
|
}
|
||||||
|
var processMetrics ProcessMetrics
|
||||||
|
processMetrics.Port = addr
|
||||||
|
processMetrics.ProductName = productName
|
||||||
|
processMetrics.ServiceRegion = serviceRegion
|
||||||
|
processMetrics.Appname = appName
|
||||||
|
return &processMetrics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProcessMetrics struct {
|
||||||
|
Port string // port default 9166
|
||||||
|
ProductName string
|
||||||
|
ServiceRegion string
|
||||||
|
Appname string
|
||||||
|
}
|
||||||
|
|
||||||
|
// get real work port
|
||||||
|
func (s *ProcessMetrics) GetMetricsPort() string {
|
||||||
|
if len(s.Port) > 0 {
|
||||||
|
return s.Port
|
||||||
|
}
|
||||||
|
return defaultAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init Export on GF default port 9166
|
||||||
|
// If you want custom listen port, just pass your port to MetricsParams struct
|
||||||
|
// return exporter http server
|
||||||
|
func (s *ProcessMetrics) InitExporter() *http.Server {
|
||||||
|
h := http.NewServeMux()
|
||||||
|
h.Handle("/metrics", promhttp.Handler())
|
||||||
|
h.HandleFunc("/health_checker", s.healthChecker)
|
||||||
|
addr := defaultAddr
|
||||||
|
if len(s.Port) > 0 {
|
||||||
|
addr = s.Port
|
||||||
|
} else {
|
||||||
|
s.Port = defaultAddr
|
||||||
|
}
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: ":" + addr,
|
||||||
|
Handler: h,
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
ReadHeaderTimeout: 30 * time.Second,
|
||||||
|
IdleTimeout: 30 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var tags []string
|
||||||
|
|
||||||
|
tags = append(tags, fmt.Sprintf("app=%s", s.ProductName))
|
||||||
|
tags = append(tags, fmt.Sprintf("country=%s", s.ServiceRegion))
|
||||||
|
s.initExporterOnExistHttpServer(addr, "/health_checker", tags, s.Appname)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
tlog.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProcessMetrics) healthChecker(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"code": 0,
|
||||||
|
"message": "ok",
|
||||||
|
"server_time": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
res, _ := json.Marshal(data)
|
||||||
|
resp.Write(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init Pusher
|
||||||
|
// return prometheus pusher
|
||||||
|
func InitPusher(gateway string, job string) *push.Pusher {
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
return push.New(gateway, job).Grouping("instance", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitExporterOnExistHttpServer Init http exporter
|
||||||
|
// Why we init exporter on exist http server:
|
||||||
|
// to avoid new custom port
|
||||||
|
// which should add healthCheckRouter - like /healthChecker func
|
||||||
|
// must export a route: /metrics router for prometheus
|
||||||
|
// promhttp.Handler().ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||||
|
func (s *ProcessMetrics) initExporterOnExistHttpServer(port string, healthCheckRouter string, tags []string, name string, id ...string) {
|
||||||
|
config := consulapi.DefaultConfig()
|
||||||
|
client, err := consulapi.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Error("consul client error : ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
registration := new(consulapi.AgentServiceRegistration)
|
||||||
|
registration.Name = name
|
||||||
|
intPort, _ := strconv.Atoi(port)
|
||||||
|
registration.Port = intPort
|
||||||
|
hostName, _ := os.Hostname()
|
||||||
|
if len(id) > 0 {
|
||||||
|
registration.ID = registration.Name + ":" + id[0]
|
||||||
|
} else {
|
||||||
|
registration.ID = registration.Name + ":" + hostName
|
||||||
|
}
|
||||||
|
|
||||||
|
registration.Tags = append(tags, "lang=go")
|
||||||
|
|
||||||
|
registration.Check = new(consulapi.AgentServiceCheck)
|
||||||
|
registration.Check.HTTP = fmt.Sprintf("http://%s:%s%s",
|
||||||
|
"127.0.0.1", port, healthCheckRouter)
|
||||||
|
registration.Check.Interval = "5s"
|
||||||
|
registration.Check.Timeout = "3s"
|
||||||
|
// 为兼容现有的 平滑逻辑
|
||||||
|
//err = client.Agent().ServiceDeregister(registration.ID)
|
||||||
|
|
||||||
|
err = client.Agent().ServiceRegister(registration)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
tlog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
169
metrics/prometheus/prometheus.go
Normal file
169
metrics/prometheus/prometheus.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"globalfintech/golib/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Counter implements Counter, via a Prometheus CounterVec.
|
||||||
|
type Counter struct {
|
||||||
|
Cv *prometheus.CounterVec
|
||||||
|
lvs metrics.LabelValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCounterFrom constructs and registers a Prometheus CounterVec,
|
||||||
|
// and returns a usable Counter object.
|
||||||
|
func NewCounterFrom(opts prometheus.CounterOpts, labelNames []string) *Counter {
|
||||||
|
cv := prometheus.NewCounterVec(opts, labelNames)
|
||||||
|
prometheus.MustRegister(cv)
|
||||||
|
return NewCounter(cv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCounter wraps the CounterVec and returns a usable Counter object.
|
||||||
|
func NewCounter(cv *prometheus.CounterVec) *Counter {
|
||||||
|
return &Counter{
|
||||||
|
Cv: cv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With implements Counter.
|
||||||
|
func (c *Counter) With(labelValues ...string) metrics.Counter {
|
||||||
|
return &Counter{
|
||||||
|
Cv: c.Cv,
|
||||||
|
lvs: c.lvs.With(labelValues...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add implements Counter.
|
||||||
|
func (c *Counter) Add(delta float64) {
|
||||||
|
c.Cv.With(makeLabels(c.lvs...)).Add(delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc implement Counter.
|
||||||
|
func (c *Counter) Inc() {
|
||||||
|
c.Cv.With(makeLabels(c.lvs...)).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gauge implements Gauge, via a Prometheus GaugeVec.
|
||||||
|
type Gauge struct {
|
||||||
|
gv *prometheus.GaugeVec
|
||||||
|
lvs metrics.LabelValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGaugeFrom construts and registers a Prometheus GaugeVec,
|
||||||
|
// and returns a usable Gauge object.
|
||||||
|
func NewGaugeFrom(opts prometheus.GaugeOpts, labelNames []string) *Gauge {
|
||||||
|
gv := prometheus.NewGaugeVec(opts, labelNames)
|
||||||
|
prometheus.MustRegister(gv)
|
||||||
|
return NewGauge(gv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGauge wraps the GaugeVec and returns a usable Gauge object.
|
||||||
|
func NewGauge(gv *prometheus.GaugeVec) *Gauge {
|
||||||
|
return &Gauge{
|
||||||
|
gv: gv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With implements Gauge.
|
||||||
|
func (g *Gauge) With(labelValues ...string) metrics.Gauge {
|
||||||
|
return &Gauge{
|
||||||
|
gv: g.gv,
|
||||||
|
lvs: g.lvs.With(labelValues...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set implements Gauge.
|
||||||
|
func (g *Gauge) Set(value float64) {
|
||||||
|
g.gv.With(makeLabels(g.lvs...)).Set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add is supported by Prometheus GaugeVecs.
|
||||||
|
func (g *Gauge) Add(delta float64) {
|
||||||
|
g.gv.With(makeLabels(g.lvs...)).Add(delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub is supported by Prometheus GaugeVecs.
|
||||||
|
func (g *Gauge) Sub(delta float64) {
|
||||||
|
g.gv.With(makeLabels(g.lvs...)).Sub(delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary implements Histogram, via a Prometheus SummaryVec. The difference
|
||||||
|
// between a Summary and a Histogram is that Summaries don't require predefined
|
||||||
|
// quantile buckets, but cannot be statistically aggregated.
|
||||||
|
type Summary struct {
|
||||||
|
sv *prometheus.SummaryVec
|
||||||
|
lvs metrics.LabelValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSummaryFrom constructs and registers a Prometheus SummaryVec,
|
||||||
|
// and returns a usable Summary object.
|
||||||
|
func NewSummaryFrom(opts prometheus.SummaryOpts, labelNames []string) *Summary {
|
||||||
|
sv := prometheus.NewSummaryVec(opts, labelNames)
|
||||||
|
prometheus.MustRegister(sv)
|
||||||
|
return NewSummary(sv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSummary wraps the SummaryVec and returns a usable Summary object.
|
||||||
|
func NewSummary(sv *prometheus.SummaryVec) *Summary {
|
||||||
|
return &Summary{
|
||||||
|
sv: sv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With implements Histogram.
|
||||||
|
func (s *Summary) With(labelValues ...string) metrics.Histogram {
|
||||||
|
return &Summary{
|
||||||
|
sv: s.sv,
|
||||||
|
lvs: s.lvs.With(labelValues...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe implements Histogram.
|
||||||
|
func (s *Summary) Observe(value float64) {
|
||||||
|
s.sv.With(makeLabels(s.lvs...)).Observe(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Histogram implements Histogram via a Prometheus HistogramVec. The difference
|
||||||
|
// between a Histogram and a Summary is that Histograms require predefined
|
||||||
|
// quantile buckets, and can be statistically aggregated.
|
||||||
|
type Histogram struct {
|
||||||
|
hv *prometheus.HistogramVec
|
||||||
|
lvs metrics.LabelValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistogramFrom constructs and registers a Prometheus HistogramVec,
|
||||||
|
// and returns a usable Histogram object.
|
||||||
|
func NewHistogramFrom(opts prometheus.HistogramOpts, labelNames []string) *Histogram {
|
||||||
|
hv := prometheus.NewHistogramVec(opts, labelNames)
|
||||||
|
prometheus.MustRegister(hv)
|
||||||
|
return NewHistogram(hv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistogram wraps the HistogramVec and returns a usable Histogram object.
|
||||||
|
func NewHistogram(hv *prometheus.HistogramVec) *Histogram {
|
||||||
|
return &Histogram{
|
||||||
|
hv: hv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With implements Histogram.
|
||||||
|
func (h *Histogram) With(labelValues ...string) metrics.Histogram {
|
||||||
|
return &Histogram{
|
||||||
|
hv: h.hv,
|
||||||
|
lvs: h.lvs.With(labelValues...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe implements Histogram.
|
||||||
|
func (h *Histogram) Observe(value float64) {
|
||||||
|
h.hv.With(makeLabels(h.lvs...)).Observe(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLabels(labelValues ...string) prometheus.Labels {
|
||||||
|
labels := prometheus.Labels{}
|
||||||
|
for i := 0; i < len(labelValues); i += 2 {
|
||||||
|
labels[labelValues[i]] = labelValues[i+1]
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
}
|
||||||
61
metrics/prometheus/prometheus_test.go
Normal file
61
metrics/prometheus/prometheus_test.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"globalfintech/golib/metrics"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCounter(t *testing.T) {
|
||||||
|
s := httptest.NewServer(promhttp.Handler())
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
scrape := func() string {
|
||||||
|
resp, _ := http.Get(s.URL)
|
||||||
|
buf, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace, subsystem, name := "ns", "ss", "foo"
|
||||||
|
re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{alpha="alpha-value",beta="beta-value"} ([0-9\.]+)`)
|
||||||
|
|
||||||
|
counter := NewCounterFrom(stdprometheus.CounterOpts{
|
||||||
|
Namespace: namespace,
|
||||||
|
Subsystem: subsystem,
|
||||||
|
Name: name,
|
||||||
|
Help: "This is the help string.",
|
||||||
|
}, []string{"alpha", "beta"}).With("beta", "beta-value", "alpha", "alpha-value") // order shouldn't matter
|
||||||
|
|
||||||
|
value := func() float64 {
|
||||||
|
matches := re.FindStringSubmatch(scrape())
|
||||||
|
f, _ := strconv.ParseFloat(matches[1], 64)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
want := FillCounter(counter)
|
||||||
|
if have := value(); want != have {
|
||||||
|
t.Fatal(fmt.Errorf("want %f, have %f", want, have))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillCounter puts some deltas through the counter and returns the total value.
|
||||||
|
func FillCounter(counter metrics.Counter) float64 {
|
||||||
|
a := rand.Perm(100)
|
||||||
|
n := rand.Intn(len(a))
|
||||||
|
|
||||||
|
var want float64
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
f := float64(a[i])
|
||||||
|
counter.Add(f)
|
||||||
|
want += f
|
||||||
|
}
|
||||||
|
return want
|
||||||
|
}
|
||||||
66
qgrpc/config.go
Normal file
66
qgrpc/config.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package qgrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 基础配置,公共字段
|
||||||
|
type BasicConfig struct {
|
||||||
|
DialTimeout int `toml:"dial_timeout"` // 连接超时时间
|
||||||
|
KeepAlive int `toml:"keep_alive"` // 保活时间
|
||||||
|
EndPoints []string `toml:"endpoints"` // etcd的地址
|
||||||
|
Prefix string `toml:"prefix"` // etcd服务的路径前缀,区分环境用
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务端配置
|
||||||
|
type ServerConfig struct {
|
||||||
|
BasicConfig
|
||||||
|
Servers []string `toml:"servers"` // 注册的服务名称,可以是多个
|
||||||
|
Addr string `toml:"addr"` // 服务地址
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ServerConfig) GetAddr() string {
|
||||||
|
if strings.HasPrefix(self.Addr, ":") {
|
||||||
|
localIP := getLocalIP()
|
||||||
|
return fmt.Sprintf("%s%s", localIP, self.Addr)
|
||||||
|
}
|
||||||
|
return self.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端配置
|
||||||
|
type ClientConfig struct {
|
||||||
|
BasicConfig
|
||||||
|
WatchServers []string `toml:"watch_servers"` // 依赖的服务名称,多个服务用','分割;每个服务名后增加'|ip:port'表示默认连接的服务地址
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHostName() string {
|
||||||
|
if h, err := os.Hostname(); err == nil {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return getLocalIP()
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取本机的内网Ip, 如果发现对方的ip 和自己的ip 相同,用127.0.0.1 替代
|
||||||
|
func getLocalIP() string {
|
||||||
|
ifaces, _ := net.Interfaces()
|
||||||
|
for _, i := range ifaces {
|
||||||
|
addrs, _ := i.Addrs()
|
||||||
|
for _, addr := range addrs {
|
||||||
|
var ip net.IP
|
||||||
|
switch v := addr.(type) {
|
||||||
|
case *net.IPNet:
|
||||||
|
ip = v.IP
|
||||||
|
case *net.IPAddr:
|
||||||
|
ip = v.IP
|
||||||
|
}
|
||||||
|
ipAddr := ip.String()
|
||||||
|
if strings.HasPrefix(ipAddr, "172.") || strings.HasPrefix(ipAddr, "192.") || strings.HasPrefix(ipAddr, "10.") {
|
||||||
|
return ipAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "127.0.0.1"
|
||||||
|
}
|
||||||
141
qgrpc/op.go
Normal file
141
qgrpc/op.go
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package qgrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"globalfintech/golib/qgrpc/ping"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/keepalive"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
userCfg *ServerConfig
|
||||||
|
etcdCli *clientv3.Client
|
||||||
|
etcdId clientv3.LeaseID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
userCfg *ClientConfig
|
||||||
|
etcdCli *clientv3.Client
|
||||||
|
etcdId clientv3.LeaseID
|
||||||
|
rpcServers map[string]*grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type RpcServer struct {
|
||||||
|
Name string //服务的名字
|
||||||
|
Addrs []string //默认的地址
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DFT_DIAL_TIMEOUT = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
func etcdDialWrapper(cfg *BasicConfig) (*clientv3.Client, error) {
|
||||||
|
dialTimeout := DFT_DIAL_TIMEOUT
|
||||||
|
if cfg.DialTimeout > 0 {
|
||||||
|
dialTimeout = cfg.DialTimeout
|
||||||
|
}
|
||||||
|
return clientv3.New(clientv3.Config{
|
||||||
|
Endpoints: cfg.EndPoints,
|
||||||
|
DialTimeout: time.Duration(dialTimeout) * time.Second,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServerRegist(cfg *ServerConfig) (server *Server, err error) {
|
||||||
|
server = &Server{userCfg: cfg}
|
||||||
|
|
||||||
|
if server.etcdCli, err = etcdDialWrapper(&cfg.BasicConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
server.register()
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(cfg *ClientConfig) (client *Client, err error) {
|
||||||
|
client = &Client{userCfg: cfg}
|
||||||
|
|
||||||
|
if client.etcdCli, err = etcdDialWrapper(&cfg.BasicConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.rpcServers = make(map[string]*grpc.ClientConn)
|
||||||
|
|
||||||
|
keepAlive := 3
|
||||||
|
if cfg.KeepAlive > 0 {
|
||||||
|
keepAlive = cfg.KeepAlive
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, watchServer := range cfg.WatchServers {
|
||||||
|
rpcServer := ParseServerName(watchServer)
|
||||||
|
resolver := newresolver(client, rpcServer.Name, rpcServer.Addrs)
|
||||||
|
rrBalancer := grpc.RoundRobin(resolver)
|
||||||
|
|
||||||
|
client.rpcServers[rpcServer.Name], err = grpc.Dial("", grpc.WithInsecure(),
|
||||||
|
grpc.WithTimeout(time.Second),
|
||||||
|
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: time.Duration(keepAlive) * time.Second}),
|
||||||
|
grpc.WithBalancer(rrBalancer))
|
||||||
|
}
|
||||||
|
|
||||||
|
go client.ping()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//user.User|127.0.0.1:9011 -> Server{Name: user.User Addrs: []string{127.0.0.1:9011}}
|
||||||
|
func ParseServerName(serverName string) *RpcServer {
|
||||||
|
ss := strings.Split(serverName, "|")
|
||||||
|
s := &RpcServer{Name: ss[0]}
|
||||||
|
|
||||||
|
//没有配置默认地址
|
||||||
|
if len(ss) < 2 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Addrs = strings.Split(ss[1], ",")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据server-name 获得对应的连接池
|
||||||
|
func (self *Client) GetConn(serverName string) *grpc.ClientConn {
|
||||||
|
return self.rpcServers[serverName]
|
||||||
|
}
|
||||||
|
|
||||||
|
//从etcd注销
|
||||||
|
func (self *Server) Revoke() error {
|
||||||
|
_, err := self.etcdCli.Revoke(context.Background(), self.etcdId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Client) Call(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) (err error) {
|
||||||
|
ss := strings.Split(method, "/")
|
||||||
|
fmt.Println("ss:", ss, len(ss))
|
||||||
|
if len(ss) < 3 {
|
||||||
|
tlog.Errorf("no grpc client available:%s", method)
|
||||||
|
return fmt.Errorf("no grpc client available:%s", method)
|
||||||
|
}
|
||||||
|
conn, ok := self.rpcServers[ss[1]]
|
||||||
|
fmt.Println("conn:", conn)
|
||||||
|
if !ok {
|
||||||
|
tlog.Errorf("no grpc client available:%s", method)
|
||||||
|
return fmt.Errorf("no grpc client available:%s", method)
|
||||||
|
}
|
||||||
|
err = grpc.Invoke(ctx, method, args, reply, conn, opts...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Client) ping() {
|
||||||
|
for range time.NewTicker(time.Second).C {
|
||||||
|
for server, conn := range self.rpcServers {
|
||||||
|
req, resp := &ping.NoArgs{}, &ping.NoArgs{}
|
||||||
|
if err := grpc.Invoke(context.Background(), strings.Join([]string{server, "Ping"}, "/"), req, resp, conn); err != nil {
|
||||||
|
tlog.Errorf("qgrpc ping||server=%s||err=%s", server, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
qgrpc/ping/ping.pb.go
Normal file
51
qgrpc/ping/ping.pb.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// source: ping.proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package ping is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
ping.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
NoArgs
|
||||||
|
*/
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type NoArgs struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *NoArgs) Reset() { *m = NoArgs{} }
|
||||||
|
func (m *NoArgs) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*NoArgs) ProtoMessage() {}
|
||||||
|
func (*NoArgs) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*NoArgs)(nil), "ping.NoArgs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("ping.proto", fileDescriptor0) }
|
||||||
|
|
||||||
|
var fileDescriptor0 = []byte{
|
||||||
|
// 66 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0xc8, 0xcc, 0x4b,
|
||||||
|
0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0xd8, 0xb9, 0x58, 0x5d, 0x73,
|
||||||
|
0x0b, 0x4a, 0x2a, 0x9d, 0x04, 0xa2, 0xf8, 0x72, 0x32, 0x93, 0xf4, 0xc1, 0x72, 0xfa, 0x20, 0xa9,
|
||||||
|
0x24, 0x36, 0x30, 0xdb, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x65, 0x5a, 0x5a, 0x35, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
}
|
||||||
60
qgrpc/register.go
Normal file
60
qgrpc/register.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package qgrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"globalfintech/golib/tlog"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func etcdGrantWrapper(cli *clientv3.Client) (clientv3.LeaseID, error) {
|
||||||
|
resp, err := cli.Grant(context.Background(), 5)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Server) register() (err error) {
|
||||||
|
|
||||||
|
if len(self.userCfg.Prefix) < 1 || len(self.userCfg.Addr) < 1 {
|
||||||
|
tlog.Info("qgrpc server||register||prefix or addr less than 1 char")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.etcdId, err = etcdGrantWrapper(self.etcdCli); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range self.userCfg.Servers {
|
||||||
|
addr := self.userCfg.GetAddr()
|
||||||
|
etcdKey := path.Join(self.userCfg.Prefix, server, addr)
|
||||||
|
if _, err = self.etcdCli.Put(context.Background(), etcdKey, addr, clientv3.WithLease(self.etcdId)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := self.etcdCli.KeepAlive(context.Background(), self.etcdId)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go self.keepalive(ch)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Server) keepalive(ch <-chan *clientv3.LeaseKeepAliveResponse) {
|
||||||
|
for {
|
||||||
|
resp := <-ch
|
||||||
|
if resp == nil {
|
||||||
|
tlog.Error("qgrpc server||keepalive||nil response")
|
||||||
|
self.Revoke()
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
self.register()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
148
qgrpc/watch.go
Normal file
148
qgrpc/watch.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package qgrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||||
|
"google.golang.org/grpc/naming"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type watcher struct {
|
||||||
|
prefix string
|
||||||
|
addrs []string
|
||||||
|
c *clientv3.Client
|
||||||
|
hasStart bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type resolver struct {
|
||||||
|
client *Client
|
||||||
|
name string
|
||||||
|
addrs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newresolver(client *Client, name string, addrs []string) *resolver {
|
||||||
|
return &resolver{client: client, name: name, addrs: addrs}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *resolver) Resolve(name string) (naming.Watcher, error) {
|
||||||
|
return self.client.newwatcher(self.name, self.addrs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Client) newwatcher(name string, addrs []string) naming.Watcher {
|
||||||
|
prefix := path.Join(self.userCfg.Prefix, name)
|
||||||
|
return &watcher{prefix: prefix, addrs: addrs, c: self.etcdCli}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *watcher) Close() {}
|
||||||
|
|
||||||
|
func (w *watcher) Next() ([]*naming.Update, error) {
|
||||||
|
//第一次启动从etcd上获取所有的地址同时加上默认的地址
|
||||||
|
if !w.hasStart {
|
||||||
|
updates := []*naming.Update{}
|
||||||
|
|
||||||
|
//etcd 的连接正常
|
||||||
|
if w.c != nil {
|
||||||
|
resp, err := w.c.Get(context.Background(), w.prefix, clientv3.WithPrefix())
|
||||||
|
if err == nil {
|
||||||
|
addrs := extractAddrs(resp)
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: addr})
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "127.0.0.1:", -1)})
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "localhost:", -1)})
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), getHostName()+":", -1)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//将默认的地址加进入
|
||||||
|
for _, addr := range w.addrs {
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: addr})
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "127.0.0.1:", -1)})
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), "localhost:", -1)})
|
||||||
|
updates = append(updates, &naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(addr, fmt.Sprintf("%s:", getLocalIP()), getHostName()+":", -1)})
|
||||||
|
}
|
||||||
|
|
||||||
|
w.hasStart = true
|
||||||
|
return updates, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.c == nil {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rch := w.c.Watch(context.Background(), w.prefix, clientv3.WithPrefix())
|
||||||
|
for wresp := range rch {
|
||||||
|
for _, ev := range wresp.Events {
|
||||||
|
switch ev.Type {
|
||||||
|
case mvccpb.PUT:
|
||||||
|
_, value := string(ev.Kv.Key), string(ev.Kv.Value)
|
||||||
|
//fmt.Println(time.Now(), "[PUT]", key, value)
|
||||||
|
return []*naming.Update{
|
||||||
|
&naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: value},
|
||||||
|
&naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(value, fmt.Sprintf("%s:", getLocalIP()), "127.0.0.1:", -1)},
|
||||||
|
&naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(value, fmt.Sprintf("%s:", getLocalIP()), "localhost:", -1)},
|
||||||
|
&naming.Update{
|
||||||
|
Op: naming.Add,
|
||||||
|
Addr: strings.Replace(value, fmt.Sprintf("%s:", getLocalIP()), getHostName()+":", -1)},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case mvccpb.DELETE:
|
||||||
|
_, value := string(ev.Kv.Key), string(ev.Kv.Value)
|
||||||
|
//fmt.Println(time.Now(), "[DELETE]", key)
|
||||||
|
return []*naming.Update{
|
||||||
|
&naming.Update{
|
||||||
|
Op: naming.Delete,
|
||||||
|
Addr: value,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractAddrs(resp *clientv3.GetResponse) []string {
|
||||||
|
addrs := []string{}
|
||||||
|
|
||||||
|
if resp == nil || resp.Kvs == nil {
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range resp.Kvs {
|
||||||
|
if v.Value != nil {
|
||||||
|
addrs = append(addrs, string(v.Value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
81
tlog/clog.go
Normal file
81
tlog/clog.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package tlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Cdebug(ctx context.Context, args ...interface{}) {
|
||||||
|
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
|
||||||
|
data := append([]interface{}{id}, args...)
|
||||||
|
l.p(DEBUG, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cdebugf(ctx context.Context, format string, args ...interface{}) {
|
||||||
|
f := "trace_id=%s||" + format
|
||||||
|
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
|
||||||
|
l.pf(DEBUG, f, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cinfo(ctx context.Context, args ...interface{}) {
|
||||||
|
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
|
||||||
|
data := append([]interface{}{id}, args...)
|
||||||
|
l.p(INFO, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cinfof(ctx context.Context, format string, args ...interface{}) {
|
||||||
|
f := "trace_id=%s||" + format
|
||||||
|
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
|
||||||
|
l.pf(INFO, f, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cerror(ctx context.Context, args ...interface{}) {
|
||||||
|
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
|
||||||
|
data := append([]interface{}{id}, args...)
|
||||||
|
l.p(ERROR, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cerrorf(ctx context.Context, format string, args ...interface{}) {
|
||||||
|
f := "trace_id=%s||" + format
|
||||||
|
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
|
||||||
|
l.pf(ERROR, f, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cwarning(ctx context.Context, args ...interface{}) {
|
||||||
|
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
|
||||||
|
data := append([]interface{}{id}, args...)
|
||||||
|
l.p(WARNING, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cwarningf(ctx context.Context, format string, args ...interface{}) {
|
||||||
|
f := "trace_id=%s||" + format
|
||||||
|
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
|
||||||
|
l.pf(WARNING, f, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cfatal(ctx context.Context, args ...interface{}) {
|
||||||
|
id := fmt.Sprintf("trace_id=%s", getTraceIdFromCtx(ctx))
|
||||||
|
data := append([]interface{}{id}, args...)
|
||||||
|
l.p(FATAL, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cfatalf(ctx context.Context, format string, args ...interface{}) {
|
||||||
|
f := "trace_id=%s||" + format
|
||||||
|
data := append([]interface{}{getTraceIdFromCtx(ctx)}, args...)
|
||||||
|
l.pf(FATAL, f, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTraceIdFromCtx(ctx context.Context) string {
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
v, ok := md["trace_id"]
|
||||||
|
if !ok || len(v) < 1 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
return v[0]
|
||||||
|
}
|
||||||
38
tlog/level.go
Normal file
38
tlog/level.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package tlog
|
||||||
|
|
||||||
|
type LEVEL byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ALL LEVEL = iota
|
||||||
|
DEBUG
|
||||||
|
INFO
|
||||||
|
WARNING
|
||||||
|
ERROR
|
||||||
|
FATAL
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelText = map[LEVEL]string{
|
||||||
|
ALL: "ALL",
|
||||||
|
DEBUG: "DEBUG",
|
||||||
|
INFO: "INFO",
|
||||||
|
WARNING: "WARNING",
|
||||||
|
ERROR: "ERROR",
|
||||||
|
FATAL: "FATAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLevel(level string) LEVEL {
|
||||||
|
switch level {
|
||||||
|
case "DEBUG":
|
||||||
|
return DEBUG
|
||||||
|
case "INFO":
|
||||||
|
return INFO
|
||||||
|
case "WARNING":
|
||||||
|
return WARNING
|
||||||
|
case "ERROR":
|
||||||
|
return ERROR
|
||||||
|
case "FATAL":
|
||||||
|
return FATAL
|
||||||
|
default:
|
||||||
|
return ALL
|
||||||
|
}
|
||||||
|
}
|
||||||
334
tlog/logger.go
Normal file
334
tlog/logger.go
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
package tlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
//"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
//"github.com/getsentry/raven-go"
|
||||||
|
"log/syslog"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var l *Logger
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
fileSize int64
|
||||||
|
fileNum int
|
||||||
|
fileName string
|
||||||
|
host string
|
||||||
|
debug bool
|
||||||
|
tag string
|
||||||
|
level LEVEL
|
||||||
|
dir string
|
||||||
|
ch chan *Atom
|
||||||
|
f *os.File
|
||||||
|
w *bufio.Writer
|
||||||
|
useSyslog bool
|
||||||
|
bytePool *sync.Pool
|
||||||
|
syslogW *syslog.Writer
|
||||||
|
//jsonEncoder *json.Encoder
|
||||||
|
//sentryCh chan *raven.Packet
|
||||||
|
//client *raven.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type Atom struct {
|
||||||
|
//isJson bool
|
||||||
|
line int
|
||||||
|
file string
|
||||||
|
format string
|
||||||
|
level LEVEL
|
||||||
|
args []interface{}
|
||||||
|
data map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogger(config Config) {
|
||||||
|
l = &Logger{
|
||||||
|
dir: config.Dir,
|
||||||
|
fileSize: int64(config.FileSize * 1024 * 1024),
|
||||||
|
fileNum: config.FileNum,
|
||||||
|
fileName: path.Join(config.Dir, config.FileName+".log"),
|
||||||
|
tag: config.Tag,
|
||||||
|
debug: config.Debug,
|
||||||
|
level: getLevel(config.Level),
|
||||||
|
ch: make(chan *Atom, 102400),
|
||||||
|
bytePool: &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }},
|
||||||
|
}
|
||||||
|
host, _ := os.Hostname()
|
||||||
|
ss := strings.Split(host, "-")
|
||||||
|
if len(ss) < 2 {
|
||||||
|
l.host = host
|
||||||
|
} else {
|
||||||
|
l.host = ss[len(ss)-2] + ss[len(ss)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.debug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os.MkdirAll(l.dir, 0755)
|
||||||
|
l.f, _ = os.OpenFile(l.fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
l.w = bufio.NewWriterSize(l.f, 1024*1024)
|
||||||
|
/*
|
||||||
|
l.jsonEncoder = json.NewEncoder(l.w)
|
||||||
|
if config.SentryUrl != "" {
|
||||||
|
l.sentryCh = make(chan *raven.Packet, 1024)
|
||||||
|
l.client, _ = raven.New(config.SentryUrl)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if config.UseSyslog {
|
||||||
|
var err error
|
||||||
|
l.syslogW, err = syslog.New(syslog.LOG_LOCAL3|syslog.LOG_INFO, config.SyslogTag)
|
||||||
|
if err == nil {
|
||||||
|
l.useSyslog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) start() {
|
||||||
|
/*
|
||||||
|
if l.client != nil && l.sentryCh != nil {
|
||||||
|
go l.sentry()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
for {
|
||||||
|
a := <-l.ch
|
||||||
|
if a == nil {
|
||||||
|
l.w.Flush()
|
||||||
|
fileInfo, err := os.Stat(l.fileName)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
l.f.Close()
|
||||||
|
l.f, _ = os.OpenFile(l.fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
l.w.Reset(l.f)
|
||||||
|
}
|
||||||
|
if fileInfo.Size() > l.fileSize {
|
||||||
|
l.f.Close()
|
||||||
|
os.Rename(l.fileName, l.logname())
|
||||||
|
l.f, _ = os.OpenFile(l.fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
l.w.Reset(l.f)
|
||||||
|
l.rm()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b := l.bytes(a)
|
||||||
|
l.w.Write(b)
|
||||||
|
if l.useSyslog {
|
||||||
|
l.syslogW.Write(b)
|
||||||
|
}
|
||||||
|
//发送sentry
|
||||||
|
//l.toSentryCh(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (l *Logger) sentry() {
|
||||||
|
for {
|
||||||
|
p := <-l.sentryCh
|
||||||
|
_, ch := l.client.Capture(p, nil)
|
||||||
|
if ch != nil {
|
||||||
|
if err := <-ch; err != nil {
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (l *Logger) toSentryCh(a *Atom) {
|
||||||
|
if a.level >= ERROR {
|
||||||
|
packet := l.formatSentryPacket(a)
|
||||||
|
select {
|
||||||
|
case l.sentryCh <- packet:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) formatSentryPacket(a *Atom) *raven.Packet {
|
||||||
|
packet := &raven.Packet{Message: l.fileName}
|
||||||
|
if a.level == ERROR {
|
||||||
|
packet.Level = raven.ERROR
|
||||||
|
}
|
||||||
|
if a.level == FATAL {
|
||||||
|
packet.Level = raven.FATAL
|
||||||
|
}
|
||||||
|
if a.isJson {
|
||||||
|
packet.Extra = a.data
|
||||||
|
packet.Extra["fileloc"] = fmt.Sprintf("%s:%d", a.file, a.line)
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
packet.Extra = map[string]interface{}{"fileloc": fmt.Sprintf("%s:%d", a.file, a.line)}
|
||||||
|
if a.format == "" {
|
||||||
|
packet.Culprit = fmt.Sprint(a.args...)
|
||||||
|
} else {
|
||||||
|
packet.Culprit = fmt.Sprintf(a.format, a.args...)
|
||||||
|
}
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (l *Logger) run() {
|
||||||
|
if l.debug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go l.flush()
|
||||||
|
go l.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) stop() {
|
||||||
|
if l != nil && l.w != nil {
|
||||||
|
l.w.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytePool *sync.Pool = &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
|
||||||
|
|
||||||
|
func (l *Logger) bytes(a *Atom) []byte {
|
||||||
|
w := l.bytePool.Get().(*bytes.Buffer)
|
||||||
|
defer func() {
|
||||||
|
recover()
|
||||||
|
w.Reset()
|
||||||
|
l.bytePool.Put(w)
|
||||||
|
}()
|
||||||
|
w.Write(l.genTime())
|
||||||
|
fmt.Fprintf(w, "%s %s %s %s:%d ", l.host, l.tag, levelText[a.level], a.file, a.line)
|
||||||
|
if len(a.format) < 1 {
|
||||||
|
for _, arg := range a.args {
|
||||||
|
w.WriteByte(' ')
|
||||||
|
fmt.Fprint(w, arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, a.format, a.args...)
|
||||||
|
}
|
||||||
|
w.WriteByte(10)
|
||||||
|
b := make([]byte, w.Len())
|
||||||
|
copy(b, w.Bytes())
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) rm() {
|
||||||
|
if out, err := exec.Command("ls", l.dir).Output(); err == nil {
|
||||||
|
files := bytes.Split(out, []byte("\n"))
|
||||||
|
totol, idx := len(files)-1, 0
|
||||||
|
for i := totol; i >= 0; i-- {
|
||||||
|
file := path.Join(l.dir, string(files[i]))
|
||||||
|
if strings.HasPrefix(file, l.fileName) && file != l.fileName {
|
||||||
|
idx++
|
||||||
|
if idx > l.fileNum {
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) flush() {
|
||||||
|
for range time.NewTicker(time.Second).C {
|
||||||
|
l.ch <- nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) logname() string {
|
||||||
|
t := fmt.Sprintf("%s", time.Now())[:19]
|
||||||
|
tt := strings.Replace(
|
||||||
|
strings.Replace(
|
||||||
|
strings.Replace(t, "-", "", -1),
|
||||||
|
" ", "", -1),
|
||||||
|
":", "", -1)
|
||||||
|
return fmt.Sprintf("%s.%s", l.fileName, tt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) genTime() []byte {
|
||||||
|
now := time.Now()
|
||||||
|
_, month, day := now.Date()
|
||||||
|
hour, minute, second := now.Clock()
|
||||||
|
return []byte{byte(month/10) + 48, byte(month%10) + 48, '-', byte(day/10) + 48, byte(day%10) + 48, ' ',
|
||||||
|
byte(hour/10) + 48, byte(hour%10) + 48, ':', byte(minute/10) + 48, byte(minute%10) + 48, ':',
|
||||||
|
byte(second/10) + 48, byte(second%10) + 48, ' '}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) getTag() string {
|
||||||
|
if l != nil {
|
||||||
|
return l.tag
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) p(level LEVEL, args ...interface{}) {
|
||||||
|
file, line := l.getFileNameAndLine()
|
||||||
|
if l == nil || l.debug {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
fmt.Printf("%s%s %s %s:%d ", l.genTime(), l.getTag(), levelText[level], file, line)
|
||||||
|
fmt.Println(args...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if level >= l.level {
|
||||||
|
select {
|
||||||
|
case l.ch <- &Atom{file: file, line: line, level: level, args: args}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) pf(level LEVEL, format string, args ...interface{}) {
|
||||||
|
file, line := l.getFileNameAndLine()
|
||||||
|
if l == nil || l.debug {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
fmt.Printf("%s%s %s %s:%d ", l.genTime(), l.getTag(), levelText[level], file, line)
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
fmt.Println()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if level >= l.level {
|
||||||
|
select {
|
||||||
|
case l.ch <- &Atom{file: file, line: line, format: format, level: level, args: args}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//暂不支持json
|
||||||
|
func (l *Logger) pj(level LEVEL, m map[string]interface{}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
file, line := l.getFileNameAndLine()
|
||||||
|
if l == nil || l.debug {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
m["time"] = string(l.genTime())
|
||||||
|
m["fileloc"] = fmt.Sprintf("%s:%d", file, line)
|
||||||
|
m["level"] = levelText[level]
|
||||||
|
json.NewEncoder(os.Stdout).Encode(m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if level >= l.level {
|
||||||
|
select {
|
||||||
|
case l.ch <- &Atom{file: file, line: line, data: m, level: level, isJson: true}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (l *Logger) getFileNameAndLine() (string, int) {
|
||||||
|
_, file, line, ok := runtime.Caller(3)
|
||||||
|
if !ok {
|
||||||
|
return "???", 1
|
||||||
|
}
|
||||||
|
dirs := strings.Split(file, "/")
|
||||||
|
if len(dirs) >= 2 {
|
||||||
|
return dirs[len(dirs)-2] + "/" + dirs[len(dirs)-1], line
|
||||||
|
}
|
||||||
|
return file, line
|
||||||
|
}
|
||||||
101
tlog/tlog.go
Normal file
101
tlog/tlog.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package tlog
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
FileSize int `toml:"filesize" json:"filesize"`
|
||||||
|
FileNum int `toml:"filenum" json:"filenum"`
|
||||||
|
FileName string `toml:"filename" json:"filename"`
|
||||||
|
Tag string `toml:"tag" json:"tag"`
|
||||||
|
Level string `toml:"level" json:"level"`
|
||||||
|
Debug bool `toml:"debug" json:"debug"`
|
||||||
|
Dir string `toml:"dir" json:"dir"`
|
||||||
|
SentryUrl string `toml:"sentry_url" json:"sentry_url"`
|
||||||
|
UseSyslog bool `toml:"use_syslog" json:"use_syslog"`
|
||||||
|
SyslogTag string `toml:"syslog_tag" json:"syslog_tag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) check() {
|
||||||
|
if c.FileSize == 0 {
|
||||||
|
c.FileSize = 128
|
||||||
|
}
|
||||||
|
if c.FileNum == 0 {
|
||||||
|
c.FileNum = 10
|
||||||
|
}
|
||||||
|
if c.FileName == "" {
|
||||||
|
c.FileName = "INFO"
|
||||||
|
}
|
||||||
|
if c.Dir == "" {
|
||||||
|
c.Dir = "./logs"
|
||||||
|
}
|
||||||
|
if c.Level == "" {
|
||||||
|
c.Level = "DEBUG"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(c Config) {
|
||||||
|
c.check()
|
||||||
|
newLogger(c)
|
||||||
|
l.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
l.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(args ...interface{}) {
|
||||||
|
l.p(DEBUG, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
l.pf(DEBUG, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DebugJson(m map[string]interface{}) {
|
||||||
|
l.pj(DEBUG, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(args ...interface{}) {
|
||||||
|
l.p(INFO, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infof(format string, args ...interface{}) {
|
||||||
|
l.pf(INFO, format, args...)
|
||||||
|
}
|
||||||
|
func InfoJson(m map[string]interface{}) {
|
||||||
|
l.pj(INFO, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warning(args ...interface{}) {
|
||||||
|
l.p(WARNING, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warningf(format string, args ...interface{}) {
|
||||||
|
l.pf(WARNING, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WarningJson(m map[string]interface{}) {
|
||||||
|
l.pj(WARNING, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(args ...interface{}) {
|
||||||
|
l.p(ERROR, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
l.pf(ERROR, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorJson(m map[string]interface{}) {
|
||||||
|
l.pj(ERROR, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(args ...interface{}) {
|
||||||
|
l.p(FATAL, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
l.pf(FATAL, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FatalJson(m map[string]interface{}) {
|
||||||
|
l.pj(FATAL, m)
|
||||||
|
}
|
||||||
30
tlog/tlog_test.go
Normal file
30
tlog/tlog_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package tlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTlog(t *testing.T) {
|
||||||
|
Init(Config{
|
||||||
|
FileSize: 128,
|
||||||
|
FileNum: 5,
|
||||||
|
FileName: "test",
|
||||||
|
Level: "DEBUG",
|
||||||
|
Dir: "./logs",
|
||||||
|
Tag: "test",
|
||||||
|
//Debug: true,
|
||||||
|
//SentryUrl: "http://d25c96785a184d6f8d051712a209a3a8:7f4a22c48897423caa9a0aec6bb93416@172.20.0.84:9000/5",
|
||||||
|
UseSyslog: true,
|
||||||
|
SyslogTag: "test",
|
||||||
|
})
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
Info("xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
|
||||||
|
Infof("%s", "xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
|
||||||
|
InfoJson(map[string]interface{}{"test": time.Now().UnixNano()})
|
||||||
|
Error("xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
|
||||||
|
Errorf("%s", "xxxxxxxxxxasfsadjflasjfdlasjdfsajdfsadfjasjfdafjsfsa")
|
||||||
|
ErrorJson(map[string]interface{}{"test": time.Now().UnixNano()})
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
52
trace/trace.go
Normal file
52
trace/trace.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
var t *Trace
|
||||||
|
|
||||||
|
type Trace struct {
|
||||||
|
queue chan int64
|
||||||
|
tick <-chan time.Time
|
||||||
|
ts int64
|
||||||
|
serverId int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrace(serverId int64) {
|
||||||
|
if t != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t = &Trace{
|
||||||
|
queue: make(chan int64, 1000),
|
||||||
|
tick: time.Tick(time.Second),
|
||||||
|
serverId: serverId % 65536,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.ts = t.serverId<<47 | time.Now().Unix()<<16
|
||||||
|
go func() {
|
||||||
|
var i int64 = 0
|
||||||
|
for {
|
||||||
|
t.queue <- i % 65536
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for now := range t.tick {
|
||||||
|
t.ts = t.serverId<<47 | now.Unix()<<16
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenId() int64 {
|
||||||
|
if t == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case seq := <-t.queue:
|
||||||
|
return seq | t.ts
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user