github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/conf/log/log.go (about) 1 package log 2 3 import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "os" 8 "runtime" 9 "strconv" 10 "sync" 11 "time" 12 13 "github.com/sirupsen/logrus" 14 "go.opentelemetry.io/otel/sdk/trace" 15 16 "github.com/machinefi/w3bstream/pkg/depends/base/consts" 17 "github.com/machinefi/w3bstream/pkg/depends/kit/metax" 18 ) 19 20 type Log struct { 21 Name string 22 Level Level `env:""` 23 Output LoggerOutputType `env:""` 24 Format LoggerFormatType 25 CKEndpoint string `env:""` 26 Exporter trace.SpanExporter `env:"-"` 27 ReportCaller bool 28 } 29 30 func (l *Log) SetDefault() { 31 if l.Level == 0 { 32 l.Level = DebugLevel 33 } 34 if l.Output == 0 { 35 l.Output = LOGGER_OUTPUT_TYPE__ALWAYS 36 } 37 if l.Format == 0 { 38 l.Format = LOGGER_FORMAT_TYPE__JSON 39 } 40 if l.Name == "" { 41 l.Name = "unknown" 42 if v := os.Getenv(consts.EnvProjectName); v != "" { 43 l.Name = v 44 } 45 } 46 } 47 48 func (l *Log) InitLogrus() { 49 if l.Format == LOGGER_FORMAT_TYPE__JSON { 50 logrus.SetFormatter(JsonFormatter) 51 } else { 52 logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) 53 } 54 55 logrus.SetLevel(l.Level.LogrusLogLevel()) 56 logrus.SetReportCaller(l.ReportCaller) 57 // TODO add hook with goid meta logrus.AddHook(goid.Default) 58 logrus.AddHook(&ProjectAndMetaHook{l.Name}) 59 60 if l.CKEndpoint != "" { 61 if ckHook, err := newClickhouseHook(l.CKEndpoint); err != nil { 62 logrus.Errorf("new ck hook error: %s", err) 63 } else { 64 logrus.AddHook(ckHook) 65 } 66 } 67 logrus.SetOutput(os.Stdout) 68 } 69 70 func (l *Log) InitSpanLog() { 71 if l.Exporter == nil { 72 return 73 } 74 if err := InstallPipeline(l.Output, l.Format, l.Exporter); err != nil { 75 panic(err) 76 } 77 } 78 79 func (l *Log) Init() { 80 l.InitLogrus() 81 l.InitSpanLog() 82 } 83 84 var ckBatchCount = 1000 85 86 type ClickhouseHook struct { 87 CKEndpoint string 88 ckDB *sql.DB 89 insertSql string 90 entries []logrus.Entry 91 lastInsertTime time.Time 92 duration time.Duration 93 lock *sync.Mutex 94 } 95 96 func newClickhouseHook(endpoint string) (*ClickhouseHook, error) { 97 clickhouseHook := &ClickhouseHook{CKEndpoint: endpoint, entries: make([]logrus.Entry, 0, ckBatchCount*2)} 98 clickhouseHook.insertSql = `INSERT INTO w3b.server_logs (Timestamp, ProjectName, Msg) VALUES (?, ?, ?)` 99 clickhouseHook.lastInsertTime = time.Now() 100 clickhouseHook.duration = time.Second 101 clickhouseHook.lock = &sync.Mutex{} 102 db, err := clickhouseHook.connection() 103 if err != nil { 104 return nil, err 105 } 106 clickhouseHook.ckDB = db 107 return clickhouseHook, nil 108 } 109 110 func (ck *ClickhouseHook) Fire(entry *logrus.Entry) error { 111 if !ck.ping() { 112 conn, err := ck.connection() 113 if err != nil { 114 logrus.Errorf("ck connection error: %s", err) 115 return err 116 } 117 ck.ckDB = conn 118 } 119 120 ck.lock.Lock() 121 defer ck.lock.Unlock() 122 ck.entries = append(ck.entries, *entry) 123 if len(ck.entries) >= ckBatchCount || time.Since(ck.lastInsertTime) > ck.duration { 124 ck.insertLogs(ck.entries) 125 } 126 return nil 127 } 128 129 func (ck *ClickhouseHook) Levels() []logrus.Level { 130 return logrus.AllLevels 131 } 132 133 func (ck *ClickhouseHook) connection() (*sql.DB, error) { 134 conn, err := sql.Open("clickhouse", ck.CKEndpoint) 135 if err != nil { 136 return nil, err 137 } 138 err = conn.Ping() 139 if err != nil { 140 return nil, err 141 } 142 return conn, nil 143 } 144 145 func (ck *ClickhouseHook) ping() bool { 146 if err := ck.ckDB.Ping(); err != nil { 147 logrus.Errorf("ck ping error: %s", err) 148 return false 149 } 150 return true 151 } 152 153 func (ck *ClickhouseHook) insertLogs(entries []logrus.Entry) error { 154 err := ck.doWithTx(func(tx *sql.Tx) error { 155 statement, err := tx.PrepareContext(context.Background(), ck.insertSql) 156 if err != nil { 157 return fmt.Errorf(" ck prepareContext error: %w", err) 158 } 159 defer func() { 160 if err := statement.Close(); err != nil { 161 logrus.Errorf("ck statement close error: %s", err) 162 } 163 }() 164 for _, item := range entries { 165 msg, _ := item.Bytes() 166 if _, err := statement.ExecContext(context.Background(), item.Time, item.Data["@prj"], msg); err != nil { 167 return fmt.Errorf("ck execContext error:%w", err) 168 } 169 } 170 return nil 171 }) 172 if err == nil { 173 ck.entries = ck.entries[:0] 174 } 175 return err 176 } 177 178 func (ck *ClickhouseHook) doWithTx(fn func(tx *sql.Tx) error) error { 179 tx, err := ck.ckDB.Begin() 180 if err != nil { 181 return fmt.Errorf("ck tx begin error: %w", err) 182 } 183 defer func() { 184 tx.Rollback() 185 }() 186 if err := fn(tx); err != nil { 187 return err 188 } 189 return tx.Commit() 190 } 191 192 type ProjectAndMetaHook struct { 193 Name string 194 } 195 196 func (h *ProjectAndMetaHook) Fire(entry *logrus.Entry) error { 197 ctx := entry.Context 198 if ctx == nil { 199 ctx = context.Background() 200 } 201 meta := metax.GetMetaFrom(ctx) 202 if entry.Data["@prj"] == nil { 203 entry.Data["@prj"] = h.Name 204 } 205 for k, v := range meta { 206 entry.Data["meta."+k] = v 207 } 208 return nil 209 } 210 211 func (h *ProjectAndMetaHook) Levels() []logrus.Level { return logrus.AllLevels } 212 213 var ( 214 project = "unknown" 215 JsonFormatter = &logrus.JSONFormatter{ 216 FieldMap: logrus.FieldMap{ 217 logrus.FieldKeyLevel: "@lv", 218 logrus.FieldKeyTime: "@ts", 219 logrus.FieldKeyFunc: "@fn", 220 logrus.FieldKeyFile: "@fl", 221 }, 222 CallerPrettyfier: func(f *runtime.Frame) (fn string, file string) { 223 return f.Function + " line:" + strconv.FormatInt(int64(f.Line), 10), "" 224 }, 225 TimestampFormat: "20060102-150405.000Z07:00", 226 } 227 ) 228 229 func init() { 230 if v := os.Getenv(consts.EnvProjectName); v != "" { 231 project = v 232 if version := os.Getenv(consts.EnvProjectVersion); version != "" { 233 project = project + "@" + version 234 } 235 } 236 logrus.SetFormatter(JsonFormatter) 237 logrus.SetReportCaller(true) 238 }