feat: 新增 survey 包,包含了运营日志的基本功能实现
This commit is contained in:
parent
9740dfd46e
commit
e962009eff
|
|
@ -0,0 +1,68 @@
|
|||
package survey
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/kercylan98/minotaur/utils/super"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// All 处理特定记录器特定日期的所有记录,当 handle 返回 false 时停止处理
|
||||
func All(name string, t time.Time, handle func(record map[string]any) bool) {
|
||||
logger := survey[name]
|
||||
if logger == nil {
|
||||
return
|
||||
}
|
||||
fp := logger.filePath(t.Format(logger.layout))
|
||||
logger.wl.Lock()
|
||||
defer logger.wl.Unlock()
|
||||
|
||||
f, err := os.Open(fp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
reader := bufio.NewReader(f)
|
||||
var m = make(map[string]any)
|
||||
for {
|
||||
line, _, err := reader.ReadLine()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = super.UnmarshalJSON(line[logger.dataLayoutLen:], &m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !handle(m) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sum 处理特定记录器特定日期的所有记录,根据指定的字段进行汇总
|
||||
func Sum(name string, t time.Time, field string) float64 {
|
||||
var res float64
|
||||
All(name, t, func(record map[string]any) bool {
|
||||
v, exist := record[field]
|
||||
if !exist {
|
||||
return true
|
||||
}
|
||||
switch value := v.(type) {
|
||||
case float64:
|
||||
res += value
|
||||
case int:
|
||||
res += float64(value)
|
||||
case int64:
|
||||
res += float64(value)
|
||||
case string:
|
||||
res += super.StringToFloat64(value)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package survey
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
DATETIME_FORMAT = "2006-01-02 15:04:05"
|
||||
DATE_FORMAT = "2006-01-02"
|
||||
dateLen = len(DATE_FORMAT)
|
||||
datetimeLen = len(DATETIME_FORMAT)
|
||||
)
|
||||
|
||||
// logger 用于埋点数据的运营日志记录器
|
||||
type logger struct {
|
||||
bl sync.Mutex // writer lock
|
||||
wl sync.Mutex // flush lock
|
||||
dir string
|
||||
fn string
|
||||
fe string
|
||||
bs []string
|
||||
layout string
|
||||
layoutLen int
|
||||
dataLayout string
|
||||
dataLayoutLen int
|
||||
}
|
||||
|
||||
// flush 将记录器缓冲区的数据写入到文件
|
||||
func (slf *logger) flush() {
|
||||
slf.bl.Lock()
|
||||
count := len(slf.bs)
|
||||
if count == 0 {
|
||||
slf.bl.Unlock()
|
||||
return
|
||||
}
|
||||
ds := slf.bs[:]
|
||||
slf.bs = slf.bs[count:]
|
||||
slf.bl.Unlock()
|
||||
|
||||
slf.wl.Lock()
|
||||
defer slf.wl.Unlock()
|
||||
|
||||
var (
|
||||
file *os.File
|
||||
writer *bufio.Writer
|
||||
err error
|
||||
last string
|
||||
)
|
||||
for _, data := range ds {
|
||||
tick := data[0:slf.layoutLen]
|
||||
if tick != last {
|
||||
if file != nil {
|
||||
_ = writer.Flush()
|
||||
_ = file.Close()
|
||||
}
|
||||
fp := slf.filePath(tick)
|
||||
file, err = os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatal("Survey", log.String("Action", "DateSwitch"), log.String("FilePath", fp), log.Err(err))
|
||||
return
|
||||
}
|
||||
writer = bufio.NewWriterSize(file, 1024*10240)
|
||||
last = tick
|
||||
}
|
||||
_, _ = writer.WriteString(data)
|
||||
}
|
||||
_ = writer.Flush()
|
||||
_ = file.Close()
|
||||
}
|
||||
|
||||
// writer 写入数据到记录器缓冲区
|
||||
func (slf *logger) writer(d string) {
|
||||
slf.bl.Lock()
|
||||
slf.bs = append(slf.bs, d)
|
||||
slf.bl.Unlock()
|
||||
}
|
||||
|
||||
// filePath 获取文件路径
|
||||
func (slf *logger) filePath(t string) string {
|
||||
return filepath.Join(slf.dir, fmt.Sprintf("%s.%s%s", slf.fn, t, slf.fe))
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package survey
|
||||
|
||||
// Option 选项
|
||||
type Option func(logger *logger)
|
||||
|
||||
// WithLayout 设置日志文件名的时间戳格式
|
||||
// - 默认为 time.DateOnly
|
||||
func WithLayout(layout string) Option {
|
||||
return func(logger *logger) {
|
||||
logger.layout = layout
|
||||
logger.layoutLen = len(layout)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package survey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/log"
|
||||
"github.com/kercylan98/minotaur/utils/super"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
survey = make(map[string]*logger)
|
||||
)
|
||||
|
||||
// RegSurvey 注册一个运营日志记录器
|
||||
func RegSurvey(name, filePath string, options ...Option) {
|
||||
fn := filepath.Base(filePath)
|
||||
ext := filepath.Ext(fn)
|
||||
fn = strings.TrimSuffix(fn, ext)
|
||||
_, exist := survey[name]
|
||||
if exist {
|
||||
panic(fmt.Errorf("survey %s already exist", name))
|
||||
}
|
||||
dir := filepath.Dir(filePath)
|
||||
logger := &logger{
|
||||
dir: dir,
|
||||
fn: fn,
|
||||
fe: ext,
|
||||
layout: time.DateOnly,
|
||||
layoutLen: len(time.DateOnly),
|
||||
dataLayout: time.DateTime,
|
||||
dataLayoutLen: len(time.DateTime) + 3,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(logger)
|
||||
}
|
||||
survey[name] = logger
|
||||
log.Info("Survey", log.String("Action", "RegSurvey"), log.String("Name", name), log.String("FilePath", dir+"/"+fn+".${DATE}"+ext))
|
||||
}
|
||||
|
||||
// Record 记录一条运营日志
|
||||
func Record(name string, data map[string]any) {
|
||||
logger, exist := survey[name]
|
||||
if !exist {
|
||||
panic(fmt.Errorf("survey %s not exist", name))
|
||||
}
|
||||
logger.writer(fmt.Sprintf("%s - %s\n", time.Now().Format(time.DateTime), super.MarshalJSON(data)))
|
||||
}
|
||||
|
||||
// Flush 将所有运营日志记录器的缓冲区数据写入到文件
|
||||
func Flush() {
|
||||
for _, logger := range survey {
|
||||
logger.flush()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package survey_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/log/survey"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRecord(t *testing.T) {
|
||||
_ = os.MkdirAll("./test", os.ModePerm)
|
||||
survey.RegSurvey("GLOBAL_DATA", "./test/global_data.log")
|
||||
survey.Record("GLOBAL_DATA", map[string]any{
|
||||
"joinTime": time.Now().Unix(),
|
||||
"action": 1,
|
||||
})
|
||||
survey.Flush()
|
||||
|
||||
fmt.Println(survey.Sum("GLOBAL_DATA", time.Now(), "action"))
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
2023-08-22 19:34:15 - {"action":1,"joinTime":1692704055}
|
||||
Loading…
Reference in New Issue