translated all logs to english

This commit is contained in:
onysd 2026-07-25 07:31:27 +03:00
parent e2e6f54ce6
commit 0e5da4c8aa
13 changed files with 277 additions and 90 deletions

View file

@ -106,16 +106,16 @@ func main() {
priv, err := mtprotoedge.LoadOrGenerateRSAKey(*rsaPath)
if err != nil {
logger.Fatal("加载 RSA key 失败", zap.Error(err))
logger.Fatal("load RSA key failed", zap.Error(err))
}
host, portStr, err := net.SplitHostPort(*addr)
if err != nil {
logger.Fatal("解析地址失败", zap.Error(err))
logger.Fatal("parse address failed", zap.Error(err))
}
port, err := strconv.Atoi(portStr)
if err != nil {
logger.Fatal("解析端口失败", zap.Error(err))
logger.Fatal("parse port failed", zap.Error(err))
}
var client *telegram.Client
@ -240,7 +240,7 @@ func main() {
<-ctx.Done()
return nil
}); err != nil {
logger.Fatal("运行失败", zap.Error(err))
logger.Fatal("run failed", zap.Error(err))
}
fmt.Println("已退出")
}
@ -286,7 +286,7 @@ func echoUpdates(ctx context.Context, raw *tg.Client, u tg.UpdatesClass, logger
Message: "echo: " + msg.Message,
RandomID: rid,
}); err != nil {
logger.Warn("echo 回复失败", zap.Int64("to", from), zap.Error(err))
logger.Warn("echo reply failed", zap.Int64("to", from), zap.Error(err))
continue
}
fmt.Printf(">> 已回复 %d: %q\n", from, "echo: "+msg.Message)

View file

@ -158,11 +158,11 @@ func main() {
priv, err := mtprotoedge.LoadOrGenerateRSAKey(*rsaPath)
if err != nil {
logger.Fatal("加载 RSA key 失败", zap.Error(err))
logger.Fatal("load RSA key failed", zap.Error(err))
}
host, portStr, err := net.SplitHostPort(*addr)
if err != nil {
logger.Fatal("解析地址失败", zap.Error(err))
logger.Fatal("parse address failed", zap.Error(err))
}
port, _ := strconv.Atoi(portStr)
@ -196,7 +196,7 @@ func main() {
// 3) 定时主动发送。
return runLoop(ctx, api, uploader.NewUploader(api), peer, cfg, logger)
}); err != nil {
logger.Fatal("运行失败", zap.Error(err))
logger.Fatal("run failed", zap.Error(err))
}
fmt.Println("已退出")
}
@ -286,7 +286,7 @@ func runLoop(ctx context.Context, api *tg.Client, up *uploader.Uploader, peer tg
// 立即发第一条(不必等满一个 interval随后按 ticker 周期发。
if err := send(); err != nil {
logger.Warn("发送失败", zap.Error(err))
logger.Warn("send failed", zap.Error(err))
}
if cfg.count > 0 && seq >= cfg.count {
return nil
@ -297,7 +297,7 @@ func runLoop(ctx context.Context, api *tg.Client, up *uploader.Uploader, peer tg
return nil
case <-ticker.C:
if err := send(); err != nil {
logger.Warn("发送失败", zap.Error(err))
logger.Warn("send failed", zap.Error(err))
continue
}
if cfg.count > 0 && seq >= cfg.count {

View file

@ -91,7 +91,7 @@ func main() {
defer func() { _ = logger.Sync() }()
priv, err := mtprotoedge.LoadOrGenerateRSAKey(*rsaPath)
if err != nil {
logger.Fatal("加载 RSA key 失败", zap.Error(err))
logger.Fatal("load RSA key failed", zap.Error(err))
}
host, portStr, _ := net.SplitHostPort(*addr)
port, _ := strconv.Atoi(portStr)
@ -183,7 +183,7 @@ func main() {
}
return nil
}); err != nil {
logger.Fatal("运行失败", zap.Error(err))
logger.Fatal("run failed", zap.Error(err))
}
}

View file

@ -101,11 +101,11 @@ func main() {
priv, err := mtprotoedge.LoadOrGenerateRSAKey(*rsaPath)
if err != nil {
logger.Fatal("加载 RSA key 失败", zap.Error(err))
logger.Fatal("load RSA key failed", zap.Error(err))
}
host, portStr, err := net.SplitHostPort(*addr)
if err != nil {
logger.Fatal("解析地址失败", zap.Error(err))
logger.Fatal("parse address failed", zap.Error(err))
}
port, _ := strconv.Atoi(portStr)
@ -193,7 +193,7 @@ func main() {
}
return nil
}); err != nil {
logger.Fatal("运行失败", zap.Error(err))
logger.Fatal("run failed", zap.Error(err))
}
}

