Skip to content

Commit aaf24d5

Browse files
committed
add mermaid exporter
1 parent 26948b8 commit aaf24d5

4 files changed

Lines changed: 138 additions & 4 deletions

File tree

examples/go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ module github.com/spdeepak/flowtracker/examples
33
go 1.24
44

55
require (
6+
github.com/confluentinc/confluent-kafka-go v1.9.2
67
github.com/spdeepak/flowtracker v0.0.3
78
github.com/spdeepak/flowtracker/confluent-kafka v0.0.1
89
)
910

10-
require github.com/confluentinc/confluent-kafka-go v1.9.2 // indirect
11-
1211
replace (
1312
github.com/spdeepak/flowtracker => ../
1413
github.com/spdeepak/flowtracker/confluent-kafka => ../addons/confluent-kafka/

examples/mermaid/mermaid.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/spdeepak/flowtracker"
7+
"github.com/spdeepak/flowtracker/examples"
8+
"github.com/spdeepak/flowtracker/exporters"
9+
)
10+
11+
func main() {
12+
mux := http.NewServeMux()
13+
mux.HandleFunc("/", examples.Handler)
14+
15+
slogExporter := exporters.MermaidExporter{}
16+
mw := flowtracker.NewMiddleware(flowtracker.WithExporter(&slogExporter), flowtracker.WithExporter(&exporters.SlogExporter{}))
17+
18+
http.ListenAndServe(":8080", mw(mux))
19+
}

examples/sankey/sankey.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ func main() {
1212
mux := http.NewServeMux()
1313
mux.HandleFunc("/", examples.Handler)
1414

15-
slogExporter := exporters.SankeyExporter{}
16-
mw := flowtracker.NewMiddleware(flowtracker.WithExporter(&slogExporter), flowtracker.WithExporter(&exporters.SlogExporter{}))
15+
sankeyExporter := exporters.SankeyExporter{}
16+
mw := flowtracker.NewMiddleware(flowtracker.WithExporter(&sankeyExporter), flowtracker.WithExporter(&exporters.SlogExporter{}))
1717

1818
http.ListenAndServe(":8080", mw(mux))
1919
}

exporters/mermaid.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package exporters
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
"strings"
7+
8+
"github.com/spdeepak/flowtracker"
9+
)
10+
11+
// MermaidExporter outputs the trace in Mermaid.js Flowchart syntax.
12+
type MermaidExporter struct {
13+
// Orientation can be "TD" (Top-Down) or "LR" (Left-Right). Default "TD".
14+
Orientation Orientation
15+
16+
// IncludeTags defines a specific list of tags to append to the span name.
17+
// All tags will be included if this is empty.
18+
IncludeTags []string
19+
}
20+
21+
type Orientation string
22+
23+
var (
24+
TopDown Orientation = "TD"
25+
LeftRight Orientation = "LR"
26+
)
27+
28+
// Export visual representation of output can be seen using https://mermaid.live/
29+
func (m *MermaidExporter) Export(tr *flowtracker.Trace) {
30+
var sb strings.Builder
31+
32+
// 1. Header
33+
sb.WriteString("\n----- MERMAID OUTPUT -----\n")
34+
sb.WriteString("```mermaid\n")
35+
36+
orient := m.Orientation
37+
if orient == "" {
38+
orient = TopDown
39+
}
40+
sb.WriteString(fmt.Sprintf("graph %s\n", orient))
41+
42+
// 2. Helper to escape strings
43+
escape := func(s string) string {
44+
return strings.ReplaceAll(s, "\"", "'")
45+
}
46+
47+
// 3. Pre-calculate Node Labels
48+
nodeLabels := make(map[string]string)
49+
50+
for _, span := range tr.Spans {
51+
name := span.Name
52+
var tagSuffixes []string
53+
54+
if span.Tags != nil && len(span.Tags) > 0 {
55+
var keysToDisplay []string
56+
57+
// Logic: Decide which keys to show
58+
if len(m.IncludeTags) > 0 {
59+
// Get only user-specified keys
60+
for _, k := range m.IncludeTags {
61+
if _, exists := span.Tags[k]; exists {
62+
keysToDisplay = append(keysToDisplay, k)
63+
}
64+
}
65+
// No need to sort strict list, user order is preserved
66+
} else {
67+
// Get ALL keys from the map
68+
for k := range span.Tags {
69+
keysToDisplay = append(keysToDisplay, k)
70+
}
71+
// Sort keys to ensure deterministic diagram output
72+
sort.Strings(keysToDisplay)
73+
}
74+
75+
// Build the display string
76+
for _, key := range keysToDisplay {
77+
val := span.Tags[key]
78+
tagSuffixes = append(tagSuffixes, fmt.Sprintf("%s:%s", key, val))
79+
}
80+
}
81+
82+
// Append tags to name: "SpanName (key:val, key2:val)"
83+
if len(tagSuffixes) > 0 {
84+
name = fmt.Sprintf("%s (%s)", name, strings.Join(tagSuffixes, ", "))
85+
}
86+
87+
nodeLabels[span.ID] = escape(name)
88+
}
89+
90+
// 4. Build Links
91+
for _, span := range tr.Spans {
92+
if span.ParentID == "" {
93+
continue
94+
}
95+
96+
parentLabel, pOk := nodeLabels[span.ParentID]
97+
childLabel, cOk := nodeLabels[span.ID]
98+
99+
if !pOk || !cOk {
100+
continue
101+
}
102+
103+
// Syntax: N<ID>["Label"] -->|Duration| N<ID>["Label"]
104+
sb.WriteString(fmt.Sprintf(" N%s[\"%s\"] -->|%dms| N%s[\"%s\"]\n",
105+
span.ParentID, parentLabel,
106+
span.Duration,
107+
span.ID, childLabel,
108+
))
109+
}
110+
111+
// 5. Footer
112+
sb.WriteString("```\n")
113+
sb.WriteString("--------------------------\n\n")
114+
115+
fmt.Print(sb.String())
116+
}

0 commit comments

Comments
 (0)