/
log.go
189 lines (167 loc) · 5.51 KB
/
log.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package accesslog
import (
"fmt"
"io"
"strings"
"time"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/core/memstore"
)
// Log represents the log data specifically for the accesslog middleware.
//
//easyjson:json
type Log struct {
// The AccessLog instance this Log was created of.
Logger *AccessLog `json:"-" yaml:"-" toml:"-"`
// The time the log is created.
Now time.Time `json:"-" yaml:"-" toml:"-"`
// TimeFormat selected to print the Time as string,
// useful on Template Formatter.
TimeFormat string `json:"-" yaml:"-" toml:"-"`
// Timestamp the Now's unix timestamp (milliseconds).
Timestamp int64 `json:"timestamp" csv:"timestamp"`
// Request-Response latency.
Latency time.Duration `json:"latency" csv:"latency"`
// The response status code.
Code int `json:"code" csv:"code"`
// Init request's Method and Path.
Method string `json:"method" csv:"method"`
Path string `json:"path" csv:"path"`
// The Remote Address.
IP string `json:"ip,omitempty" csv:"ip,omitempty"`
// Sorted URL Query arguments.
Query []memstore.StringEntry `json:"query,omitempty" csv:"query,omitempty"`
// Dynamic path parameters.
PathParams memstore.Store `json:"params,omitempty" csv:"params,omitempty"`
// Fields any data information useful to represent this Log.
Fields memstore.Store `json:"fields,omitempty" csv:"fields,omitempty"`
// The Request and Response raw bodies.
// If they are escaped (e.g. JSON),
// A third-party software can read it through:
// data, _ := strconv.Unquote(log.Request)
// err := json.Unmarshal([]byte(data), &customStruct)
Request string `json:"request,omitempty" csv:"request,omitempty"`
Response string `json:"response,omitempty" csv:"response,omitempty"`
// The actual number of bytes received and sent on the network (headers + body or body only).
BytesReceived int `json:"bytes_received,omitempty" csv:"bytes_received,omitempty"`
BytesSent int `json:"bytes_sent,omitempty" csv:"bytes_sent,omitempty"`
// A copy of the Request's Context when Async is true (safe to use concurrently),
// otherwise it's the current Context (not safe for concurrent access).
Ctx *context.Context `json:"-" yaml:"-" toml:"-"`
}
// Clone returns a raw copy value of this Log.
func (l *Log) Clone() Log {
return *l
}
// RequestValuesLine returns a string line which
// combines the path parameters, query and custom fields.
func (l *Log) RequestValuesLine() string {
buf := new(strings.Builder)
_, n := parseRequestValues(buf, l.Code, l.PathParams, l.Query, l.Fields)
if n == 0 {
return ""
}
requestValues := buf.String()
if n > 1 {
return requestValues[0 : n-1] // remove last space.
}
return requestValues
}
// BytesReceivedLine returns the formatted bytes received length.
func (l *Log) BytesReceivedLine() string {
if !l.Logger.BytesReceived && !l.Logger.BytesReceivedBody {
return ""
}
return formatBytes(l.BytesReceived)
}
// BytesSentLine returns the formatted bytes sent length.
func (l *Log) BytesSentLine() string {
if !l.Logger.BytesSent && !l.Logger.BytesSentBody {
return ""
}
return formatBytes(l.BytesSent)
}
func formatBytes(b int) string {
if b <= 0 {
return "0 B"
}
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB",
float64(b)/float64(div), "KMGTPE"[exp])
}
const (
eq = '='
space = ' '
)
// parses the request values (params, query and fields).
// returns the length of written bytes for parsing request values
// and the total.
func parseRequestValues(buf interface {
io.StringWriter
io.ByteWriter
Len() int
}, code int, pathParams memstore.Store, query []memstore.StringEntry, fields memstore.Store) (int, int) {
n := buf.Len()
if !context.StatusCodeNotSuccessful(code) {
// collect path parameters on a successful request-response only.
for _, entry := range pathParams {
buf.WriteString(entry.Key)
buf.WriteByte(eq)
buf.WriteString(fmt.Sprintf("%v", entry.ValueRaw))
buf.WriteByte(space)
}
}
for _, entry := range query {
buf.WriteString(entry.Key)
buf.WriteByte(eq)
buf.WriteString(entry.Value)
buf.WriteByte(space)
}
for _, entry := range fields {
buf.WriteString(entry.Key)
buf.WriteByte(eq)
buf.WriteString(fmt.Sprintf("%v", entry.ValueRaw))
buf.WriteByte(space)
}
total := buf.Len()
return total - n, total
}
type (
// Formatter is responsible to print a Log to the accesslog's writer.
Formatter interface {
// SetOutput should inject the accesslog's direct output,
// if this "dest" is used then the Formatter
// should manually control its concurrent use.
SetOutput(dest io.Writer)
// Format should print the Log.
// Returns nil error on handle successfully,
// otherwise the log will be printed using the default formatter
// and the error will be printed to the Iris Application's error log level.
// Should return true if this handled the logging, otherwise false to
// continue with the default print format.
Format(log *Log) (bool, error)
}
// Flusher can be implemented by a Writer or Formatter
// to call its Flush method on AccessLog.Close
// and on panic errors.
Flusher interface{ Flush() error }
// BufferTruncater can be implemented by writers
// that support buffering.
BufferTruncater interface{ Truncate(n int) }
// FileTruncater can be implemented by files
// that can support runtime size change.
FileTruncater interface{ Truncate(size int64) error }
)
var (
_ Formatter = (*JSON)(nil)
_ Formatter = (*Template)(nil)
_ Formatter = (*CSV)(nil)
)