177
cmd/telesrv/log_encoder.go Normal file
View file

@ -0,0 +1,177 @@
package main
import (
"fmt"
"strings"
"time"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)
var compactBufferPool = buffer.NewPool()
// compactEncoder renders one readable line per entry: short time, level,
// logger, message, then "key=value" pairs in call order — instead of zap's
// stock console encoder, which appends fields as a raw JSON blob and makes
// high-volume RPC logs unreadable at a glance. It writes fields straight
// into a pooled buffer.Buffer (no map/interface{} boxing on the hot path),
// so it stays close to the JSON/console encoders' cost.
type compactEncoder struct {
cfg zapcore.EncoderConfig
buf *buffer.Buffer // accumulated fields from Logger.With(...)
}
func newCompactEncoder(cfg zapcore.EncoderConfig) *compactEncoder {
return &compactEncoder{cfg: cfg, buf: compactBufferPool.Get()}
}
func (e *compactEncoder) Clone() zapcore.Encoder {
clone := &compactEncoder{cfg: e.cfg, buf: compactBufferPool.Get()}
clone.buf.AppendString(e.buf.String())
return clone
}
func (e *compactEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := compactBufferPool.Get()
line.AppendString(ent.Time.Format("15:04:05.000"))
line.AppendByte(' ')
lvl := ent.Level.CapitalString()
line.AppendString(lvl)
for i := len(lvl); i < 5; i++ {
line.AppendByte(' ')
}
line.AppendByte(' ')
if ent.LoggerName != "" {
line.AppendByte('[')
line.AppendString(ent.LoggerName)
line.AppendString("] ")
}
line.AppendString(ent.Message)
if e.buf.Len() > 0 {
line.AppendByte(' ')
line.AppendString(e.buf.String())
}
if len(fields) > 0 {
tmp := &compactEncoder{cfg: e.cfg, buf: compactBufferPool.Get()}
for _, f := range fields {
f.AddTo(tmp)
}
if tmp.buf.Len() > 0 {
line.AppendByte(' ')
line.AppendString(tmp.buf.String())
}
tmp.buf.Free()
}
if ent.Caller.Defined {
line.AppendString(" (")
line.AppendString(ent.Caller.TrimmedPath())
line.AppendByte(')')
}
if ent.Stack != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
line.AppendByte('\n')
return line, nil
}
func (e *compactEncoder) addKey(key string) {
if e.buf.Len() > 0 {
e.buf.AppendByte(' ')
}
e.buf.AppendString(key)
e.buf.AppendByte('=')
}
// appendSafeString keeps "key=value value2" pairs unambiguous: values with
// whitespace, '=' or '"' get quoted, everything else (the vast majority —
// method names, hex ids, enum-ish strings) is written bare.
func (e *compactEncoder) appendSafeString(s string) {
if s == "" || strings.ContainsAny(s, " \t\n=\"") {
e.buf.AppendByte('"')
e.buf.AppendString(strings.ReplaceAll(s, `"`, `\"`))
e.buf.AppendByte('"')
return
}
e.buf.AppendString(s)
}
func (e *compactEncoder) AddBool(key string, v bool) { e.addKey(key); e.buf.AppendBool(v) }
func (e *compactEncoder) AddInt64(key string, v int64) { e.addKey(key); e.buf.AppendInt(v) }
func (e *compactEncoder) AddInt(key string, v int) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddInt32(key string, v int32) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddInt16(key string, v int16) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddInt8(key string, v int8) { e.AddInt64(key, int64(v)) }
func (e *compactEncoder) AddUint64(key string, v uint64) { e.addKey(key); e.buf.AppendUint(v) }
func (e *compactEncoder) AddUint(key string, v uint) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUint32(key string, v uint32) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUint16(key string, v uint16) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUint8(key string, v uint8) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddUintptr(key string, v uintptr) { e.AddUint64(key, uint64(v)) }
func (e *compactEncoder) AddFloat64(key string, v float64) { e.addKey(key); e.buf.AppendFloat(v, 64) }
func (e *compactEncoder) AddFloat32(key string, v float32) {
e.addKey(key)
e.buf.AppendFloat(float64(v), 32)
}
func (e *compactEncoder) AddDuration(key string, v time.Duration) {
e.addKey(key)
e.buf.AppendString(v.String())
}
func (e *compactEncoder) AddTime(key string, v time.Time) {
e.addKey(key)
e.buf.AppendTime(v, "15:04:05.000")
}
func (e *compactEncoder) AddString(key, v string) { e.addKey(key); e.appendSafeString(v) }
func (e *compactEncoder) AddByteString(key string, v []byte) {
e.addKey(key)
e.appendSafeString(string(v))
}
func (e *compactEncoder) AddBinary(key string, v []byte) { e.addKey(key); fmt.Fprintf(e.buf, "%x", v) }
func (e *compactEncoder) AddComplex128(key string, v complex128) {
e.addKey(key)
fmt.Fprintf(e.buf, "%v", v)
}
func (e *compactEncoder) AddComplex64(key string, v complex64) { e.AddComplex128(key, complex128(v)) }
func (e *compactEncoder) AddReflected(key string, v interface{}) error {
e.addKey(key)
fmt.Fprintf(e.buf, "%v", v)
return nil
}
// OpenNamespace isn't used by any log call in this codebase today; nested
// fields would just render as flat top-level keys, which is an acceptable
// degradation for a format whose whole point is staying flat and scannable.
func (e *compactEncoder) OpenNamespace(string) {}
func (e *compactEncoder) AddArray(key string, m zapcore.ArrayMarshaler) error {
tmp := zapcore.NewMapObjectEncoder()
if err := tmp.AddArray("v", m); err != nil {
e.addKey(key)
e.buf.AppendString("!ERROR")
return err
}
e.addKey(key)
fmt.Fprintf(e.buf, "%v", tmp.Fields["v"])
return nil
}
func (e *compactEncoder) AddObject(key string, m zapcore.ObjectMarshaler) error {
tmp := zapcore.NewMapObjectEncoder()
if err := tmp.AddObject("v", m); err != nil {
e.addKey(key)
e.buf.AppendString("!ERROR")
return err
}
e.addKey(key)
fmt.Fprintf(e.buf, "%v", tmp.Fields["v"])
return nil
}

