github.com/15mga/kiwi@v0.0.2-0.20240324021231-b95d5c3ac751/log/mgo.go (about)

     1  package log
     2  
     3  import (
     4  	"context"
     5  	"github.com/15mga/kiwi"
     6  	"github.com/15mga/kiwi/util"
     7  	"github.com/15mga/kiwi/worker"
     8  	"go.mongodb.org/mongo-driver/bson"
     9  	"go.mongodb.org/mongo-driver/mongo"
    10  	"go.mongodb.org/mongo-driver/mongo/options"
    11  	"go.mongodb.org/mongo-driver/mongo/readpref"
    12  	"os"
    13  	"time"
    14  )
    15  
    16  const (
    17  	cmdLog   = "log"
    18  	cmdTrace = "trace"
    19  	cmdSpan  = "span"
    20  	cmdFlush = "flush"
    21  	cmdOver  = "over"
    22  )
    23  
    24  type (
    25  	mgoOption struct {
    26  		logLvl, traceLvl kiwi.TLevel
    27  		timeLayout       string
    28  		db               string
    29  		ttl              int32
    30  		dbOpts           *options.DatabaseOptions
    31  		clientOpts       *options.ClientOptions
    32  		logOpt           *options.CreateCollectionOptions
    33  		logIdx           []mongo.IndexModel
    34  		spanOpt          *options.CreateCollectionOptions
    35  		spanIdx          []mongo.IndexModel
    36  		traceOpt         *options.CreateCollectionOptions
    37  		traceIdx         []mongo.IndexModel
    38  	}
    39  	MgoOption func(opt *mgoOption)
    40  )
    41  
    42  func MgoLogLvl(levels ...string) MgoOption {
    43  	return func(opt *mgoOption) {
    44  		opt.logLvl = kiwi.StrLvlToMask(levels...)
    45  	}
    46  }
    47  
    48  func MgoTraceLvl(levels ...string) MgoOption {
    49  	return func(opt *mgoOption) {
    50  		opt.traceLvl = kiwi.StrLvlToMask(levels...)
    51  	}
    52  }
    53  
    54  func MgoTimeLayout(layout string) MgoOption {
    55  	return func(opt *mgoOption) {
    56  		opt.timeLayout = layout
    57  	}
    58  }
    59  
    60  func MgoDb(db string) MgoOption {
    61  	return func(opt *mgoOption) {
    62  		opt.db = db
    63  	}
    64  }
    65  
    66  func MgoTtl(ttl int32) MgoOption {
    67  	return func(opt *mgoOption) {
    68  		opt.ttl = ttl
    69  	}
    70  }
    71  
    72  func MgoClientOptions(opts *options.ClientOptions) MgoOption {
    73  	return func(opt *mgoOption) {
    74  		opt.clientOpts = opts
    75  	}
    76  }
    77  
    78  func MgoDbOptions(opts *options.DatabaseOptions) MgoOption {
    79  	return func(opt *mgoOption) {
    80  		opt.dbOpts = opts
    81  	}
    82  }
    83  
    84  func MgoLogOpt(option *options.CreateCollectionOptions) MgoOption {
    85  	return func(opt *mgoOption) {
    86  		opt.logOpt = option
    87  	}
    88  }
    89  
    90  func MgoLogIdx(index ...mongo.IndexModel) MgoOption {
    91  	return func(opt *mgoOption) {
    92  		opt.logIdx = index
    93  	}
    94  }
    95  
    96  func MgoSpanOpt(option *options.CreateCollectionOptions) MgoOption {
    97  	return func(opt *mgoOption) {
    98  		opt.spanOpt = option
    99  	}
   100  }
   101  
   102  func MgoSpanIdx(index ...mongo.IndexModel) MgoOption {
   103  	return func(opt *mgoOption) {
   104  		opt.spanIdx = index
   105  	}
   106  }
   107  
   108  func MgoTraceOpt(option *options.CreateCollectionOptions) MgoOption {
   109  	return func(opt *mgoOption) {
   110  		opt.traceOpt = option
   111  	}
   112  }
   113  
   114  func MgoTraceIdx(index ...mongo.IndexModel) MgoOption {
   115  	return func(opt *mgoOption) {
   116  		opt.traceIdx = index
   117  	}
   118  }
   119  
   120  func NewMgo(opts ...MgoOption) *mgoLogger {
   121  	opt := &mgoOption{
   122  		logLvl:     kiwi.LvlToMask(kiwi.TestLevels...),
   123  		traceLvl:   kiwi.LvlToMask(kiwi.TestLevels...),
   124  		timeLayout: kiwi.DefTimeFormatter,
   125  		db:         "log",
   126  		ttl:        3600 * 24 * 7,
   127  	}
   128  	for _, o := range opts {
   129  		o(opt)
   130  	}
   131  	if opt.clientOpts == nil {
   132  		opt.clientOpts = options.Client().ApplyURI("mongodb://localhost:27017")
   133  	}
   134  	l := &mgoLogger{
   135  		option: opt,
   136  	}
   137  	err := l.conn()
   138  	if err != nil {
   139  		panic(err.Error())
   140  	}
   141  
   142  	names, e := l.db.ListCollectionNames(context.TODO(), bson.D{})
   143  	if err != nil {
   144  		panic(e.Error())
   145  	}
   146  	nameMap := make(map[string]struct{})
   147  	for _, name := range names {
   148  		nameMap[name] = struct{}{}
   149  	}
   150  
   151  	if _, ok := nameMap[cmdLog]; !ok {
   152  		e = l.db.CreateCollection(context.TODO(), cmdLog, l.option.logOpt)
   153  		if e != nil {
   154  			panic(e.Error())
   155  		}
   156  	}
   157  	logColl := l.db.Collection(cmdLog)
   158  	_, _ = logColl.Indexes().CreateMany(context.TODO(),
   159  		append(l.option.logIdx,
   160  			mongo.IndexModel{
   161  				Keys:    bson.D{{"ts", -1}},
   162  				Options: options.Index().SetExpireAfterSeconds(opt.ttl),
   163  			},
   164  			mongo.IndexModel{
   165  				Keys: bson.D{{"lvl", 1}},
   166  			}))
   167  	l.logBuffer = newMgoBuffer(16, logColl)
   168  
   169  	if _, ok := nameMap[cmdLog]; !ok {
   170  		e = l.db.CreateCollection(context.TODO(), cmdTrace, l.option.traceOpt)
   171  		if e != nil {
   172  			panic(e.Error())
   173  		}
   174  	}
   175  	traceColl := l.db.Collection(cmdTrace)
   176  	_, _ = traceColl.Indexes().CreateMany(context.TODO(),
   177  		append(l.option.spanIdx,
   178  			mongo.IndexModel{
   179  				Keys:    bson.D{{"ts", -1}},
   180  				Options: options.Index().SetExpireAfterSeconds(opt.ttl),
   181  			},
   182  			mongo.IndexModel{
   183  				Keys: bson.D{{"pid", -1}},
   184  			},
   185  			mongo.IndexModel{
   186  				Keys: bson.D{{"tid", -1}},
   187  			},
   188  			mongo.IndexModel{
   189  				Keys: bson.D{{"msg", 1}},
   190  			}))
   191  	l.traceBuffer = newMgoBuffer(32, traceColl)
   192  
   193  	if _, ok := nameMap[cmdSpan]; !ok {
   194  		e = l.db.CreateCollection(context.TODO(), cmdSpan, l.option.spanOpt)
   195  		if e != nil {
   196  			panic(e.Error())
   197  		}
   198  	}
   199  	spanColl := l.db.Collection(cmdSpan)
   200  	_, _ = spanColl.Indexes().CreateMany(context.TODO(),
   201  		append(l.option.traceIdx,
   202  			mongo.IndexModel{
   203  				Keys:    bson.D{{"ts", -1}},
   204  				Options: options.Index().SetExpireAfterSeconds(opt.ttl),
   205  			},
   206  			mongo.IndexModel{
   207  				Keys: bson.D{{"tid", -1}},
   208  			},
   209  			mongo.IndexModel{
   210  				Keys: bson.D{{"lvl", 1}},
   211  			},
   212  			mongo.IndexModel{
   213  				Keys: bson.D{{"msg", 1}},
   214  			}))
   215  	l.spanBuffer = newMgoBuffer(128, spanColl)
   216  	l.worker = worker.NewJobWorker(l.process)
   217  	l.worker.Start()
   218  	clearCh := make(chan chan struct{}, 1)
   219  	kiwi.BeforeExitFn("mgo log", func() {
   220  		overCh := make(chan struct{}, 1)
   221  		go func() {
   222  			time.Sleep(time.Millisecond * 100)
   223  			clearCh <- overCh
   224  		}()
   225  		<-overCh
   226  	})
   227  	go func() {
   228  		ticker := time.NewTicker(time.Second * 5)
   229  		for {
   230  			select {
   231  			case <-ticker.C:
   232  				l.worker.Push(cmdFlush)
   233  			case ch := <-clearCh:
   234  				l.worker.Push(cmdOver, ch)
   235  				return
   236  			}
   237  		}
   238  	}()
   239  	return l
   240  }
   241  
   242  type mgoLogger struct {
   243  	option      *mgoOption
   244  	client      *mongo.Client
   245  	db          *mongo.Database
   246  	worker      *worker.JobWorker
   247  	logBuffer   *mgoBuffer
   248  	traceBuffer *mgoBuffer
   249  	spanBuffer  *mgoBuffer
   250  }
   251  
   252  func (l *mgoLogger) conn() *util.Err {
   253  	client, e := mongo.Connect(context.TODO(), l.option.clientOpts)
   254  	if e != nil {
   255  		return util.WrapErr(util.EcConnectErr, e)
   256  	}
   257  
   258  	e = client.Ping(context.TODO(), readpref.Primary())
   259  	if e != nil {
   260  		return util.WrapErr(util.EcConnectErr, e)
   261  	}
   262  	l.client = client
   263  	l.db = client.Database(l.option.db, l.option.dbOpts)
   264  	return nil
   265  }
   266  
   267  func (l *mgoLogger) Log(level kiwi.TLevel, msg, caller string, stack []byte, params util.M) {
   268  	if !util.TestMask(level, l.option.logLvl) {
   269  		return
   270  	}
   271  	l.worker.Push(cmdLog, &log{
   272  		Timestamp: time.Now().UnixMilli(),
   273  		Level:     level,
   274  		Message:   msg,
   275  		Stack:     string(stack),
   276  		Caller:    caller,
   277  		Params:    params,
   278  	})
   279  }
   280  
   281  func (l *mgoLogger) Trace(pid, tid int64, caller string, params util.M) {
   282  	l.worker.Push(cmdTrace, &trace{
   283  		Timestamp: time.Now().UnixMilli(),
   284  		Pid:       pid,
   285  		Tid:       tid,
   286  		Caller:    caller,
   287  		Params:    params,
   288  	})
   289  }
   290  
   291  func (l *mgoLogger) Span(level kiwi.TLevel, tid int64, msg, caller string, stack []byte, params util.M) {
   292  	l.worker.Push(cmdSpan, &span{
   293  		Timestamp: time.Now().UnixMilli(),
   294  		Level:     level,
   295  		Tid:       tid,
   296  		Message:   msg,
   297  		Stack:     string(stack),
   298  		Caller:    caller,
   299  		Params:    params,
   300  	})
   301  }
   302  
   303  func (l *mgoLogger) process(job *worker.Job) {
   304  	switch job.Name {
   305  	case cmdLog:
   306  		l.logBuffer.push(job.Data[0])
   307  	case cmdTrace:
   308  		l.traceBuffer.push(job.Data[0])
   309  	case cmdSpan:
   310  		l.spanBuffer.push(job.Data[0])
   311  	case cmdFlush:
   312  		l.logBuffer.flush()
   313  		l.traceBuffer.flush()
   314  		l.spanBuffer.flush()
   315  	case cmdOver:
   316  		l.logBuffer.flush()
   317  		l.traceBuffer.flush()
   318  		l.spanBuffer.flush()
   319  		job.Data[0].(chan struct{}) <- struct{}{}
   320  	}
   321  }
   322  
   323  func newMgoBuffer(cap int, coll *mongo.Collection) *mgoBuffer {
   324  	b := &mgoBuffer{
   325  		buffer: make([]any, cap),
   326  		idx:    0,
   327  		cap:    cap,
   328  		coll:   coll,
   329  	}
   330  	return b
   331  }
   332  
   333  type mgoBuffer struct {
   334  	buffer []any
   335  	idx    int
   336  	cap    int
   337  	coll   *mongo.Collection
   338  }
   339  
   340  func (b *mgoBuffer) push(m any) {
   341  	b.buffer[b.idx] = m
   342  	b.idx++
   343  	if b.idx < b.cap {
   344  		return
   345  	}
   346  	b.flush()
   347  }
   348  
   349  func (b *mgoBuffer) flush() {
   350  	if b.idx == 0 {
   351  		return
   352  	}
   353  	_, err := b.coll.InsertMany(context.TODO(), b.buffer[:b.idx])
   354  	if err != nil {
   355  		_, _ = os.Stderr.WriteString(err.Error() + "\n")
   356  	}
   357  	b.idx = 0
   358  }
   359  
   360  type log struct {
   361  	Timestamp int64       `bson:"ts"`
   362  	Level     kiwi.TLevel `bson:"lvl"`
   363  	Message   string      `bson:"msg"`
   364  	Stack     string      `bson:"stk"`
   365  	Caller    string      `bson:"cl"`
   366  	Params    util.M      `bson:"p"`
   367  }
   368  
   369  type trace struct {
   370  	Timestamp int64  `bson:"ts"`
   371  	Pid       int64  `bson:"pid"`
   372  	Tid       int64  `bson:"tid"`
   373  	Caller    string `bson:"cl"`
   374  	Params    util.M `bson:"p"`
   375  }
   376  
   377  type span struct {
   378  	Timestamp int64       `bson:"ts"`
   379  	Level     kiwi.TLevel `bson:"lvl"`
   380  	Tid       int64       `bson:"tid"`
   381  	Message   string      `bson:"msg"`
   382  	Stack     string      `bson:"stk"`
   383  	Caller    string      `bson:"cl"`
   384  	Params    util.M      `bson:"p"`
   385  }