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

mendersoftware / mender-server / 1978029483

11 Aug 2025 02:15PM UTC coverage: 65.755% (+0.3%) from 65.495%
1978029483

Pull #860

gitlab-ci

kjaskiewiczz
docs(useradm): move API specification to single file

Changelog: Title
Ticket: QA-1094
Signed-off-by: Krzysztof Jaskiewicz <krzysztof.jaskiewicz@northern.tech>
Pull Request #860: docs(useradm): move API specification to single file

29261 of 44500 relevant lines covered (65.76%)

1.44 hits per line

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

69.35
/backend/pkg/log/log.go
1
// Copyright 2023 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 log provides a thin wrapper over logrus, with a definition
16
// of a global root logger, its setup functions and convenience wrappers.
17
//
18
// The wrappers are introduced to reduce verbosity:
19
// - logrus.Fields becomes log.Ctx
20
// - logrus.WithFields becomes log.F(), defined on a Logger type
21
//
22
// The usage scenario in a multilayer app is as follows:
23
// - a new Logger is created in the upper layer with an initial context (request id, api method...)
24
// - it is passed to lower layer which automatically includes the context, and can further enrich it
25
// - result - logs across layers are tied together with a common context
26
//
27
// Note on concurrency:
28
// - all Loggers in fact point to the single base log, which serializes logging with its mutexes
29
// - all context is copied - each layer operates on an independent copy
30

31
package log
32

33
import (
34
        "context"
35
        "fmt"
36
        "io"
37
        "os"
38
        "path"
39
        "runtime"
40
        "strconv"
41
        "strings"
42
        "time"
43

44
        "github.com/sirupsen/logrus"
45
)
46

47
var (
48
        // log is a global logger instance
49
        Log = logrus.New()
50
)
51

52
const (
53
        envLogFormat        = "LOG_FORMAT"
54
        envLogLevel         = "LOG_LEVEL"
55
        envLogDisableCaller = "LOG_DISABLE_CALLER_CONTEXT"
56

57
        logFormatJSON    = "json"
58
        logFormatJSONAlt = "ndjson"
59

60
        logFieldCaller    = "caller"
61
        logFieldCallerFmt = "%s@%s:%d"
62

63
        pkgSirupsen = "github.com/sirupsen/logrus"
64
)
65

66
type loggerContextKeyType int
67

68
const (
69
        loggerContextKey loggerContextKeyType = 0
70
)
71

72
// ContextLogger interface for components which support
73
// logging with context, via setting a logger to an exisiting one,
74
// thereby inheriting its context.
75
type ContextLogger interface {
76
        UseLog(l *Logger)
77
}
78

79
// init initializes the global logger to sane defaults.
80
func init() {
9✔
81
        var opts Options
9✔
82
        switch strings.ToLower(os.Getenv(envLogFormat)) {
9✔
83
        case logFormatJSON, logFormatJSONAlt:
×
84
                opts.Format = FormatJSON
×
85
        default:
9✔
86
                opts.Format = FormatConsole
9✔
87
        }
88
        opts.Level = Level(logrus.InfoLevel)
9✔
89
        if lvl := os.Getenv(envLogLevel); lvl != "" {
9✔
90
                logLevel, err := logrus.ParseLevel(lvl)
×
91
                if err == nil {
×
92
                        opts.Level = Level(logLevel)
×
93
                }
×
94
        }
95
        opts.TimestampFormat = time.RFC3339
9✔
96
        opts.DisableCaller, _ = strconv.ParseBool(os.Getenv(envLogDisableCaller))
9✔
97
        Configure(opts)
9✔
98

9✔
99
        Log.ExitFunc = func(int) {}
9✔
100
}
101

102
type Level logrus.Level
103

104
const (
105
        LevelPanic = Level(logrus.PanicLevel)
106
        LevelFatal = Level(logrus.FatalLevel)
107
        LevelError = Level(logrus.ErrorLevel)
108
        LevelWarn  = Level(logrus.WarnLevel)
109
        LevelInfo  = Level(logrus.InfoLevel)
110
        LevelDebug = Level(logrus.DebugLevel)
111
        LevelTrace = Level(logrus.TraceLevel)
112
)
113

114
type Format int
115

116
const (
117
        FormatConsole Format = iota
118
        FormatJSON
119
)
120

121
type Options struct {
122
        TimestampFormat string
123

124
        Level Level
125

126
        DisableCaller bool
127

128
        Format Format
129

130
        Output io.Writer
131
}
132

133
func Configure(opts Options) {
9✔
134
        Log = logrus.New()
9✔
135

9✔
136
        if opts.Output != nil {
9✔
137
                Log.SetOutput(opts.Output)
×
138
        }
×
139
        Log.SetLevel(logrus.Level(opts.Level))
9✔
140

9✔
141
        if !opts.DisableCaller {
18✔
142
                Log.AddHook(ContextHook{})
9✔
143
        }
9✔
144

145
        var formatter logrus.Formatter
9✔
146

9✔
147
        switch opts.Format {
9✔
148
        case FormatConsole:
9✔
149
                formatter = &logrus.TextFormatter{
9✔
150
                        FullTimestamp:   true,
9✔
151
                        TimestampFormat: opts.TimestampFormat,
9✔
152
                }
9✔
153
        case FormatJSON:
×
154
                formatter = &logrus.JSONFormatter{
×
155
                        TimestampFormat: opts.TimestampFormat,
×
156
                }
×
157
        }
158
        Log.Formatter = formatter
9✔
159
}
160

