diff --git a/README.md b/README.md index 4616140..70b459d 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ $ go get -v github.com/ncarlier/webhookd ```bash $ sudo curl -s https://raw.githubusercontent.com/ncarlier/webhookd/master/install.sh | bash or -$ curl -sf https://gobinaries.com/ncarlier/za | sh +$ curl -sf https://gobinaries.com/ncarlier/webhookd | sh ``` **Or** use Docker: @@ -204,10 +204,12 @@ $ # Retrieve logs afterwards $ curl http://localhost:8080/echo/2 ``` +If needed, you can also redirect hook logs to the server output (configured by the `WHD_HOOK_LOG_OUTPUT` environment variable). + ### Post hook notifications The output of the script is collected and stored into a log file -(configured by the `WHD_LOG_DIR` environment variable). +(configured by the `WHD_HOOK_LOG_DIR` environment variable). Once the script is executed, you can send the result and this log file to a notification channel. Currently, only two channels are supported: `Email` and `HTTP`. @@ -255,9 +257,11 @@ The following JSON payload is POST to the target URL: } ``` -Note that because the payload have a `text` attribute, you can use a [Mattermost][mattermost] webhook endpoint. +Note that because the payload have a `text` attribute, you can use a [Mattermost][mattermost], [Slack][slack] or [Discord][discord] webhook endpoint. [mattermost]: https://docs.mattermost.com/developer/webhooks-incoming.html +[discord]: https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook +[slack]: https://api.slack.com/messaging/webhooks #### Email notification diff --git a/etc/default/webhookd.env b/etc/default/webhookd.env index 04f662d..56a08f0 100644 --- a/etc/default/webhookd.env +++ b/etc/default/webhookd.env @@ -2,8 +2,11 @@ # Webhookd configuration ### -# Output debug logs, default is false -#WHD_DEBUG=false +# Hook execution logs location, default is OS temporary directory +#WHD_HOOK_LOG_DIR="/tmp" + +# Output hook execution logs to server logs, default is false +#WHD_HOOK_LOG_OUTPUT=false # Maximum hook execution time in second, default is 10 #WHD_HOOK_TIMEOUT=10 @@ -12,8 +15,8 @@ # Example: `localhost:8080` or `:8080` for all interfaces #WHD_LISTEN_ADDR=":8080" -# Hook execution logs location, default is OS temporary directory -#WHD_LOG_DIR="/tmp" +# Log level (debug, info, warn, error), default is "info" +#WHD_LOG_LEVEL=info # Number of workers to start, default is 2 #WHD_NB_WORKERS=2 diff --git a/main.go b/main.go index 3668691..7a73106 100644 --- a/main.go +++ b/main.go @@ -30,14 +30,14 @@ func main() { os.Exit(0) } - level := "info" - if conf.Debug { - level = "debug" + if conf.HookLogOutput { + logger.Init(conf.LogLevel, "out") + } else { + logger.Init(conf.LogLevel) } - logger.Init(level) - if conf.LogDir == "" { - conf.LogDir = os.TempDir() + if conf.HookLogDir == "" { + conf.HookLogDir = os.TempDir() } logger.Debug.Println("starting webhookd server...") diff --git a/pkg/api/index.go b/pkg/api/index.go index a42de6f..5f8b403 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -33,7 +33,7 @@ func atoiFallback(str string, fallback int) int { func index(conf *config.Config) http.Handler { defaultTimeout = conf.HookTimeout scriptDir = conf.ScriptDir - outputDir = conf.LogDir + outputDir = conf.HookLogDir return http.HandlerFunc(webhookHandler) } diff --git a/pkg/config/config.go b/pkg/config/config.go index dcdf500..9b531db 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -8,11 +8,12 @@ type Config struct { TLSKeyFile string `flag:"tls-key-file" desc:"TLS key file" default:"server.key"` TLSDomain string `flag:"tls-domain" desc:"TLS domain name used by ACME"` NbWorkers int `flag:"nb-workers" desc:"Number of workers to start" default:"2"` - Debug bool `flag:"debug" desc:"Output debug logs" default:"false"` HookTimeout int `flag:"hook-timeout" desc:"Maximum hook execution time in second" default:"10"` + HookLogDir string `flag:"hook-log-dir" desc:"Hook execution logs location" default:""` + HookLogOutput bool `flag:"hook-log-output" desc:"Output hook execution logs to server logs" default:"false"` ScriptDir string `flag:"scripts" desc:"Scripts location" default:"scripts"` PasswdFile string `flag:"passwd-file" desc:"Password file for basic HTTP authentication" default:".htpasswd"` - LogDir string `flag:"log-dir" desc:"Hook execution logs location" default:""` + LogLevel string `flag:"log-level" desc:"Log level (debug, info, warn, error)" default:"info"` StaticDir string `flag:"static-dir" desc:"Static file directory to serve on /static path" default:""` NotificationURI string `flag:"notification-uri" desc:"Notification URI"` TrustStoreFile string `flag:"trust-store-file" desc:"Trust store used by HTTP signature verifier (.pem or .p12)"` diff --git a/pkg/logger/color.go b/pkg/logger/color.go index 56629b0..0e23199 100644 --- a/pkg/logger/color.go +++ b/pkg/logger/color.go @@ -43,3 +43,8 @@ func Orange(text string) string { func Red(text string) string { return colorize(text, red) } + +// Purple ANSI color applied to a string +func Purple(text string) string { + return colorize(text, purple) +} diff --git a/pkg/logger/main.go b/pkg/logger/main.go index d8d8172..542ffd0 100644 --- a/pkg/logger/main.go +++ b/pkg/logger/main.go @@ -16,15 +16,18 @@ var ( Warning *log.Logger // Error level Error *log.Logger + // Output special level used for script output + Output *log.Logger ) // Init logger level -func Init(level string) { - var debugHandle, infoHandle, warnHandle, errorHandle io.Writer +func Init(level string, with ...string) { + var debugHandle, infoHandle, warnHandle, errorHandle, outputHandle io.Writer debugHandle = os.Stdout infoHandle = os.Stdout warnHandle = os.Stderr errorHandle = os.Stderr + outputHandle = ioutil.Discard switch level { case "info": debugHandle = ioutil.Discard @@ -37,6 +40,10 @@ func Init(level string) { warnHandle = ioutil.Discard } + if contains(with, "out") { + outputHandle = os.Stdout + } + commonFlags := log.LstdFlags | log.Lmicroseconds if level == "debug" { commonFlags = log.LstdFlags | log.Lmicroseconds | log.Lshortfile @@ -46,4 +53,14 @@ func Init(level string) { Info = log.New(infoHandle, Green("INF "), commonFlags) Warning = log.New(warnHandle, Orange("WRN "), commonFlags) Error = log.New(errorHandle, Red("ERR "), commonFlags) + Output = log.New(outputHandle, Purple("OUT "), commonFlags) +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false } diff --git a/pkg/worker/test/work_runner_test.go b/pkg/worker/test/work_runner_test.go index bcf7710..91df41c 100644 --- a/pkg/worker/test/work_runner_test.go +++ b/pkg/worker/test/work_runner_test.go @@ -24,7 +24,7 @@ func printWorkMessages(work *model.WorkRequest) { } func TestWorkRunner(t *testing.T) { - logger.Init("debug") + logger.Init("debug", "out") script := "./test_simple.sh" args := []string{ "name=foo", diff --git a/pkg/worker/work_runner.go b/pkg/worker/work_runner.go index 9eecaff..98274db 100644 --- a/pkg/worker/work_runner.go +++ b/pkg/worker/work_runner.go @@ -88,6 +88,8 @@ func Run(work *model.WorkRequest) error { logger.Error.Printf("hook %s#%d is over ; unable to write more data into the channel: %s\n", work.Name, work.ID, line) break } + // write to stdout if configured + logger.Output.Println(line) // writing to outfile if _, err := wLogFile.WriteString(line + "\n"); err != nil { logger.Error.Println("error while writing into the log file:", logFile.Name(), err)