tlog.app/go/tlog@v0.23.1/ext/tlflag/flag.go (about) 1 package tlflag 2 3 import ( 4 "io" 5 "net" 6 "net/url" 7 "os" 8 "path" 9 "path/filepath" 10 "strconv" 11 "strings" 12 "time" 13 14 "tlog.app/go/eazy" 15 "tlog.app/go/errors" 16 "tlog.app/go/loc" 17 18 "tlog.app/go/tlog" 19 "tlog.app/go/tlog/convert" 20 "tlog.app/go/tlog/rotating" 21 "tlog.app/go/tlog/tlio" 22 "tlog.app/go/tlog/tlwire" 23 ) 24 25 type ( 26 FileOpener = func(name string, flags int, mode os.FileMode) (interface{}, error) 27 28 writerWrapper func(io.Writer, io.Closer) (io.Writer, io.Closer, error) 29 readerWrapper func(io.Reader, io.Closer) (io.Reader, io.Closer, error) 30 ) 31 32 var ( 33 OpenFileWriter FileOpener = OSOpenFile 34 OpenFileReader FileOpener = OpenFileReReader(OSOpenFile) 35 36 EazyBlockSize = eazy.MiB 37 EazyHTable = 2 * 1024 38 ) 39 40 func OpenWriter(dst string) (wc io.WriteCloser, err error) { 41 var ws tlio.MultiWriter 42 43 defer func() { 44 if err == nil { 45 return 46 } 47 48 _ = ws.Close() 49 }() 50 51 for _, d := range strings.Split(dst, ",") { 52 if d == "" { 53 continue 54 } 55 56 u, err := ParseURL(d) 57 if err != nil { 58 return nil, errors.Wrap(err, "parse %v", d) 59 } 60 61 tlog.V("writer_url").Printw(d, "scheme", u.Scheme, "host", u.Host, "path", u.Path, "query", u.RawQuery, "from", loc.Caller(1)) 62 63 w, err := openw(u) 64 if err != nil { 65 return nil, errors.Wrap(err, "%v", d) 66 } 67 68 ws = append(ws, w) 69 } 70 71 if len(ws) == 1 { 72 var ok bool 73 if wc, ok = ws[0].(io.WriteCloser); ok { 74 return wc, nil 75 } 76 } 77 78 return ws, nil 79 } 80 81 func openw(u *url.URL) (io.Writer, error) { 82 // fmt.Fprintf(os.Stderr, "openw %q\n", fname) 83 84 w, c, err := openwc(u, u.Path) 85 if err != nil { 86 return nil, err 87 } 88 89 return writeCloser(w, c), nil 90 } 91 92 func openwc(u *url.URL, base string) (w io.Writer, c io.Closer, err error) { 93 // fmt.Fprintf(os.Stderr, "openwc %q %q\n", base, ext) 94 95 // w := os.Create("file.json.ez") 96 // w = tlz.NewEncoder(w) 97 // w = convert.NewJSON(w) 98 99 var wrap []writerWrapper 100 101 more: 102 ext := filepath.Ext(base) 103 base = strings.TrimSuffix(base, ext) 104 105 switch ext { 106 case ".tlog", ".tl": 107 case ".tlogdump", ".tldump": 108 wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) { 109 w = tlwire.NewDumper(w) 110 111 return w, c, nil 112 }) 113 case ".log", "": 114 wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) { 115 ff := tlog.LstdFlags 116 ff = updateConsoleFlags(ff, u.RawQuery) 117 118 w = tlog.NewConsoleWriter(w, ff) 119 120 return w, c, nil 121 }) 122 case ".tlz", ".eazy", ".ez": 123 wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) { 124 if f, ok := w.(*rotating.File); ok { 125 f.OpenFile = RotatedTLZFileOpener(f.OpenFile) 126 127 return f, c, nil 128 } 129 130 w = eazy.NewWriter(w, EazyBlockSize, EazyHTable) 131 132 return w, c, nil 133 }) 134 135 if ext == ".eazy" || ext == ".ez" { 136 goto more 137 } 138 case ".json": 139 wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) { 140 w = convert.NewJSON(w) 141 142 return w, c, nil 143 }) 144 case ".logfmt": 145 wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) { 146 w = convert.NewLogfmt(w) 147 148 return w, c, nil 149 }) 150 case ".html": 151 wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) { 152 wc := writeCloser(w, c) 153 w = convert.NewWeb(wc) 154 c, _ = w.(io.Closer) 155 156 return w, c, nil 157 }) 158 case ".eazydump", ".ezdump": 159 wrap = append(wrap, func(w io.Writer, c io.Closer) (io.Writer, io.Closer, error) { 160 w = eazy.NewDumper(w) 161 w = eazy.NewWriter(w, EazyBlockSize, EazyHTable) 162 163 return w, c, nil 164 }) 165 default: 166 return nil, nil, errors.New("unsupported format: %v", ext) 167 } 168 169 w, c, err = openwriter(u, base) 170 if err != nil { 171 return nil, nil, err 172 } 173 174 for _, wrap := range wrap { 175 w, c, err = wrap(w, c) 176 if err != nil { 177 return 178 } 179 } 180 181 return w, c, nil 182 } 183 184 func openwriter(u *url.URL, base string) (w io.Writer, c io.Closer, err error) { 185 switch base { 186 case "-", "stdout": 187 return os.Stdout, nil, nil 188 case "", "stderr": 189 return os.Stderr, nil, nil 190 case "discard": 191 return io.Discard, nil, nil 192 } 193 194 var f interface{} 195 196 if u.Scheme != "" { 197 f, err = openwurl(u) 198 } else { 199 f, err = openwfile(u) 200 } 201 if err != nil { 202 return nil, nil, err 203 } 204 205 w = f.(io.Writer) 206 c, _ = f.(io.Closer) 207 208 return w, c, nil 209 } 210 211 func openwfile(u *url.URL) (interface{}, error) { 212 fname := u.Path 213 214 of := os.O_APPEND | os.O_WRONLY | os.O_CREATE 215 of = updateFileFlags(of, u.RawQuery) 216 217 mode := os.FileMode(0o644) 218 219 q := u.Query() 220 221 if rotating.IsPattern(filepath.Base(fname)) || q.Get("rotating") != "" { 222 f := rotating.Create(fname) 223 f.Flags = of 224 f.Mode = mode 225 f.OpenFile = openFileWriter 226 227 if v := q.Get("rotating_max_file_size"); v != "" { 228 x, err := ParseBytes(v) 229 if err == nil { 230 f.MaxFileSize = x 231 } 232 } 233 234 if v := q.Get("rotating_max_file_age"); v != "" { 235 x, err := time.ParseDuration(v) 236 if err == nil { 237 f.MaxFileAge = x 238 } 239 } 240 241 if v := q.Get("rotating_max_total_size"); v != "" { 242 x, err := ParseBytes(v) 243 if err == nil { 244 f.MaxTotalSize = x 245 } 246 } 247 248 if v := q.Get("rotating_max_total_age"); v != "" { 249 x, err := time.ParseDuration(v) 250 if err == nil { 251 f.MaxTotalAge = x 252 } 253 } 254 255 return f, nil 256 } 257 258 return OpenFileWriter(fname, of, mode) 259 } 260 261 func openwurl(u *url.URL) (f interface{}, err error) { 262 if u.Scheme == "file" { 263 return openwfile(u) 264 } 265 266 switch u.Scheme { 267 case "unix", "unixgram": 268 default: 269 return nil, errors.New("unsupported scheme: %v", u.Scheme) 270 } 271 272 return tlio.NewReWriter(func(w io.Writer, err error) (io.Writer, error) { 273 if c, ok := w.(io.Closer); ok { 274 _ = c.Close() 275 } 276 277 return net.Dial(u.Scheme, u.Path) 278 }), nil 279 } 280 281 func OpenReader(src string) (rc io.ReadCloser, err error) { 282 u, err := ParseURL(src) 283 if err != nil { 284 return nil, errors.Wrap(err, "parse %v", src) 285 } 286 287 r, err := openr(u) 288 if err != nil { 289 return nil, err 290 } 291 292 var ok bool 293 if rc, ok = r.(io.ReadCloser); ok { 294 return rc, nil 295 } 296 297 return tlio.NopCloser{ 298 Reader: r, 299 }, nil 300 } 301 302 func openr(u *url.URL) (io.Reader, error) { 303 r, c, err := openrc(u, u.Path) 304 if err != nil { 305 return nil, err 306 } 307 308 if c == nil { 309 if _, ok := r.(io.Closer); ok { 310 return tlio.NopCloser{Reader: r}, nil 311 } 312 313 return r, nil 314 } 315 316 if r.(interface{}) == c.(interface{}) { 317 return r, nil 318 } 319 320 return tlio.ReadCloser{ 321 Reader: r, 322 Closer: c, 323 }, nil 324 } 325 326 func openrc(u *url.URL, base string) (r io.Reader, c io.Closer, err error) { 327 var wrap []readerWrapper 328 329 more: 330 ext := filepath.Ext(base) 331 base = strings.TrimSuffix(base, ext) 332 333 switch ext { 334 case ".tlog", ".tl", "": 335 case ".tlz", ".eazy", ".ez": 336 wrap = append(wrap, func(r io.Reader, c io.Closer) (io.Reader, io.Closer, error) { 337 r = eazy.NewReader(r) 338 339 return r, c, nil 340 }) 341 342 if ext == ".eazy" || ext == ".ez" { 343 goto more 344 } 345 default: 346 return nil, nil, errors.New("unsupported format: %v", ext) 347 } 348 349 r, c, err = openreader(u, base) 350 if err != nil { 351 return nil, nil, err 352 } 353 354 for _, wrap := range wrap { 355 r, c, err = wrap(r, c) 356 if err != nil { 357 return 358 } 359 } 360 361 return r, c, nil 362 } 363 364 func openreader(u *url.URL, base string) (r io.Reader, c io.Closer, err error) { 365 switch base { 366 case "-", "", "stdin": 367 return os.Stdin, nil, nil 368 } 369 370 var f interface{} 371 372 if u.Scheme != "" { 373 f, err = openrurl(u) 374 } else { 375 f, err = openrfile(u) 376 } 377 if err != nil { 378 return nil, nil, err 379 } 380 381 if rc, ok := f.(tlio.ReadCloser); ok { 382 return rc.Reader, rc.Closer, nil 383 } 384 385 r = f.(io.Reader) 386 c, _ = f.(io.Closer) 387 388 return r, c, nil 389 } 390 391 func openrfile(u *url.URL) (interface{}, error) { 392 return OpenFileReader(u.Path, os.O_RDONLY, 0) 393 } 394 395 func openrurl(u *url.URL) (interface{}, error) { 396 if u.Scheme == "file" { 397 return openrfile(u) 398 } 399 400 switch u.Scheme { //nolint:gocritic 401 default: 402 return nil, errors.New("unsupported scheme: %v", u.Scheme) 403 } 404 } 405 406 func updateFileFlags(of int, q string) int { 407 for _, c := range q { 408 if c == '0' { 409 of |= os.O_TRUNC 410 } 411 } 412 413 return of 414 } 415 416 func updateConsoleFlags(ff int, q string) int { 417 for _, c := range q { 418 switch c { 419 case 'd': 420 ff |= tlog.LdetFlags 421 case 'm': 422 ff |= tlog.Lmilliseconds 423 case 'M': 424 ff |= tlog.Lmicroseconds 425 case 'n': 426 ff |= tlog.Lfuncname 427 case 'f': 428 ff &^= tlog.Llongfile 429 ff |= tlog.Lshortfile 430 case 'F': 431 ff &^= tlog.Lshortfile 432 ff |= tlog.Llongfile 433 case 'U': 434 ff |= tlog.LUTC 435 } 436 } 437 438 return ff 439 } 440 441 func OSOpenFile(name string, flags int, mode os.FileMode) (interface{}, error) { 442 return os.OpenFile(name, flags, mode) 443 } 444 445 func OpenFileReReader(open FileOpener) FileOpener { 446 return func(name string, flags int, mode os.FileMode) (interface{}, error) { 447 f, err := open(name, flags, mode) 448 if err != nil { 449 return nil, err 450 } 451 452 rs := f.(tlio.ReadSeeker) 453 454 r, err := tlio.NewReReader(rs) 455 if err != nil { 456 return nil, errors.Wrap(err, "open ReReader") 457 } 458 459 r.Hook = func(old, cur int64) { 460 tlog.Printw("file truncated", "name", name, "old_len", old) 461 } 462 463 c, ok := f.(io.Closer) 464 if !ok { 465 return r, nil 466 } 467 468 return tlio.ReadCloser{ 469 Reader: r, 470 Closer: c, 471 }, nil 472 } 473 } 474 475 func OpenFileDumpReader(open FileOpener) FileOpener { 476 tr := tlog.Start("read dumper") 477 478 return func(name string, flags int, mode os.FileMode) (interface{}, error) { 479 f, err := open(name, flags, mode) 480 if err != nil { 481 return nil, err 482 } 483 484 r := f.(io.Reader) 485 486 r = &tlio.DumpReader{ 487 Reader: r, 488 Span: tr.Spawn("open dump reader", "file", name), 489 } 490 491 return r, nil 492 } 493 } 494 495 func RotatedTLZFileOpener(below rotating.FileOpener) rotating.FileOpener { 496 return func(name string, flags int, mode os.FileMode) (io.Writer, error) { 497 w, err := below(name, flags, mode) 498 if err != nil { 499 return nil, errors.Wrap(err, "") 500 } 501 502 s := tlio.NewSandwichWriter(w) 503 504 s.Writer = eazy.NewWriter(s.Inner(), EazyBlockSize, EazyHTable) 505 506 return s, nil 507 } 508 } 509 510 func openFileWriter(name string, flags int, mode os.FileMode) (io.Writer, error) { 511 file, err := OpenFileWriter(name, flags, mode) 512 if err != nil { 513 return nil, err 514 } 515 516 return file.(io.Writer), nil 517 } 518 519 func ParseURL(d string) (u *url.URL, err error) { 520 u, err = url.Parse(d) 521 if err != nil { 522 return nil, err 523 } 524 525 if u.Opaque != "" { 526 return nil, errors.New("unexpected opaque url") 527 } 528 529 if (u.Scheme == "file" || u.Scheme == "unix" || u.Scheme == "unixgram") && u.Host != "" { 530 u.Path = path.Join(u.Host, u.Path) 531 u.Host = "" 532 } 533 534 return u, nil 535 } 536 537 func ParseBytes(s string) (int64, error) { 538 base := 10 539 neg := false 540 541 for strings.HasPrefix(s, "-") { 542 neg = !neg 543 s = s[1:] 544 } 545 546 if strings.HasPrefix(s, "0x") { 547 s = s[2:] 548 base = 16 549 } 550 551 l := 0 552 553 for l < len(s) && s[l] >= '0' && (s[l] <= '9' || base == 16 && (s[l] >= 'a' && s[l] <= 'f' || s[l] >= 'A' && s[l] <= 'F')) { 554 l++ 555 } 556 557 if l == 0 { 558 return 0, errors.New("bad size") 559 } 560 561 var m int64 562 563 switch strings.ToLower(s[l:]) { 564 case "b", "": 565 m = 1 566 case "kb", "k": 567 m = 1000 568 case "kib", "ki": 569 m = 1024 570 case "mb", "m": 571 m = 1e6 572 case "mib", "mi": 573 m = 1 << 20 574 case "gb", "g": 575 m = 1e9 576 case "gib", "gi": 577 m = 1 << 30 578 case "tb", "t": 579 m = 1e12 580 case "tib", "ti": 581 m = 1 << 40 582 default: 583 return 0, errors.New("unsupported suffix: %v", s[l:]) 584 } 585 586 x, err := strconv.ParseInt(s[:l], base, 64) 587 if err != nil { 588 return 0, err 589 } 590 591 if neg { 592 x = -x 593 } 594 595 return x * m, nil 596 } 597 598 func Describe(tr tlog.Span, x interface{}) { 599 describe(tr, x, 0) 600 } 601 602 func describe(tr tlog.Span, x interface{}, d int) { 603 switch x := x.(type) { 604 case tlio.MultiWriter: 605 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x) 606 607 for _, w := range x { 608 describe(tr, w, d+1) 609 } 610 case tlio.ReadCloser: 611 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x) 612 613 describe(tr, x.Reader, d+1) 614 describe(tr, x.Closer, d+1) 615 case *tlio.ReReader: 616 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x) 617 618 describe(tr, x.ReadSeeker, d+1) 619 case *eazy.Reader: 620 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x) 621 622 describe(tr, x.Reader, d+1) 623 case *eazy.Writer: 624 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x) 625 626 describe(tr, x.Writer, d+1) 627 case *tlog.ConsoleWriter: 628 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x) 629 630 describe(tr, x.Writer, d+1) 631 case *os.File: 632 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x, "name", x.Name()) 633 634 case *net.UnixConn: 635 f, err := x.File() 636 637 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x, "get_file_err", err) 638 639 describe(tr, f, d+1) 640 default: 641 tr.Printw("describe", "d", d, "typ", tlog.NextAsType, x) 642 } 643 } 644 645 func writeCloser(w io.Writer, c io.Closer) io.Writer { 646 if c == nil { 647 if _, ok := w.(io.Closer); ok { 648 return tlio.NopCloser{Writer: w} 649 } 650 651 return w 652 } 653 654 if w.(interface{}) == c.(interface{}) { 655 return w 656 } 657 658 return tlio.WriteCloser{ 659 Writer: w, 660 Closer: c, 661 } 662 }