mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Desktop integration (#2073)
* Open stash in system tray on Windows/MacOS * Add desktop notifications * MacOS Bundling * Add binary icon Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
e48b2ba3e8
commit
0e514183a7
20
vendor/github.com/go-chi/httplog/LICENSE
generated
vendored
Normal file
20
vendor/github.com/go-chi/httplog/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka).
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
67
vendor/github.com/go-chi/httplog/README.md
generated
vendored
Normal file
67
vendor/github.com/go-chi/httplog/README.md
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
httplog
|
||||
=======
|
||||
|
||||
Small but powerful structured logging package for HTTP request logging in Go.
|
||||
|
||||
## Example
|
||||
|
||||
(see [_example/](./_example/main.go))
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/httplog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Logger
|
||||
logger := httplog.NewLogger("httplog-example", httplog.Options{
|
||||
JSON: true,
|
||||
})
|
||||
|
||||
// Service
|
||||
r := chi.NewRouter()
|
||||
r.Use(httplog.RequestLogger(logger))
|
||||
r.Use(middleware.Heartbeat("/ping"))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hello world"))
|
||||
})
|
||||
|
||||
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("oh no")
|
||||
})
|
||||
|
||||
r.Get("/info", func(w http.ResponseWriter, r *http.Request) {
|
||||
oplog := httplog.LogEntry(r.Context())
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
oplog.Info().Msg("info here")
|
||||
w.Write([]byte("info here"))
|
||||
})
|
||||
|
||||
r.Get("/warn", func(w http.ResponseWriter, r *http.Request) {
|
||||
oplog := httplog.LogEntry(r.Context())
|
||||
oplog.Warn().Msg("warn here")
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("warn here"))
|
||||
})
|
||||
|
||||
r.Get("/err", func(w http.ResponseWriter, r *http.Request) {
|
||||
oplog := httplog.LogEntry(r.Context())
|
||||
oplog.Error().Msg("err here")
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("err here"))
|
||||
})
|
||||
|
||||
http.ListenAndServe(":5555", r)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
86
vendor/github.com/go-chi/httplog/config.go
generated
vendored
Normal file
86
vendor/github.com/go-chi/httplog/config.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package httplog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var DefaultOptions = Options{
|
||||
LogLevel: "info",
|
||||
LevelFieldName: "level",
|
||||
JSON: false,
|
||||
Concise: false,
|
||||
Tags: nil,
|
||||
SkipHeaders: nil,
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// LogLevel defines the minimum level of severity that app should log.
|
||||
//
|
||||
// Must be one of: ["trace", "debug", "info", "warn", "error", "critical"]
|
||||
LogLevel string
|
||||
|
||||
// LevelFieldName sets the field name for the log level or severity.
|
||||
// Some providers parse and search for different field names.
|
||||
LevelFieldName string
|
||||
|
||||
// JSON enables structured logging output in json. Make sure to enable this
|
||||
// in production mode so log aggregators can receive data in parsable format.
|
||||
//
|
||||
// In local development mode, its appropriate to set this value to false to
|
||||
// receive pretty output and stacktraces to stdout.
|
||||
JSON bool
|
||||
|
||||
// Concise mode includes fewer log details during the request flow. For example
|
||||
// exluding details like request content length, user-agent and other details.
|
||||
// This is useful if during development your console is too noisy.
|
||||
Concise bool
|
||||
|
||||
// Tags are additional fields included at the root level of all logs.
|
||||
// These can be useful for example the commit hash of a build, or an environment
|
||||
// name like prod/stg/dev
|
||||
Tags map[string]string
|
||||
|
||||
// SkipHeaders are additional headers which are redacted from the logs
|
||||
SkipHeaders []string
|
||||
}
|
||||
|
||||
// Configure will set new global/default options for the httplog and behaviour
|
||||
// of underlying zerolog pkg and its global logger.
|
||||
func Configure(opts Options) {
|
||||
if opts.LogLevel == "" {
|
||||
opts.LogLevel = "info"
|
||||
}
|
||||
|
||||
if opts.LevelFieldName == "" {
|
||||
opts.LevelFieldName = "level"
|
||||
}
|
||||
|
||||
// Pre-downcase all SkipHeaders
|
||||
for i, header := range opts.SkipHeaders {
|
||||
opts.SkipHeaders[i] = strings.ToLower(header)
|
||||
}
|
||||
|
||||
DefaultOptions = opts
|
||||
|
||||
// Config the zerolog global logger
|
||||
logLevel, err := zerolog.ParseLevel(strings.ToLower(opts.LogLevel))
|
||||
if err != nil {
|
||||
fmt.Printf("httplog: error! %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
zerolog.SetGlobalLevel(logLevel)
|
||||
|
||||
zerolog.LevelFieldName = strings.ToLower(opts.LevelFieldName)
|
||||
zerolog.TimestampFieldName = "timestamp"
|
||||
zerolog.TimeFieldFormat = time.RFC3339Nano
|
||||
|
||||
if !opts.JSON {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
|
||||
}
|
||||
}
|
||||
248
vendor/github.com/go-chi/httplog/httplog.go
generated
vendored
Normal file
248
vendor/github.com/go-chi/httplog/httplog.go
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
package httplog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func NewLogger(serviceName string, opts ...Options) zerolog.Logger {
|
||||
if len(opts) > 0 {
|
||||
Configure(opts[0])
|
||||
} else {
|
||||
Configure(DefaultOptions)
|
||||
}
|
||||
logger := log.With().Str("service", strings.ToLower(serviceName))
|
||||
if !DefaultOptions.Concise && len(DefaultOptions.Tags) > 0 {
|
||||
logger = logger.Fields(map[string]interface{}{
|
||||
"tags": DefaultOptions.Tags,
|
||||
})
|
||||
}
|
||||
return logger.Logger()
|
||||
}
|
||||
|
||||
// RequestLogger is an http middleware to log http requests and responses.
|
||||
//
|
||||
// NOTE: for simplicty, RequestLogger automatically makes use of the chi RequestID and
|
||||
// Recoverer middleware.
|
||||
func RequestLogger(logger zerolog.Logger) func(next http.Handler) http.Handler {
|
||||
return chi.Chain(
|
||||
middleware.RequestID,
|
||||
Handler(logger),
|
||||
middleware.Recoverer,
|
||||
).Handler
|
||||
}
|
||||
|
||||
func Handler(logger zerolog.Logger) func(next http.Handler) http.Handler {
|
||||
var f middleware.LogFormatter = &requestLogger{logger}
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
entry := f.NewLogEntry(r)
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
|
||||
buf := newLimitBuffer(512)
|
||||
ww.Tee(buf)
|
||||
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
var respBody []byte
|
||||
if ww.Status() >= 400 {
|
||||
respBody, _ = ioutil.ReadAll(buf)
|
||||
}
|
||||
entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), respBody)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(ww, middleware.WithLogEntry(r, entry))
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
type requestLogger struct {
|
||||
Logger zerolog.Logger
|
||||
}
|
||||
|
||||
func (l *requestLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
|
||||
entry := &RequestLoggerEntry{}
|
||||
msg := fmt.Sprintf("Request: %s %s", r.Method, r.URL.Path)
|
||||
entry.Logger = l.Logger.With().Fields(requestLogFields(r, true)).Logger()
|
||||
if !DefaultOptions.Concise {
|
||||
entry.Logger.Info().Fields(requestLogFields(r, DefaultOptions.Concise)).Msgf(msg)
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
type RequestLoggerEntry struct {
|
||||
Logger zerolog.Logger
|
||||
msg string
|
||||
}
|
||||
|
||||
func (l *RequestLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
||||
msg := fmt.Sprintf("Response: %d %s", status, statusLabel(status))
|
||||
if l.msg != "" {
|
||||
msg = fmt.Sprintf("%s - %s", msg, l.msg)
|
||||
}
|
||||
|
||||
responseLog := map[string]interface{}{
|
||||
"status": status,
|
||||
"bytes": bytes,
|
||||
"elapsed": float64(elapsed.Nanoseconds()) / 1000000.0, // in milliseconds
|
||||
}
|
||||
|
||||
if !DefaultOptions.Concise {
|
||||
// Include response header, as well for error status codes (>400) we include
|
||||
// the response body so we may inspect the log message sent back to the client.
|
||||
if status >= 400 {
|
||||
body, _ := extra.([]byte)
|
||||
responseLog["body"] = string(body)
|
||||
}
|
||||
if len(header) > 0 {
|
||||
responseLog["header"] = headerLogField(header)
|
||||
}
|
||||
}
|
||||
|
||||
l.Logger.WithLevel(statusLevel(status)).Fields(map[string]interface{}{
|
||||
"httpResponse": responseLog,
|
||||
}).Msgf(msg)
|
||||
}
|
||||
|
||||
func (l *RequestLoggerEntry) Panic(v interface{}, stack []byte) {
|
||||
stacktrace := "#"
|
||||
if DefaultOptions.JSON {
|
||||
stacktrace = string(stack)
|
||||
}
|
||||
|
||||
l.Logger = l.Logger.With().
|
||||
Str("stacktrace", stacktrace).
|
||||
Str("panic", fmt.Sprintf("%+v", v)).
|
||||
Logger()
|
||||
|
||||
l.msg = fmt.Sprintf("%+v", v)
|
||||
|
||||
if !DefaultOptions.JSON {
|
||||
middleware.PrintPrettyStack(v)
|
||||
}
|
||||
}
|
||||
|
||||
func requestLogFields(r *http.Request, concise bool) map[string]interface{} {
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
requestURL := fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
|
||||
|
||||
requestFields := map[string]interface{}{
|
||||
"requestURL": requestURL,
|
||||
"requestMethod": r.Method,
|
||||
"requestPath": r.URL.Path,
|
||||
"remoteIP": r.RemoteAddr,
|
||||
"proto": r.Proto,
|
||||
}
|
||||
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
|
||||
requestFields["requestID"] = reqID
|
||||
}
|
||||
|
||||
if concise {
|
||||
return map[string]interface{}{
|
||||
"httpRequest": requestFields,
|
||||
}
|
||||
}
|
||||
|
||||
requestFields["scheme"] = scheme
|
||||
|
||||
if len(r.Header) > 0 {
|
||||
requestFields["header"] = headerLogField(r.Header)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"httpRequest": requestFields,
|
||||
}
|
||||
}
|
||||
|
||||
func headerLogField(header http.Header) map[string]string {
|
||||
headerField := map[string]string{}
|
||||
for k, v := range header {
|
||||
k = strings.ToLower(k)
|
||||
switch {
|
||||
case len(v) == 0:
|
||||
continue
|
||||
case len(v) == 1:
|
||||
headerField[k] = v[0]
|
||||
default:
|
||||
headerField[k] = fmt.Sprintf("[%s]", strings.Join(v, "], ["))
|
||||
}
|
||||
if k == "authorization" || k == "cookie" || k == "set-cookie" {
|
||||
headerField[k] = "***"
|
||||
}
|
||||
|
||||
for _, skip := range DefaultOptions.SkipHeaders {
|
||||
if k == skip {
|
||||
headerField[k] = "***"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return headerField
|
||||
}
|
||||
|
||||
func statusLevel(status int) zerolog.Level {
|
||||
switch {
|
||||
case status <= 0:
|
||||
return zerolog.WarnLevel
|
||||
case status < 400: // for codes in 100s, 200s, 300s
|
||||
return zerolog.InfoLevel
|
||||
case status >= 400 && status < 500:
|
||||
return zerolog.WarnLevel
|
||||
case status >= 500:
|
||||
return zerolog.ErrorLevel
|
||||
default:
|
||||
return zerolog.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
func statusLabel(status int) string {
|
||||
switch {
|
||||
case status >= 100 && status < 300:
|
||||
return "OK"
|
||||
case status >= 300 && status < 400:
|
||||
return "Redirect"
|
||||
case status >= 400 && status < 500:
|
||||
return "Client Error"
|
||||
case status >= 500:
|
||||
return "Server Error"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods used by the application to get the request-scoped
|
||||
// logger entry and set additional fields between handlers.
|
||||
//
|
||||
// This is a useful pattern to use to set state on the entry as it
|
||||
// passes through the handler chain, which at any point can be logged
|
||||
// with a call to .Print(), .Info(), etc.
|
||||
|
||||
func LogEntry(ctx context.Context) zerolog.Logger {
|
||||
entry := ctx.Value(middleware.LogEntryCtxKey).(*RequestLoggerEntry)
|
||||
return entry.Logger
|
||||
}
|
||||
|
||||
func LogEntrySetField(ctx context.Context, key, value string) {
|
||||
if entry, ok := ctx.Value(middleware.LogEntryCtxKey).(*RequestLoggerEntry); ok {
|
||||
entry.Logger = entry.Logger.With().Str(key, value).Logger()
|
||||
}
|
||||
}
|
||||
|
||||
func LogEntrySetFields(ctx context.Context, fields map[string]interface{}) {
|
||||
if entry, ok := ctx.Value(middleware.LogEntryCtxKey).(*RequestLoggerEntry); ok {
|
||||
entry.Logger = entry.Logger.With().Fields(fields).Logger()
|
||||
}
|
||||
}
|
||||
37
vendor/github.com/go-chi/httplog/util.go
generated
vendored
Normal file
37
vendor/github.com/go-chi/httplog/util.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package httplog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// limitBuffer is used to pipe response body information from the
|
||||
// response writer to a certain limit amount. The idea is to read
|
||||
// a portion of the response body such as an error response so we
|
||||
// may log it.
|
||||
type limitBuffer struct {
|
||||
*bytes.Buffer
|
||||
limit int
|
||||
}
|
||||
|
||||
func newLimitBuffer(size int) io.ReadWriter {
|
||||
return limitBuffer{
|
||||
Buffer: bytes.NewBuffer(make([]byte, 0, size)),
|
||||
limit: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (b limitBuffer) Write(p []byte) (n int, err error) {
|
||||
if b.Buffer.Len() >= b.limit {
|
||||
return len(p), nil
|
||||
}
|
||||
limit := b.limit
|
||||
if len(p) < limit {
|
||||
limit = len(p)
|
||||
}
|
||||
return b.Buffer.Write(p[:limit])
|
||||
}
|
||||
|
||||
func (b limitBuffer) Read(p []byte) (n int, err error) {
|
||||
return b.Buffer.Read(p)
|
||||
}
|
||||
Reference in New Issue
Block a user