-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathlx.go
More file actions
138 lines (119 loc) · 2.72 KB
/
lx.go
File metadata and controls
138 lines (119 loc) · 2.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package lx
import (
"encoding/json"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"sync"
)
var traceMu sync.Mutex
type tracePayload struct {
Kind string `json:"kind"`
Function string `json:"function"`
Value interface{} `json:"value"`
File string `json:"file"`
Line int `json:"line"`
}
// Gen captures the prompt at runtime when LX_MODE=capture and LX_TRACE_TOKEN is set.
// Otherwise it is a no-op.
func Gen(prompt string) {
if os.Getenv("LX_MODE") != "capture" {
return
}
token := os.Getenv("LX_TRACE_TOKEN")
if token == "" {
// Safe default: if token missing, do not emit traces.
return
}
pc, file, line, ok := runtime.Caller(1)
if !ok {
return
}
fn := runtime.FuncForPC(pc)
if fn == nil {
return
}
sendTrace(token, tracePayload{
Kind: "INPUT",
Function: fn.Name(),
Value: prompt,
File: file,
Line: line,
})
}
// Spy captures return values at runtime when LX_MODE=capture and LX_TRACE_TOKEN is set.
// Otherwise it returns val unchanged.
func Spy[T any](funcName string, val T) T {
if os.Getenv("LX_MODE") != "capture" {
return val
}
token := os.Getenv("LX_TRACE_TOKEN")
if token == "" {
return val
}
_, file, line, _ := runtime.Caller(1)
sendTrace(token, tracePayload{
Kind: "OUTPUT",
Function: funcName,
Value: val,
File: file,
Line: line,
})
return val
}
func sendTrace(token string, p tracePayload) {
// Optional bound to prevent huge trace lines (DoS risk).
maxBytes := traceMaxBytes()
// Marshal once; if too big, replace Value with a compact summary.
b, err := json.Marshal(p)
if err != nil {
return
}
if maxBytes > 0 && len(b) > maxBytes {
p.Value = fmt.Sprintf("[lx] value omitted (trace %d bytes > max %d)", len(b), maxBytes)
b, err = json.Marshal(p)
if err != nil {
return
}
}
start := "LX_TRACE_START_" + token
end := "LX_TRACE_END_" + token
// Mutex reduces interleaving from concurrent goroutines.
traceMu.Lock()
defer traceMu.Unlock()
// Single line output for robust scanner parsing.
fmt.Printf("%s%s%s\n", start, string(b), end)
}
func traceMaxBytes() int {
// Default 64KB.
def := 64 * 1024
s := strings.TrimSpace(os.Getenv("LX_TRACE_MAX_BYTES"))
if s == "" {
return def
}
n, err := strconv.Atoi(s)
if err != nil || n <= 0 {
return def
}
return n
}
func SpyVoid(funcName string) {
if os.Getenv("LX_MODE") != "capture" {
return
}
token := os.Getenv("LX_TRACE_TOKEN")
if token == "" {
return
}
_, file, line, _ := runtime.Caller(1)
// Value에 nil을 명시적으로 넣습니다.
sendTrace(token, tracePayload{
Kind: "OUTPUT",
Function: funcName,
Value: nil, // JSON으로 변환되면 null이 됩니다.
File: file,
Line: line,
})
}