Skip to content

Commit 3d43b8c

Browse files
mapleafgoclaude
andcommitted
refactor(api): 统一回调为 CoreSetEventCallback,新增日志推送,移除 QueryLogs
- 3 个独立回调 (URLTest/ModeUpdate/ConnEvent) 合并为单一 CoreSetEventCallback - 事件类型: 0=Log, 1=URLTest, 2=ModeUpdate, 3=ConnEvent - 日志通过回调实时推送,移除内存环形缓冲和 QueryLogs 查询方法 - 修复 FFI 回调内存管理: ownership transfer 替代 defer C.free (dart-lang/sdk#54554) - Mobile SDK: 3 个 Listener 接口合并为 EventListener.OnEvent Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2713938 commit 3d43b8c

8 files changed

Lines changed: 154 additions & 320 deletions

File tree

cmd/lib/exports.go

Lines changed: 14 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,8 @@ package main
33
/*
44
#include <stdlib.h>
55
6-
typedef void (*VoidCallback)();
7-
typedef void (*StringCallback)(const char*);
8-
typedef void (*IntStringCallback)(int, const char*);
9-
10-
static void invokeVoidCB(VoidCallback cb) { cb(); }
11-
static void invokeStringCB(StringCallback cb, const char* s) { cb(s); }
12-
static void invokeIntStringCB(IntStringCallback cb, int i, const char* s) { cb(i, s); }
6+
typedef void (*EventCallback)(int, const char*);
7+
static void invokeEventCB(EventCallback cb, int t, const char* s) { cb(t, s); }
138
*/
149
import "C"
1510

@@ -22,34 +17,17 @@ import (
2217

2318
var api = ffi.Create()
2419

25-
var (
26-
cURLTestCB unsafe.Pointer
27-
cModeUpdateCB unsafe.Pointer
28-
cConnEventCB unsafe.Pointer
29-
)
20+
var cEventCB unsafe.Pointer
3021

3122
func init() {
32-
api.SetCallbackFuncs(
33-
func() {
34-
if cURLTestCB != nil {
35-
C.invokeVoidCB(C.VoidCallback(cURLTestCB))
36-
}
37-
},
38-
func(mode string) {
39-
if cModeUpdateCB != nil {
40-
cs := C.CString(mode)
41-
defer C.free(unsafe.Pointer(cs))
42-
C.invokeStringCB(C.StringCallback(cModeUpdateCB), cs)
43-
}
44-
},
45-
func(eventType int32, connJSON string) {
46-
if cConnEventCB != nil {
47-
cs := C.CString(connJSON)
48-
defer C.free(unsafe.Pointer(cs))
49-
C.invokeIntStringCB(C.IntStringCallback(cConnEventCB), C.int(eventType), cs)
50-
}
51-
},
52-
)
23+
api.SetCallbackFuncs(func(eventType int32, json string) {
24+
if cEventCB != nil {
25+
// Ownership transfer: Dart side must call CoreFreeString after copying.
26+
// Do NOT free here — NativeCallable.listener is async (dart-lang/sdk#54554).
27+
cs := C.CString(json)
28+
C.invokeEventCB(C.EventCallback(cEventCB), C.int(eventType), cs)
29+
}
30+
})
5331
}
5432

5533
func resultJSON(err error) *C.char {
@@ -136,9 +114,6 @@ func CoreQueryProxies() *C.char { return cString(api.QueryProxies()) }
136114
//export CoreQueryStats
137115
func CoreQueryStats() *C.char { return cString(api.QueryStats()) }
138116

139-
//export CoreQueryLogs
140-
func CoreQueryLogs() *C.char { return cString(api.QueryLogs()) }
141-
142117
//export CoreQueryConnections
143118
func CoreQueryConnections() *C.char { return cString(api.QueryConnections()) }
144119

@@ -196,13 +171,7 @@ func CoreSetMemoryLimit(bytes C.longlong) { api.SetMemoryLimit(int64(bytes)) }
196171
//export CoreGetVersion
197172
func CoreGetVersion() *C.char { return cString(api.Version()) }
198173

199-
// --- Event Callbacks ---
200-
201-
//export CoreSetURLTestCallback
202-
func CoreSetURLTestCallback(cb unsafe.Pointer) { cURLTestCB = cb }
203-
204-
//export CoreSetModeUpdateCallback
205-
func CoreSetModeUpdateCallback(cb unsafe.Pointer) { cModeUpdateCB = cb }
174+
// --- Event Callback ---
206175

207-
//export CoreSetConnEventCallback
208-
func CoreSetConnEventCallback(cb unsafe.Pointer) { cConnEventCB = cb }
176+
//export CoreSetEventCallback
177+
func CoreSetEventCallback(cb unsafe.Pointer) { cEventCB = cb }

core/logging.go

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@ import (
77
"log/slog"
88
"os"
99
"strings"
10-
"sync"
1110
"sync/atomic"
1211
)
1312

14-
const maxCoreLogEntries = 500
15-
1613
// Log level constants. Lower number = higher severity.
1714
const (
1815
LogLevelError int32 = 2
@@ -23,18 +20,18 @@ const (
2320
)
2421

2522
var (
26-
coreLogMu sync.Mutex
27-
28-
// Fixed-size ring buffer avoids slice-shift memory waste.
29-
coreLogRing [maxCoreLogEntries]LogEntry
30-
coreLogPos int // next write position
31-
coreLogLen int // current length (0..maxCoreLogEntries)
32-
3323
// Runtime log level filter. Lower number = higher severity.
3424
// Error=2, Warn=3, Info=4, Debug=5, Trace=6.
3525
coreLogLevel atomic.Int32
26+
27+
// Global log event callback, set via SetOnLogEvent.
28+
onLogEvent func(int32, string)
3629
)
3730

31+
// SetOnLogEvent registers a callback invoked for each new log entry.
32+
// The callback receives (EventLog, json) where json is a serialized LogEntry.
33+
func SetOnLogEvent(fn func(int32, string)) { onLogEvent = fn }
34+
3835
func init() {
3936
coreLogLevel.Store(LogLevelInfo)
4037
slog.SetDefault(slog.New(&coreLogHandler{}))
@@ -46,9 +43,9 @@ func SetLogLevel(level int32) { coreLogLevel.Store(level) }
4643
// GetLogLevel returns the current minimum log level.
4744
func GetLogLevel() int32 { return coreLogLevel.Load() }
4845

49-
// coreLogHandler implements slog.Handler, routing log entries to an in-memory
50-
// ring buffer and the event callback. No file I/O is performed; the frontend
51-
// handles all log persistence.
46+
// coreLogHandler implements slog.Handler, routing log entries to stderr
47+
// and the event callback. No in-memory retention — the frontend handles
48+
// log persistence via the callback.
5249
type coreLogHandler struct {
5350
extra string // accumulated key=value pairs from WithAttrs
5451
}
@@ -71,19 +68,15 @@ func (h *coreLogHandler) Handle(_ context.Context, r slog.Record) error {
7168
// Always mirror to stderr for debugging (gomobile forwards to Android logcat).
7269
fmt.Fprintln(os.Stderr, b.String())
7370

74-
entry := LogEntry{
75-
Level: slogToCoreLevel(r.Level),
76-
Message: b.String(),
77-
Timestamp: r.Time.UnixMilli(),
71+
if onLogEvent != nil {
72+
data, _ := json.Marshal(LogEntry{
73+
Level: slogToCoreLevel(r.Level),
74+
Message: b.String(),
75+
Timestamp: r.Time.UnixMilli(),
76+
})
77+
onLogEvent(EventLog, string(data))
7878
}
7979

80-
coreLogMu.Lock()
81-
coreLogRing[coreLogPos] = entry
82-
coreLogPos = (coreLogPos + 1) % maxCoreLogEntries
83-
if coreLogLen < maxCoreLogEntries {
84-
coreLogLen++
85-
}
86-
coreLogMu.Unlock()
8780
return nil
8881
}
8982

@@ -152,26 +145,3 @@ func syncLogLevelFromConfig(jsonContent string) {
152145
slog.Info("[syncLogLevel] syncing log level", "configLevel", cfg.Log.Level, "oldLevel", oldLevel, "newLevel", newLevel)
153146
SetLogLevel(newLevel)
154147
}
155-
156-
func queryCoreLogs() string {
157-
entries := queryCoreLogEntries()
158-
if len(entries) == 0 {
159-
return "[]"
160-
}
161-
data, _ := json.Marshal(entries)
162-
return string(data)
163-
}
164-
165-
func queryCoreLogEntries() []LogEntry {
166-
coreLogMu.Lock()
167-
defer coreLogMu.Unlock()
168-
if coreLogLen == 0 {
169-
return nil
170-
}
171-
entries := make([]LogEntry, 0, coreLogLen)
172-
start := (coreLogPos - coreLogLen + maxCoreLogEntries) % maxCoreLogEntries
173-
for i := range coreLogLen {
174-
entries = append(entries, coreLogRing[(start+i)%maxCoreLogEntries])
175-
}
176-
return entries
177-
}

core/logging_test.go

Lines changed: 42 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -4,100 +4,65 @@ import (
44
"encoding/json"
55
"log/slog"
66
"strings"
7+
"sync"
78
"testing"
89
)
910

10-
func TestLogging_RingBufferOrder(t *testing.T) {
11-
coreLogMu.Lock()
12-
coreLogPos = 0
13-
coreLogLen = 0
14-
coreLogMu.Unlock()
11+
func TestLogging_Callback(t *testing.T) {
12+
var mu sync.Mutex
13+
var gotEntries []LogEntry
1514

16-
for i := 0; i < 5; i++ {
17-
slog.Info("test message", "index", i)
18-
}
19-
20-
got := queryCoreLogs()
21-
var entries []LogEntry
22-
if err := json.Unmarshal([]byte(got), &entries); err != nil {
23-
t.Fatalf("unmarshal: %v", err)
24-
}
25-
if len(entries) != 5 {
26-
t.Fatalf("expected 5 entries, got %d", len(entries))
27-
}
28-
for i, e := range entries {
29-
if !strings.Contains(e.Message, "test message") {
30-
t.Errorf("entry[%d] = %q, should contain 'test message'", i, e.Message)
15+
SetOnLogEvent(func(eventType int32, jsonStr string) {
16+
if eventType != EventLog {
17+
t.Errorf("eventType = %d, want %d", eventType, EventLog)
3118
}
32-
}
33-
}
34-
35-
func TestLogging_RingBufferOverflow(t *testing.T) {
36-
coreLogMu.Lock()
37-
coreLogPos = 0
38-
coreLogLen = 0
39-
coreLogMu.Unlock()
40-
41-
prevLevel := GetLogLevel()
42-
SetLogLevel(6) // Trace
43-
defer SetLogLevel(prevLevel)
44-
45-
for i := 0; i < maxCoreLogEntries+100; i++ {
46-
slog.Debug("overflow-test", "i", i)
47-
}
19+
var entry LogEntry
20+
if err := json.Unmarshal([]byte(jsonStr), &entry); err != nil {
21+
t.Errorf("unmarshal: %v", err)
22+
}
23+
mu.Lock()
24+
gotEntries = append(gotEntries, entry)
25+
mu.Unlock()
26+
})
27+
defer SetOnLogEvent(nil)
4828

49-
coreLogMu.Lock()
50-
if coreLogLen != maxCoreLogEntries {
51-
t.Errorf("coreLogLen = %d, want %d", coreLogLen, maxCoreLogEntries)
52-
}
53-
coreLogMu.Unlock()
29+
slog.Info("callback-test", "key", "val")
5430

55-
got := queryCoreLogs()
56-
var entries []LogEntry
57-
if err := json.Unmarshal([]byte(got), &entries); err != nil {
58-
t.Fatalf("unmarshal: %v", err)
59-
}
60-
if len(entries) != maxCoreLogEntries {
61-
t.Errorf("queryCoreLogs returned %d entries, want %d", len(entries), maxCoreLogEntries)
31+
mu.Lock()
32+
defer mu.Unlock()
33+
if len(gotEntries) == 0 {
34+
t.Fatal("expected at least one log entry via callback")
6235
}
63-
if !strings.Contains(entries[0].Message, "overflow-test") {
64-
t.Errorf("oldest entry = %q, should contain overflow-test", entries[0].Message)
65-
}
66-
}
67-
68-
func TestLogging_QueryEmpty(t *testing.T) {
69-
coreLogMu.Lock()
70-
coreLogPos = 0
71-
coreLogLen = 0
72-
coreLogMu.Unlock()
73-
74-
got := queryCoreLogs()
75-
if got != "[]" {
76-
t.Errorf("empty ring = %q, want []", got)
36+
if !strings.Contains(gotEntries[len(gotEntries)-1].Message, "callback-test") {
37+
t.Errorf("entry = %q, should contain callback-test", gotEntries[len(gotEntries)-1].Message)
7738
}
7839
}
7940

8041
func TestLogging_HandleAttrs(t *testing.T) {
81-
coreLogMu.Lock()
82-
coreLogPos = 0
83-
coreLogLen = 0
84-
coreLogMu.Unlock()
42+
var mu sync.Mutex
43+
var messages []string
44+
45+
SetOnLogEvent(func(_ int32, jsonStr string) {
46+
var entry LogEntry
47+
json.Unmarshal([]byte(jsonStr), &entry)
48+
mu.Lock()
49+
messages = append(messages, entry.Message)
50+
mu.Unlock()
51+
})
52+
defer SetOnLogEvent(nil)
8553

8654
slog.Info("with-attrs", "key1", "val1", "key2", 42)
8755

88-
got := queryCoreLogs()
89-
var entries []LogEntry
90-
if err := json.Unmarshal([]byte(got), &entries); err != nil {
91-
t.Fatalf("unmarshal: %v", err)
92-
}
93-
if len(entries) != 1 {
94-
t.Fatalf("expected 1 entry, got %d", len(entries))
56+
mu.Lock()
57+
defer mu.Unlock()
58+
if len(messages) == 0 {
59+
t.Fatal("expected at least one entry")
9560
}
96-
if !strings.Contains(entries[0].Message, "key1=val1") {
97-
t.Errorf("entry should contain attrs: %q", entries[0].Message)
61+
if !strings.Contains(messages[len(messages)-1], "key1=val1") {
62+
t.Errorf("entry should contain key1=val1: %q", messages[len(messages)-1])
9863
}
99-
if !strings.Contains(entries[0].Message, "key2=42") {
100-
t.Errorf("entry should contain attrs: %q", entries[0].Message)
64+
if !strings.Contains(messages[len(messages)-1], "key2=42") {
65+
t.Errorf("entry should contain key2=42: %q", messages[len(messages)-1])
10166
}
10267
}
10368

0 commit comments

Comments
 (0)