refactor(logx): общий Holder вместо пакетных переменных Log

This commit is contained in:
samosvalishe
2026-09-21 13:39:06 +03:00
parent 322c7001fb
commit 56dfc9aedd
11 changed files with 86 additions and 48 deletions
+1
View File
@@ -29,6 +29,7 @@ func main() {
// Резолв подписки до парсинга даёт обязательный peer для валидации.
if subURL := config.PeekSubURL(args); subURL != "" {
sub.SetLogger(logx.New(false))
s, ferr := sub.Fetch(context.Background(), subURL)
if ferr != nil {
log.Fatalf("failed to fetch subscription: %v", ferr)
+14 -12
View File
@@ -25,9 +25,11 @@ import (
_ "golang.org/x/crypto/x509roots/fallback"
)
var Log logx.Logger = logx.Nop()
var logHolder logx.Holder
func SetLogger(l logx.Logger) { Log = logx.OrNop(l) }
func SetLogger(l logx.Logger) { logHolder.Set(l) }
func Log() logx.Logger { return logHolder.Get() }
const (
dohQueryTimeout = 6 * time.Second
@@ -161,7 +163,7 @@ func (r *DohResolver) forwardRaw(ctx context.Context, query []byte) ([]byte, err
body, err := r.postWire(epCtx, ep, query)
cancel()
if err != nil {
Log.Warnf("[DoH] %s: %v", ep.Hostname, err)
Log().Warnf("[DoH] %s: %v", ep.Hostname, err)
lastErr = err
continue
}
@@ -238,7 +240,7 @@ func startDohForwarder(r *DohResolver) (_ *dohForwarder, err error) {
udpAddr: udpConn.LocalAddr().String(),
tcpAddr: tcpLn.Addr().String(),
}
Log.Infof("[DoH] forwarder listening udp=%s tcp=%s", fwd.udpAddr, fwd.tcpAddr)
Log().Infof("[DoH] forwarder listening udp=%s tcp=%s", fwd.udpAddr, fwd.tcpAddr)
go fwd.serveUDP(udpConn, r)
go fwd.serveTCP(tcpLn, r)
@@ -251,7 +253,7 @@ func (*dohForwarder) serveUDP(conn *net.UDPConn, r *DohResolver) {
for {
n, client, err := conn.ReadFromUDP(buf)
if err != nil {
Log.Errorf("[DoH] udp read: %v", err)
Log().Errorf("[DoH] udp read: %v", err)
return
}
query := append([]byte(nil), buf[:n]...)
@@ -260,11 +262,11 @@ func (*dohForwarder) serveUDP(conn *net.UDPConn, r *DohResolver) {
defer cancel()
resp, err := r.forwardRaw(ctx, q)
if err != nil {
Log.Warnf("[DoH] udp forward failed: %v", err)
Log().Warnf("[DoH] udp forward failed: %v", err)
return
}
if _, err := conn.WriteToUDP(resp, c); err != nil {
Log.Warnf("[DoH] udp write: %v", err)
Log().Warnf("[DoH] udp write: %v", err)
}
}(query, client)
}
@@ -275,7 +277,7 @@ func (*dohForwarder) serveTCP(ln *net.TCPListener, r *DohResolver) {
for {
conn, err := ln.Accept()
if err != nil {
Log.Errorf("[DoH] tcp accept: %v", err)
Log().Errorf("[DoH] tcp accept: %v", err)
return
}
go handleDohForwarderTCP(conn, r)
@@ -303,11 +305,11 @@ func handleDohForwarderTCP(conn net.Conn, r *DohResolver) {
resp, err := r.forwardRaw(ctx, query)
cancel()
if err != nil {
Log.Warnf("[DoH] tcp forward failed: %v", err)
Log().Warnf("[DoH] tcp forward failed: %v", err)
return
}
if len(resp) > 0xFFFF {
Log.Warnf("[DoH] response too large for TCP framing: %d", len(resp))
Log().Warnf("[DoH] response too large for TCP framing: %d", len(resp))
return
}
out := make([]byte, 2+len(resp))
@@ -432,9 +434,9 @@ func autoDial(r *DohResolver) dialFunc {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
probed.Do(func() {
if udpProbe(autoUDPBudget) {
Log.Infof("[DNS] UDP/53 probe OK, using UDP")
Log().Infof("[DNS] UDP/53 probe OK, using UDP")
} else {
Log.Warnf("[DNS] UDP/53 unreachable; sticky-switching to DoH")
Log().Warnf("[DNS] UDP/53 unreachable; sticky-switching to DoH")
useDoH.Store(true)
}
})
+22 -1
View File
@@ -1,7 +1,10 @@
// Package logx предоставляет уровневый логгер поверх stdlib log.
package logx
import "log"
import (
"log"
"sync/atomic"
)
// Logger - интерфейс уровневого логирования.
type Logger interface {
@@ -48,3 +51,21 @@ func (nopLogger) Infof(string, ...any) {}
func (nopLogger) Warnf(string, ...any) {}
func (nopLogger) Errorf(string, ...any) {}
func (nopLogger) DebugEnabled() bool { return false }
// Holder хранит пакетный логгер, подменяемый на лету: ядро перезапускается в том же
// процессе (mobile), поэтому Set гонится с пишущими горутинами.
type Holder struct {
ptr atomic.Pointer[Logger]
}
func (h *Holder) Set(l Logger) {
v := OrNop(l)
h.ptr.Store(&v)
}
func (h *Holder) Get() Logger {
if p := h.ptr.Load(); p != nil {
return *p
}
return Nop()
}
@@ -23,9 +23,11 @@ import (
"github.com/samosvalishe/free-turn-proxy/internal/randx"
)
var Log logx.Logger = logx.Nop()
var logHolder logx.Holder
func SetLogger(l logx.Logger) { Log = logx.OrNop(l) }
func SetLogger(l logx.Logger) { logHolder.Set(l) }
func Log() logx.Logger { return logHolder.Get() }
const (
captchaAPIVersion = "5.131"
@@ -119,7 +121,7 @@ func (s *captchaSession) logger() logx.Logger {
if s.log != nil {
return s.log
}
return Log
return Log()
}
// Solve запускает авторешение captcha против VK captchaNotRobot API.
@@ -441,7 +443,7 @@ func parseCaptchaDebugInfo(html string) string {
return ""
}
if len(found) > 1 {
Log.Warnf("[Captcha] window.vk holds %d uuid values, debug_info may be the wrong one", len(found))
Log().Warnf("[Captcha] window.vk holds %d uuid values, debug_info may be the wrong one", len(found))
}
return found[0][1]
}
@@ -23,14 +23,14 @@ type Error struct {
func ParseError(errData map[string]any) *Error {
codeFloat, ok := errData["error_code"].(float64)
if !ok {
Log.Warnf("[Captcha] missing error_code in error data")
Log().Warnf("[Captcha] missing error_code in error data")
return nil
}
code := int(codeFloat)
redirectURI, ok := errData["redirect_uri"].(string)
if !ok {
Log.Warnf("[Captcha] missing redirect_uri in error data")
Log().Warnf("[Captcha] missing redirect_uri in error data")
return nil
}
@@ -43,7 +43,7 @@ func ParseError(errData map[string]any) *Error {
errorMsg, ok := errData["error_msg"].(string)
if !ok {
Log.Warnf("[Captcha] missing error_msg in error data")
Log().Warnf("[Captcha] missing error_msg in error data")
return nil
}
@@ -52,7 +52,7 @@ func ParseError(errData map[string]any) *Error {
if parsed, err := neturl.Parse(redirectURI); err == nil {
sessionToken = parsed.Query().Get("session_token")
} else {
Log.Warnf("[Captcha] failed to parse redirect_uri: %v", err)
Log().Warnf("[Captcha] failed to parse redirect_uri: %v", err)
return nil
}
}
@@ -17,6 +17,7 @@ import (
"regexp"
"runtime"
"strings"
"sync/atomic"
"time"
"github.com/samosvalishe/free-turn-proxy/internal/client/ish"
@@ -26,12 +27,16 @@ import (
"github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/personanet"
)
// Debug включает логирование проксируемого браузерного трафика.
var Debug bool
// debugFlag включает логирование проксируемого браузерного трафика.
var debugFlag atomic.Bool
var Log logx.Logger = logx.Nop()
func SetDebug(v bool) { debugFlag.Store(v) }
func SetLogger(l logx.Logger) { Log = logx.OrNop(l) }
var logHolder logx.Holder
func SetLogger(l logx.Logger) { logHolder.Set(l) }
func Log() logx.Logger { return logHolder.Get() }
const captchaListenPort = "8765"
@@ -343,12 +348,12 @@ func startCaptchaServer(srv *http.Server, logPrefix string) error {
listening = true
wrappedListener, err := ish.WrapListener(listener)
if err != nil {
Log.Warnf("%s: failed to wrap listener for iSH: %v", logPrefix, err)
Log().Warnf("%s: failed to wrap listener for iSH: %v", logPrefix, err)
wrappedListener = listener
}
go func(listener net.Listener) {
if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
Log.Errorf("%s: %s", logPrefix, err)
Log().Errorf("%s: %s", logPrefix, err)
}
}(wrappedListener)
}
@@ -375,7 +380,7 @@ func runCaptchaServerAndWait(ctx context.Context, handler http.Handler, captchaU
shutCtx, shutCancel := context.WithTimeout(context.Background(), 3*time.Second)
defer shutCancel()
if err := srv.Shutdown(shutCtx); err != nil {
Log.Warnf("%s: shutdown warning: %v", logPrefix, err)
Log().Warnf("%s: shutdown warning: %v", logPrefix, err)
}
}()
@@ -435,18 +440,18 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
if isCaptchaRequest {
b, err := io.ReadAll(req.Body)
if err != nil {
Log.Warnf("[Captcha Proxy] failed to read request body: %v", err)
Log().Warnf("[Captcha Proxy] failed to read request body: %v", err)
b = nil
}
req.Body = io.NopCloser(bytes.NewReader(b))
if Debug {
Log.Debugf("[Captcha Proxy] real browser sent %s data: %s", req.URL.Path, string(b))
if debugFlag.Load() {
Log().Debugf("[Captcha Proxy] real browser sent %s data: %s", req.URL.Path, string(b))
if env := browserPowEnvelope(b); env != "" {
Log.Debugf("[Captcha Proxy] real browser pow: %s", env)
Log().Debugf("[Captcha Proxy] real browser pow: %s", env)
}
for k, v := range req.Header {
Log.Debugf("[Captcha Proxy] header (%s): %s = %s", req.URL.Path, k, strings.Join(v, ", "))
Log().Debugf("[Captcha Proxy] header (%s): %s = %s", req.URL.Path, k, strings.Join(v, ", "))
}
}
}
@@ -456,11 +461,11 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
// Весь трафик виджета, а не только check/componentDone: иначе не видно, зовёт
// ли живой браузер то, чего не зовём мы, и с какими паузами.
if err != nil {
Log.Debugf("[Captcha Proxy] http %s %s failed t=%s after=%s %s err=%v",
Log().Debugf("[Captcha Proxy] http %s %s failed t=%s after=%s %s err=%v",
req.Method, captcha.SafeURL(req.URL.String()), t.elapsed(), time.Since(start).Truncate(time.Millisecond), navSummary(req), err)
return nil, err
}
Log.Debugf("[Captcha Proxy] http %s %s status=%d t=%s after=%s %s",
Log().Debugf("[Captcha Proxy] http %s %s status=%d t=%s after=%s %s",
req.Method, captcha.SafeURL(req.URL.String()), resp.StatusCode, t.elapsed(), time.Since(start).Truncate(time.Millisecond), navSummary(req))
return resp, nil
}
@@ -509,7 +514,7 @@ func solveViaProxy(ctx context.Context, redirectURI string, dialer net.Dialer, p
rewriteProxyRequest(req.Out, targetURL)
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
Log.Errorf("[Captcha Proxy] %s %s: %v", r.Method, r.URL.String(), err)
Log().Errorf("[Captcha Proxy] %s %s: %v", r.Method, r.URL.String(), err)
http.Error(w, "ошибка прокси капчи, детали в консоли клиента", http.StatusBadGateway)
},
ModifyResponse: func(res *http.Response) error {
@@ -528,8 +533,8 @@ func solveViaProxy(ctx context.Context, redirectURI string, dialer net.Dialer, p
contentType := res.Header.Get("Content-Type")
contentEncoding := res.Header.Get("Content-Encoding")
if Debug {
Log.Debugf("[Captcha Proxy] %s %d | Content-Type: %q, Encoding: %q", res.Request.Method, res.StatusCode, contentType, contentEncoding)
if debugFlag.Load() {
Log().Debugf("[Captcha Proxy] %s %d | Content-Type: %q, Encoding: %q", res.Request.Method, res.StatusCode, contentType, contentEncoding)
}
shouldInspectBody := strings.Contains(contentType, "text/html") ||
@@ -547,7 +552,7 @@ func solveViaProxy(ctx context.Context, redirectURI string, dialer net.Dialer, p
reader = gzReader
defer func() {
if err := gzReader.Close(); err != nil {
Log.Warnf("[Captcha Proxy] close gzip reader: %v", err)
Log().Warnf("[Captcha Proxy] close gzip reader: %v", err)
}
}()
}
@@ -599,10 +604,10 @@ func solveViaProxy(ctx context.Context, redirectURI string, dialer net.Dialer, p
mux.HandleFunc("/local-captcha-result", func(w http.ResponseWriter, r *http.Request) {
token := r.FormValue("token")
if token != "" {
Log.Infof("[Captcha] received success token from browser (%d bytes)", len(token))
Log().Infof("[Captcha] received success token from browser (%d bytes)", len(token))
notifyKey(keyCh, token)
} else {
Log.Warnf("[Captcha] received empty token from browser")
Log().Warnf("[Captcha] received empty token from browser")
}
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "text/plain")
@@ -665,7 +670,7 @@ func solveViaProxy(ctx context.Context, redirectURI string, dialer net.Dialer, p
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
Log.Debugf("[Captcha Proxy] HTTP %s %s", r.Method, r.URL.Path)
Log().Debugf("[Captcha Proxy] HTTP %s %s", r.Method, r.URL.Path)
if r.URL.Path == "/" && targetURL.Path != "" && targetURL.Path != "/" && r.URL.RawQuery == "" {
// не логируем полный redirect URL - шум в консоли
http.Redirect(w, r, localCaptchaURLForTarget(targetURL), http.StatusTemporaryRedirect)
@@ -176,9 +176,9 @@ func splitSliderSteps(steps []int) (int, []int, int, error) {
// Штатный формат: [size, ...пары свапов, attempts].
attempts = tail[len(tail)-1]
tail = tail[:len(tail)-1]
Log.Debugf("[Captcha] slider attempts from payload=%d", attempts)
Log().Debugf("[Captcha] slider attempts from payload=%d", attempts)
} else {
Log.Debugf("[Captcha] slider payload without attempts counter; default=%d", attempts)
Log().Debugf("[Captcha] slider payload without attempts counter; default=%d", attempts)
}
if attempts <= 0 {
attempts = 4
@@ -17,7 +17,7 @@ func (*Client) defaultAutoSolve(
client tlsclient.HttpClient,
profile browserprofile.Profile,
) (string, error) {
log := captcha.Log
log := captcha.Log()
log.Infof("[STREAM %d] [Captcha] Solving captcha (platform=%s)...", streamID, profile.Platform)
if captchaErr.SessionToken == "" {
+1 -1
View File
@@ -43,7 +43,7 @@ func New(cfg Config, solver ManualSolverFunc) (*Provider, error) {
}
captcha.SetLogger(cfg.Log)
manualcaptcha.SetLogger(cfg.Log)
manualcaptcha.Debug = cfg.Debug
manualcaptcha.SetDebug(cfg.Debug)
auth := vkauth.New(vkauth.Config{
Credentials: cfg.Credentials,
Dialer: cfg.Dialer,
+8 -2
View File
@@ -5,14 +5,20 @@ import (
"context"
"fmt"
"io"
"log"
"net/http"
"strings"
"time"
"github.com/samosvalishe/free-turn-proxy/internal/logx"
"github.com/samosvalishe/free-turn-proxy/internal/uri"
)
var logHolder logx.Holder
func SetLogger(l logx.Logger) { logHolder.Set(l) }
func log() logx.Logger { return logHolder.Get() }
// Sub представляет структуру подписки на серверы.
type Sub struct {
Name string
@@ -132,7 +138,7 @@ func Parse(r io.Reader) (*Sub, error) {
s.Nodes = append(s.Nodes, node)
lastNode = &s.Nodes[len(s.Nodes)-1]
} else {
log.Printf("warning: skipped invalid freeturn URI in subscription: %v", err)
log().Warnf("[Sub] skipped invalid freeturn URI: %v", err)
}
continue
}
+1
View File
@@ -250,6 +250,7 @@ func startLocked(configJSON string, tunFD int, withTunnel bool) error {
// Резолв подписки до парсинга даёт обязательный peer.
overlayURI := ""
if subURL := config.PeekSubURLJSON(raw); subURL != "" {
sub.SetLogger(coreLog())
s, err := sub.Fetch(context.Background(), subURL)
if err != nil {
return fmt.Errorf("failed to fetch subscription: %w", err)