github.com/jxskiss/gopkg@v0.17.3/easy/log.go (about) 1 package easy 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "log" 9 "os" 10 "reflect" 11 "runtime" 12 "sort" 13 "strings" 14 "sync" 15 "unicode/utf8" 16 17 "github.com/jxskiss/gopkg/internal/linkname" 18 "github.com/jxskiss/gopkg/internal/unsafeheader" 19 "github.com/jxskiss/gopkg/json" 20 "github.com/jxskiss/gopkg/reflectx" 21 "github.com/jxskiss/gopkg/strutil" 22 ) 23 24 func ConfigLog(cfg LogCfg) { 25 _logcfg = cfg 26 } 27 28 var _logcfg LogCfg 29 30 type LogCfg struct { 31 EnableDebug func() bool 32 Logger func() ErrDebugLogger 33 CtxLogger func(context.Context) ErrDebugLogger 34 } 35 36 func (p LogCfg) getLogger(ctxp *context.Context) ErrDebugLogger { 37 if p.CtxLogger != nil && ctxp != nil { 38 if lg := p.CtxLogger(*ctxp); lg != nil { 39 return lg 40 } 41 } 42 if p.Logger != nil { 43 if lg := p.Logger(); lg != nil { 44 return lg 45 } 46 } 47 return stdLogger{} 48 } 49 50 var log_std = linkname.LogStd 51 52 type stdLogger struct{} 53 54 const _stdLogDepth = 2 55 56 func (_ stdLogger) Debugf(format string, args ...interface{}) { 57 log_std.Output(_stdLogDepth, fmt.Sprintf("[DEBUG]: "+format, args...)) 58 } 59 60 func (_ stdLogger) Errorf(format string, args ...interface{}) { 61 log_std.Output(_stdLogDepth, fmt.Sprintf("[ERROR]: "+format, args...)) 62 } 63 64 // ErrLogger is an interface which log an message at ERROR level. 65 // It's implemented by *logrus.Logger, *logrus.Entry, *zap.SugaredLogger, 66 // and many other logging packages. 67 type ErrLogger interface { 68 Errorf(format string, args ...interface{}) 69 } 70 71 // DebugLogger is an interface which log an message at DEBUG level. 72 // It's implemented by *logrus.Logger, *logrus.Entry, *zap.SugaredLogger, 73 // and many other logging packages. 74 type DebugLogger interface { 75 Debugf(format string, args ...interface{}) 76 } 77 78 // ErrDebugLogger is an interface which log messages at ERROR and DEBUG level. 79 // It's implemented by *logrus.Logger, *logrus.Entry, *zap.SugaredLogger, 80 // and many other logging packages. 81 type ErrDebugLogger interface { 82 ErrLogger 83 DebugLogger 84 } 85 86 // PrintFunc is a function to print the given arguments in format to somewhere. 87 // It implements the interface `ErrDebugLogger`. 88 type PrintFunc func(format string, args ...interface{}) 89 90 func (f PrintFunc) Errorf(format string, args ...interface{}) { f(format, args...) } 91 92 func (f PrintFunc) Debugf(format string, args ...interface{}) { f(format, args...) } 93 94 // JSON converts given object to a json string, it never returns error. 95 // The marshalling method used here does not escape HTML characters, 96 // and map keys are sorted, which helps human reading. 97 func JSON(v interface{}) string { 98 b, err := json.MarshalNoHTMLEscape(v, "", "") 99 if err != nil { 100 return fmt.Sprintf("<error: %v>", err) 101 } 102 b = bytes.TrimSpace(b) 103 return unsafeheader.BytesToString(b) 104 } 105 106 // Logfmt converts given object to a string in logfmt format, it never 107 // returns error. Note that only struct and map of basic types are 108 // supported, non-basic types are simply ignored. 109 func Logfmt(v interface{}) string { 110 if reflectx.IsNilInterface(v) { 111 return "null" 112 } 113 var src []byte 114 switch v := v.(type) { 115 case []byte: 116 src = v 117 case string: 118 src = unsafeheader.StringToBytes(v) 119 } 120 if src != nil && utf8.Valid(src) { 121 srcstr := string(src) 122 if bytes.IndexFunc(src, needsQuoteValueRune) != -1 { 123 return JSON(srcstr) 124 } 125 return srcstr 126 } 127 128 // simple values 129 val := reflect.Indirect(reflect.ValueOf(v)) 130 if !val.IsValid() { 131 return "null" 132 } 133 if isBasicType(val.Type()) { 134 return fmt.Sprint(val) 135 } 136 if val.Kind() != reflect.Struct && val.Kind() != reflect.Map { 137 return "<error: unsupported logfmt type>" 138 } 139 140 keyValues := make([]interface{}, 0) 141 if val.Kind() == reflect.Map { 142 keys := make([]string, 0, val.Len()) 143 values := make(map[string]interface{}, val.Len()) 144 for iter := val.MapRange(); iter.Next(); { 145 k, v := iter.Key(), reflect.Indirect(iter.Value()) 146 if !isBasicType(k.Type()) || !v.IsValid() { 147 continue 148 } 149 v = reflect.ValueOf(v.Interface()) 150 if !v.IsValid() { 151 continue 152 } 153 kstr := fmt.Sprint(k.Interface()) 154 if isBasicType(v.Type()) { 155 keys = append(keys, kstr) 156 values[kstr] = v.Interface() 157 continue 158 } 159 if bv, ok := v.Interface().([]byte); ok { 160 if len(bv) > 0 && utf8.Valid(bv) { 161 keys = append(keys, kstr) 162 values[kstr] = string(bv) 163 } 164 continue 165 } 166 if v.Kind() == reflect.Slice && isBasicType(v.Elem().Type()) { 167 keys = append(keys, kstr) 168 values[kstr] = JSON(v.Interface()) 169 continue 170 } 171 } 172 sort.Strings(keys) 173 for _, k := range keys { 174 v := values[k] 175 keyValues = append(keyValues, k, v) 176 } 177 } else { // reflect.Struct 178 typ := val.Type() 179 fieldNum := val.NumField() 180 for i := 0; i < fieldNum; i++ { 181 field := typ.Field(i) 182 // ignore unexported fields which we can't take interface 183 if len(field.PkgPath) != 0 { 184 continue 185 } 186 fk := strutil.ToSnakeCase(field.Name) 187 fv := reflect.Indirect(val.Field(i)) 188 if !(fv.IsValid() && fv.CanInterface()) { 189 continue 190 } 191 if isBasicType(fv.Type()) { 192 keyValues = append(keyValues, fk, fv.Interface()) 193 continue 194 } 195 if bv, ok := fv.Interface().([]byte); ok { 196 if len(bv) > 0 && utf8.Valid(bv) { 197 keyValues = append(keyValues, fk, string(bv)) 198 } 199 continue 200 } 201 if fv.Kind() == reflect.Slice && isBasicType(fv.Elem().Type()) { 202 keyValues = append(keyValues, fk, JSON(fv.Interface())) 203 continue 204 } 205 } 206 } 207 if len(keyValues) == 0 { 208 return "" 209 } 210 211 buf := &strings.Builder{} 212 needSpace := false 213 for i := 0; i < len(keyValues); i += 2 { 214 k, v := keyValues[i], keyValues[i+1] 215 if needSpace { 216 buf.WriteByte(' ') 217 } 218 addLogfmtString(buf, k) 219 buf.WriteByte('=') 220 addLogfmtString(buf, v) 221 needSpace = true 222 } 223 return buf.String() 224 } 225 226 func addLogfmtString(buf *strings.Builder, val interface{}) { 227 str, ok := val.(string) 228 if !ok { 229 str = fmt.Sprint(val) 230 } 231 if strings.IndexFunc(str, needsQuoteValueRune) != -1 { 232 str = JSON(str) 233 } 234 buf.WriteString(str) 235 } 236 237 func needsQuoteValueRune(r rune) bool { 238 switch r { 239 case '\\', '"', '=', '\n', '\r', '\t': 240 return true 241 default: 242 return r <= ' ' 243 } 244 } 245 246 // Pretty converts given object to a pretty formatted json string. 247 // If the input is a json string, it will be formatted using json.Indent 248 // with four space characters as indent. 249 func Pretty(v interface{}) string { 250 return prettyIndent(v, " ") 251 } 252 253 // Pretty2 is like Pretty, but it uses two space characters as indent, 254 // instead of four. 255 func Pretty2(v interface{}) string { 256 return prettyIndent(v, " ") 257 } 258 259 func prettyIndent(v interface{}, indent string) string { 260 var src []byte 261 switch v := v.(type) { 262 case []byte: 263 src = v 264 case string: 265 src = unsafeheader.StringToBytes(v) 266 } 267 if src != nil { 268 if json.Valid(src) { 269 buf := bytes.NewBuffer(nil) 270 _ = json.Indent(buf, src, "", indent) 271 return unsafeheader.BytesToString(buf.Bytes()) 272 } 273 if utf8.Valid(src) { 274 return string(src) 275 } 276 return "<pretty: non-printable bytes>" 277 } 278 buf, err := json.MarshalNoHTMLEscape(v, "", indent) 279 if err != nil { 280 return fmt.Sprintf("<error: %v>", err) 281 } 282 buf = bytes.TrimSpace(buf) 283 return unsafeheader.BytesToString(buf) 284 } 285 286 // Caller returns function name, filename, and the line number of the caller. 287 // The argument skip is the number of stack frames to ascend, with 0 288 // identifying the caller of Caller. 289 func Caller(skip int) (name, file string, line int) { 290 pc, file, line, _ := runtime.Caller(skip + 1) 291 name = runtime.FuncForPC(pc).Name() 292 for i := len(name) - 1; i >= 0; i-- { 293 if name[i] == '/' { 294 name = name[i+1:] 295 break 296 } 297 } 298 pathSepCnt := 0 299 for i := len(file) - 1; i >= 0; i-- { 300 if file[i] == '/' { 301 pathSepCnt++ 302 if pathSepCnt == 2 { 303 file = file[i+1:] 304 break 305 } 306 } 307 } 308 return 309 } 310 311 var ( 312 stdoutMu sync.Mutex 313 stdlogMu sync.Mutex 314 ) 315 316 // CopyStdout replaces os.Stdout with a file created by `os.Pipe()`, and 317 // copies the content written to os.Stdout. 318 // This is not safe and most likely problematic, it's mainly to help intercepting 319 // output in testing. 320 func CopyStdout(f func()) ([]byte, error) { 321 stdoutMu.Lock() 322 defer stdoutMu.Unlock() 323 old := os.Stdout 324 defer func() { os.Stdout = old }() 325 326 r, w, err := os.Pipe() 327 // just to make sure the error didn't happen 328 // in case of unfortunate, we should still do the specified work 329 if err != nil { 330 f() 331 return nil, err 332 } 333 334 // copy the output in a separate goroutine, so printing can't block indefinitely 335 outCh := make(chan []byte) 336 go func() { 337 var buf bytes.Buffer 338 multi := io.MultiWriter(&buf, old) 339 io.Copy(multi, r) 340 outCh <- buf.Bytes() 341 }() 342 343 // do the work, write the stdout to pipe 344 os.Stdout = w 345 f() 346 w.Close() 347 348 out := <-outCh 349 return out, nil 350 } 351 352 // CopyStdLog replaces the out Writer of the default logger of `log` package, 353 // and copies the content written to it. 354 // This is unsafe and most likely problematic, it's mainly to help intercepting 355 // log output in testing. 356 // 357 // Also NOTE if the out Writer of the default logger has already been replaced 358 // with another writer, we won't know anything about that writer and will 359 // restore the out Writer to os.Stderr before it returns. 360 // It will be a real mess. 361 func CopyStdLog(f func()) []byte { 362 stdlogMu.Lock() 363 defer stdlogMu.Unlock() 364 defer log.SetOutput(os.Stderr) 365 366 var buf bytes.Buffer 367 multi := io.MultiWriter(&buf, os.Stderr) 368 log.SetOutput(multi) 369 f() 370 return buf.Bytes() 371 } 372 373 func formatArgs(stringer stringerFunc, args []interface{}) []interface{} { 374 retArgs := make([]interface{}, 0, len(args)) 375 for _, v := range args { 376 x := v 377 if v != nil { 378 typ := reflect.TypeOf(v) 379 for typ.Kind() == reflect.Ptr && isBasicType(typ.Elem()) { 380 typ = typ.Elem() 381 v = reflect.ValueOf(v).Elem().Interface() 382 } 383 if isBasicType(typ) { 384 x = v 385 } else if bv, ok := v.([]byte); ok && utf8.Valid(bv) { 386 x = string(bv) 387 } else { 388 x = stringer(v) 389 } 390 } 391 retArgs = append(retArgs, x) 392 } 393 return retArgs 394 } 395 396 func isBasicType(typ reflect.Type) bool { 397 switch typ.Kind() { 398 case reflect.Bool, reflect.String, 399 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 400 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 401 reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 402 return true 403 } 404 return false 405 }