161
// Setup allows to override the global logger setup.
162
func Setup(debug bool) {
8✔
163
        if debug {
8✔
164
                Log.Level = logrus.DebugLevel
×
165
        }
×
166
}
167

168
// Ctx short for log context, alias for the more verbose logrus.Fields.
169
type Ctx map[string]interface{}
170

171
// Logger is a wrapper for logrus.Entry.
172
type Logger struct {
173
        *logrus.Entry
174
}
175

176
// New returns a new Logger with a given context, derived from the global Log.
177
func New(ctx Ctx) *Logger {
9✔
178
        return NewFromLogger(Log, ctx)
9✔
179
}
9✔
180

181
// NewEmpty returns a new logger with empty context
182
func NewEmpty() *Logger {
4✔
183
        return New(Ctx{})
4✔
184
}
4✔
185

186
// NewFromLogger returns a new Logger derived from a given logrus.Logger,
187
// instead of the global one.
188
func NewFromLogger(log *logrus.Logger, ctx Ctx) *Logger {
9✔
189
        return &Logger{log.WithFields(logrus.Fields(ctx))}
9✔
190
}
9✔
191

192
// NewFromLogger returns a new Logger derived from a given logrus.Logger,
193
// instead of the global one.
194
func NewFromEntry(log *logrus.Entry, ctx Ctx) *Logger {
×
195
        return &Logger{log.WithFields(logrus.Fields(ctx))}
×
196
}
×
197

198
// F returns a new Logger enriched with new context fields.
199
// It's a less verbose wrapper over logrus.WithFields.
200
func (l *Logger) F(ctx Ctx) *Logger {
9✔
201
        return &Logger{l.Entry.WithFields(logrus.Fields(ctx))}
9✔
202
}
9✔
203

204
func (l *Logger) Level() logrus.Level {
×
205
        return l.Entry.Logger.Level
×
206
}
×
207

208
type ContextHook struct {
209
}
210

211
func (hook ContextHook) Levels() []logrus.Level {
9✔
212
        return logrus.AllLevels
9✔
213
}
9✔
214

215
func FmtCaller(caller runtime.Frame) string {
9✔
216
        return fmt.Sprintf(
9✔
217
                logFieldCallerFmt,
9✔
218
                path.Base(caller.Function),
9✔
219
                path.Base(caller.File),
9✔
220
                caller.Line,
9✔
221
        )
9✔
222
}
9✔
223

224
func (hook ContextHook) Fire(entry *logrus.Entry) error {
9✔
225
        const (
9✔
226
                minCallDepth = 6 // logrus.Logger.Log
9✔
227
                maxCallDepth = 8 // logrus.Logger.<Level>f
9✔
228
        )
9✔
229
        var pcs [1 + maxCallDepth - minCallDepth]uintptr
9✔
230
        if _, ok := entry.Data[logFieldCaller]; !ok {
18✔
231
                // We don't know how deep we are in the callstack since the hook can be fired
9✔
232
                // at different levels. Search between depth 6 -> 8.
9✔
233
                i := runtime.Callers(minCallDepth, pcs[:])
9✔
234
                frames := runtime.CallersFrames(pcs[:i])
9✔
235
                var caller *runtime.Frame
9✔
236
                for frame, _ := frames.Next(); frame.PC != 0; frame, _ = frames.Next() {
18✔
237
                        if !strings.HasPrefix(frame.Function, pkgSirupsen) {
18✔
238
                                caller = &frame
9✔
239
                                break
9✔
240
                        }
241
                }
242
                if caller != nil {
18✔
243
                        entry.Data[logFieldCaller] = FmtCaller(*caller)
9✔
244
                }
9✔
245
        }
246
        return nil
9✔
247
}
248

249
// WithCallerContext returns a new logger with caller set to the parent caller
250
// context. The skipParents select how many caller contexts to skip, a value of
251
// 0 sets the context to the caller of this function.
252
func (l *Logger) WithCallerContext(skipParents int) *Logger {
×
253
        const calleeDepth = 2
×
254
        var pc [1]uintptr
×
255
        newEntry := l
×
256
        i := runtime.Callers(calleeDepth+skipParents, pc[:])
×
257
        frame, _ := runtime.CallersFrames(pc[:i]).
×
258
                Next()
×
259
        if frame.Func != nil {
×
260
                newEntry = &Logger{Entry: l.Dup()}
×
261
                newEntry.Data[logFieldCaller] = FmtCaller(frame)
×
262
        }
×
263
        return newEntry
×
264
}
265

266
// Grab an instance of Logger that may have been passed in context.Context.
267
// Returns the logger or creates a new instance if none was found in ctx. Since
268
// Logger is based on logrus.Entry, if logger instance from context is any of
269
// logrus.Logger, logrus.Entry, necessary adaption will be applied.
270
func FromContext(ctx context.Context) *Logger {
9✔
271
        l := ctx.Value(loggerContextKey)
9✔
272
        if l == nil {
18✔
273
                return New(Ctx{})
9✔
274
        }
9✔
275

276
        switch v := l.(type) {
9✔
277
        case *Logger:
9✔
278
                return v
9✔
279
        case *logrus.Entry:
×
280
                return NewFromEntry(v, Ctx{})
×
281
        case *logrus.Logger:
×
282
                return NewFromLogger(v, Ctx{})
×
283
        default:
×
284
                return New(Ctx{})
×
285
        }
286
}
287

288
// WithContext adds logger to context `ctx` and returns the resulting context.
289
func WithContext(ctx context.Context, log *Logger) context.Context {
9✔
290
        return context.WithValue(ctx, loggerContextKey, log)
9✔
291
}
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