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 }