Compare commits

...

3 Commits

Author SHA1 Message Date
9b7c8d9d24 fix: writer 2024-06-25 16:36:43 +08:00
c13263fe0d fix: logger missing status while 404 2024-06-17 14:31:49 +08:00
d4fe4e0112 feat: refact default logger 2024-06-07 17:39:06 +08:00
10 changed files with 300 additions and 57 deletions

12
app.go
View File

@ -202,17 +202,19 @@ func (a *App) handleHTTPRequest(c *Ctx) {
} }
if len(allowed) > 0 { if len(allowed) > 0 {
c.handlers = a.combineHandlers() c.handlers = a.combineHandlers(a.config.MethodNotAllowedHandler)
serveError(c, a.config.MethodNotAllowedHandler) _ = c.Next()
return return
} }
} }
c.handlers = a.combineHandlers() c.handlers = a.combineHandlers(a.config.NotFoundHandler)
serveError(c, a.config.NotFoundHandler) _ = c.Next()
return
} }
func errorHandler(c *Ctx) error { func errorHandler(c *Ctx) error {
@ -271,6 +273,6 @@ func redirectRequest(c *Ctx) {
//debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) //debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
http.Redirect(c.writer, req, rURL, code) http.Redirect(c.Writer, req, rURL, code)
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()
} }

17
ctx.go
View File

@ -17,7 +17,7 @@ import (
type Ctx struct { type Ctx struct {
lock sync.Mutex lock sync.Mutex
writermem responseWriter writermem responseWriter
writer http.ResponseWriter Writer ResponseWriter
Request *http.Request Request *http.Request
path string path string
method string method string
@ -39,7 +39,6 @@ func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ct
ctx := &Ctx{ ctx := &Ctx{
lock: sync.Mutex{}, lock: sync.Mutex{},
writer: writer,
Request: request, Request: request,
path: request.URL.Path, path: request.URL.Path,
method: request.Method, method: request.Method,
@ -54,11 +53,13 @@ func newContext(app *App, writer http.ResponseWriter, request *http.Request) *Ct
} }
ctx.writermem = responseWriter{ ctx.writermem = responseWriter{
ResponseWriter: ctx.writer, ResponseWriter: writer,
size: -1, size: -1,
status: 0, status: 0,
} }
ctx.Writer = &ctx.writermem
return ctx return ctx
} }
@ -303,11 +304,11 @@ func (c *Ctx) SSEvent(event string, data interface{}) error {
c.Set("Cache-Control", "no-cache") c.Set("Cache-Control", "no-cache")
c.Set("Transfer-Encoding", "chunked") c.Set("Transfer-Encoding", "chunked")
return sse.Encode(c.writer, sse.Event{Event: event, Data: data}) return sse.Encode(c.Writer, sse.Event{Event: event, Data: data})
} }
func (c *Ctx) Flush() error { func (c *Ctx) Flush() error {
if f, ok := c.writer.(http.Flusher); ok { if f, ok := c.Writer.(http.Flusher); ok {
f.Flush() f.Flush()
return nil return nil
} }
@ -315,13 +316,9 @@ func (c *Ctx) Flush() error {
return errors.New("http.Flusher is not implemented") return errors.New("http.Flusher is not implemented")
} }
func (c *Ctx) RawWriter() http.ResponseWriter {
return c.writer
}
func (c *Ctx) HTML(html string) error { func (c *Ctx) HTML(html string) error {
c.SetHeader("Content-Type", "text/html") c.SetHeader("Content-Type", "text/html")
_, err := c.writer.Write([]byte(html)) _, err := c.Write([]byte(html))
return err return err
} }

11
go.mod
View File

@ -1,3 +1,14 @@
module github.com/loveuer/nf module github.com/loveuer/nf
go 1.20 go 1.20
require (
github.com/fatih/color v1.17.0
github.com/google/uuid v1.6.0
)
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.18.0 // indirect
)

13
go.sum
View File

@ -0,0 +1,13 @@
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View File

@ -2,9 +2,11 @@ package nf
import ( import (
"fmt" "fmt"
"log" "github.com/google/uuid"
"github.com/loveuer/nf/nft/log"
"os" "os"
"runtime/debug" "runtime/debug"
"strings"
"time" "time"
) )
@ -27,51 +29,44 @@ func NewRecover(enableStackTrace bool) HandlerFunc {
} }
} }
func NewLogger() HandlerFunc { func NewLogger(traceHeader ...string) HandlerFunc {
l := log.New(os.Stdout, "[NF] ", 0) Header := "X-Trace-ID"
if len(traceHeader) > 0 && traceHeader[0] != "" {
durationFormat := func(num int64) string { Header = traceHeader[0]
var (
unit = "ns"
)
if num > 1000 {
num = num / 1000
unit = "µs"
}
if num > 1000 {
num = num / 1000
unit = "ms"
}
if num > 1000 {
num = num / 1000
unit = "s"
}
return fmt.Sprintf("%3d %2s", num, unit)
} }
return func(c *Ctx) error { return func(c *Ctx) error {
start := time.Now() var (
now = time.Now()
trace = c.Get(Header)
logFn func(msg string, data ...any)
ip = c.IP()
)
if trace == "" {
trace = uuid.Must(uuid.NewV7()).String()
}
c.SetHeader(Header, trace)
traces := strings.Split(trace, "-")
shortTrace := traces[len(traces)-1]
err := c.Next() err := c.Next()
duration := time.Since(now)
var ( msg := fmt.Sprintf("NF | %s | %15s | %3d | %s | %6s | %s", shortTrace, ip, c.StatusCode, HumanDuration(duration.Nanoseconds()), c.Method(), c.Path())
duration = time.Now().Sub(start).Nanoseconds()
status = c.StatusCode
path = c.path
method = c.Request.Method
)
l.Printf("%s | %5s | %d | %s | %s", switch {
start.Format("06/01/02T15:04:05"), case c.StatusCode >= 500:
method, logFn = log.Error
status, case c.StatusCode >= 400:
durationFormat(duration), logFn = log.Warn
path, default:
) logFn = log.Info
}
logFn(msg)
return err return err
} }

67
nft/log/default.go Normal file
View File

@ -0,0 +1,67 @@
package log
import (
"fmt"
"os"
"sync"
)
var (
nilLogger = func(prefix, timestamp, msg string, data ...any) {}
normalLogger = func(prefix, timestamp, msg string, data ...any) {
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
}
panicLogger = func(prefix, timestamp, msg string, data ...any) {
panic(fmt.Sprintf(prefix+"| "+timestamp+" | "+msg+"\n", data...))
}
fatalLogger = func(prefix, timestamp, msg string, data ...any) {
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
os.Exit(1)
}
defaultLogger = &logger{
Mutex: sync.Mutex{},
timeFormat: "2006-01-02T15:04:05",
writer: os.Stdout,
level: LogLevelInfo,
debug: nilLogger,
info: normalLogger,
warn: normalLogger,
error: normalLogger,
panic: panicLogger,
fatal: fatalLogger,
}
)
func SetTimeFormat(format string) {
defaultLogger.SetTimeFormat(format)
}
func SetLogLevel(level LogLevel) {
defaultLogger.SetLogLevel(level)
}
func Debug(msg string, data ...any) {
defaultLogger.Debug(msg, data...)
}
func Info(msg string, data ...any) {
defaultLogger.Info(msg, data...)
}
func Warn(msg string, data ...any) {
defaultLogger.Warn(msg, data...)
}
func Error(msg string, data ...any) {
defaultLogger.Error(msg, data...)
}
func Panic(msg string, data ...any) {
defaultLogger.Panic(msg, data...)
}
func Fatal(msg string, data ...any) {
defaultLogger.Fatal(msg, data...)
}

115
nft/log/log.go Normal file
View File

@ -0,0 +1,115 @@
package log
import (
"github.com/fatih/color"
"io"
"sync"
"time"
)
type LogLevel uint32
const (
LogLevelDebug = iota
LogLevelInfo
LogLevelWarn
LogLevelError
LogLevelPanic
LogLevelFatal
)
type logger struct {
sync.Mutex
timeFormat string
writer io.Writer
level LogLevel
debug func(prefix, timestamp, msg string, data ...any)
info func(prefix, timestamp, msg string, data ...any)
warn func(prefix, timestamp, msg string, data ...any)
error func(prefix, timestamp, msg string, data ...any)
panic func(prefix, timestamp, msg string, data ...any)
fatal func(prefix, timestamp, msg string, data ...any)
}
var (
red = color.New(color.FgRed)
hired = color.New(color.FgHiRed)
green = color.New(color.FgGreen)
yellow = color.New(color.FgYellow)
white = color.New(color.FgWhite)
)
func (l *logger) SetTimeFormat(format string) {
l.Lock()
defer l.Unlock()
l.timeFormat = format
}
func (l *logger) SetLogLevel(level LogLevel) {
l.Lock()
defer l.Unlock()
if level > LogLevelDebug {
l.debug = nilLogger
} else {
l.debug = normalLogger
}
if level > LogLevelInfo {
l.info = nilLogger
} else {
l.info = normalLogger
}
if level > LogLevelWarn {
l.warn = nilLogger
} else {
l.warn = normalLogger
}
if level > LogLevelError {
l.error = nilLogger
} else {
l.error = normalLogger
}
if level > LogLevelPanic {
l.panic = nilLogger
} else {
l.panic = panicLogger
}
if level > LogLevelFatal {
l.fatal = nilLogger
} else {
l.fatal = fatalLogger
}
}
func (l *logger) Debug(msg string, data ...any) {
l.debug(white.Sprint("Debug "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Info(msg string, data ...any) {
l.info(green.Sprint("Info "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Warn(msg string, data ...any) {
l.warn(yellow.Sprint("Warn "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Error(msg string, data ...any) {
l.error(red.Sprint("Error "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Panic(msg string, data ...any) {
l.panic(hired.Sprint("Panic "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Fatal(msg string, data ...any) {
l.fatal(hired.Sprint("Fatal "), time.Now().Format(l.timeFormat), msg, data...)
}
type WroteLogger interface {
Info(msg string, data ...any)
}

21
nft/log/new.go Normal file
View File

@ -0,0 +1,21 @@
package log
import (
"os"
"sync"
)
func New() *logger {
return &logger{
Mutex: sync.Mutex{},
timeFormat: "2006-01-02T15:04:05",
writer: os.Stdout,
level: LogLevelInfo,
debug: nilLogger,
info: normalLogger,
warn: normalLogger,
error: normalLogger,
panic: panicLogger,
fatal: fatalLogger,
}
}

View File

@ -42,8 +42,9 @@ type ResponseWriter interface {
type responseWriter struct { type responseWriter struct {
http.ResponseWriter http.ResponseWriter
size int written bool
status int size int
status int
} }
var _ ResponseWriter = (*responseWriter)(nil) var _ ResponseWriter = (*responseWriter)(nil)
@ -103,7 +104,7 @@ func (w *responseWriter) Size() int {
} }
func (w *responseWriter) Written() bool { func (w *responseWriter) Written() bool {
return w.size != noWritten return w.size != noWritten || w.written
} }
// Hijack implements the http.Hijacker interface. // Hijack implements the http.Hijacker interface.

21
util.go
View File

@ -202,3 +202,24 @@ func bufApp(buf *[]byte, s string, w int, c byte) {
} }
b[w] = c b[w] = c
} }
func HumanDuration(nano int64) string {
duration := float64(nano)
unit := "ns"
if duration >= 1000 {
duration /= 1000
unit = "us"
}
if duration >= 1000 {
duration /= 1000
unit = "ms"
}
if duration >= 1000 {
duration /= 1000
unit = " s"
}
return fmt.Sprintf("%6.2f%s", duration, unit)
}