• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-server / 1902703503

02 Jul 2025 02:55PM UTC coverage: 65.459%. First build
1902703503

Pull #784

gitlab-ci

bahaa-ghazal
chore: Remove github.com/ant0ine/go-json-rest package from the backend

Ticket: MEN-8233
Signed-off-by: Bahaa Aldeen Ghazal <bahaa.ghazal@northern.tech>
Pull Request #784: refactor: Remove github.com/ant0ine/go-json-rest from the backend

52 of 72 new or added lines in 13 files covered. (72.22%)

31902 of 48736 relevant lines covered (65.46%)

1.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.04
/backend/pkg/accesslog/middleware_gin.go
1
// Copyright 2024 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package accesslog
16

17
import (
18
        "context"
19
        "fmt"
20
        "net"
21
        "net/http"
22
        "os"
23
        "path"
24
        "runtime"
25
        "strconv"
26
        "strings"
27
        "time"
28

29
        "github.com/gin-gonic/gin"
30
        "github.com/pkg/errors"
31
        "github.com/sirupsen/logrus"
32

33
        "github.com/mendersoftware/mender-server/pkg/log"
34
        "github.com/mendersoftware/mender-server/pkg/netutils"
35
        "github.com/mendersoftware/mender-server/pkg/rest.utils"
36
)
37

38
const (
39
        StatusClientClosedConnection = 499
40

41
        // nolint:lll
42
        DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
43
        SimpleLogFormat  = "%s %Dμs %r %u %{User-Agent}i"
44

45
        envProxyDepth = "ACCESSLOG_PROXY_DEPTH"
46
)
47

48
type AccessLogger struct {
49
        DisableLog   func(c *gin.Context) bool
50
        ClientIPHook func(r *http.Request) net.IP
51
}
52

53
func getClientIPFromEnv() func(r *http.Request) net.IP {
9✔
54
        if proxyDepthEnv, ok := os.LookupEnv(envProxyDepth); ok {
9✔
NEW
55
                proxyDepth, err := strconv.ParseUint(proxyDepthEnv, 10, 8)
×
NEW
56
                if err == nil {
×
NEW
57
                        return func(r *http.Request) net.IP {
×
NEW
58
                                return netutils.GetIPFromXFFDepth(r, int(proxyDepth))
×
NEW
59
                        }
×
60
                }
61
        }
62
        return nil
9✔
63
}
64

65
const MaxTraceback = 32
66

67
func collectTrace() string {
1✔
68
        var (
1✔
69
                trace     [MaxTraceback]uintptr
1✔
70
                traceback strings.Builder
1✔
71
        )
1✔
72
        // Skip 4
1✔
73
        // = accesslog.LogFunc
1✔
74
        // + accesslog.collectTrace
1✔
75
        // + runtime.Callers
1✔
76
        // + runtime.gopanic
1✔
77
        n := runtime.Callers(4, trace[:])
1✔
78
        frames := runtime.CallersFrames(trace[:n])
1✔
79
        for frame, more := frames.Next(); frame.PC != 0 &&
1✔
80
                n >= 0; frame, more = frames.Next() {
2✔
81
                funcName := frame.Function
1✔
82
                if funcName == "" {
1✔
NEW
83
                        fmt.Fprint(&traceback, "???\n")
×
84
                } else {
1✔
85
                        fmt.Fprintf(&traceback, "%s@%s:%d",
1✔
86
                                frame.Function,
1✔
87
                                path.Base(frame.File),
1✔
88
                                frame.Line,
1✔
89
                        )
1✔
90
                }
1✔
91
                if more {
2✔
92
                        fmt.Fprintln(&traceback)
1✔
93
                }
1✔
94
                n--
1✔
95
        }
96
        return traceback.String()
1✔
97
}
98

99
func formatPathParams(params gin.Params) string {
9✔
100
        var b strings.Builder
9✔
101
        lastIdx := len(params) - 1
9✔
102
        delimiter := " "
9✔
103
        for i, p := range params {
18✔
104
                if i == lastIdx {
18✔
105
                        delimiter = ""
9✔
106
                }
9✔
107
                key := strings.TrimPrefix(p.Key, ":")
9✔
108
                fmt.Fprintf(&b, "%s=%s%s", key, p.Value, delimiter)
9✔
109
        }
110
        return b.String()
9✔
111
}
112

