Files
frpc-client-win/internal/logger/logger.go
T
cnbugs 69801ecc3f Initial commit: frpc-client-win v1.0
Single-file Windows frpc service wrapper.

Features:
- Embeds frpc v0.70.1 (16.7MB) into one 18.9MB EXE
- Registers as Windows Service (auto-start, no login required)
- Auto-detects frpc config by three-tier search:
  1. frpc.ini  2. frpc.toml  3. any *.ini/*.toml in exe dir
- Self-healing supervisor: frpc crashes are restarted with
  exponential backoff (2s..60s), capped to avoid log spam
- Console subsystem so install/start show feedback
- FreeConsole on SCM start to suppress console window
- Logs to <exe-dir>/logs/service.log

Subcommands:
  install  start  stop  remove  import  -d

Smoke test (smoke_linux.go) covers all config detection paths
including the custom-name (e.g. frpc-mike.ini) use case.
2026-08-10 22:11:43 +08:00

81 lines
2.0 KiB
Go

// Package logger 提供一个轻量日志器,把消息写入指定目录下的 service.log。
//
// 设计要点:
//
// - 文件日志始终启用,便于事后取证
// - 所有写入加锁,frpc 输出 + 本服务输出可能并发
// - 服务模式下,日志必须写到 EXE 同目录(SCM 默认工作目录是 System32)
// - 不再同时写 stdout - 服务模式已经 FreeConsole,CLI 模式 stdout 由 main 自己处理
package logger
import (
"fmt"
"io"
"os"
"path/filepath"
"sync"
"time"
)
// Log 是简易同步日志器。
type Log struct {
mu sync.Mutex
f *os.File
}
const timeLayout = "2006-01-02 15:04:05"
// New 在 logDir 下创建日志文件并返回 Log。
//
// logDir 必须传绝对路径;不存在会自动创建。
func New(logDir string) (*Log, error) {
if err := os.MkdirAll(logDir, 0o755); err != nil {
return nil, err
}
f, err := os.OpenFile(
filepath.Join(logDir, "service.log"),
os.O_CREATE|os.O_APPEND|os.O_WRONLY,
0o644,
)
if err != nil {
return nil, err
}
return &Log{f: f}, nil
}
// Close 关闭底层文件句柄。
func (l *Log) Close() error {
if l == nil || l.f == nil {
return nil
}
return l.f.Close()
}
// Info 写一条普通日志。
func (l *Log) Info(format string, args ...any) { l.write("INFO", format, args...) }
func (l *Log) Warn(format string, args ...any) { l.write("WARN", format, args...) }
func (l *Log) Error(format string, args ...any) { l.write("ERROR", format, args...) }
// FrpcRedirect 返回一个 io.Writer,frpc 的 stdout/stderr 重定向到这里。
func (l *Log) FrpcRedirect() io.Writer {
return &frpcWriter{l: l}
}
type frpcWriter struct{ l *Log }
func (w *frpcWriter) Write(p []byte) (int, error) {
w.l.write("FRPC", "%s", string(p))
return len(p), nil
}
func (l *Log) write(level, format string, args ...any) {
l.mu.Lock()
defer l.mu.Unlock()
msg := fmt.Sprintf(format, args...)
line := fmt.Sprintf("[%s] [%s] %s\n", time.Now().Format(timeLayout), level, msg)
if l.f != nil {
_, _ = l.f.WriteString(line)
}
}