Skip to content

Commit 39ee1dc

Browse files
feat: log-mode UI with banner and colorized requests
1 parent 303b8fc commit 39ee1dc

4 files changed

Lines changed: 95 additions & 0 deletions

File tree

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
module github.com/ArpitRajputGithub/httpsdev
22

33
go 1.26.5
4+
5+
require github.com/fatih/color v1.19.0
6+
7+
require (
8+
github.com/mattn/go-colorable v0.1.14 // indirect
9+
github.com/mattn/go-isatty v0.0.20 // indirect
10+
golang.org/x/sys v0.42.0 // indirect
11+
)

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
2+
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
3+
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
4+
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
5+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
6+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
7+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8+
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
9+
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=

terminal.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"os"
6+
)
7+
8+
func isTerminal(w io.Writer) bool {
9+
f, ok := w.(*os.File)
10+
if !ok {
11+
return false
12+
}
13+
stat, err := f.Stat()
14+
if err != nil {
15+
return false
16+
}
17+
return (stat.Mode() & os.ModeCharDevice) != 0
18+
}

ui_log.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"time"
7+
8+
"github.com/fatih/color"
9+
)
10+
11+
// runLogUI prints the startup banner then one line per incoming Event.
12+
// Returns after events channel is closed. Prints a summary on exit.
13+
func runLogUI(w io.Writer, listenAddr, upstream string, events <-chan Event) {
14+
color.NoColor = !isTerminal(w)
15+
16+
fmt.Fprintln(w)
17+
fmt.Fprintf(w, " %s httpsdev v%s\n\n", color.CyanString("▲"), Version)
18+
fmt.Fprintf(w, " %s Local: %s\n", color.GreenString("➜"), color.New(color.Bold).Sprintf("https://%s", listenAddr))
19+
fmt.Fprintf(w, " %s Upstream: %s\n", color.GreenString("➜"), upstream)
20+
fmt.Fprintf(w, " %s Cert: mkcert · valid 30 days\n\n", color.GreenString("➜"))
21+
fmt.Fprintf(w, " press Ctrl+C to quit\n\n")
22+
23+
var total, errors int
24+
var totalDur time.Duration
25+
start := time.Now()
26+
27+
for ev := range events {
28+
total++
29+
totalDur += ev.Duration
30+
if ev.Status >= 400 {
31+
errors++
32+
}
33+
fmt.Fprintln(w, formatEvent(ev))
34+
}
35+
36+
if total == 0 {
37+
return
38+
}
39+
avg := totalDur / time.Duration(total)
40+
fmt.Fprintf(w, "\nserved %d requests · %d errors · avg %s · uptime %s\n",
41+
total, errors, avg.Round(time.Millisecond), time.Since(start).Round(time.Second))
42+
}
43+
44+
func formatEvent(ev Event) string {
45+
statusColor := color.New(color.FgGreen)
46+
switch {
47+
case ev.Status >= 500:
48+
statusColor = color.New(color.FgRed, color.Bold)
49+
case ev.Status >= 400:
50+
statusColor = color.New(color.FgRed)
51+
case ev.Status >= 300:
52+
statusColor = color.New(color.FgYellow)
53+
}
54+
return fmt.Sprintf("%-6s %-30s %s %s",
55+
color.CyanString("%s", ev.Method),
56+
ev.Path,
57+
statusColor.Sprintf("%d", ev.Status),
58+
ev.Duration.Round(time.Millisecond),
59+
)
60+
}

0 commit comments

Comments
 (0)