Skip to content

Commit f160aca

Browse files
committed
MAJOR: log: rework how logging works in dataplaneapi
Added multiple loggers and two log_types: app and access. Access logs are Apache Common Log formated logs for dataplaneapi, while app logs are standard application logging. Using configuration file, now you can configure multiple logging targets, e.g. logging to multiple files, or syslogs all with separate configurations. Leaving old options as deprecated as they will be used only if no log_targets are configured in the configuration file.
1 parent 838d5a3 commit f160aca

27 files changed

+677
-329
lines changed

adapters/adapters.go

+9-115
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,17 @@ package adapters
1717

1818
import (
1919
"fmt"
20-
"io"
21-
"math/rand"
2220
"net/http"
2321
"runtime"
2422
"strconv"
2523
"strings"
26-
"time"
2724

28-
"github.com/docker/go-units"
2925
clientnative "github.com/haproxytech/client-native/v2"
3026
"github.com/haproxytech/client-native/v2/models"
31-
apachelog "github.com/lestrrat-go/apache-logformat"
32-
"github.com/oklog/ulid/v2"
33-
"github.com/sirupsen/logrus"
27+
"github.com/haproxytech/dataplaneapi/log"
3428
)
3529

36-
var (
37-
apacheLogWritter *io.PipeWriter
38-
configVersion string
39-
)
30+
var configVersion string
4031

