1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-02-02 11:34:20 +02:00

New logging; JSON and structured log formats

This commit is contained in:
DarthSim 2019-09-16 15:53:45 +06:00
parent 83c00a9e2b
commit 16784fb1e2
13 changed files with 322 additions and 111 deletions

View File

@ -19,12 +19,12 @@ func (e *imgproxyError) Error() string {
return e.Message return e.Message
} }
func (e *imgproxyError) ErrorWithStack() string { func (e *imgproxyError) FormatStack() string {
if e.stack == nil { if e.stack == nil {
return e.Message return ""
} }
return fmt.Sprintf("%s\n%s", e.Message, formatStack(e.stack)) return formatStack(e.stack)
} }
func (e *imgproxyError) StackTrace() []uintptr { func (e *imgproxyError) StackTrace() []uintptr {

3
go.mod
View File

@ -30,12 +30,13 @@ require (
github.com/prometheus/common v0.1.0 // indirect github.com/prometheus/common v0.1.0 // indirect
github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74 // indirect github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74 // indirect
github.com/shirou/gopsutil v2.18.12+incompatible // indirect github.com/shirou/gopsutil v2.18.12+incompatible // indirect
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b
golang.org/x/net v0.0.0-20190110200230-915654e7eabc golang.org/x/net v0.0.0-20190110200230-915654e7eabc
golang.org/x/oauth2 v0.0.0-20190110195249-fd3eaa146cbb // indirect golang.org/x/oauth2 v0.0.0-20190110195249-fd3eaa146cbb // indirect
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb golang.org/x/sys v0.0.0-20190422165155-953cdadca894
google.golang.org/api v0.1.0 google.golang.org/api v0.1.0
google.golang.org/genproto v0.0.0-20190110221437-6909d8a4a91b // indirect google.golang.org/genproto v0.0.0-20190110221437-6909d8a4a91b // indirect
) )

7
go.sum
View File

@ -64,6 +64,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -108,7 +109,10 @@ github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74 h1:d1Xoc24yp/pXm
github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190104112138-b1a0a9a36d74/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -117,6 +121,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938= go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM= golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM=
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
@ -145,6 +150,8 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb h1:1w588/yEchbPNpa9sEvOcMZYbWHedwJjg4VOAdDHWHk= golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb h1:1w588/yEchbPNpa9sEvOcMZYbWHedwJjg4VOAdDHWHk=
golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190109145017-48ac38b7c8cb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

123
log.go
View File

@ -1,92 +1,97 @@
package main package main
import ( import (
"fmt"
"log"
"log/syslog"
"net/http" "net/http"
logrus "github.com/sirupsen/logrus"
) )
const ( func initLog() {
logRequestFmt = "[%s] %s: %s" logFormat := "pretty"
logRequestSyslogFmt = "REQUEST [%s] %s: %s" strEnvConfig(&logFormat, "IMGPROXY_LOG_FORMAT")
logResponseFmt = "[%s] |\033[7;%dm %d \033[0m| %s"
logResponseSyslogFmt = "RESPONSE [%s] | %d | %s" switch logFormat {
logWarningFmt = "\033[1;33m[WARNING]\033[0m %s" case "structured":
logWarningSyslogFmt = "WARNING %s" logrus.SetFormatter(&logStructuredFormatter{})
logFatalSyslogFmt = "FATAL %s" case "json":
) logrus.SetFormatter(&logrus.JSONFormatter{})
default:
logrus.SetFormatter(newLogPrettyFormatter())
}
logrus.SetLevel(logrus.DebugLevel)
if isSyslogEnabled() {
slHook, err := newSyslogHook()
if err != nil {
logFatal("Unable to connect to local syslog daemon")
}
logrus.AddHook(slHook)
}
}
func logRequest(reqID string, r *http.Request) { func logRequest(reqID string, r *http.Request) {
path := r.URL.RequestURI() path := r.URL.RequestURI()
log.Printf(logRequestFmt, reqID, r.Method, path) logrus.WithFields(logrus.Fields{
"request_id": reqID,
if syslogWriter != nil { "method": r.Method,
syslogWriter.Notice(fmt.Sprintf(logRequestSyslogFmt, reqID, r.Method, path)) }).Infof("Started %s", path)
}
} }
func logResponse(reqID string, status int, msg string) { func logResponse(reqID string, r *http.Request, status int, err *imgproxyError, imageURL *string, po *processingOptions) {
var color int var level logrus.Level
switch { switch {
case status >= 500: case status >= 500:
color = 31 level = logrus.ErrorLevel
case status >= 400: case status >= 400:
color = 33 level = logrus.WarnLevel
default: default:
color = 32 level = logrus.InfoLevel
} }
log.Printf(logResponseFmt, reqID, color, status, msg) fields := logrus.Fields{
"request_id": reqID,
"method": r.Method,
"status": status,
}
if syslogWriter != nil { if err != nil {
syslogMsg := fmt.Sprintf(logResponseSyslogFmt, reqID, status, msg) fields["error"] = err
switch { if stack := err.FormatStack(); len(stack) > 0 {
case status >= 500: fields["stack"] = stack
if syslogLevel >= syslog.LOG_ERR {
syslogWriter.Err(syslogMsg)
}
case status >= 400:
if syslogLevel >= syslog.LOG_WARNING {
syslogWriter.Warning(syslogMsg)
}
default:
if syslogLevel >= syslog.LOG_NOTICE {
syslogWriter.Notice(syslogMsg)
}
} }
} }
if imageURL != nil {
fields["image_url"] = *imageURL
}
if po != nil {
fields["processing_options"] = po
}
logrus.WithFields(fields).Logf(
level,
"Completed in %s %s", getTimerSince(r.Context()), r.URL.RequestURI(),
)
} }
func logNotice(f string, args ...interface{}) { func logNotice(f string, args ...interface{}) {
msg := fmt.Sprintf(f, args...) logrus.Infof(f, args...)
log.Print(msg)
if syslogWriter != nil && syslogLevel >= syslog.LOG_NOTICE {
syslogWriter.Notice(msg)
}
} }
func logWarning(f string, args ...interface{}) { func logWarning(f string, args ...interface{}) {
msg := fmt.Sprintf(f, args...) logrus.Warnf(f, args...)
log.Printf(logWarningFmt, msg)
if syslogWriter != nil && syslogLevel >= syslog.LOG_WARNING {
syslogWriter.Warning(fmt.Sprintf(logWarningSyslogFmt, msg))
}
} }
func logFatal(f string, args ...interface{}) { func logFatal(f string, args ...interface{}) {
msg := fmt.Sprintf(f, args...) logrus.Fatalf(f, args...)
}
if syslogWriter != nil {
syslogWriter.Crit(fmt.Sprintf(logFatalSyslogFmt, msg)) func logDebug(f string, args ...interface{}) {
} logrus.Debugf(f, args...)
log.Fatal(msg)
} }

155
log_formatter.go Normal file
View File

@ -0,0 +1,155 @@
package main
import (
"bytes"
"fmt"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
logrus "github.com/sirupsen/logrus"
)
var (
logKeysPriorities = map[string]int{
"request_id": 3,
"method": 2,
"status": 1,
"error": -1,
"stack": -2,
}
logQuotingRe = regexp.MustCompile(`^[a-zA-Z0-9\-._/@^+]+$`)
)
type logKeys []string
func (p logKeys) Len() int { return len(p) }
func (p logKeys) Less(i, j int) bool { return logKeysPriorities[p[i]] > logKeysPriorities[p[j]] }
func (p logKeys) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type logPrettyFormatter struct {
levelFormat string
}
func newLogPrettyFormatter() *logPrettyFormatter {
f := new(logPrettyFormatter)
levelLenMax := 0
for _, level := range logrus.AllLevels {
levelLen := utf8.RuneCount([]byte(level.String()))
if levelLen > levelLenMax {
levelLenMax = levelLen
}
}
f.levelFormat = fmt.Sprintf("%%-%ds", levelLenMax)
return f
}
func (f *logPrettyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
keys := make([]string, 0, len(entry.Data))
for k := range entry.Data {
if k != "stack" {
keys = append(keys, k)
}
}
sort.Sort(logKeys(keys))
levelColor := 36
switch entry.Level {
case logrus.DebugLevel, logrus.TraceLevel:
levelColor = 37
case logrus.WarnLevel:
levelColor = 33
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
levelColor = 31
}
levelText := fmt.Sprintf(f.levelFormat, strings.ToUpper(entry.Level.String()))
msg := strings.TrimSuffix(entry.Message, "\n")
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = new(bytes.Buffer)
}
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m [%s] %s ", levelColor, levelText, entry.Time.Format(time.RFC3339), msg)
for _, k := range keys {
v := entry.Data[k]
fmt.Fprintf(b, " \x1b[1m%s\x1b[0m=", k)
f.appendValue(b, v)
}
b.WriteByte('\n')
if stack, ok := entry.Data["stack"]; ok {
fmt.Fprintln(b, stack)
}
return b.Bytes(), nil
}
func (f *logPrettyFormatter) appendValue(b *bytes.Buffer, value interface{}) {
strValue, ok := value.(string)
if !ok {
strValue = fmt.Sprint(value)
}
if logQuotingRe.MatchString(strValue) {
b.WriteString(strValue)
} else {
fmt.Fprintf(b, "%q", strValue)
}
}
type logStructuredFormatter struct{}
func (f *logStructuredFormatter) Format(entry *logrus.Entry) ([]byte, error) {
keys := make([]string, 0, len(entry.Data))
for k := range entry.Data {
keys = append(keys, k)
}
sort.Sort(logKeys(keys))
msg := strings.TrimSuffix(entry.Message, "\n")
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = new(bytes.Buffer)
}
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
f.appendKeyValue(b, "level", entry.Level.String())
f.appendKeyValue(b, "message", msg)
for _, k := range keys {
f.appendKeyValue(b, k, entry.Data[k])
}
b.WriteByte('\n')
return b.Bytes(), nil
}
func (f *logStructuredFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
if b.Len() != 0 {
b.WriteByte(' ')
}
strValue, ok := value.(string)
if !ok {
strValue = fmt.Sprint(value)
}
fmt.Fprintf(b, "%s=%q", key, strValue)
}

