mirror of https://github.com/chubin/wttr.in
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
113 lines
2.1 KiB
113 lines
2.1 KiB
2 years ago
|
package logging
|
||
2 years ago
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"sync"
|
||
|
"time"
|
||
2 years ago
|
|
||
|
"github.com/chubin/wttr.in/internal/util"
|
||
2 years ago
|
)
|
||
|
|
||
|
// Logging request.
|
||
|
//
|
||
|
|
||
|
// RequestLogger logs all incoming HTTP requests.
|
||
|
type RequestLogger struct {
|
||
|
buf map[logEntry]int
|
||
|
filename string
|
||
|
m sync.Mutex
|
||
|
|
||
|
period time.Duration
|
||
|
lastFlush time.Time
|
||
|
}
|
||
|
|
||
|
type logEntry struct {
|
||
|
Proto string
|
||
|
IP string
|
||
|
URI string
|
||
|
UserAgent string
|
||
|
}
|
||
|
|
||
|
// NewRequestLogger returns a new RequestLogger for the specified log file.
|
||
|
// Flush logging entries after period of time.
|
||
|
func NewRequestLogger(filename string, period time.Duration) *RequestLogger {
|
||
|
return &RequestLogger{
|
||
|
buf: map[logEntry]int{},
|
||
|
filename: filename,
|
||
|
m: sync.Mutex{},
|
||
|
period: period,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Log logs information about a HTTP request.
|
||
|
func (rl *RequestLogger) Log(r *http.Request) error {
|
||
|
le := logEntry{
|
||
|
Proto: "http",
|
||
2 years ago
|
IP: util.ReadUserIP(r),
|
||
2 years ago
|
URI: r.RequestURI,
|
||
|
UserAgent: r.Header.Get("User-Agent"),
|
||
|
}
|
||
2 years ago
|
if r.TLS != nil {
|
||
|
le.Proto = "https"
|
||
|
}
|
||
2 years ago
|
|
||
|
rl.m.Lock()
|
||
|
rl.buf[le]++
|
||
|
rl.m.Unlock()
|
||
|
|
||
|
if time.Since(rl.lastFlush) > rl.period {
|
||
|
return rl.flush()
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// flush stores log data to disk, and flushes the buffer.
|
||
|
func (rl *RequestLogger) flush() error {
|
||
|
rl.m.Lock()
|
||
|
defer rl.m.Unlock()
|
||
|
|
||
|
// It is possible, that while waiting the mutex,
|
||
|
// the buffer was already flushed.
|
||
|
if time.Since(rl.lastFlush) <= rl.period {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Generate log output.
|
||
|
output := ""
|
||
|
for k, hitsNumber := range rl.buf {
|
||
|
output += fmt.Sprintf("%s %3d %s\n", time.Now().Format(time.RFC3339), hitsNumber, k.String())
|
||
|
}
|
||
|
|
||
|
// Open log file.
|
||
|
f, err := os.OpenFile(rl.filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
|
||
|
// Save output to log file.
|
||
|
_, err = f.Write([]byte(output))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Flush buffer.
|
||
|
rl.buf = map[logEntry]int{}
|
||
|
rl.lastFlush = time.Now()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
2 years ago
|
// String returns string representation of logEntry.
|
||
2 years ago
|
func (e *logEntry) String() string {
|
||
|
return fmt.Sprintf(
|
||
|
"%s %s %s %s",
|
||
|
e.Proto,
|
||
|
e.IP,
|
||
|
e.URI,
|
||
|
e.UserAgent,
|
||
|
)
|
||
|
}
|