View file

@ -84,19 +84,22 @@ func main() {
defer func() { _ = logger.Sync() }()
if err := run(logger); err != nil {
logger.Error("telesrv 退出", zap.Error(err))
_ = logger.Sync() // os.Exit 跳过 defer缓冲写需显式 flush 错误日志
logger.Error("telesrv exiting", zap.Error(err))
_ = logger.Sync() // os.Exit skips defer; flush buffered log writes explicitly
os.Exit(1)
}
}
// newLogger 构建运行日志器。项关键改造(相对旧的 zap.NewDevelopment
// newLogger 构建运行日志器。项关键改造(相对旧的 zap.NewDevelopment
// - 级别可配TELESRV_LOG_LEVEL默认 info旧版固定 Debug热路径 65 处 Debug
// mtprotoedge 每帧一条)在连接洪峰会刷爆日志。生产/压测用 info 即可,需要时设 debug。
// - 缓冲异步写BufferedWriteSyncer旧版每条日志一次 stderr 同步写 + 全局锁,高并发下
// 在日志锁上串行累积——实测连接时 12 个并发 RPC 的 client_info 阶段被拖成 ~1s 惊群
// mutex profile 91% 竞争在 zap 写)。缓冲后写入批量化,锁持有时间从「每条一次系统调用」
// 降到「攒一批刷一次」。FlushInterval 控制日志可见延迟上界。
// - compactEncoder见 log_encoder.go取代 zap 自带的 console encoder后者把结构化字段
// 整体序列化成一坨 JSON 挂在行尾人眼扫描困难compactEncoder 直接把字段写成
// `key=value` 平铺在一行,短时间戳,同时不经 map/interface{} 装箱,性能开销接近原 encoder。
func newLogger() (*zap.Logger, error) {
level := zapcore.InfoLevel
if v := strings.TrimSpace(os.Getenv("TELESRV_LOG_LEVEL")); v != "" {
@ -108,7 +111,7 @@ func newLogger() (*zap.Logger, error) {
WS: zapcore.AddSync(os.Stderr),
FlushInterval: 500 * time.Millisecond,
}
core := zapcore.NewCore(zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), ws, level)
core := zapcore.NewCore(newCompactEncoder(zap.NewDevelopmentEncoderConfig()), ws, level)
return zap.New(core,
zap.AddCaller(),
zap.AddStacktrace(zapcore.ErrorLevel),
@ -135,7 +138,7 @@ func newBusinessAutomationOptions(cfg config.Config, online messageapp.BusinessA
opts = append(opts, messageapp.WithBusinessAutomationReplyProvider(messageapp.NewAIBusinessAutomationProvider(generator)))
logger.Info("Business automation reply provider", zap.String("provider", "ai"))
default:
logger.Warn("未知 Business automation AI provider回退 quick reply 模板", zap.String("provider", cfg.BusinessAIProvider))
logger.Warn("unknown Business automation AI provider, falling back to quick reply template", zap.String("provider", cfg.BusinessAIProvider))
}
return opts
}
@ -164,11 +167,11 @@ func newAIComposeOptions(cfg config.Config, limiter aiapp.RateLimiter, premium a
Thinking: pc.Thinking,
})
if err != nil {
logger.Warn("AI compose provider 已跳过", zap.String("provider", pc.Name), zap.String("kind", pc.Kind), zap.Error(err))
logger.Warn("AI compose provider skipped", zap.String("provider", pc.Name), zap.String("kind", pc.Kind), zap.Error(err))
continue
}
providers = append(providers, provider)
logger.Info("AI compose provider 已启用", zap.String("provider", provider.Name()), zap.String("kind", pc.Kind))
logger.Info("AI compose provider enabled", zap.String("provider", provider.Name()), zap.String("kind", pc.Kind))
}
if len(providers) > 0 {
opts = append(opts, aiapp.WithProviders(providers...))
@ -203,16 +206,16 @@ func newTranslationOptions(cfg config.Config, limiter translationapp.RateLimiter
OmitTemperature: pc.OmitTemperature, Thinking: pc.Thinking,
})
if err != nil {
logger.Warn("translation provider 已跳过", zap.String("provider", pc.Name), zap.Error(err))
logger.Warn("translation provider skipped", zap.String("provider", pc.Name), zap.Error(err))
continue
}
providers = append(providers, translationapp.NewAIProvider(provider))
logger.Info("translation provider 已启用", zap.String("provider", provider.Name()), zap.String("kind", pc.Kind))
logger.Info("translation provider enabled", zap.String("provider", provider.Name()), zap.String("kind", pc.Kind))
}
if len(providers) > 0 {
opts = append(opts, translationapp.WithProviders(providers...))
} else if cfg.TranslationEnabled {
logger.Warn("translation 已启用但没有远程 providermessages.translateText 将返回 TRANSLATIONS_DISABLED")
logger.Warn("translation is enabled but has no remote provider; messages.translateText will return TRANSLATIONS_DISABLED")
}
return opts
}
@ -243,10 +246,10 @@ func startDebugServer(ctx context.Context, addr string, logger *zap.Logger) {
srv := &http.Server{Addr: addr, Handler: mux}
go func() {
logger.Info("pprof 调试端点已启用", zap.String("addr", addr),
logger.Info("pprof debug endpoint enabled", zap.String("addr", addr),
zap.String("hint", "go tool pprof http://"+addr+"/debug/pprof/profile?seconds=30"))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Warn("pprof 端点退出", zap.Error(err))
logger.Warn("pprof endpoint exited", zap.Error(err))
}
}()
go func() {
@ -306,7 +309,7 @@ func run(logger *zap.Logger) error {
// tg.Layer 由当前导入的 canonical schema 生成;纳入未来 Layer 后无需
// 在 telesrv 另维护一份常量。
logger.Info("telesrv 启动",
logger.Info("telesrv starting",
zap.String("listen", cfg.ListenAddr),
zap.Int("dc", cfg.DC),
zap.String("advertise", net.JoinHostPort(cfg.AdvertiseIP, portStr)),
@ -334,7 +337,7 @@ func run(logger *zap.Logger) error {
if err != nil {
return fmt.Errorf("postgres migrate: %w", err)
}
logger.Info("PostgreSQL schema 已迁移",
logger.Info("PostgreSQL schema migrated",
zap.Uint("schema_version", migrationStatus.Version),
zap.Bool("schema_dirty", migrationStatus.Dirty),
zap.Bool("schema_empty", migrationStatus.Empty),
@ -387,7 +390,7 @@ func run(logger *zap.Logger) error {
return fmt.Errorf("connect redis: %w", err)
}
defer func() { _ = rdb.Close() }()
logger.Info("持久化依赖就绪", zap.String("redis", cfg.RedisAddr))
logger.Info("persistence dependencies ready", zap.String("redis", cfg.RedisAddr))
if cfg.TelegramLoginEnabled {
telegramLoginHTTPHandler, err = telegramloginhttp.NewHandler(telegramloginhttp.Config{
Service: telegramLoginService, Tokens: telegramLoginIDTokens,
@ -452,7 +455,7 @@ func run(logger *zap.Logger) error {
if err != nil {
return fmt.Errorf("init blob backend: %w", err)
}
logger.Info("blob backend 就绪",
logger.Info("blob backend ready",
zap.String("backend", "localfs"),
zap.String("dir", cfg.BlobDir),
)
@ -468,18 +471,18 @@ func run(logger *zap.Logger) error {
webPagePreviewOption(cfg),
)
if cfg.MapboxToken != "" {
logger.Info("地图缩略图代理已启用", zap.String("provider", "mapbox"), zap.String("cache_dir", cfg.MapTileCacheDir))
logger.Info("map tile thumbnail proxy enabled", zap.String("provider", "mapbox"), zap.String("cache_dir", cfg.MapTileCacheDir))
}
if cfg.ExternalMediaEnable {
logger.Info("外链媒体抓取已启用", zap.Int64("max_bytes", cfg.ExternalMediaMaxBytes), zap.Int("rate_per_min", cfg.ExternalMediaRatePerMin))
logger.Info("external link media fetching enabled", zap.Int64("max_bytes", cfg.ExternalMediaMaxBytes), zap.Int("rate_per_min", cfg.ExternalMediaRatePerMin))
}
if cfg.WebPagePreviewEnable {
logger.Info("链接预览抓取已启用", zap.Int64("max_bytes", cfg.WebPagePreviewMaxBytes), zap.Int("rate_per_min", cfg.WebPagePreviewRatePerMin))
logger.Info("link preview fetching enabled", zap.Int64("max_bytes", cfg.WebPagePreviewMaxBytes), zap.Int("rate_per_min", cfg.WebPagePreviewRatePerMin))
}
if stats, err := filesService.SeedMedia(ctx, cfg.StickerSeedDir, cfg.StickerSeedMaxSets); err != nil {
return fmt.Errorf("seed media: %w", err)
} else if !stats.Skipped {
logger.Info("媒体种子导入完成",
logger.Info("media seed import complete",
zap.String("dir", cfg.StickerSeedDir),
zap.Int("reactions", stats.Reactions),
zap.Int("sticker_sets", stats.StickerSets),
@ -491,7 +494,7 @@ func run(logger *zap.Logger) error {
if stats, err := filesService.SeedAppearance(ctx); err != nil {
return fmt.Errorf("seed appearance: %w", err)
} else if !stats.Skipped {
logger.Info("外观种子导入完成",
logger.Info("appearance seed import complete",
zap.String("source", "default-seed"),
zap.Int("wallpapers", stats.Wallpapers),
zap.Int("documents", stats.Documents),
@ -501,38 +504,39 @@ func run(logger *zap.Logger) error {
if seeded, err := filesService.SeedOfficialSystemAvatar(ctx); err != nil {
return fmt.Errorf("seed official system avatar: %w", err)
} else if seeded {
logger.Info("官方系统账号头像种子导入完成", zap.Int64("photo_id", domain.OfficialSystemUserPhotoID))
logger.Info("official system account avatar seed import complete", zap.Int64("photo_id", domain.OfficialSystemUserPhotoID))
}
if seeded, err := filesService.SeedBotFatherAvatar(ctx); err != nil {
return fmt.Errorf("seed botfather avatar: %w", err)
} else if seeded {
logger.Info("BotFather 头像种子导入完成", zap.Int64("photo_id", domain.BotFatherUserPhotoID))
logger.Info("BotFather avatar seed import complete", zap.Int64("photo_id", domain.BotFatherUserPhotoID))
}
if seeded, err := filesService.SeedStickersBotAvatar(ctx); err != nil {
return fmt.Errorf("seed stickers bot avatar: %w", err)
} else if seeded {
logger.Info("@Stickers 头像种子导入完成", zap.Int64("photo_id", domain.StickersBotUserPhotoID))
logger.Info("@Stickers avatar seed import complete", zap.Int64("photo_id", domain.StickersBotUserPhotoID))
}
if seeded, err := filesService.SeedChatBotAvatar(ctx); err != nil {
return fmt.Errorf("seed chatbot avatar: %w", err)
} else if seeded {
logger.Info("@ChatBot 头像种子导入完成", zap.Int64("photo_id", domain.ChatBotUserPhotoID))
logger.Info("@ChatBot avatar seed import complete", zap.Int64("photo_id", domain.ChatBotUserPhotoID))
}
if stats, err := filesService.WarmCaches(ctx); err != nil {
logger.Warn("媒体资源缓存预热失败", zap.Error(err))
logger.Warn("media resource cache warmup failed", zap.Error(err))
} else if stats.StickerSets > 0 || stats.Documents > 0 || stats.Blobs > 0 {
logger.Info("媒体资源缓存预热完成",
logger.Info("media resource cache warmup complete",
zap.Int("sticker_sets", stats.StickerSets),
zap.Int("documents", stats.Documents),
zap.Int("blobs", stats.Blobs),
)
}
// 默认 emoji status 系统集:从 animated_emoji 精选合成(幂等,已 seed 的存量
// 库重启后自动补上);缺失时 premium 用户的 status 选择器会是空的。
// Default emoji status system set: curated from animated_emoji (idempotent —
// an already-seeded install picks this back up automatically after restart);
// without it, premium users' status picker would be empty.
if count, created, err := filesService.EnsureDefaultEmojiStatusSet(ctx); err != nil {
logger.Warn("默认 emoji status 系统集合成失败", zap.Error(err))
logger.Warn("default emoji status system set assembly failed", zap.Error(err))
} else if created {
logger.Info("默认 emoji status 系统集已合成", zap.Int("documents", count))
logger.Info("default emoji status system set assembled", zap.Int("documents", count))
}
langPackStore := postgres.NewLangPackStore(pool)
passwordStore := postgres.NewPasswordStore(pool)
@ -576,16 +580,19 @@ func run(logger *zap.Logger) error {
if seeded, err := langPackService.SeedDirectory(ctx, cfg.LangPackSeedDir); err != nil {
return fmt.Errorf("seed langpack: %w", err)
} else if seeded > 0 {
logger.Info("语言包种子导入完成", zap.String("dir", cfg.LangPackSeedDir), zap.Int("strings", seeded))
logger.Info("langpack seed import complete", zap.String("dir", cfg.LangPackSeedDir), zap.Int("strings", seeded))
}
// 国家区号目录:把 catalog 固化的官方全量(~235 国)幂等 upsert 进 PG,覆盖迁移里仅
// seed 的 2 国(US/CN)默认值。否则 countries 表非空,ListCountries 返回那 2 行就会
// 绕过 catalog,登录页/号码格式只显示 2 国。upsert 失败仅告警不阻断启动(回退旧 2 行)。
// Country dialing code catalog: idempotently upsert the full official set
// (~235 countries) baked into the catalog into PG, overriding the migration's
// 2-country (US/CN) seed default. Otherwise the countries table is non-empty,
// ListCountries returns just those 2 rows and bypasses the catalog, so the
// login page/number formatting would only ever show 2 countries. An upsert
// failure only warns and doesn't block startup (falls back to the old 2 rows).
if cs := catalog.Countries().Countries; len(cs) > 0 {
if err := helpStore.UpsertCountries(ctx, cs); err != nil {
logger.Warn("国家区号种子导入失败", zap.Error(err))
logger.Warn("country dialing code seed import failed", zap.Error(err))
} else {
logger.Info("国家区号种子导入完成", zap.Int("countries", len(cs)))
logger.Info("country dialing code seed import complete", zap.Int("countries", len(cs)))
}
}
@ -626,7 +633,7 @@ func run(logger *zap.Logger) error {
return fmt.Errorf("configure OTP webhook: %w", err)
}
webhookSender = configured
logger.Info("OTP Webhook 投递已启用",
logger.Info("OTP webhook delivery enabled",
zap.Bool("phone", cfg.PhoneCodeDeliveryProvider == "webhook"),
zap.Bool("email", (cfg.LoginEmailEnable || cfg.EmailSignupEnable) && cfg.EmailCodeDeliveryProvider == "webhook"))
}
@ -732,12 +739,13 @@ func run(logger *zap.Logger) error {
defer t.Close()
turnService = t
}
// 服务端重启恢复SFU 状态全失,把全部活跃通话的参与者批量置 leftversion++
// 客户端经 checkGroupCall 发现自己 ssrc 消失后自动 rejoin。
// Restart recovery: SFU state is entirely lost, so mark every active call's
// participants left in bulk (version++); clients notice their own ssrc is
// gone via checkGroupCall and rejoin automatically.
if calls, err := groupCallsService.ResetAllParticipants(ctx, int(time.Now().Unix())); err != nil {
logger.Warn("重启清理群通话参与者失败", zap.Error(err))
logger.Warn("clearing group call participants on restart failed", zap.Error(err))
} else if len(calls) > 0 {
logger.Info("重启清理群通话参与者", zap.Int("calls", len(calls)))
logger.Info("cleared group call participants on restart", zap.Int("calls", len(calls)))
}
phoneService := phoneapp.NewService(phoneapp.Config{
RingTimeout: cfg.CallRingTimeout,
@ -835,7 +843,7 @@ func run(logger *zap.Logger) error {
auth.WithCodeMaxAttempts(cfg.AuthCodeMaxAttempts),
auth.WithPhoneCodeDelivery(phoneCodeSender, cfg.PhoneCodeLength),
auth.WithOTPDeliveryFailureObserver(func(_ context.Context, request otpdelivery.Request, err error) {
logger.Named("otp").Warn("附加 OTP provider 投递失败777000 App-code 保持有效",
logger.Named("otp").Warn("supplementary OTP provider delivery failed, 777000 app-code login remains valid",
zap.String("delivery_id", request.DeliveryID),
zap.String("purpose", string(request.Purpose)),
zap.String("channel", string(request.Channel)),
@ -1079,7 +1087,7 @@ func run(logger *zap.Logger) error {
OutboundTrackedGlobalMaxBytes: cfg.MTProtoOutboundTrackedGlobalMaxBytes,
OutboundWriteGlobalMaxBytes: cfg.MTProtoOutboundWriteGlobalMaxBytes,
OnServing: func(_ net.Addr) {
logger.Info("telesrv 服务就绪",
logger.Info("telesrv service ready",
zap.String("listen", cfg.ListenAddr),
zap.String("advertise", net.JoinHostPort(cfg.AdvertiseIP, portStr)),
zap.Int("pid", os.Getpid()),