113
func (a AccessLogger) LogFunc(
114
        ctx context.Context,
115
        c *gin.Context,
116
        startTime time.Time,
117
) {
9✔
118
        logCtx := logrus.Fields{
9✔
119
                "method":      c.Request.Method,
9✔
120
                "path":        c.FullPath(),
9✔
121
                "path_params": formatPathParams(c.Params),
9✔
122
                "qs":          c.Request.URL.RawQuery,
9✔
123
                "ts": startTime.
9✔
124
                        Truncate(time.Millisecond).
9✔
125
                        Format(time.RFC3339Nano),
9✔
126
                "type":      c.Request.Proto,
9✔
127
                "useragent": c.Request.UserAgent(),
9✔
128
        }
9✔
129
        if a.ClientIPHook != nil {
9✔
130
                logCtx["clientip"] = a.ClientIPHook(c.Request)
×
131
        }
×
132
        lc := fromContext(ctx)
9✔
133
        if lc != nil {
18✔
134
                lc.addFields(logCtx)
9✔
135
        }
9✔
136
        if r := recover(); r != nil {
10✔
137
                trace := collectTrace()
1✔
138
                logCtx["trace"] = trace
1✔
139
                logCtx["panic"] = r
1✔
140

1✔
141
                func() {
2✔
142
                        // Try to respond with an internal server error.
1✔
143
                        // If the connection is broken it might panic again.
1✔
144
                        defer func() { recover() }() // nolint:errcheck
2✔
145
                        rest.RenderError(c,
1✔
146
                                http.StatusInternalServerError,
1✔
147
                                errors.New("internal error"),
1✔
148
                        )
1✔
149
                }()
150
        } else if a.DisableLog != nil && a.DisableLog(c) {
10✔
151
                return
1✔
152
        }
1✔
153
        latency := time.Since(startTime)
9✔
154
        // We do not need more than 3 digit fraction
9✔
155
        if latency > time.Second {
11✔
156
                latency = latency.Round(time.Millisecond)
2✔
157
        } else if latency > time.Millisecond {
19✔
158
                latency = latency.Round(time.Microsecond)
8✔
159
        }
8✔
160
        code := c.Writer.Status()
9✔
161
        select {
9✔
162
        case <-ctx.Done():
×
163
                if errors.Is(ctx.Err(), context.Canceled) {
×
164
                        code = StatusClientClosedConnection
×
165
                }
×
166
        default:
9✔
167
        }
168
        logCtx["responsetime"] = latency.String()
9✔
169
        logCtx["status"] = c.Writer.Status()
9✔
170
        logCtx["byteswritten"] = c.Writer.Size()
9✔
171

9✔
172
        var logLevel logrus.Level = logrus.InfoLevel
9✔
173
        if code >= 500 {
12✔
174
                logLevel = logrus.ErrorLevel
3✔
175
        } else if code >= 400 {
19✔
176
                logLevel = logrus.WarnLevel
7✔
177
        }
7✔
178
        if len(c.Errors) > 0 {
16✔
179
                errs := c.Errors.Errors()
7✔
180
                var errMsg string
7✔
181
                if len(errs) == 1 {
14✔
182
                        errMsg = errs[0]
7✔
183
                } else {
9✔
184
                        for i, err := range errs {
4✔
185
                                errMsg = errMsg + fmt.Sprintf(
2✔
186
                                        "#%02d: %s\n", i+1, err,
2✔
187
                                )
2✔
188
                        }
2✔
189
                }
190
                logCtx["error"] = errMsg
7✔
191
        }
192
        log.FromContext(c.Request.Context()).
9✔
193
                WithFields(logCtx).
9✔
194
                Log(logLevel)
9✔
195
}
196

197
// Middleware implementsa gin compatible MiddlewareFunc
198
//
199
// NOTE: This accesslog middleware also implements the legacy requestlog
200
// middleware.
201
func (a AccessLogger) Middleware(c *gin.Context) {
9✔
202
        ctx := c.Request.Context()
9✔
203
        startTime := time.Now()
9✔
204
        ctx = log.WithContext(ctx, log.New(log.Ctx{}))
9✔
205
        ctx = withContext(ctx, &logContext{maxErrors: DefaultMaxErrors})
9✔
206
        c.Request = c.Request.WithContext(ctx)
9✔
207
        defer a.LogFunc(ctx, c, startTime)
9✔
208
        c.Next()
9✔
209
}
9✔
210

211
// Middleware provides accesslog middleware for the gin-gonic framework.
212
// This middleware will recover any panic from occurring in the API
213
// handler and log it to error level with panic and trace showing the panic
214
// message and traceback respectively.
215
// If an error status is returned in the response, the middleware tries
216
// to pop the topmost error from the gin.Context (c.Error) and puts it in
217
// the "error" context to the final log entry.
218
func Middleware() gin.HandlerFunc {
9✔
219
        return AccessLogger{ClientIPHook: getClientIPFromEnv()}.Middleware
9✔
220
}
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc