logger.go 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "sync"
  8. "time"
  9. )
  10. // Logger 简单文件日志(windowsgui 子系统无控制台,统一落盘)。
  11. // 所有写出内容都经过 Redact:访问 token 不会明文落盘。
  12. type Logger struct {
  13. mu sync.Mutex
  14. file *os.File
  15. path string
  16. }
  17. func NewLogger() *Logger {
  18. dir := logDir()
  19. _ = os.MkdirAll(dir, 0o755)
  20. p := filepath.Join(dir, "tray-"+time.Now().Format("20060102")+".log")
  21. f, err := os.OpenFile(p, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
  22. if err != nil {
  23. f = nil
  24. }
  25. return &Logger{file: f, path: p}
  26. }
  27. func (l *Logger) Printf(format string, args ...any) {
  28. l.mu.Lock()
  29. defer l.mu.Unlock()
  30. body := Redact(fmt.Sprintf(format, args...))
  31. line := fmt.Sprintf("[%s] %s\n", time.Now().Format("2006-01-02 15:04:05"), body)
  32. if l.file != nil {
  33. _, _ = l.file.WriteString(line)
  34. }
  35. }
  36. func (l *Logger) Path() string {
  37. return l.path
  38. }
  39. func (l *Logger) Close() {
  40. l.mu.Lock()
  41. defer l.mu.Unlock()
  42. if l.file != nil {
  43. _ = l.file.Close()
  44. l.file = nil
  45. }
  46. }
  47. // Redact 脱敏访问 token(token=xxxx -> token=***),避免凭据明文落盘。
  48. func Redact(s string) string {
  49. const marker = "token="
  50. var b strings.Builder
  51. for {
  52. i := strings.Index(s, marker)
  53. if i < 0 {
  54. b.WriteString(s)
  55. return b.String()
  56. }
  57. end := i + len(marker)
  58. b.WriteString(s[:end])
  59. b.WriteString("***")
  60. j := end
  61. for j < len(s) && !isTokenTerminator(s[j]) {
  62. j++
  63. }
  64. s = s[j:]
  65. }
  66. }
  67. func isTokenTerminator(c byte) bool {
  68. switch c {
  69. case ' ', '\t', '\r', '\n', '"', '\'', '&', '<', '>', '#', '?':
  70. return true
  71. }
  72. return false
  73. }