logger.go 2.0 KB

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