View File

@ -17,7 +17,7 @@ type ctxKey string
func initialize() { func initialize() {
log.SetOutput(os.Stdout) log.SetOutput(os.Stdout)
initSyslog() initLog()
configure() configure()
initNewrelic() initNewrelic()
initPrometheus() initPrometheus()
@ -38,7 +38,7 @@ func main() {
if logMemStats { if logMemStats {
var m runtime.MemStats var m runtime.MemStats
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
logNotice("[MEMORY USAGE] Sys: %d; HeapIdle: %d; HeapInuse: %d", m.Sys/1024/1024, m.HeapIdle/1024/1024, m.HeapInuse/1024/1024) logDebug("MEMORY USAGE: Sys=%d HeapIdle=%d HeapInuse=%d", m.Sys/1024/1024, m.HeapIdle/1024/1024, m.HeapInuse/1024/1024)
} }
} }
}() }()

View File

@ -83,11 +83,22 @@ func respondWithImage(ctx context.Context, reqID string, r *http.Request, rw htt
rw.Write(data) rw.Write(data)
} }
logResponse(reqID, 200, fmt.Sprintf("Processed in %s: %s; %+v", getTimerSince(ctx), getImageURL(ctx), po)) imageURL := getImageURL(ctx)
logResponse(reqID, r, 200, nil, &imageURL, po)
// logResponse(reqID, r, 200, getTimerSince(ctx), getImageURL(ctx), po))
}
func respondWithNotModified(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter) {
rw.WriteHeader(304)
imageURL := getImageURL(ctx)
logResponse(reqID, r, 304, nil, &imageURL, getProcessingOptions(ctx))
} }
func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) { func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := r.Context()
if newRelicEnabled { if newRelicEnabled {
var newRelicCancel context.CancelFunc var newRelicCancel context.CancelFunc
@ -103,7 +114,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
processingSem <- struct{}{} processingSem <- struct{}{}
defer func() { <-processingSem }() defer func() { <-processingSem }()
ctx, timeoutCancel := startTimer(ctx, time.Duration(conf.WriteTimeout)*time.Second) ctx, timeoutCancel := context.WithTimeout(ctx, time.Duration(conf.WriteTimeout)*time.Second)
defer timeoutCancel() defer timeoutCancel()
ctx, err := parsePath(ctx, r) ctx, err := parsePath(ctx, r)
@ -130,8 +141,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("ETag", eTag) rw.Header().Set("ETag", eTag)
if eTag == r.Header.Get("If-None-Match") { if eTag == r.Header.Get("If-None-Match") {
logResponse(reqID, 304, "Not modified") respondWithNotModified(ctx, reqID, r, rw)
rw.WriteHeader(304)
return return
} }
} }

View File

@ -189,6 +189,10 @@ func (po *processingOptions) presetUsed(name string) {
po.UsedPresets = append(po.UsedPresets, name) po.UsedPresets = append(po.UsedPresets, name)
} }
func (po *processingOptions) String() string {
return fmt.Sprintf("%+v", *po)
}
func colorFromHex(hexcolor string) (rgbColor, error) { func colorFromHex(hexcolor string) (rgbColor, error) {
c := rgbColor{} c := rgbColor{}

View File

@ -108,7 +108,7 @@ func initPrometheus() {
logFatal(err.Error()) logFatal(err.Error())
} }
logNotice("Starting Prometheus server at %s\n", conf.PrometheusBind) logNotice("Starting Prometheus server at %s", conf.PrometheusBind)
if err := s.Serve(l); err != nil && err != http.ErrServerClosed { if err := s.Serve(l); err != nil && err != http.ErrServerClosed {
logFatal(err.Error()) logFatal(err.Error())
} }

View File

@ -65,6 +65,8 @@ func (r *router) OPTIONS(prefix string, handler routeHandler, exact bool) {
} }
func (r *router) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (r *router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
req = req.WithContext(setTimerSince(req.Context()))
reqID := req.Header.Get(xRequestIDHeader) reqID := req.Header.Get(xRequestIDHeader)
if len(reqID) == 0 || !requestIDRe.MatchString(reqID) { if len(reqID) == 0 || !requestIDRe.MatchString(reqID) {

View File

@ -111,7 +111,7 @@ func handlePanic(reqID string, rw http.ResponseWriter, r *http.Request, err erro
reportError(err, r) reportError(err, r)
} }
logResponse(reqID, ierr.StatusCode, ierr.ErrorWithStack()) logResponse(reqID, r, ierr.StatusCode, ierr, nil, nil)
rw.WriteHeader(ierr.StatusCode) rw.WriteHeader(ierr.StatusCode)
@ -123,17 +123,18 @@ func handlePanic(reqID string, rw http.ResponseWriter, r *http.Request, err erro
} }
func handleHealth(reqID string, rw http.ResponseWriter, r *http.Request) { func handleHealth(reqID string, rw http.ResponseWriter, r *http.Request) {
logResponse(reqID, 200, string(imgproxyIsRunningMsg)) logResponse(reqID, r, 200, nil, nil, nil)
rw.WriteHeader(200) rw.WriteHeader(200)
rw.Write(imgproxyIsRunningMsg) rw.Write(imgproxyIsRunningMsg)
} }
func handleOptions(reqID string, rw http.ResponseWriter, r *http.Request) { func handleOptions(reqID string, rw http.ResponseWriter, r *http.Request) {
logResponse(reqID, 200, "Respond with options") logResponse(reqID, r, 200, nil, nil, nil)
rw.WriteHeader(200) rw.WriteHeader(200)
} }
func handleFavicon(reqID string, rw http.ResponseWriter, r *http.Request) { func handleFavicon(reqID string, rw http.ResponseWriter, r *http.Request) {
logResponse(reqID, r, 200, nil, nil, nil)
// TODO: Add a real favicon maybe? // TODO: Add a real favicon maybe?
rw.WriteHeader(200) rw.WriteHeader(200)
} }

View File

@ -1,55 +1,84 @@
package main package main
import ( import (
"log" "fmt"
"log/syslog" "log/syslog"
"os"
"github.com/sirupsen/logrus"
) )
var ( var (
syslogWriter *syslog.Writer syslogLevels = map[string]logrus.Level{
syslogLevel syslog.Priority "crit": logrus.FatalLevel,
"error": logrus.ErrorLevel,
"warning": logrus.WarnLevel,
"info": logrus.InfoLevel,
}
) )
var syslogLevels = map[string]syslog.Priority{ type syslogHook struct {
"crit": syslog.LOG_CRIT, writer *syslog.Writer
"error": syslog.LOG_ERR, levels []logrus.Level
"warning": syslog.LOG_WARNING, formatter logrus.Formatter
"notice": syslog.LOG_NOTICE,
} }
func initSyslog() { func isSyslogEnabled() (enabled bool) {
var (
err error
enabled bool
network, addr string
)
boolEnvConfig(&enabled, "IMGPROXY_SYSLOG_ENABLE") boolEnvConfig(&enabled, "IMGPROXY_SYSLOG_ENABLE")
return
}
if !enabled { func newSyslogHook() (*syslogHook, error) {
return var (
} network, addr string
level logrus.Level
tag = "imgproxy"
levelStr = "notice"
)
strEnvConfig(&network, "IMGPROXY_SYSLOG_NETWORK") strEnvConfig(&network, "IMGPROXY_SYSLOG_NETWORK")
strEnvConfig(&addr, "IMGPROXY_SYSLOG_ADDRESS") strEnvConfig(&addr, "IMGPROXY_SYSLOG_ADDRESS")
tag := "imgproxy"
strEnvConfig(&tag, "IMGPROXY_SYSLOG_TAG") strEnvConfig(&tag, "IMGPROXY_SYSLOG_TAG")
syslogWriter, err = syslog.Dial(network, addr, syslog.LOG_NOTICE, tag)
if err != nil {
log.Fatalf("Can't connect to syslog: %s", err)
}
levelStr := "notice"
strEnvConfig(&levelStr, "IMGPROXY_SYSLOG_LEVEL") strEnvConfig(&levelStr, "IMGPROXY_SYSLOG_LEVEL")
if l, ok := syslogLevels[levelStr]; ok { if l, ok := syslogLevels[levelStr]; ok {
syslogLevel = l level = l
} else { } else {
syslogLevel = syslog.LOG_NOTICE level = logrus.InfoLevel
logWarning("Syslog level '%s' is invalid, 'notice' is used", levelStr) logWarning("Syslog level '%s' is invalid, 'info' is used", levelStr)
}
w, err := syslog.Dial(network, addr, syslog.LOG_NOTICE, tag)
return &syslogHook{
writer: w,
levels: logrus.AllLevels[:int(level)+1],
formatter: &logStructuredFormatter{},
}, err
}
func (hook *syslogHook) Fire(entry *logrus.Entry) error {
line, err := hook.formatter.Format(entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read entry, %v\n", err)
return err
}
switch entry.Level {
case logrus.PanicLevel, logrus.FatalLevel:
return hook.writer.Crit(string(line))
case logrus.ErrorLevel:
return hook.writer.Err(string(line))
case logrus.WarnLevel:
return hook.writer.Warning(string(line))
case logrus.InfoLevel:
return hook.writer.Info(string(line))
default:
return nil
} }
} }
func (hook *syslogHook) Levels() []logrus.Level {
return hook.levels
}

View File

@ -8,11 +8,8 @@ import (
var timerSinceCtxKey = ctxKey("timerSince") var timerSinceCtxKey = ctxKey("timerSince")
func startTimer(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) { func setTimerSince(ctx context.Context) context.Context {
return context.WithTimeout( return context.WithValue(ctx, timerSinceCtxKey, time.Now())
context.WithValue(ctx, timerSinceCtxKey, time.Now()),
d,
)
} }
func getTimerSince(ctx context.Context) time.Duration { func getTimerSince(ctx context.Context) time.Duration {