add doc
parent
d58f22e1d1
commit
5b572e5fa6
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
@ -1,41 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"zero/kq"
|
||||
)
|
||||
|
||||
type (
|
||||
Condition struct {
|
||||
Key string
|
||||
Value string
|
||||
Type string `json:",default=match,options=match|contains"`
|
||||
Op string `json:",default=and,options=and|or"`
|
||||
}
|
||||
|
||||
ElasticSearchConf struct {
|
||||
Hosts []string
|
||||
DailyIndexPrefix string
|
||||
TimeZone string `json:",optional"`
|
||||
MaxChunkBytes int `json:",default=1048576"`
|
||||
Compress bool `json:",default=false"`
|
||||
}
|
||||
|
||||
Filter struct {
|
||||
Action string `json:",options=drop|remove_field"`
|
||||
Conditions []Condition `json:",optional"`
|
||||
Fields []string `json:",optional"`
|
||||
}
|
||||
|
||||
Config struct {
|
||||
Input struct {
|
||||
Kafka kq.KqConf
|
||||
}
|
||||
Filters []Filter
|
||||
Output struct {
|
||||
ElasticSearch ElasticSearchConf
|
||||
}
|
||||
GracePeriod time.Duration `json:",default=10s"`
|
||||
}
|
||||
)
|
@ -1,82 +0,0 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"zero/core/fx"
|
||||
"zero/core/logx"
|
||||
"zero/core/syncx"
|
||||
|
||||
"github.com/olivere/elastic"
|
||||
)
|
||||
|
||||
const sharedCallsKey = "ensureIndex"
|
||||
|
||||
type (
|
||||
IndexFormat func(time.Time) string
|
||||
IndexFunc func() string
|
||||
|
||||
Index struct {
|
||||
client *elastic.Client
|
||||
indexFormat IndexFormat
|
||||
index string
|
||||
lock sync.RWMutex
|
||||
sharedCalls syncx.SharedCalls
|
||||
}
|
||||
)
|
||||
|
||||
func NewIndex(client *elastic.Client, indexFormat IndexFormat) *Index {
|
||||
return &Index{
|
||||
client: client,
|
||||
indexFormat: indexFormat,
|
||||
sharedCalls: syncx.NewSharedCalls(),
|
||||
}
|
||||
}
|
||||
|
||||
func (idx *Index) GetIndex(t time.Time) string {
|
||||
index := idx.indexFormat(t)
|
||||
if err := idx.ensureIndex(index); err != nil {
|
||||
logx.Error(err)
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func (idx *Index) ensureIndex(index string) error {
|
||||
idx.lock.RLock()
|
||||
if index == idx.index {
|
||||
idx.lock.RUnlock()
|
||||
return nil
|
||||
}
|
||||
idx.lock.RUnlock()
|
||||
|
||||
_, err := idx.sharedCalls.Do(sharedCallsKey, func() (i interface{}, err error) {
|
||||
idx.lock.Lock()
|
||||
defer idx.lock.Unlock()
|
||||
|
||||
existsService := elastic.NewIndicesExistsService(idx.client)
|
||||
existsService.Index([]string{index})
|
||||
exist, err := existsService.Do(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
idx.index = index
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
createService := idx.client.CreateIndex(index)
|
||||
if err := fx.DoWithRetries(func() error {
|
||||
// is it necessary to check the result?
|
||||
_, err := createService.Do(context.Background())
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idx.index = index
|
||||
return nil, nil
|
||||
})
|
||||
return err
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"zero/core/executors"
|
||||
"zero/core/logx"
|
||||
"zero/stash/config"
|
||||
|
||||
"github.com/olivere/elastic"
|
||||
)
|
||||
|
||||
const docType = "doc"
|
||||
|
||||
type (
|
||||
Writer struct {
|
||||
client *elastic.Client
|
||||
indexer *Index
|
||||
inserter *executors.ChunkExecutor
|
||||
}
|
||||
|
||||
valueWithTime struct {
|
||||
t time.Time
|
||||
val string
|
||||
}
|
||||
)
|
||||
|
||||
func NewWriter(c config.ElasticSearchConf, indexer *Index) (*Writer, error) {
|
||||
client, err := elastic.NewClient(
|
||||
elastic.SetSniff(false),
|
||||
elastic.SetURL(c.Hosts...),
|
||||
elastic.SetGzip(c.Compress),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writer := Writer{
|
||||
client: client,
|
||||
indexer: indexer,
|
||||
}
|
||||
writer.inserter = executors.NewChunkExecutor(writer.execute, executors.WithChunkBytes(c.MaxChunkBytes))
|
||||
return &writer, nil
|
||||
}
|
||||
|
||||
func (w *Writer) Write(t time.Time, val string) error {
|
||||
return w.inserter.Add(valueWithTime{
|
||||
t: t,
|
||||
val: val,
|
||||
}, len(val))
|
||||
}
|
||||
|
||||
func (w *Writer) execute(vals []interface{}) {
|
||||
var bulk = w.client.Bulk()
|
||||
for _, val := range vals {
|
||||
pair := val.(valueWithTime)
|
||||
req := elastic.NewBulkIndexRequest().Index(w.indexer.GetIndex(pair.t)).Type(docType).Doc(pair.val)
|
||||
bulk.Add(req)
|
||||
}
|
||||
_, err := bulk.Do(context.Background())
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
{
|
||||
"Input": {
|
||||
"Kafka": {
|
||||
"Name": "easystash",
|
||||
"Brokers": [
|
||||
"172.16.186.156:19092",
|
||||
"172.16.186.157:19092",
|
||||
"172.16.186.158:19092",
|
||||
"172.16.186.159:19092",
|
||||
"172.16.186.160:19092",
|
||||
"172.16.186.161:19092"
|
||||
],
|
||||
"Topic": "k8slog",
|
||||
"Group": "pro",
|
||||
"NumProducers": 16,
|
||||
"MetricsUrl": "http://localhost:2222/add"
|
||||
}
|
||||
},
|
||||
"Filters": [
|
||||
{
|
||||
"Action": "drop",
|
||||
"Conditions": [
|
||||
{
|
||||
"Key": "k8s_container_name",
|
||||
"Value": "-rpc",
|
||||
"Type": "contains"
|
||||
},
|
||||
{
|
||||
"Key": "level",
|
||||
"Value": "info",
|
||||
"Type": "match",
|
||||
"Op": "and"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Action": "remove_field",
|
||||
"Fields": [
|
||||
"message",
|
||||
"_source",
|
||||
"_type",
|
||||
"_score",
|
||||
"_id",
|
||||
"@version",
|
||||
"topic",
|
||||
"index",
|
||||
"beat",
|
||||
"docker_container",
|
||||
"offset",
|
||||
"prospector",
|
||||
"source",
|
||||
"stream"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Output": {
|
||||
"ElasticSearch": {
|
||||
"Hosts": [
|
||||
"172.16.141.14:9200",
|
||||
"172.16.141.15:9200",
|
||||
"172.16.141.16:9200",
|
||||
"172.16.141.17:9200",
|
||||
"172.16.140.195:9200",
|
||||
"172.16.140.196:9200",
|
||||
"172.16.140.197:9200",
|
||||
"172.16.140.198:9200",
|
||||
"172.16.140.199:9200",
|
||||
"172.16.140.200:9200",
|
||||
"172.16.140.201:9200",
|
||||
"172.16.140.202:9200"
|
||||
],
|
||||
"DailyIndexPrefix": "k8s_pro-"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"zero/stash/config"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
filterDrop = "drop"
|
||||
filterRemoveFields = "remove_field"
|
||||
opAnd = "and"
|
||||
opOr = "or"
|
||||
typeContains = "contains"
|
||||
typeMatch = "match"
|
||||
)
|
||||
|
||||
type FilterFunc func(map[string]interface{}) map[string]interface{}
|
||||
|
||||
func CreateFilters(c config.Config) []FilterFunc {
|
||||
var filters []FilterFunc
|
||||
|
||||
for _, f := range c.Filters {
|
||||
switch f.Action {
|
||||
case filterDrop:
|
||||
filters = append(filters, DropFilter(f.Conditions))
|
||||
case filterRemoveFields:
|
||||
filters = append(filters, RemoveFieldFilter(f.Fields))
|
||||
}
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
func DropFilter(conds []config.Condition) FilterFunc {
|
||||
return func(m map[string]interface{}) map[string]interface{} {
|
||||
var qualify bool
|
||||
for _, cond := range conds {
|
||||
var qualifyOnce bool
|
||||
switch cond.Type {
|
||||
case typeMatch:
|
||||
qualifyOnce = cond.Value == m[cond.Key]
|
||||
case typeContains:
|
||||
if val, ok := m[cond.Key].(string); ok {
|
||||
qualifyOnce = strings.Contains(val, cond.Value)
|
||||
}
|
||||
}
|
||||
|
||||
switch cond.Op {
|
||||
case opAnd:
|
||||
if !qualifyOnce {
|
||||
return m
|
||||
} else {
|
||||
qualify = true
|
||||
}
|
||||
case opOr:
|
||||
if qualifyOnce {
|
||||
qualify = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if qualify {
|
||||
return nil
|
||||
} else {
|
||||
return m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveFieldFilter(fields []string) FilterFunc {
|
||||
return func(m map[string]interface{}) map[string]interface{} {
|
||||
for _, field := range fields {
|
||||
delete(m, field)
|
||||
}
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
func AddUriFieldFilter(inField, outFirld string) FilterFunc {
|
||||
return func(m map[string]interface{}) map[string]interface{} {
|
||||
if val, ok := m[inField].(string); ok {
|
||||
var datas []string
|
||||
idx := strings.Index(val, "?")
|
||||
if idx < 0 {
|
||||
datas = strings.Split(val, "/")
|
||||
} else {
|
||||
datas = strings.Split(val[:idx], "/")
|
||||
}
|
||||
|
||||
for i, data := range datas {
|
||||
if bson.IsObjectIdHex(data) {
|
||||
datas[i] = "*"
|
||||
}
|
||||
}
|
||||
|
||||
m[outFirld] = strings.Join(datas, "/")
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"zero/stash/es"
|
||||
"zero/stash/filter"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
const (
|
||||
timestampFormat = "2006-01-02T15:04:05.000Z"
|
||||
timestampKey = "@timestamp"
|
||||
)
|
||||
|
||||
type MessageHandler struct {
|
||||
writer *es.Writer
|
||||
filters []filter.FilterFunc
|
||||
}
|
||||
|
||||
func NewHandler(writer *es.Writer) *MessageHandler {
|
||||
return &MessageHandler{
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func (mh *MessageHandler) AddFilters(filters ...filter.FilterFunc) {
|
||||
for _, f := range filters {
|
||||
mh.filters = append(mh.filters, f)
|
||||
}
|
||||
}
|
||||
|
||||
func (mh *MessageHandler) Consume(_, val string) error {
|
||||
m := make(map[string]interface{})
|
||||
if err := jsoniter.Unmarshal([]byte(val), &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, proc := range mh.filters {
|
||||
if m = proc(m); m == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
bs, err := jsoniter.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mh.writer.Write(mh.getTime(m), string(bs))
|
||||
}
|
||||
|
||||
func (mh *MessageHandler) getTime(m map[string]interface{}) time.Time {
|
||||
if ti, ok := m[timestampKey]; ok {
|
||||
if ts, ok := ti.(string); ok {
|
||||
if t, err := time.Parse(timestampFormat, ts); err == nil {
|
||||
return t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return time.Now()
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"time"
|
||||
|
||||
"zero/core/conf"
|
||||
"zero/core/lang"
|
||||
"zero/core/proc"
|
||||
"zero/kq"
|
||||
"zero/stash/config"
|
||||
"zero/stash/es"
|
||||
"zero/stash/filter"
|
||||
"zero/stash/handler"
|
||||
|
||||
"github.com/olivere/elastic"
|
||||
)
|
||||
|
||||
const dateFormat = "2006.01.02"
|
||||
|
||||
var configFile = flag.String("f", "etc/config.json", "Specify the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
proc.SetTimeoutToForceQuit(c.GracePeriod)
|
||||
|
||||
client, err := elastic.NewClient(
|
||||
elastic.SetSniff(false),
|
||||
elastic.SetURL(c.Output.ElasticSearch.Hosts...),
|
||||
)
|
||||
lang.Must(err)
|
||||
|
||||
indexFormat := c.Output.ElasticSearch.DailyIndexPrefix + dateFormat
|
||||
var loc *time.Location
|
||||
if len(c.Output.ElasticSearch.TimeZone) > 0 {
|
||||
loc, err = time.LoadLocation(c.Output.ElasticSearch.TimeZone)
|
||||
lang.Must(err)
|
||||
} else {
|
||||
loc = time.Local
|
||||
}
|
||||
indexer := es.NewIndex(client, func(t time.Time) string {
|
||||
return t.In(loc).Format(indexFormat)
|
||||
})
|
||||
|
||||
filters := filter.CreateFilters(c)
|
||||
writer, err := es.NewWriter(c.Output.ElasticSearch, indexer)
|
||||
lang.Must(err)
|
||||
|
||||
handle := handler.NewHandler(writer)
|
||||
handle.AddFilters(filters...)
|
||||
handle.AddFilters(filter.AddUriFieldFilter("url", "uri"))
|
||||
q := kq.MustNewQueue(c.Input.Kafka, handle)
|
||||
q.Start()
|
||||
}
|
Loading…
Reference in New Issue