4132
func ConfigVersion() string {
4233
return configVersion
@@ -45,47 +36,6 @@ func ConfigVersion() string {
4536
// Adapter is just a wrapper over http handler function
4637
type Adapter func(http.Handler) http.Handler
4738

48-
type statusResponseWriter struct {
49-
http.ResponseWriter
50-
status int
51-
length int
52-
}
53-
54-
func (srw *statusResponseWriter) WriteHeader(s int) {
55-
srw.status = s
56-
srw.ResponseWriter.WriteHeader(s)
57-
}
58-
59-
func (srw *statusResponseWriter) Write(b []byte) (int, error) {
60-
if srw.status == 0 {
61-
// The status will be StatusOK if WriteHeader has not been called yet
62-
srw.status = 200
63-
srw.ResponseWriter.WriteHeader(srw.Status())
64-
}
65-
size, err := srw.ResponseWriter.Write(b)
66-
srw.length = size
67-
return size, err
68-
}
69-
70-
func (srw *statusResponseWriter) Header() http.Header {
71-
return srw.ResponseWriter.Header()
72-
}
73-
74-
func (srw *statusResponseWriter) Status() int {
75-
return srw.status
76-
}
77-
78-
func (srw *statusResponseWriter) Length() int {
79-
return srw.length
80-
}
81-
82-
func newStatusResponseWriter(rw http.ResponseWriter) *statusResponseWriter {
83-
nsrw := &statusResponseWriter{
84-
ResponseWriter: rw,
85-
}
86-
return nsrw
87-
}
88-
8939
type frame struct {
9040
File string
9141
Line int
@@ -133,15 +83,16 @@ func stripPackage(n string) string {
13383
}
13484

13585
// RecoverMiddleware used for recovering from panic, logs the panic to given logger and returns 500
136-
func RecoverMiddleware(entry logrus.FieldLogger) func(h http.Handler) http.Handler {
86+
func RecoverMiddleware(logger *log.Logger) func(h http.Handler) http.Handler {
13787
return func(h http.Handler) http.Handler {
13888
fn := func(w http.ResponseWriter, r *http.Request) {
13989
defer func() {
14090
if err := recover(); err != nil {
14191
frames := callers()
14292

143-
entry := entry.WithField("stack_trace", frames.String())
144-
entry.Error(fmt.Sprintf("Panic %v", err))
93+
fields := make(map[string]interface{})
94+
fields["stack_trace"] = frames.String()
95+
logger.WithFieldsf(fields, log.ErrorLevel, "Panic %v", err)
14596

14697
w.WriteHeader(http.StatusInternalServerError)
14798

@@ -167,70 +118,13 @@ func RecoverMiddleware(entry logrus.FieldLogger) func(h http.Handler) http.Handl
167118
}
168119
}
169120

170-
func UniqueIDMiddleware(entry *logrus.Entry) Adapter {
171-
return func(h http.Handler) http.Handler {
172-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
173-
reqID := r.Header.Get("X-Request-Id")
174-
if len(reqID) == 0 {
175-
t := time.Now()
176-
// we need performance and math/rand does the trick, although it's "unsafe":
177-
// speed is absolutely required here since we're going to generate an ULID for each request
178-
// that doesn't contain a prefilled X-Request-Id header.
179-
entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) // nolint:gosec
180-
reqID = ulid.MustNew(ulid.Timestamp(t), entropy).String()
181-
}
182-
*entry = *entry.WithField("request_id", reqID)
183-
184-
h.ServeHTTP(w, r)
185-
})
186-
}
187-
}
188-
189-
func ApacheLogMiddleware(entry *logrus.Entry, format *apachelog.ApacheLog) Adapter {
121+
func ApacheLogMiddleware(logger *log.ACLLogger) Adapter {
190122
return func(h http.Handler) http.Handler {
191-
if apacheLogWritter != nil {
192-
apacheLogWritter.Close()
193-
}
194-
apacheLogWritter = entry.Logger.Writer()
195-
return format.Wrap(h, apacheLogWritter)
123+
apacheLogWritter := logger.Writer()
124+
return logger.ApacheLog.Wrap(h, apacheLogWritter)
196125
}
197126
}
198127

199-
// LoggingMiddleware logs before and after the response to given logger
200-
func LoggingMiddleware(entry *logrus.Entry) Adapter {
201-
return func(h http.Handler) http.Handler {
202-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
203-
start := time.Now()
204-
logBefore(entry, r)
205-
res := newStatusResponseWriter(w)
206-
defer logAfter(entry, res, start)
207-
h.ServeHTTP(res, r)
208-
})
209-
}
210-
}
211-
212-
func logBefore(entry *logrus.Entry, req *http.Request) {
213-
entry.WithFields(logrus.Fields{
214-
"request": req.RequestURI,
215-
"method": req.Method,
216-
"remote": func() (remote string) {
217-
remote = req.RemoteAddr
218-
if realIP := req.Header.Get("X-Real-IP"); realIP != "" {
219-
remote = realIP
220-
}
221-
return
222-
}(),
223-
}).Info("started handling request")
224-
}
225-
226-
func logAfter(entry *logrus.Entry, res *statusResponseWriter, start time.Time) {
227-
entry.WithFields(logrus.Fields{
228-
"status": res.Status(),
229-
"length": units.HumanSize(float64(res.Length())),
230-
"took": time.Since(start),
231-
}).Info("completed handling request")
232-
}
233-
234128
func ConfigVersionMiddleware(client *clientnative.HAProxyClient) Adapter {
235129
return func(h http.Handler) http.Handler {
236130
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

cmd/dataplaneapi/main.go

+25-89
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,20 @@ package main
1717

1818
import (
1919
"fmt"
20-
"io/ioutil"
2120
"os"
2221
"path"
23-
"path/filepath"
2422
"syscall"
2523

2624
loads "github.com/go-openapi/loads"
2725
"github.com/go-openapi/runtime"
2826
"github.com/go-openapi/runtime/security"
2927
flags "github.com/jessevdk/go-flags"
30-
log "github.com/sirupsen/logrus"
3128

3229
"github.com/haproxytech/client-native/v2/storage"
3330
"github.com/haproxytech/dataplaneapi"
3431
"github.com/haproxytech/dataplaneapi/configuration"
32+
"github.com/haproxytech/dataplaneapi/log"
3533
"github.com/haproxytech/dataplaneapi/operations"
36-
"github.com/haproxytech/dataplaneapi/syslog"
3734
)
3835

3936
// GitRepo ...
@@ -51,14 +48,6 @@ var GitDirty = ".dirty"
5148
// BuildTime ...
5249
var BuildTime = ""
5350

54-
func init() {
55-
log.SetFormatter(&log.TextFormatter{
56-
FullTimestamp: true,
57-
DisableColors: true,
58-
})
59-
log.SetOutput(os.Stdout)
60-
}
61-
6251
var cliOptions struct {
6352
Version bool `short:"v" long:"version" description:"Version and build information"`
6453
}
@@ -76,7 +65,8 @@ func main() {
7665
func startServer(cfg *configuration.Configuration) (reload configuration.AtomicBool) {
7766
swaggerSpec, err := loads.Embedded(dataplaneapi.SwaggerJSON, dataplaneapi.FlatSwaggerJSON)
7867
if err != nil {
79-
log.Fatalln(err)
68+
fmt.Println(err)
69+
os.Exit(1)
8070
}
8171

8272
dataplaneapi.BuildTime = BuildTime
@@ -93,21 +83,24 @@ func startServer(cfg *configuration.Configuration) (reload configuration.AtomicB
9383
for _, optsGroup := range api.CommandLineOptionsGroups {
9484
_, err = parser.AddGroup(optsGroup.ShortDescription, optsGroup.LongDescription, optsGroup.Options)
9585
if err != nil {
96-
log.Fatalln(err)
86+
fmt.Println(err)
87+
os.Exit(1)
9788
}
9889
}
9990

10091
_, err = parser.AddGroup("Show version", "Show build and version information", &cliOptions)
10192
if err != nil {
102-
log.Fatalln(err)
93+
fmt.Println(err)
94+
os.Exit(1)
10395
}
10496

10597
if _, err = parser.Parse(); err != nil {
10698
if fe, ok := err.(*flags.Error); ok {
10799
if fe.Type == flags.ErrHelp {
108100
os.Exit(0)
109101
} else {
110-
log.Fatalln(err)
102+
fmt.Println(err)
103+
os.Exit(1)
111104
}
112105
}
113106
}
@@ -121,26 +114,30 @@ func startServer(cfg *configuration.Configuration) (reload configuration.AtomicB
121114

122115
err = cfg.Load()
123116
if err != nil {
124-
log.Fatalln(err)
117+
fmt.Println(err)
118+
os.Exit(1)
125119
}
126120

127121
if cfg.HAProxy.UID != 0 {
128122
if err = syscall.Setuid(cfg.HAProxy.UID); err != nil {
129-
log.Fatalln("set uid:", err)
123+
fmt.Println("set uid:", err)
124+
os.Exit(1)
130125
}
131126
}
132127

133128
if cfg.HAProxy.GID != 0 {
134129
if err = syscall.Setgid(cfg.HAProxy.GID); err != nil {
135-
log.Fatalln("set gid:", err)
130+
fmt.Println("set gid:", err)
131+
os.Exit(1)
136132
}
137133
}
138134

139135
// incorporate changes from file to global settings
140136
dataplaneapi.SyncWithFileSettings(server, cfg)
141137
err = cfg.LoadRuntimeVars(dataplaneapi.SwaggerJSON, server.Host, server.Port)
142138
if err != nil {
143-
log.Fatalln(err)
139+
fmt.Println(err)
140+
os.Exit(1)
144141
}
145142

146143
configuration.HandlePIDFile(cfg.HAProxy)
@@ -172,15 +169,18 @@ func startServer(cfg *configuration.Configuration) (reload configuration.AtomicB
172169
}
173170
}
174171

175-
setupLogging(cfg)
172+
if err = log.InitWithConfiguration(cfg.LogTargets, cfg.Logging, cfg.Syslog); err != nil {
173+
fmt.Println(err)
174+
os.Exit(1)
175+
}
176176

177177
log.Infof("HAProxy Data Plane API %s %s%s", GitTag, GitCommit, GitDirty)
178178
log.Infof("Build from: %s", GitRepo)
179179
log.Infof("Build date: %s", BuildTime)
180180

181181
err = cfg.Save()
182182
if err != nil {
183-
log.Fatalln(err)
183+
log.Fatalf("Error saving configuration: %s", err.Error())
184184
}
185185

186186
// Applies when the Authorization header is set with the Basic scheme
@@ -204,7 +204,7 @@ func startServer(cfg *configuration.Configuration) (reload configuration.AtomicB
204204
dataplaneapi.ContextHandler.Cancel()
205205
err := server.Shutdown()
206206
if err != nil {
207-
log.Fatalln(err)
207+
log.Fatalf("Error reloading HAProxy Data Plane API: %s", err.Error())
208208
}
209209
}()
210210

@@ -214,7 +214,7 @@ func startServer(cfg *configuration.Configuration) (reload configuration.AtomicB
214214
log.Info("HAProxy Data Plane API shutting down")
215215
err := server.Shutdown()
216216
if err != nil {
217-
log.Fatalln(err)
217+
log.Fatalf("Error shutting down HAProxy Data Plane API: %s", err.Error())
218218
}
219219
os.Exit(0)
220220
case <-dataplaneapi.ContextHandler.Context().Done():
@@ -224,74 +224,10 @@ func startServer(cfg *configuration.Configuration) (reload configuration.AtomicB
224224

225225
server.ConfigureAPI()
226226
if err := server.Serve(); err != nil {
227-
log.Fatalln(err)
227+
log.Fatalf("Error running HAProxy Data Plane API: %s", err.Error())
228228
}
229229

230230
defer server.Shutdown() // nolint:errcheck
231231

232232
return reload
233233
}
234-
235-
func setupLogging(cfg *configuration.Configuration) {
236-
appLogger := log.StandardLogger()
237-
configureLogger(appLogger, cfg.Logging, func(opts configuration.SyslogOptions) configuration.SyslogOptions {
238-
opts.SyslogMsgID = "app"
239-
return opts
240-
}(cfg.Syslog))
241-
242-
accLogger := log.StandardLogger()
243-
configureLogger(accLogger, cfg.Logging, func(opts configuration.SyslogOptions) configuration.SyslogOptions {
244-
opts.SyslogMsgID = "accesslog"
245-
return opts
246-
}(cfg.Syslog))
247-
248-
dataplaneapi.AppLogger = appLogger
249-
dataplaneapi.AccLogger = accLogger
250-
}
251-
252-
func configureLogger(logger *log.Logger, loggingOptions configuration.LoggingOptions, syslogOptions configuration.SyslogOptions) {
253-
switch loggingOptions.LogFormat {
254-
case "text":
255-
logger.SetFormatter(&log.TextFormatter{
256-
FullTimestamp: true,
257-
DisableColors: true,
258-
})
259-
case "JSON":
260-
logger.SetFormatter(&log.JSONFormatter{})
261-
}
262-
263-
switch loggingOptions.LogTo {
264-
case "stdout":
265-
logger.SetOutput(os.Stdout)
266-
case "file":
267-
dir := filepath.Dir(loggingOptions.LogFile)
268-
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
269-
log.Warning("Error opening log file, no logging implemented: " + err.Error())
270-
}
271-
//nolint:govet
272-
logFile, err := os.OpenFile(loggingOptions.LogFile, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
273-
if err != nil {
274-
log.Warning("Error opening log file, no logging implemented: " + err.Error())
275-
}
276-
log.SetOutput(logFile)
277-
case "syslog":
278-
logger.SetOutput(ioutil.Discard)
279-
hook, err := syslog.NewRFC5424Hook(syslogOptions)
280-
if err != nil {
281-
log.Warningf("Error configuring Syslog logging: %s", err.Error())
282-
break
283-
}
284-
logger.AddHook(hook)
285-
}
286-
287-
switch loggingOptions.LogLevel {
288-
case "debug":
289-
logger.SetLevel(log.DebugLevel)
290-
case "info":
291-
logger.SetLevel(log.InfoLevel)
292-
case "warning":
293-
logger.SetLevel(log.WarnLevel)
294-
case "error":
295-
logger.SetLevel(log.ErrorLevel)
296-
}
297-
}

0 commit comments

Comments
 (0)