diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b173eda0a5268da9219a7be38ee489b2e4fd8fa4..4998c189d55e9afb0f5378a76517569d71d71fac 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -8,6 +8,10 @@ "Comment": "2016.08.31", "Rev": "ec89d50f00d39494f5b3ec5cf2fe75c53467a937" }, + { + "ImportPath": "github.com/client9/reopen", + "Rev": "4b86f9c0ead51cc410d05655596e30f281ed9071" + }, { "ImportPath": "github.com/dgrijalva/jwt-go", "Comment": "v3.0.0", diff --git a/internal/helper/logging.go b/internal/helper/logging.go index d5d51d982ca6fa4a74aaa786cb72790e12c94a3e..0a51d1849a322cb1b360a8da0efb7ad5f36bd6c2 100644 --- a/internal/helper/logging.go +++ b/internal/helper/logging.go @@ -2,10 +2,23 @@ package helper import ( "fmt" + "io" + "log" "net/http" + "os" "time" ) +var responseLogger *log.Logger + +func init() { + SetCustomResponseLogger(os.Stderr) +} + +func SetCustomResponseLogger(writer io.Writer) { + responseLogger = log.New(writer, "", 0) +} + type LoggingResponseWriter struct { rw http.ResponseWriter status int @@ -44,7 +57,7 @@ func (l *LoggingResponseWriter) WriteHeader(status int) { func (l *LoggingResponseWriter) Log(r *http.Request) { duration := time.Since(l.started) - fmt.Printf("%s %s - - [%s] %q %d %d %q %q %f\n", + responseLogger.Printf("%s %s - - [%s] %q %d %d %q %q %f\n", r.Host, r.RemoteAddr, l.started, fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto), l.status, l.written, r.Referer(), r.UserAgent(), duration.Seconds(), diff --git a/logging.go b/logging.go new file mode 100644 index 0000000000000000000000000000000000000000..cad98dcd8be42ca53887d23547665d0ab85bf672 --- /dev/null +++ b/logging.go @@ -0,0 +1,39 @@ +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + + "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" + + "github.com/client9/reopen" +) + +func reopenLogWriter(l reopen.WriteCloser, sighup chan os.Signal) { + for _ = range sighup { + log.Printf("Reopening log file") + l.Reopen() + } +} + +func startLogging(logFile string) { + var logWriter = reopen.Stderr + + if logFile != "" { + file, err := reopen.NewFileWriter(logFile) + if err != nil { + log.Fatalf("Unable to set output log: %s", err) + } + logWriter = file + } + + log.SetOutput(logWriter) + helper.SetCustomResponseLogger(logWriter) + + sighup := make(chan os.Signal, 1) + signal.Notify(sighup, syscall.SIGHUP) + + go reopenLogWriter(logWriter, sighup) +} diff --git a/main.go b/main.go index 54ece4b5daefb06a43860bf94ec3d84fc5df6747..daff549fa87e7f5c1b8a12ddc05f02367ece3a3d 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ var secretPath = flag.String("secretPath", "./.gitlab_workhorse_secret", "File w var apiLimit = flag.Uint("apiLimit", 0, "Number of API requests allowed at single time") var apiQueueLimit = flag.Uint("apiQueueLimit", 0, "Number of API requests allowed to be queued") var apiQueueTimeout = flag.Duration("apiQueueDuration", queueing.DefaultTimeout, "Maximum queueing duration of requests") +var logFile = flag.String("logFile", "", "Log file to be used") func main() { flag.Usage = func() { @@ -60,10 +61,11 @@ func main() { os.Exit(0) } + startLogging(*logFile) + backendURL, err := parseAuthBackend(*authBackend) if err != nil { - fmt.Fprintf(os.Stderr, "invalid authBackend: %v\n", err) - os.Exit(1) + log.Fatalf("invalid authBackend: %v", err) } log.Printf("Starting %s", version) diff --git a/vendor/github.com/client9/reopen/.gitignore b/vendor/github.com/client9/reopen/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..41aacb3ae785df5eec38428cad9ad4c803d9680e --- /dev/null +++ b/vendor/github.com/client9/reopen/.gitignore @@ -0,0 +1,27 @@ +# emacs turds +*~ + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/client9/reopen/.travis.yml b/vendor/github.com/client9/reopen/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..98f9246dfc74c8190f67c935b9d374d43f66d095 --- /dev/null +++ b/vendor/github.com/client9/reopen/.travis.yml @@ -0,0 +1,5 @@ +sudo: required +dist: trusty +language: generic +script: + - make -e docker-ci diff --git a/vendor/github.com/client9/reopen/LICENSE b/vendor/github.com/client9/reopen/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4345e0bafef120715bb9a30dc68ffc7f627d594b --- /dev/null +++ b/vendor/github.com/client9/reopen/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Nick Galbreath + +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. + diff --git a/vendor/github.com/client9/reopen/Makefile b/vendor/github.com/client9/reopen/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..35283b5bdc086d2329855554d75c681075d18319 --- /dev/null +++ b/vendor/github.com/client9/reopen/Makefile @@ -0,0 +1,31 @@ +all: build test lint + +build: + go build ./... + +test: + go test ./... + +lint: + golint ./... + gofmt -w -s . ./example* + goimports -w . ./example* + +clean: + rm -f *~ ./example*/*~ + rm -f ./example1/example1 + rm -f ./example2/example2 + go clean ./... + git gc + +ci: build test lint + +docker-ci: + docker run --rm \ + -e COVERALLS_REPO_TOKEN=$(COVERALLS_REPO_TOKEN) \ + -v $(PWD):/go/src/github.com/client9/reopen \ + -w /go/src/github.com/client9/reopen \ + nickg/golang-dev-docker \ + make ci + +.PHONY: ci docker-ci diff --git a/vendor/github.com/client9/reopen/README.md b/vendor/github.com/client9/reopen/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4c9cf2366287c3b23494c2eca0c4adf371fd64b7 --- /dev/null +++ b/vendor/github.com/client9/reopen/README.md @@ -0,0 +1,74 @@ +[](https://travis-ci.org/client9/reopen) [](http://goreportcard.com/report/client9/reopen) [](https://godoc.org/github.com/client9/reopen) [](http://gocover.io/github.com/client9/reopen) [](https://raw.githubusercontent.com/client9/reopen/master/LICENSE) + +Makes a standard os.File a "reopenable writer" and allows SIGHUP signals +to reopen log files, as needed by +[logrotated](https://fedorahosted.org/logrotate/). This is inspired +by the C/Posix +[freopen](http://pubs.opengroup.org/onlinepubs/009695399/functions/freopen.html) + +The simple version `reopen.NewFileWriter` does unbuffered writing. A +call to `.Reopen` closes the existing file handle, and then re-opens +it using the original filename. + +The more advanced version `reopen.NewBufferedFileWriter` buffers input +and flushes when the internal buffer is full (with care) or if 30 seconds has +elapsed. + +There is also `reopen.Stderr` and `reopen.Stdout` which implements the `reopen.Reopener` interface (and does nothing on a reopen call). + +`reopen.Discard` wraps `ioutil.Discard` + +Samples are in `example1` and `example2`. The `run.sh` scripts are a +dumb test where the file is rotated underneath the server, and nothing +is lost. This is not the most robust test but gives you an idea of how it works. + + +Here's some sample code. + +```go +package main + +/* Simple logrotate logger + */ +import ( + "fmt" + "log" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/client9/reopen" +) + +func main() { + // setup logger to write to our new *reopenable* log file + + f, err := reopen.NewFileWriter("/tmp/example.log") + if err != nil { + log.Fatalf("Unable to set output log: %s", err) + } + log.SetOutput(f) + + // Handle SIGHUP + // + // channel is number of signals needed to catch (more or less) + // we only are working with one here, SIGHUP + sighup := make(chan os.Signal, 1) + signal.Notify(sighup, syscall.SIGHUP) + go func() { + for { + <-sighup + fmt.Println("Got a sighup") + f.Reopen() + } + }() + + // dumb http server that just prints and logs the path + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s", r.URL.Path) + fmt.Fprintf(w, "%s\n", r.URL.Path) + }) + log.Fatal(http.ListenAndServe("127.0.0.1:8123", nil)) +} +``` diff --git a/vendor/github.com/client9/reopen/reopen.go b/vendor/github.com/client9/reopen/reopen.go new file mode 100644 index 0000000000000000000000000000000000000000..08cc0cead08af607c48019a72203952341945959 --- /dev/null +++ b/vendor/github.com/client9/reopen/reopen.go @@ -0,0 +1,232 @@ +package reopen + +import ( + "bufio" + "io" + "io/ioutil" + "os" + "sync" + "time" +) + +// Reopener interface defines something that can be reopened +type Reopener interface { + Reopen() error +} + +// Writer is a writer that also can be reopened +type Writer interface { + Reopener + io.Writer +} + +// WriteCloser is a io.WriteCloser that can also be reopened +type WriteCloser interface { + Reopener + io.WriteCloser +} + +// FileWriter that can also be reopened +type FileWriter struct { + mu sync.Mutex // ensures close / reopen / write are not called at the same time, protects f + f *os.File + mode os.FileMode + name string +} + +// Close calls the underlyding File.Close() +func (f *FileWriter) Close() error { + f.mu.Lock() + err := f.f.Close() + f.mu.Unlock() + return err +} + +// mutex free version +func (f *FileWriter) reopen() error { + if f.f != nil { + f.f.Close() + f.f = nil + } + newf, err := os.OpenFile(f.name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, f.mode) + if err != nil { + f.f = nil + return err + } + f.f = newf + + return nil +} + +// Reopen the file +func (f *FileWriter) Reopen() error { + f.mu.Lock() + err := f.reopen() + f.mu.Unlock() + return err +} + +// Write implements the stander io.Writer interface +func (f *FileWriter) Write(p []byte) (int, error) { + f.mu.Lock() + n, err := f.f.Write(p) + f.mu.Unlock() + return n, err +} + +// NewFileWriter opens a file for appending and writing and can be reopened. +// it is a ReopenWriteCloser... +func NewFileWriter(name string) (*FileWriter, error) { + // Standard default mode + return NewFileWriterMode(name, 0666) +} + +// NewFileWriterMode opens a Reopener file with a specific permission +func NewFileWriterMode(name string, mode os.FileMode) (*FileWriter, error) { + writer := FileWriter{ + f: nil, + name: name, + mode: mode, + } + err := writer.reopen() + if err != nil { + return nil, err + } + return &writer, nil +} + +// BufferedFileWriter is buffer writer than can be reopned +type BufferedFileWriter struct { + mu sync.Mutex + OrigWriter *FileWriter + BufWriter *bufio.Writer +} + +// Reopen implement Reopener +func (bw *BufferedFileWriter) Reopen() error { + bw.mu.Lock() + bw.BufWriter.Flush() + + // use non-mutex version since we are using this one + err := bw.OrigWriter.reopen() + + bw.BufWriter.Reset(io.Writer(bw.OrigWriter)) + bw.mu.Unlock() + + return err +} + +// Close flushes the internal buffer and closes the destination file +func (bw *BufferedFileWriter) Close() error { + bw.mu.Lock() + bw.BufWriter.Flush() + bw.OrigWriter.f.Close() + bw.mu.Unlock() + return nil +} + +// Write implements io.Writer (and reopen.Writer) +func (bw *BufferedFileWriter) Write(p []byte) (int, error) { + bw.mu.Lock() + n, err := bw.BufWriter.Write(p) + + // Special Case... if the used space in the buffer is LESS than + // the input, then we did a flush in the middle of the line + // and the full log line was not sent on its way. + if bw.BufWriter.Buffered() < len(p) { + bw.BufWriter.Flush() + } + + bw.mu.Unlock() + return n, err +} + +// flushDaemon periodically flushes the log file buffers. +// props to glog +func (bw *BufferedFileWriter) flushDaemon() { + for range time.NewTicker(flushInterval).C { + bw.mu.Lock() + bw.BufWriter.Flush() + bw.OrigWriter.f.Sync() + bw.mu.Unlock() + } +} + +const bufferSize = 256 * 1024 +const flushInterval = 30 * time.Second + +// NewBufferedFileWriter opens a buffered file that is periodically +// flushed. +// TODO: allow size and interval to be passed in. +func NewBufferedFileWriter(w *FileWriter) *BufferedFileWriter { + bw := BufferedFileWriter{ + OrigWriter: w, + BufWriter: bufio.NewWriterSize(w, bufferSize), + } + go bw.flushDaemon() + return &bw +} + +type multiReopenWriter struct { + writers []Writer +} + +// Reopen reopens all child Reopeners +func (t *multiReopenWriter) Reopen() error { + for _, w := range t.writers { + err := w.Reopen() + if err != nil { + return err + } + } + return nil +} + +// Write implements standard io.Write and reopen.Write +func (t *multiReopenWriter) Write(p []byte) (int, error) { + for _, w := range t.writers { + n, err := w.Write(p) + if err != nil { + return n, err + } + if n != len(p) { + return n, io.ErrShortWrite + } + } + return len(p), nil +} + +// MultiWriter creates a writer that duplicates its writes to all the +// provided writers, similar to the Unix tee(1) command. +// Also allow reopen +func MultiWriter(writers ...Writer) Writer { + w := make([]Writer, len(writers)) + copy(w, writers) + return &multiReopenWriter{w} +} + +type nopReopenWriteCloser struct { + io.Writer +} + +func (nopReopenWriteCloser) Reopen() error { + return nil +} + +func (nopReopenWriteCloser) Close() error { + return nil +} + +// NopWriter turns a normal writer into a ReopenWriter +// by doing a NOP on Reopen +// TODO: better name +func NopWriter(w io.Writer) WriteCloser { + return nopReopenWriteCloser{w} +} + +// Reopenable versions of os.Stdout, os.Stderr, /dev/null (reopen does nothing) +var ( + Stdout = NopWriter(os.Stdout) + Stderr = NopWriter(os.Stderr) + Discard = NopWriter(ioutil.Discard) +)