Skip to content

Commit 584b953

Browse files
committed
init 🚀
0 parents  commit 584b953

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea
2+
.vscode

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Echolog
2+
The [Echo](https://github.com/labstack/echo) middleware that contains functionality of requestid, recover and logs HTTP request/response details for traceability.
3+
4+
## Installation
5+
Install echolog with:
6+
7+
```sh
8+
go get -u github.com/Qiscus-Integration/echolog
9+
```
10+
11+
## Usage
12+
```go
13+
package main
14+
15+
import (
16+
"net/http"
17+
18+
"github.com/Qiscus-Integration/echolog"
19+
"github.com/labstack/echo/v4"
20+
"github.com/rs/zerolog/log"
21+
)
22+
23+
func filter(c echo.Context) bool {
24+
return c.Request().RequestURI == "/healthcheck"
25+
}
26+
27+
func main() {
28+
e := echo.New()
29+
e.Use(echolog.Middleware(filter))
30+
// Output: {"level":"info","request_id":"9627a4a0-9d94-4ab6-844c-9599c0a15cd0","remote_ip":"[::1]:62542","host":"localhost:8080","method":"GET","path":"/","body":"","status_code":200,"latency":0,"tag":"request","time":"2023-02-19T14:07:37+07:00","message":"success"}
31+
32+
// Or without filter
33+
// r.Use(echolog.Middleware(nil))
34+
35+
e.GET("/", func(c echo.Context) error {
36+
ctx := c.Request().Context()
37+
log.Ctx(ctx).Info().Msg("hello world")
38+
// Output: {"level":"info","request_id":"9627a4a0-9d94-4ab6-844c-9599c0a15cd0","time":"2023-02-19T15:06:39+07:00","message":"hello world"}
39+
40+
return c.String(http.StatusOK, "ok")
41+
})
42+
43+
e.Logger.Fatal(e.Start(":8080"))
44+
45+
}
46+
47+
```

echolog.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package echolog
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"runtime/debug"
10+
"time"
11+
12+
"github.com/google/uuid"
13+
"github.com/labstack/echo/v4"
14+
"github.com/rs/zerolog"
15+
"github.com/rs/zerolog/log"
16+
)
17+
18+
// RequestIDHeader is the name of the HTTP Header which contains the request id.
19+
// Exported so that it can be changed by developers
20+
var RequestIDHeader = "X-Request-Id"
21+
22+
type logFields struct {
23+
RemoteIP string
24+
Host string
25+
Method string
26+
Path string
27+
Body string
28+
StatusCode int
29+
Latency float64
30+
Error error
31+
Stack []byte
32+
}
33+
34+
func (l *logFields) MarshalZerologObject(e *zerolog.Event) {
35+
e.
36+
Str("remote_ip", l.RemoteIP).
37+
Str("host", l.Host).
38+
Str("method", l.Method).
39+
Str("path", l.Path).
40+
Str("body", l.Body).
41+
Int("status_code", l.StatusCode).
42+
Float64("latency", l.Latency).
43+
Str("tag", "request")
44+
45+
if l.Error != nil {
46+
e.Err(l.Error)
47+
}
48+
49+
if l.Stack != nil {
50+
e.Bytes("stack", l.Stack)
51+
}
52+
}
53+
54+
// Middleware contains functionality of request_id, logger and recover for request traceability
55+
func Middleware(filter func(c echo.Context) bool) echo.MiddlewareFunc {
56+
return func(next echo.HandlerFunc) echo.HandlerFunc {
57+
return func(c echo.Context) error {
58+
59+
if filter != nil && filter(c) {
60+
return next(c)
61+
}
62+
63+
// Start timer
64+
start := time.Now()
65+
66+
// Generate request ID
67+
// will search for a request ID header and set into the log context
68+
if c.Request().Header.Get(RequestIDHeader) == "" {
69+
c.Request().Header.Set(RequestIDHeader, uuid.New().String())
70+
}
71+
72+
ctx := log.With().
73+
Str("request_id", c.Request().Header.Get(RequestIDHeader)).
74+
Logger().
75+
WithContext(c.Request().Context())
76+
77+
// Read request body
78+
var buf []byte
79+
if c.Request().Body != nil {
80+
buf, _ = io.ReadAll(c.Request().Body)
81+
82+
// Restore the io.ReadCloser to its original state
83+
c.Request().Body = io.NopCloser(bytes.NewBuffer(buf))
84+
}
85+
86+
// Create log fields
87+
fields := &logFields{
88+
RemoteIP: c.RealIP(),
89+
Method: c.Request().Method,
90+
Host: c.Request().Host,
91+
Path: c.Request().RequestURI,
92+
Body: formatReqBody(buf),
93+
}
94+
95+
defer func() {
96+
rvr := recover()
97+
98+
if rvr != nil {
99+
if rvr == http.ErrAbortHandler {
100+
// We don't recover http.ErrAbortHandler so the response
101+
// to the client is aborted, this should not be logged
102+
panic(rvr)
103+
}
104+
105+
err, ok := rvr.(error)
106+
if !ok {
107+
err = fmt.Errorf("%v", rvr)
108+
}
109+
110+
fields.Error = err
111+
fields.Stack = debug.Stack()
112+
113+
c.Error(err)
114+
}
115+
116+
fields.StatusCode = c.Response().Status
117+
fields.Latency = float64(time.Since(start).Nanoseconds()/1e4) / 100.0
118+
119+
switch {
120+
case rvr != nil:
121+
log.Ctx(ctx).Error().EmbedObject(fields).Msg("panic recover")
122+
case fields.StatusCode >= 500:
123+
log.Ctx(ctx).Error().EmbedObject(fields).Msg("server error")
124+
case fields.StatusCode >= 400:
125+
log.Ctx(ctx).Error().EmbedObject(fields).Msg("client error")
126+
case fields.StatusCode >= 300:
127+
log.Ctx(ctx).Warn().EmbedObject(fields).Msg("redirect")
128+
case fields.StatusCode >= 200:
129+
log.Ctx(ctx).Info().EmbedObject(fields).Msg("success")
130+
case fields.StatusCode >= 100:
131+
log.Ctx(ctx).Info().EmbedObject(fields).Msg("informative")
132+
default:
133+
log.Ctx(ctx).Warn().EmbedObject(fields).Msg("unknown status")
134+
}
135+
136+
}()
137+
138+
newReq := c.Request().WithContext(ctx)
139+
c.SetRequest(newReq)
140+
141+
return next(c)
142+
}
143+
}
144+
}
145+
146+
func formatReqBody(data []byte) string {
147+
var js map[string]interface{}
148+
if json.Unmarshal(data, &js) != nil {
149+
return string(data)
150+
}
151+
152+
result := new(bytes.Buffer)
153+
if err := json.Compact(result, data); err != nil {
154+
log.Error().Err(err).Msg("error compacting body request json")
155+
return ""
156+
}
157+
158+
return result.String()
159+
}

