如何在Go蓝牙服务(ble-service)中读取journal日志
解决systemd服务运行时通过蓝牙流式传输自身日志的问题
我用Go编写了一个蓝牙服务ble-service,想要添加一个GATT特征,通过蓝牙流式传输该服务的日志。计划使用github.com/coreos/go-systemd/v22/sdjournal在GATT通知器中访问本地journal,读取日志直到无剩余,再等待新日志。
当前代码如下:
// +build linux package service import ( "fmt" "regexp" "time" "github.com/coreos/go-systemd/v22/sdjournal" "github.com/paypal/gatt" ) var ( attrLogServiceUUID = gatt.UUID16(0xD200) attrLogs = BleAttribute{uuid: gatt.UUID16(0xD201), name: "Ble Service Logs"} ) var messageRe = regexp.MustCompile(`"message"=>"(.?)"`) func nextJournalLog(journal *sdjournal.Journal) (msg string, hasMore bool, err error) { i, err := journal.Next() if err != nil { return "", i == 0, err } if i == 0 { return "", false, err } fmt.Println("Cursor ", i) entry, err := journal.GetEntry() if err != nil { return "", true, err } fmt.Println("Entry: ", entry) msgDataValue, err := journal.GetDataValue("MESSAGE") if err != nil { return "", true, err } fmt.Println("Message: ", msgDataValue) matches := messageRe.FindStringSubmatch(msgDataValue) fmt.Println(matches) if len(matches) > 1 { return matches[1], true, nil } return "", true, nil } func NewLogService() (s *gatt.Service) { s = newService(attrLogServiceUUID) logsChar := addCharacteristic(s, attrLogs) logsChar.HandleNotifyFunc( func(r gatt.Request, n gatt.Notifier) { defer logBLENotify(r, logsChar, "Logs")() journal, err := sdjournal.NewJournal() if err != nil { fmt.Println("Error creating journal", err) return } defer func() { journal.Close() }() err = journal.SeekTail() if err != nil { fmt.Println("Error seeking journal tail", err) return } cursor, err := journal.PreviousSkip(3) if err != nil { fmt.Println("Error seeking journal tail", err) return } fmt.Println("Cursor ", cursor) for !n.Done() { msg, hasMore, err := nextJournalLog(journal) time.Sleep(time.Second) if err != nil { fmt.Println(err) continue } if !hasMore { fmt.Println("Waiting for more logs") time.Sleep(3 * time.Second) continue } _, err = n.Write([]byte(msg)) if err != nil { fmt.Println("Failed to write msg", msg) continue } } }) return }
问题:作为systemd服务运行时,订阅日志特征看不到输出;直接运行可执行文件时,能看到上次作为systemd服务运行的journald日志。需要解决如何在服务运行时访问自身日志。
解决方案
1. 过滤当前服务的journal条目
默认sdjournal.NewJournal()会读取所有系统和用户的journal,必须添加过滤器,只获取当前ble-service的日志:
// 创建journal后添加过滤规则 err = journal.AddMatch("_SYSTEMD_UNIT=ble-service.service") if err != nil { fmt.Println("Error adding systemd unit filter", err) return } // 额外添加PID过滤,确保只获取当前进程的日志 pid := os.Getpid() err = journal.AddMatch(fmt.Sprintf("_PID=%d", pid)) if err != nil { fmt.Println("Error adding PID filter", err) return }
2. 修正日志定位逻辑
原代码SeekTail()后PreviousSkip(3)可能跳到其他服务的日志尾部,改成定位到当前日志尾部,准备监听新日志:
err = journal.SeekTail() if err != nil { fmt.Println("Error seeking journal tail", err) return } // 向前移动一个条目,确保Next()能获取后续新增的日志 _, err = journal.Previous() if err != nil { fmt.Println("Error moving to previous entry", err) return }
3. 移除多余的正则匹配
journal.GetDataValue("MESSAGE")直接返回原始日志内容,不需要解析JSON格式,删除正则表达式相关代码:
func nextJournalLog(journal *sdjournal.Journal) (msg string, hasMore bool, err error) { i, err := journal.Next() if err != nil { return "", i == 0, err } if i == 0 { return "", false, nil } msgDataValue, err := journal.GetDataValue("MESSAGE") if err != nil { return "", true, err } return msgDataValue, true, nil }
4. 使用journal.Wait()替代睡眠等待
原代码的time.Sleep()效率低且可能错过日志,改用journal.Wait()等待新日志条目:
for !n.Done() { msg, hasMore, err := nextJournalLog(journal) if err != nil { fmt.Println(err) continue } if hasMore { _, err = n.Write([]byte(msg + "\n")) // 加换行提升可读性 if err != nil { fmt.Println("Failed to write msg", msg) continue } } else { // 等待新日志,超时设为5秒避免永久阻塞 waitErr := journal.Wait(5 * time.Second) if waitErr != nil && waitErr != sdjournal.ErrTimeout { fmt.Println("Error waiting for journal updates:", waitErr) } } }
5. 确保服务有journal访问权限
如果服务以非root用户运行,需将用户加入systemd-journal组:
usermod -aG systemd-journal ble-service-user
同时修改systemd服务文件指定运行用户:
[Service] User=ble-service-user Group=ble-service-user
修改后的完整代码
// +build linux package service import ( "fmt" "os" "time" "github.com/coreos/go-systemd/v22/sdjournal" "github.com/paypal/gatt" ) var ( attrLogServiceUUID = gatt.UUID16(0xD200) attrLogs = BleAttribute{uuid: gatt.UUID16(0xD201), name: "Ble Service Logs"} ) func nextJournalLog(journal *sdjournal.Journal) (msg string, hasMore bool, err error) { i, err := journal.Next() if err != nil { return "", i == 0, err } if i == 0 { return "", false, nil } msgDataValue, err := journal.GetDataValue("MESSAGE") if err != nil { return "", true, err } return msgDataValue, true, nil } func NewLogService() (s *gatt.Service) { s = newService(attrLogServiceUUID) logsChar := addCharacteristic(s, attrLogs) logsChar.HandleNotifyFunc( func(r gatt.Request, n gatt.Notifier) { defer logBLENotify(r, logsChar, "Logs")() journal, err := sdjournal.NewJournal() if err != nil { fmt.Println("Error creating journal", err) return } defer func() { journal.Close() }() // 添加过滤器,只获取当前服务的日志 err = journal.AddMatch("_SYSTEMD_UNIT=ble-service.service") if err != nil { fmt.Println("Error adding systemd unit filter", err) return } // 添加PID过滤,确保只获取当前进程的日志 pid := os.Getpid() err = journal.AddMatch(fmt.Sprintf("_PID=%d", pid)) if err != nil { fmt.Println("Error adding PID filter", err) return } // 定位到日志尾部,准备监听新日志 err = journal.SeekTail() if err != nil { fmt.Println("Error seeking journal tail", err) return } // 向前移动一个条目,确保Next()能获取后续新增日志 _, err = journal.Previous() if err != nil { fmt.Println("Error moving to previous entry", err) return } for !n.Done() { msg, hasMore, err := nextJournalLog(journal) if err != nil { fmt.Println(err) continue } if hasMore { _, err = n.Write([]byte(msg + "\n")) if err != nil { fmt.Println("Failed to write msg", msg) continue } } else { // 等待新日志,超时5秒 waitErr := journal.Wait(5 * time.Second) if waitErr != nil && waitErr != sdjournal.ErrTimeout { fmt.Println("Error waiting for journal updates:", waitErr) } } } }) return s }
内容的提问来源于stack exchange,提问作者Rowan Baker-French
相关产品推荐
相关产品推荐

