Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@ Optional observability for startup and injection troubleshooting. All file/conso
| `DIX_TRACE_DI` | off | Console step-by-step DI trace (`di_trace ...`) |
| `DIX_DIAG_FILE` | off | Append `trace` / `error` / `llm` records to JSONL |
| `DIX_TRACE_FILE` | off | Append trace-only JSONL (falls back to `DIX_DIAG_FILE`) |
| `DIX_LLM_DIAG_MODE` | `human` | Log mode: `human` / `machine` / `dual` |

```bash
export DIX_TRACE_DI=true
Expand Down
1 change: 0 additions & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@ server.ListenAndServe(":8080")
| `DIX_TRACE_DI` | 关闭 | 控制台逐步 DI trace(`di_trace ...`) |
| `DIX_DIAG_FILE` | 关闭 | 追加写入 `trace` / `error` / `llm` JSONL |
| `DIX_TRACE_FILE` | 关闭 | 仅 trace 的 JSONL(未设置时回退到 `DIX_DIAG_FILE`) |
| `DIX_LLM_DIAG_MODE` | `human` | 日志模式:`human` / `machine` / `dual` |

```bash
export DIX_TRACE_DI=true
Expand Down
1 change: 0 additions & 1 deletion dixhttp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ If `DIX_DIAG_FILE` is not set, response returns `enabled=false` and empty record
"process": "my-app",
"hostname": "dev-mac",
"trace_di": true,
"llm_diag_mode": "dual",
"kind": "trace",
"event": "provider.call.start",
"occurred_at_unix_nano": 1700000000000000000,
Expand Down
2 changes: 1 addition & 1 deletion dixhttp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func (s *Server) HandleErrors(w http.ResponseWriter, r *http.Request) {

// HandleDiagnostics returns records from DIX_DIAG_FILE (JSONL).
// Query params:
// - kind: trace|error|llm (optional)
// - kind: trace|error (optional)
// - event: trace event fuzzy match (optional)
// - q: full-text search over record JSON (optional)
// - limit: optional positive integer, default 200, max 2000
Expand Down
76 changes: 76 additions & 0 deletions dixinternal/di_event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package dixinternal

import (
"sort"
"time"

"github.com/pubgo/dix/v2/dixtrace"
)

// di_event.go 把 DI 过程中的点事件(point events)统一发布到 tracer 事件流,
// console(DIX_TRACE_DI)与 diag file(DIX_DIAG_FILE)是它的两个订阅者。
// 此前这两路各自直连埋点(双轨),现收敛为"一次发布、多处订阅"。

// emitDIEvent 发布一个 DI 点事件到容器 trace 通道(私有 tracer 或全局)。
func (dix *Dix) emitDIEvent(event string, args ...any) {
dixtrace.EmitTo(dix.traceTracer, dixtrace.Event{
Operation: "di",
Event: event,
ContainerID: dix.containerID,
OccurredAt: time.Now().UnixNano(),
Attrs: dixtrace.TraceToAttrs(args...),
})
}

// consoleDISink 订阅点事件,在 DIX_TRACE_DI 开启时输出 `di_trace <event>` 日志。
type consoleDISink struct{}

func (consoleDISink) Write(e dixtrace.Event) {
if e.Operation != "di" || !shouldTraceDependencyFlow() || logger == nil {
return
}
logger.Info("di_trace "+e.Event, kvArgs(e.Attrs)...)
}

// diagTraceSink 订阅点事件,写入 DIX_DIAG_FILE 的 kind:trace 记录。
type diagTraceSink struct{}

func (diagTraceSink) Write(e dixtrace.Event) {
if e.Operation != "di" {
return
}
emitDiagFileTraceEvent(e.Event, kvArgs(e.Attrs)...)
}

// installDISinks 把 DI 点事件订阅者挂到容器私有 tracer 上。
func installDISinks(tr *dixtrace.Tracer) {
if tr == nil {
return
}
tr.AddSink(consoleDISink{})
tr.AddSink(diagTraceSink{})
}

func init() {
// 全局 tracer 默认挂载订阅者;console 是否输出由 DIX_TRACE_DI 逐条判定,
// diag file 是否落盘由 emitDiagFileTraceEvent 内部按环境变量判定。
dixtrace.AddDefaultSink(consoleDISink{})
dixtrace.AddDefaultSink(diagTraceSink{})
}

// kvArgs 把属性 map 展开为 slog 风格的 key,value 交替参数(key 排序,输出稳定)。
func kvArgs(m map[string]any) []any {
if len(m) == 0 {
return nil
}
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
out := make([]any, 0, len(keys)*2)
for _, k := range keys {
out = append(out, k, m[k])
}
return out
}
96 changes: 44 additions & 52 deletions dixinternal/diag_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,32 @@ var (
)

type diagFileRecord struct {
RecordID int64 `json:"record_id,omitempty"`
Source string `json:"source,omitempty"`
PID int `json:"pid,omitempty"`
Process string `json:"process,omitempty"`
Hostname string `json:"hostname,omitempty"`
TraceDI bool `json:"trace_di,omitempty"`
LLMDiagMode string `json:"llm_diag_mode,omitempty"`
Kind string `json:"kind"`
OccurredAt int64 `json:"occurred_at_unix_nano"`
Event string `json:"event,omitempty"`
Fields map[string]any `json:"fields,omitempty"`
Payload any `json:"payload,omitempty"`
RecordID int64 `json:"record_id,omitempty"`
Source string `json:"source,omitempty"`
PID int `json:"pid,omitempty"`
Process string `json:"process,omitempty"`
Hostname string `json:"hostname,omitempty"`
TraceDI bool `json:"trace_di,omitempty"`
Kind string `json:"kind"`
OccurredAt int64 `json:"occurred_at_unix_nano"`
Event string `json:"event,omitempty"`
Fields map[string]any `json:"fields,omitempty"`
Payload any `json:"payload,omitempty"`
}

// DiagFileRecord is an exported diagnostic record returned by file-query APIs.
type DiagFileRecord struct {
RecordID int64 `json:"record_id,omitempty"`
Source string `json:"source,omitempty"`
PID int `json:"pid,omitempty"`
Process string `json:"process,omitempty"`
Hostname string `json:"hostname,omitempty"`
TraceDI bool `json:"trace_di,omitempty"`
LLMDiagMode string `json:"llm_diag_mode,omitempty"`
Kind string `json:"kind"`
OccurredAt int64 `json:"occurred_at_unix_nano"`
Event string `json:"event,omitempty"`
Fields map[string]any `json:"fields,omitempty"`
Payload any `json:"payload,omitempty"`
RecordID int64 `json:"record_id,omitempty"`
Source string `json:"source,omitempty"`
PID int `json:"pid,omitempty"`
Process string `json:"process,omitempty"`
Hostname string `json:"hostname,omitempty"`
TraceDI bool `json:"trace_di,omitempty"`
Kind string `json:"kind"`
OccurredAt int64 `json:"occurred_at_unix_nano"`
Event string `json:"event,omitempty"`
Fields map[string]any `json:"fields,omitempty"`
Payload any `json:"payload,omitempty"`
}

// DiagFileQuery controls filtering and pagination for DIX_DIAG_FILE records.
Expand Down Expand Up @@ -85,18 +83,17 @@ func nextDiagRecordID() int64 {

func toDiagRecord(r diagFileRecord) DiagFileRecord {
return DiagFileRecord{
RecordID: r.RecordID,
Source: r.Source,
PID: r.PID,
Process: r.Process,
Hostname: r.Hostname,
TraceDI: r.TraceDI,
LLMDiagMode: r.LLMDiagMode,
Kind: r.Kind,
OccurredAt: r.OccurredAt,
Event: r.Event,
Fields: r.Fields,
Payload: r.Payload,
RecordID: r.RecordID,
Source: r.Source,
PID: r.PID,
Process: r.Process,
Hostname: r.Hostname,
TraceDI: r.TraceDI,
Kind: r.Kind,
OccurredAt: r.OccurredAt,
Event: r.Event,
Fields: r.Fields,
Payload: r.Payload,
}
}

Expand All @@ -106,18 +103,17 @@ func buildDiagRecord(kind, event string, fields map[string]any, payload any, occ
}
hostname, _ := os.Hostname()
return diagFileRecord{
RecordID: nextDiagRecordID(),
Source: "dix",
PID: os.Getpid(),
Process: filepath.Base(os.Args[0]),
Hostname: hostname,
TraceDI: shouldTraceDependencyFlow(),
LLMDiagMode: currentLLMDiagMode(),
Kind: kind,
OccurredAt: occurredAt,
Event: event,
Fields: fields,
Payload: payload,
RecordID: nextDiagRecordID(),
Source: "dix",
PID: os.Getpid(),
Process: filepath.Base(os.Args[0]),
Hostname: hostname,
TraceDI: shouldTraceDependencyFlow(),
Kind: kind,
OccurredAt: occurredAt,
Event: event,
Fields: fields,
Payload: payload,
}
}

Expand Down Expand Up @@ -201,10 +197,6 @@ func emitDiagFileErrorRecord(record recentErrorRecord) {
emitDiagFileRecord(buildDiagRecord("error", "", nil, record, record.Occurred.UnixNano()))
}

func emitDiagFileLLMRecord(payload any) {
emitDiagFileRecord(buildDiagRecord("llm", "", nil, payload, time.Now().UnixNano()))
}

func kvArgsToMap(args ...any) map[string]any {
if len(args) == 0 {
return nil
Expand Down
Loading
Loading