go.mod

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module github.com/Qiscus-Integration/echolog
2+
3+
go 1.19
4+
5+
require (
6+
github.com/google/uuid v1.3.0
7+
github.com/labstack/echo/v4 v4.10.2
8+
)
9+
10+
require (
11+
github.com/labstack/gommon v0.4.0 // indirect
12+
github.com/mattn/go-colorable v0.1.13 // indirect
13+
github.com/mattn/go-isatty v0.0.17 // indirect
14+
github.com/rs/zerolog v1.29.0
15+
github.com/valyala/bytebufferpool v1.0.0 // indirect
16+
github.com/valyala/fasttemplate v1.2.2 // indirect
17+
golang.org/x/crypto v0.6.0 // indirect
18+
golang.org/x/net v0.7.0 // indirect
19+
golang.org/x/sys v0.5.0 // indirect
20+
golang.org/x/text v0.7.0 // indirect
21+
)

go.sum

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
5+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
6+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
7+
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
8+
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
9+
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
10+
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
11+
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
12+
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
13+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
14+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
15+
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
16+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
17+
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
18+
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
19+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
20+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
21+
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
22+
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
23+
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
24+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
25+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
26+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
27+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
28+
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
29+
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
30+
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
31+
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
32+
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
33+
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
34+
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
35+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36+
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
37+
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
38+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
39+
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
40+
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
41+
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
42+
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
43+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
44+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
45+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)