feat(mqtt): console_log.mqtt 控制连接/断开/订阅/取消订阅日志

为 meshtasticFilterHook 增加 consoleLog 字段;当 console_log.mqtt=true 时
向 stderr 输出 4 类事件(带 client_id、username、remote IP:port):

- OnSessionEstablished → [mqtt] connect ...     (认证后才打,避开被 IP 屏蔽拒掉的客户端)
- OnDisconnect        → [mqtt] disconnect ...   (含 expire 和 mochi 上报的断开原因)
- OnSubscribed        → [mqtt] subscribe ...    (每个 filter 一行)
- OnUnsubscribed      → [mqtt] unsubscribe ...  (每个 filter 一行)

Provides() 同步多声明这 4 个事件类型;关闭开关时各 handler 立即返回。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-19 16:21:55 +08:00
co-authored by Claude
parent 52da224a19
commit 937580e24f
+57 -2
View File
@@ -53,6 +53,7 @@ type meshtasticFilterHook struct {
settings *rspkg.Cache
pkiResolver func(toNodeNum, fromNodeNum uint32) ([]byte, []byte, bool)
autoAcker func(record map[string]any)
consoleLog bool // 控制台是否打印 MQTT 连接/订阅事件
}
// ID 返回用于识别 Meshtastic payload 过滤器的 hook 名称。
@@ -60,9 +61,14 @@ func (h *meshtasticFilterHook) ID() string {
return "meshtastic-filter"
}
// Provides 声明该 hook 处理客户端发布消息
// Provides 声明该 hook 处理客户端连接、订阅与发布事件
func (h *meshtasticFilterHook) Provides(b byte) bool {
return b == mqtt.OnConnect || b == mqtt.OnPublish
return b == mqtt.OnConnect ||
b == mqtt.OnPublish ||
b == mqtt.OnSessionEstablished ||
b == mqtt.OnDisconnect ||
b == mqtt.OnSubscribed ||
b == mqtt.OnUnsubscribed
}
// OnConnect 在 MQTT 会话建立前拒绝命中 IP 屏蔽表的客户端。
@@ -75,6 +81,54 @@ func (h *meshtasticFilterHook) OnConnect(cl *mqtt.Client, pk packets.Packet) err
return nil
}
// OnSessionEstablished 在客户端通过认证、会话建立后打印连接日志。
func (h *meshtasticFilterHook) OnSessionEstablished(cl *mqtt.Client, pk packets.Packet) {
if !h.consoleLog {
return
}
info := mqttClientInfoFromClient(cl)
fmt.Fprintf(os.Stderr, "[mqtt] connect client_id=%s username=%s remote=%s:%s\n",
info.ClientID, info.Username, info.RemoteHost, info.RemotePort)
}
// OnDisconnect 在客户端断开时打印日志,含触发原因。
func (h *meshtasticFilterHook) OnDisconnect(cl *mqtt.Client, err error, expire bool) {
if !h.consoleLog {
return
}
info := mqttClientInfoFromClient(cl)
reason := "client closed"
if err != nil {
reason = err.Error()
}
fmt.Fprintf(os.Stderr, "[mqtt] disconnect client_id=%s username=%s remote=%s:%s expire=%t reason=%s\n",
info.ClientID, info.Username, info.RemoteHost, info.RemotePort, expire, reason)
}
// OnSubscribed 客户端订阅成功后打印订阅的 topic filter 列表。
func (h *meshtasticFilterHook) OnSubscribed(cl *mqtt.Client, pk packets.Packet, reasonCodes []byte) {
if !h.consoleLog {
return
}
info := mqttClientInfoFromClient(cl)
for _, sub := range pk.Filters {
fmt.Fprintf(os.Stderr, "[mqtt] subscribe client_id=%s username=%s remote=%s:%s topic=%s\n",
info.ClientID, info.Username, info.RemoteHost, info.RemotePort, sub.Filter)
}
}
// OnUnsubscribed 客户端取消订阅后打印日志。
func (h *meshtasticFilterHook) OnUnsubscribed(cl *mqtt.Client, pk packets.Packet) {
if !h.consoleLog {
return
}
info := mqttClientInfoFromClient(cl)
for _, sub := range pk.Filters {
fmt.Fprintf(os.Stderr, "[mqtt] unsubscribe client_id=%s username=%s remote=%s:%s topic=%s\n",
info.ClientID, info.Username, info.RemoteHost, info.RemotePort, sub.Filter)
}
}
// OnPublish 在 broker 转发消息前校验 payload;无效消息会被拒绝并丢弃。
func (h *meshtasticFilterHook) OnPublish(cl *mqtt.Client, pk packets.Packet) (packets.Packet, error) {
valid, _, record := mqtpp.MQTTPP(pk.TopicName, pk.Payload, h.key, mqtpp.Options{
@@ -398,6 +452,7 @@ func startMQTTServer(cfg *configpkg.Config, store *storepkg.Store, dbQueue *stor
blocking: blocking,
settings: settings,
pkiResolver: botpkg.NewPKIKeyResolver(store),
consoleLog: cfg.ConsoleLog.MQTT,
}
if err := server.AddHook(hook, nil); err != nil {
return nil, nil, "", err