diff --git a/config/logger/logging.go b/config/logger/logging.go index 1bfa41df..dae2bcba 100644 --- a/config/logger/logging.go +++ b/config/logger/logging.go @@ -7,9 +7,12 @@ import ( "io" "log" "os" + "os/signal" "strings" + "syscall" "time" + "github.com/client9/reopen" "github.com/sirupsen/logrus" ) @@ -65,8 +68,15 @@ func (l *Config) Init() error { output = os.Stderr case "stdout": output = os.Stdout - default: + case "": return fmt.Errorf("Unknown output type '%s'", l.Output) + default: + f, err := reopen.NewFileWriter(l.Output) + if err != nil { + return fmt.Errorf("Error initializing log file '%s': %s", l.Output, err) + } + initializeSignal(f) + output = f } logrus.SetLevel(level) logrus.SetFormatter(formatter) @@ -95,3 +105,18 @@ func (f *DefaultLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { // Panic and Fatal are handled by logrus automatically return b.Bytes(), nil } + +func initializeSignal(f *reopen.FileWriter) { + // Handle SIGUSR1 + // + // channel is number of signals needed to catch (more or less) + // we only are working with one here, SIGUSR1 + sighup := make(chan os.Signal, 1) + signal.Notify(sighup, syscall.SIGUSR1) + go func() { + for { + <-sighup + f.Reopen() + } + }() +} diff --git a/config/logger/logging_test.go b/config/logger/logging_test.go index d401fefd..393fe475 100644 --- a/config/logger/logging_test.go +++ b/config/logger/logging_test.go @@ -1,8 +1,10 @@ package logger import ( + "io/ioutil" "os" "reflect" + "strings" "testing" "github.com/sirupsen/logrus" @@ -65,3 +67,32 @@ func TestDefaultFormatterPanic(t *testing.T) { }() logrus.Panicln("Panic Test") } + +func TestFileLogger(t *testing.T) { + // initialize logger + filename := "/tmp/test_log" + testLog := &Config{ + Level: "DEBUG", + Format: "text", + Output: filename, + } + err := testLog.Init() + if err != nil { + t.Errorf("Did not expect error: %v", err) + } + + // write a log message + logMsg := "this is a test" + logrus.Info(logMsg) + content, err := ioutil.ReadFile(filename) + if err != nil { + t.Errorf("Did not expect error: %v", err) + } + if len(content) == 0 { + t.Error("could not write log to file") + } + logs := string(content) + if !strings.Contains(logs, logMsg) { + t.Errorf("expected log file to contain '%s', got '%s'", logMsg, logs) + } +} diff --git a/glide.lock b/glide.lock index 761740d1..fe7c2e72 100644 --- a/glide.lock +++ b/glide.lock @@ -70,3 +70,5 @@ testImports: version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 subpackages: - assert +- name: github.com/client9/reopen + version: 1a6ccbeaae3f56aa0058f5491382cb21726e214e diff --git a/glide.yaml b/glide.yaml index e5e9de6a..22bfab67 100644 --- a/glide.yaml +++ b/glide.yaml @@ -21,3 +21,4 @@ testImport: version: v1.1.4 subpackages: - assert +- package: github.com/client9/reopen diff --git a/integration_tests/fixtures/app/Dockerfile b/integration_tests/fixtures/app/Dockerfile index 9b4c50c7..6839f601 100644 --- a/integration_tests/fixtures/app/Dockerfile +++ b/integration_tests/fixtures/app/Dockerfile @@ -10,6 +10,7 @@ RUN npm install -g json http-server COPY build/containerpilot /bin/containerpilot COPY containerpilot.json5 /etc/containerpilot.json5 COPY containerpilot-with-coprocess.json5 /etc/containerpilot-with-coprocess.json5 +COPY containerpilot-with-file-log.json5 /etc/containerpilot-with-file-log.json5 COPY reload-app.sh /reload-app.sh COPY reload-containerpilot.sh /reload-containerpilot.sh COPY sensor.sh /sensor.sh diff --git a/integration_tests/fixtures/app/containerpilot-with-file-log.json5 b/integration_tests/fixtures/app/containerpilot-with-file-log.json5 new file mode 100644 index 00000000..fdb47175 --- /dev/null +++ b/integration_tests/fixtures/app/containerpilot-with-file-log.json5 @@ -0,0 +1,17 @@ +{ + consul: "consul:8500", + logging: { + level: "DEBUG", + format: "text", + output: "/tmp/containerpilot.log" + }, + jobs: [ + { + name: "app", + exec: "echo 'hello world!'", + when: { + interval: 1 + } + } + ] +} diff --git a/integration_tests/tests/test_reopen/docker-compose.yml b/integration_tests/tests/test_reopen/docker-compose.yml new file mode 100644 index 00000000..bf73b4e3 --- /dev/null +++ b/integration_tests/tests/test_reopen/docker-compose.yml @@ -0,0 +1,10 @@ +app: + image: "cpfix_app" + mem_limit: 128m + volumes: + - './test_reopen.sh:/tmp/test_reopen.sh' + environment: + # needs to be in the container already or we'll potentially + # error when we try to rewrite it; update in the future + # with an env var change to the args instead + - CONTAINERPILOT=/etc/containerpilot-with-file-log.json5 \ No newline at end of file diff --git a/integration_tests/tests/test_reopen/run.sh b/integration_tests/tests/test_reopen/run.sh new file mode 100755 index 00000000..b949a2f2 --- /dev/null +++ b/integration_tests/tests/test_reopen/run.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +function finish { + result=$? + if [[ "$result" -ne 0 ]]; then docker logs "$app" | tee app.log; fi + exit $result +} +trap finish EXIT + +# start up app +docker-compose up -d app +app=$(docker-compose ps -q app) + +docker exec -i $app /tmp/test_reopen.sh + diff --git a/integration_tests/tests/test_reopen/test_reopen.sh b/integration_tests/tests/test_reopen/test_reopen.sh new file mode 100755 index 00000000..b2e3708e --- /dev/null +++ b/integration_tests/tests/test_reopen/test_reopen.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +CONTAINERPILOT_LOGFILE="/tmp/containerpilot.log" +CONTAINERPILOT_ROTATEDLOGFILE="/tmp/containerpilot.log.1" + +sleep 2 +logs=`cat $CONTAINERPILOT_LOGFILE` +nb_lines=`cat $CONTAINERPILOT_LOGFILE | wc -l` +if [ ! -f $CONTAINERPILOT_LOGFILE ] || [[ $logs != *"hello world"* ]]; then + exit 1 +fi + +#rotate logs +mv $CONTAINERPILOT_LOGFILE $CONTAINERPILOT_ROTATEDLOGFILE + +sleep 2 +nb_lines_rotated=`cat $CONTAINERPILOT_ROTATEDLOGFILE | wc -l` +if (( $nb_lines_rotated <= $nb_lines )); then + exit 1 +fi + +#signal containerpilot to reopen file +kill -SIGUSR1 1 + +sleep 2 +logs=`cat $CONTAINERPILOT_LOGFILE` +if [ ! -f $CONTAINERPILOT_LOGFILE ] || [[ $logs != *"hello world"* ]]; then + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/sup/sup.go b/sup/sup.go index aede563f..bd1f8236 100644 --- a/sup/sup.go +++ b/sup/sup.go @@ -30,7 +30,7 @@ func Run() { // passes them thru to the ContainerPilot worker process. func handleSignals(pid int) { sig := make(chan os.Signal, 1) - signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT, syscall.SIGCHLD) + signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT, syscall.SIGCHLD, syscall.SIGUSR1) go func() { for signal := range sig { switch signal { @@ -38,6 +38,8 @@ func handleSignals(pid int) { syscall.Kill(pid, syscall.SIGINT) case syscall.SIGTERM: syscall.Kill(pid, syscall.SIGTERM) + case syscall.SIGUSR1: + syscall.Kill(pid, syscall.SIGUSR1) case syscall.SIGCHLD: go reap() }