-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlogger.go
134 lines (114 loc) · 3.39 KB
/
logger.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package gozap2seq
import (
"bytes"
"errors"
"io"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"sync"
"github.com/tidwall/gjson"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type LogInjector struct {
client *http.Client
sequrl string
seqtoken string
consolelogger *zap.Logger // this is used in case of SEQ error and EnableFallbackConsoleLogger == true
wg *sync.WaitGroup
EnableFallbackConsoleLogger bool
}
func NewLogInjector(sequrl, token string) (*LogInjector, error) {
pu, err := url.Parse(sequrl)
if err != nil {
return nil, err
}
if pu.Hostname() == "" {
return nil, errors.New("invalid hostname in SEQ URL")
}
furl := pu.Scheme + "://" + pu.Hostname() + ":" + pu.Port()
if pu.Port() == "" {
furl += "5341"
}
return &LogInjector{
client: &http.Client{},
sequrl: furl,
seqtoken: strings.TrimSpace(token),
wg: &sync.WaitGroup{},
}, nil
}
func (i *LogInjector) Build(zapconfig zap.Config) *zap.Logger {
if i.EnableFallbackConsoleLogger {
// Create a console logger that will be used if SEQ fails
consoleencoder := zapcore.NewConsoleEncoder(zapconfig.EncoderConfig)
stderrsync := zapcore.Lock(os.Stderr)
i.consolelogger = zap.New(zapcore.NewCore(consoleencoder, stderrsync, zapconfig.Level.Level()))
}
// SEQ requires that the fields and value format follow these rules
configcopy := zapconfig
configcopy.EncoderConfig.EncodeTime = zapcore.RFC3339NanoTimeEncoder
configcopy.EncoderConfig.EncodeLevel = zapcore.LowercaseLevelEncoder
configcopy.EncoderConfig.LevelKey = "@l"
configcopy.EncoderConfig.TimeKey = "@t"
configcopy.EncoderConfig.MessageKey = "@mt"
configcopy.EncoderConfig.CallerKey = "caller"
configcopy.EncoderConfig.StacktraceKey = "trace"
jsonencoder := zapcore.NewJSONEncoder(configcopy.EncoderConfig)
seqsync := zapcore.AddSync(i)
return zap.New(zapcore.NewCore(jsonencoder, seqsync, configcopy.Level.Level()),
zap.AddCaller(),
zap.AddStacktrace(zapcore.ErrorLevel))
}
func (i *LogInjector) Write(p []byte) (n int, err error) {
i.wg.Add(1)
// Since we immediately return, we need to make a copy of the payload that takes time to be sent
pcopy := make([]byte, len(p))
copy(pcopy, p)
req, err := http.NewRequest("POST", i.sequrl+"/api/events/raw", bytes.NewBuffer(pcopy))
if err != nil {
return 0, err
}
if i.seqtoken != "" {
req.Header.Set("X-Seq-ApiKey", i.seqtoken)
}
req.Header.Set("Content-Type", "application/vnd.serilog.clef")
go func() {
defer i.wg.Done()
// Get the response
resp, err := i.client.Do(req)
if err != nil {
if i.consolelogger != nil {
i.consolelogger.Error("Failed reading SEQ response", zap.Error(err))
}
return
}
defer resp.Body.Close()
// The status is supposed to be 201 (Created)
if resp.StatusCode != 201 {
// Parse the JSON message
content, err := io.ReadAll(resp.Body)
if err != nil {
if i.consolelogger != nil {
i.consolelogger.Error("Failed reading SEQ body", zap.Error(err))
}
return
}
value := gjson.GetBytes(content, "Error")
if i.consolelogger != nil {
i.consolelogger.Error("SEQ error",
zap.String("error-message", value.String()),
zap.String("raw-content", string(content)),
zap.String("content-type", resp.Header.Get("Content-Type")))
}
return
}
}()
return len(p), nil // always success (but it might have failed)
}
func (i *LogInjector) Wait() {
runtime.Gosched()
i.wg.Wait()
}