Skip to content

Commit

Permalink
Merge pull request #6 from Moranilt/bugfix/handler
Browse files Browse the repository at this point in the history
Bugfix/handler
  • Loading branch information
Moranilt committed Feb 23, 2024
2 parents 1a44fa4 + ed5926d commit 9fe9bcf
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 34 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [Client](./client/README.md)
- [Handler](./handler/README.md)
- [Logger](./logger/README.md)
- [Mock](./mock/README.md)
- [Query](./query/README.md)
- [Response](./response/README.md)
Expand Down
14 changes: 3 additions & 11 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"strings"

"github.com/Moranilt/http-utils/logger"
"github.com/Moranilt/http-utils/response"
"github.com/Moranilt/http-utils/tiny_errors"
"github.com/gorilla/mux"
Expand All @@ -23,20 +24,11 @@ type HandlerMaker[ReqT any, RespT any] struct {
request *http.Request
response http.ResponseWriter
requestBody ReqT
logger Logger
logger logger.Logger
caller CallerFunc[ReqT, RespT]
err error
}

type Logger interface {
WithRequestInfo(r *http.Request) Logger
With(args ...any) Logger

Debug(msg string, args ...any)
Info(msg string, args ...any)
Error(msg string, args ...any)
}

// A function that is called to process request.
//
// ReqT - type of request body
Expand All @@ -46,7 +38,7 @@ type CallerFunc[ReqT any, RespT any] func(ctx context.Context, req ReqT) (RespT,
// Create new handler instance
//
// **caller** should be a function that implements type CallerFunc[ReqT, RespT]
func New[ReqT any, RespT any](w http.ResponseWriter, r *http.Request, logger Logger, caller CallerFunc[ReqT, RespT]) *HandlerMaker[ReqT, RespT] {
func New[ReqT any, RespT any](w http.ResponseWriter, r *http.Request, logger logger.Logger, caller CallerFunc[ReqT, RespT]) *HandlerMaker[ReqT, RespT] {
log := logger.WithRequestInfo(r)
return &HandlerMaker[ReqT, RespT]{
logger: log,
Expand Down
27 changes: 4 additions & 23 deletions handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,12 @@ import (
"strings"
"testing"

"github.com/Moranilt/http-utils/logger"
"github.com/Moranilt/http-utils/response"
"github.com/Moranilt/http-utils/tiny_errors"
"github.com/gorilla/mux"
)

type mockedLogger struct{}

func NewLogger() Logger {
return &mockedLogger{}
}

func (l *mockedLogger) WithRequestInfo(r *http.Request) Logger {
return l
}

func (l *mockedLogger) With(args ...any) Logger {
return l
}

func (l *mockedLogger) Debug(msg string, args ...any) {}

func (l *mockedLogger) Info(msg string, args ...any) {}

func (l *mockedLogger) Error(msg string, args ...any) {}

type mockRequest struct {
Name string `json:"name,omitempty" mapstructure:"name"`
Phone string `json:"phone,omitempty" mapstructure:"phone"`
Expand Down Expand Up @@ -70,7 +51,7 @@ func makeMockedFunction[ReqT any, RespT any](requestValidator func(request ReqT)
}

func TestHandler(t *testing.T) {
logger := NewLogger()
logger := logger.NewMock()

t.Run("default handler Run", func(t *testing.T) {
routePath := "/test-route"
Expand Down Expand Up @@ -396,7 +377,7 @@ func TestHandler(t *testing.T) {
}

func BenchmarkMultipart(b *testing.B) {
logger := NewLogger()
logger := logger.NewMock()
routePath := "/test-files"

testHandler := makeMockedFunction(func(request mockMultipartRequest) *mockResponse {
Expand Down Expand Up @@ -490,7 +471,7 @@ func newTestHandleController[ReqT any, RespT any](
}
}

func (cntr *testHandleFuncController[ReqT, RespT]) Run(t testing.TB, logger Logger) {
func (cntr *testHandleFuncController[ReqT, RespT]) Run(t testing.TB, logger logger.Logger) {
router := mux.NewRouter()
requestPath := cntr.routePath
if cntr.vars != nil {
Expand Down
31 changes: 31 additions & 0 deletions logger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Logger
Default logger for your service.

# Examples
## Default
```go
import (
"github.com/Moranilt/http-utils/logger"
)

func main() {
log := logger.New(os.Stdout, logger.TYPE_JSON)
log.Info("Hello World")
// Output: {"level":"INFO","message":"Hello World","time":"2020-07-20T17:22:54+03:00"}
}
```

## Global
```go
import (
"github.com/Moranilt/http-utils/logger"
)

func main(){
log := logger.New(os.Stdout, logger.TYPE_JSON)
logger.SetDefault(log)

logger.Default().Info("Hello World")
// Output: {"level":"INFO","message":"Hello World","time":"2020-07-20T17:22:54+03:00"}
}
```
214 changes: 214 additions & 0 deletions logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package logger

import (
"context"
"fmt"
"io"
"net"
"net/http"
"os"
"sync/atomic"

"log/slog"
)

type LoggerType string

const (
TYPE_JSON LoggerType = "json"
TYPE_DEFAULT LoggerType = "default"
)

type ContextKey string

var defaultLogger atomic.Value

func init() {
defaultLogger.Store(New(os.Stdout, TYPE_JSON))
}

func SetDefault(l Logger) {
defaultLogger.Store(l)
}

func Default() Logger {
return defaultLogger.Load().(Logger)
}

const (
CtxRequestId ContextKey = "request_id"
)

const (
LevelTrace = slog.Level(-8)
LevelNotice = slog.Level(2)
LevelFatal = slog.Level(12)
LevelError = slog.Level(4)
LevelDebug = slog.Level(1)
LevelInfo = slog.Level(0)
)

var LevelNames = map[slog.Leveler]string{
LevelTrace: "TRACE",
LevelNotice: "NOTICE",
LevelFatal: "FATAL",
LevelError: "ERROR",
LevelDebug: "DEBUG",
LevelInfo: "INFO",
}

type SLogger struct {
root *slog.Logger
}

type Logger interface {
Trace(msg string, args ...any)

Notice(msg string, args ...any)

Error(msg string, args ...any)
Fatal(msg string, args ...any)
Fatalf(format string, args ...any)
Errorf(format string, args ...any)

Debug(msg string, args ...any)
Debugf(format string, args ...any)

Info(msg string, args ...any)
Infof(format string, args ...any)

Log(ctx context.Context, level slog.Level, msg string, args ...any)

With(args ...any) Logger
WithRequestInfo(r *http.Request) Logger
WithField(key string, value any) Logger
WithFields(fields ...any) Logger
}

func New(output io.Writer, t LoggerType) Logger {
if output == nil {
output = os.Stdout
}

var l *slog.Logger
if t == TYPE_JSON {
l = slog.New(slog.NewJSONHandler(output, &slog.HandlerOptions{
Level: LevelTrace,
ReplaceAttr: renameLevel,
}))
} else {
l = slog.New(slog.NewTextHandler(output, &slog.HandlerOptions{
Level: LevelTrace,
ReplaceAttr: renameLevel,
}))
}

logger := &SLogger{
l,
}
return logger
}

func (s *SLogger) Error(msg string, args ...any) {
s.root.Log(context.Background(), LevelError, msg, args...)
}

func (s *SLogger) Debug(msg string, args ...any) {
s.root.Log(context.Background(), LevelDebug, msg, args...)
}

func (s *SLogger) Trace(msg string, args ...any) {
s.root.Log(context.Background(), LevelTrace, msg, args...)
}

func (s *SLogger) Notice(msg string, args ...any) {
s.root.Log(context.Background(), LevelNotice, msg, args...)
}

func (s *SLogger) Fatal(msg string, args ...any) {
s.root.Log(context.Background(), LevelFatal, msg, args...)
os.Exit(1)
}

func (s *SLogger) Fatalf(format string, args ...any) {
s.root.Log(context.Background(), LevelFatal, fmt.Sprintf(format, args...))
os.Exit(1)
}

func (s *SLogger) Errorf(format string, args ...any) {
s.root.Log(context.Background(), LevelError, fmt.Sprintf(format, args...))
}

func (s *SLogger) Debugf(format string, args ...any) {
s.root.Log(context.Background(), LevelDebug, fmt.Sprintf(format, args...))
}

func (s *SLogger) Log(ctx context.Context, level slog.Level, msg string, args ...any) {
s.root.Log(ctx, level, msg, args...)
}

func (s *SLogger) With(args ...any) Logger {
return &SLogger{
root: s.root.With(args...),
}
}

func (s *SLogger) WithField(key string, value any) Logger {
return &SLogger{
root: s.root.With(key, value),
}
}

func (s *SLogger) WithFields(fields ...any) Logger {
return &SLogger{
root: s.root.With(fields...),
}
}

func (l *SLogger) Infof(format string, args ...any) {
l.root.Log(context.Background(), LevelInfo, fmt.Sprintf(format, args...))
}

func (l *SLogger) Info(msg string, args ...any) {
l.root.Log(context.Background(), LevelInfo, msg, args...)
}

func (l *SLogger) WithRequestInfo(r *http.Request) Logger {
l = l.WithRequestId(r.Context())
var clientIP string

if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
clientIP = ip
}

return &SLogger{
root: l.root.With(
"path", r.URL.Path,
"method", r.Method,
"ip", clientIP,
),
}
}
func (l *SLogger) WithRequestId(ctx context.Context) *SLogger {
requestId := ctx.Value(CtxRequestId)
if requestId != "" {
return &SLogger{
root: l.root.With("request_id", requestId),
}
}
return l
}

func renameLevel(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.LevelKey {
level := a.Value.Any().(slog.Level)
levelLabel, exists := LevelNames[level]
if !exists {
levelLabel = level.String()
}

a.Value = slog.StringValue(levelLabel)
}

return a
}

0 comments on commit 9fe9bcf

Please sign in to comment.