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:
kermieisinthehouse
2022-02-02 16:20:34 -08:00
committed by GitHub
parent e48b2ba3e8
commit 0e514183a7
306 changed files with 29542 additions and 4792 deletions

20
vendor/github.com/go-chi/httplog/LICENSE generated vendored Normal file
View 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
View 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
View 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
View 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
View 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)
}