mirror of
https://github.com/ncarlier/webhookd.git
synced 2025-04-04 19:52:46 +00:00
feat(): structured logging
This commit is contained in:
parent
65427c5c72
commit
0d802d03eb
|
@ -5,9 +5,6 @@
|
|||
# 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
|
||||
|
||||
|
@ -15,9 +12,18 @@
|
|||
# Example: `localhost:8080` or `:8080` for all interfaces
|
||||
#WHD_LISTEN_ADDR=":8080"
|
||||
|
||||
# Log level (debug, info, warn, error), default is "info"
|
||||
# Log level (debug, info, warn or error), default is "info"
|
||||
#WHD_LOG_LEVEL=info
|
||||
|
||||
# Log format (text or json), default is "text"
|
||||
#WHD_LOG_FORMAT=text
|
||||
|
||||
# Log HTTP request, default is false
|
||||
#WHD_LOG_HTTP_REQUEST=false
|
||||
|
||||
# Log hook execution output, default is false
|
||||
#WHD_LOG_HOOK_OUTPUT=false
|
||||
|
||||
# Number of workers to start, default is 2
|
||||
#WHD_NB_WORKERS=2
|
||||
|
||||
|
|
31
main.go
31
main.go
|
@ -3,6 +3,8 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
@ -31,31 +33,29 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
if conf.HookLogOutput {
|
||||
logger.Init(conf.LogLevel, "out")
|
||||
} else {
|
||||
logger.Init(conf.LogLevel)
|
||||
}
|
||||
|
||||
if conf.HookLogDir == "" {
|
||||
conf.HookLogDir = os.TempDir()
|
||||
}
|
||||
|
||||
if err := conf.Validate(); err != nil {
|
||||
logger.Error.Fatal("invalid configuration:", err)
|
||||
log.Fatal("invalid configuration:", err)
|
||||
}
|
||||
|
||||
logger.Debug.Println("starting webhookd server...")
|
||||
logger.Configure(conf.LogFormat, conf.LogLevel)
|
||||
logger.HookOutputEnabled = conf.LogHookOutput
|
||||
logger.RequestOutputEnabled = conf.LogHTTPRequest
|
||||
|
||||
slog.Debug("starting webhookd server...")
|
||||
|
||||
srv := server.NewServer(conf)
|
||||
|
||||
// Configure notification
|
||||
if err := notification.Init(conf.NotificationURI); err != nil {
|
||||
logger.Error.Fatalf("unable to create notification channel: %v\n", err)
|
||||
slog.Error("unable to create notification channel", "err", err)
|
||||
}
|
||||
|
||||
// Start the dispatcher.
|
||||
logger.Debug.Printf("starting the dispatcher with %d workers...\n", conf.NbWorkers)
|
||||
slog.Debug("starting the dispatcher...", "workers", conf.NbWorkers)
|
||||
worker.StartDispatcher(conf.NbWorkers)
|
||||
|
||||
done := make(chan bool)
|
||||
|
@ -64,24 +64,25 @@ func main() {
|
|||
|
||||
go func() {
|
||||
<-quit
|
||||
logger.Debug.Println("server is shutting down...")
|
||||
slog.Debug("server is shutting down...")
|
||||
api.Shutdown()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
logger.Error.Fatalf("could not gracefully shutdown the server: %v\n", err)
|
||||
slog.Error("could not gracefully shutdown the server", "err", err)
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
logger.Info.Println("server is ready to handle requests at", conf.ListenAddr)
|
||||
api.Start()
|
||||
slog.Info("server started", "addr", conf.ListenAddr)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logger.Error.Fatalf("could not listen on %s : %v\n", conf.ListenAddr, err)
|
||||
slog.Error("unable to start the server", "addr", conf.ListenAddr, "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
<-done
|
||||
logger.Debug.Println("server stopped")
|
||||
slog.Debug("server stopped")
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package api
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path"
|
||||
|
@ -13,7 +13,6 @@ import (
|
|||
|
||||
"github.com/ncarlier/webhookd/pkg/config"
|
||||
"github.com/ncarlier/webhookd/pkg/hook"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/worker"
|
||||
)
|
||||
|
||||
|
@ -64,13 +63,13 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
_, err := hook.ResolveScript(scriptDir, hookName)
|
||||
if err != nil {
|
||||
logger.Error.Println(err.Error())
|
||||
slog.Error("hooke not found", "err", err.Error())
|
||||
http.Error(w, "hook not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
logger.Error.Printf("error reading from-data: %v", err)
|
||||
slog.Error("error reading from-data", "err", err)
|
||||
http.Error(w, "unable to parse request form", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -81,9 +80,9 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) {
|
|||
if ct != "" {
|
||||
mediatype, _, _ := mime.ParseMediaType(ct)
|
||||
if strings.HasPrefix(mediatype, "text/") || mediatype == "application/json" {
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
body, err = io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
logger.Error.Printf("error reading body: %v", err)
|
||||
slog.Error("error reading body", "err", err)
|
||||
http.Error(w, "unable to read request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -107,7 +106,7 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) {
|
|||
OutputDir: outputDir,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error.Printf("error creating hook job: %v", err)
|
||||
slog.Error("error creating hook job", "err", err)
|
||||
http.Error(w, "unable to create hook job", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -151,7 +150,7 @@ func getWebhookLog(w http.ResponseWriter, r *http.Request) {
|
|||
hookName := path.Dir(strings.TrimPrefix(r.URL.Path, "/"))
|
||||
_, err := hook.ResolveScript(scriptDir, hookName)
|
||||
if err != nil {
|
||||
logger.Error.Println(err.Error())
|
||||
slog.Error(err.Error())
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
@ -159,7 +158,7 @@ func getWebhookLog(w http.ResponseWriter, r *http.Request) {
|
|||
// Retrieve log file
|
||||
logFile, err := hook.Logs(id, hookName, outputDir)
|
||||
if err != nil {
|
||||
logger.Error.Println(err.Error())
|
||||
slog.Error(err.Error())
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/auth"
|
||||
"github.com/ncarlier/webhookd/pkg/config"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/middleware"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
@ -24,7 +25,7 @@ func buildMiddlewares(conf *config.Config) middleware.Middlewares {
|
|||
// Load trust store...
|
||||
ts, err := truststore.New(conf.TrustStoreFile)
|
||||
if err != nil {
|
||||
logger.Warning.Printf("unable to load trust store (\"%s\"): %s\n", conf.TrustStoreFile, err)
|
||||
slog.Warn("unable to load trust store", "filename", conf.TrustStoreFile, "err", err)
|
||||
}
|
||||
if ts != nil {
|
||||
middlewares = middlewares.UseAfter(middleware.Signature(ts))
|
||||
|
@ -33,7 +34,7 @@ func buildMiddlewares(conf *config.Config) middleware.Middlewares {
|
|||
// Load authenticator...
|
||||
authenticator, err := auth.NewHtpasswdFromFile(conf.PasswdFile)
|
||||
if err != nil {
|
||||
logger.Debug.Printf("unable to load htpasswd file (\"%s\"): %s\n", conf.PasswdFile, err)
|
||||
slog.Debug("unable to load htpasswd file", "filename", conf.PasswdFile, "err", err)
|
||||
}
|
||||
if authenticator != nil {
|
||||
middlewares = middlewares.UseAfter(middleware.AuthN(authenticator))
|
||||
|
|
|
@ -15,10 +15,12 @@ type Config struct {
|
|||
NbWorkers int `flag:"nb-workers" desc:"Number of workers to start" default:"2"`
|
||||
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"`
|
||||
LogLevel string `flag:"log-level" desc:"Log level (debug, info, warn, error)" default:"info"`
|
||||
LogFormat string `flag:"log-format" desc:"Log format (json, text)" default:"text"`
|
||||
LogHookOutput bool `flag:"log-hook-output" desc:"Log hook execution output" default:"false"`
|
||||
LogHTTPRequest bool `flag:"log-http-request" desc:"Log HTTP request" default:"false"`
|
||||
StaticDir string `flag:"static-dir" desc:"Static file directory to serve on /static path" default:""`
|
||||
StaticPath string `flag:"static-path" desc:"Path to serve static file directory" default:"/static"`
|
||||
NotificationURI string `flag:"notification-uri" desc:"Notification URI"`
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
|
@ -31,6 +32,7 @@ type Job struct {
|
|||
args []string
|
||||
MessageChan chan []byte
|
||||
timeout int
|
||||
start time.Time
|
||||
status Status
|
||||
logFilename string
|
||||
err error
|
||||
|
@ -83,14 +85,27 @@ func (job *Job) Meta() []string {
|
|||
func (job *Job) Terminate(err error) error {
|
||||
job.mutex.Lock()
|
||||
defer job.mutex.Unlock()
|
||||
job.status = Success
|
||||
if err != nil {
|
||||
job.status = Error
|
||||
job.err = err
|
||||
logger.Info.Printf("hook %s#%d done [ERROR]\n", job.Name(), job.ID())
|
||||
slog.Error(
|
||||
"hook executed",
|
||||
"hook", job.Name(),
|
||||
"id", job.ID(),
|
||||
"status", "error",
|
||||
"err", err,
|
||||
"took", time.Since(job.start).Microseconds(),
|
||||
)
|
||||
return err
|
||||
}
|
||||
job.status = Success
|
||||
logger.Info.Printf("hook %s#%d done [SUCCESS]\n", job.Name(), job.ID())
|
||||
slog.Info(
|
||||
"hook executed",
|
||||
"hook", job.Name(),
|
||||
"id", job.ID(),
|
||||
"status", "success",
|
||||
"took", time.Since(job.start).Microseconds(),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -160,9 +175,8 @@ func (job *Job) Run() error {
|
|||
return fmt.Errorf("unable to run job: status=%s", job.StatusLabel())
|
||||
}
|
||||
job.status = Running
|
||||
logger.Info.Printf("hook %s#%d started...\n", job.name, job.id)
|
||||
logger.Debug.Printf("hook %s#%d script: %s\n", job.name, job.id, job.script)
|
||||
logger.Debug.Printf("hook %s#%d parameter: %v\n", job.name, job.id, job.args)
|
||||
job.start = time.Now()
|
||||
slog.Info("executing hook...", "hook", job.name, "id", job.id)
|
||||
|
||||
binary, err := exec.LookPath(job.script)
|
||||
if err != nil {
|
||||
|
@ -184,7 +198,7 @@ func (job *Job) Run() error {
|
|||
return job.Terminate(err)
|
||||
}
|
||||
defer logFile.Close()
|
||||
logger.Debug.Printf("hook %s#%d output file: %s\n", job.name, job.id, logFile.Name())
|
||||
slog.Debug("hook details", "hook", job.name, "id", job.id, "script", job.script, "args", job.args, "output", logFile.Name())
|
||||
|
||||
wLogFile := bufio.NewWriter(logFile)
|
||||
defer wLogFile.Flush()
|
||||
|
@ -219,26 +233,32 @@ func (job *Job) Run() error {
|
|||
if !job.IsTerminated() {
|
||||
job.MessageChan <- []byte(line)
|
||||
} else {
|
||||
logger.Error.Printf("hook %s#%d is over ; unable to write more data into the channel: %s\n", job.name, job.id, line)
|
||||
slog.Error("hook execution done ; unable to write more data into the channel", "hook", job.name, "id", job.id, "line", line)
|
||||
break
|
||||
}
|
||||
// write to stdout if configured
|
||||
logger.Output.Println(line)
|
||||
logger.LogIf(
|
||||
logger.HookOutputEnabled,
|
||||
slog.LevelInfo+1,
|
||||
line,
|
||||
"hook", job.name,
|
||||
"id", job.id,
|
||||
)
|
||||
// writing to outfile
|
||||
if _, err := wLogFile.WriteString(line + "\n"); err != nil {
|
||||
logger.Error.Println("error while writing into the log file:", logFile.Name(), err)
|
||||
slog.Error("error while writing into the log file", "filename", logFile.Name(), "err", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
logger.Error.Printf("hook %s#%d is unable to read script stdout: %v\n", job.name, job.id, err)
|
||||
slog.Error("hook is unable to read script stdout", "hook", job.name, "id", job.id, "err", err)
|
||||
}
|
||||
wg.Done()
|
||||
}(cmdReader)
|
||||
|
||||
// Start timeout timer
|
||||
timer := time.AfterFunc(time.Duration(job.timeout)*time.Second, func() {
|
||||
logger.Warning.Printf("hook %s#%d has timed out (%ds): killing process #%d ...\n", job.name, job.id, job.timeout, cmd.Process.Pid)
|
||||
slog.Warn("hook has timed out: killing process...", "hook", job.name, "id", job.id, "timeout", job.timeout, "pid", cmd.Process.Pid)
|
||||
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/hook"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
)
|
||||
|
||||
func printJobMessages(job *hook.Job) {
|
||||
|
@ -17,13 +17,12 @@ func printJobMessages(job *hook.Job) {
|
|||
if !open {
|
||||
break
|
||||
}
|
||||
logger.Info.Println(string(msg))
|
||||
slog.Info(string(msg))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TestHookJob(t *testing.T) {
|
||||
logger.Init("debug", "out")
|
||||
req := &hook.Request{
|
||||
Name: "test_simple",
|
||||
Method: "GET",
|
||||
|
@ -54,7 +53,6 @@ func TestHookJob(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWorkRunnerWithError(t *testing.T) {
|
||||
logger.Init("debug")
|
||||
req := &hook.Request{
|
||||
Name: "test_error",
|
||||
Method: "POST",
|
||||
|
@ -75,7 +73,6 @@ func TestWorkRunnerWithError(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWorkRunnerWithTimeout(t *testing.T) {
|
||||
logger.Init("debug")
|
||||
req := &hook.Request{
|
||||
Name: "test_timeout",
|
||||
Method: "POST",
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
nocolor = "\033[0m"
|
||||
red = "\033[0;31m"
|
||||
green = "\033[0;32m"
|
||||
orange = "\033[0;33m"
|
||||
blue = "\033[0;34m"
|
||||
purple = "\033[0;35m"
|
||||
cyan = "\033[0;36m"
|
||||
gray = "\033[0;37m"
|
||||
)
|
||||
|
||||
func colorize(text string, color string) string {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
return color + text + nocolor
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// Gray ANSI color applied to a string
|
||||
func Gray(text string) string {
|
||||
return colorize(text, gray)
|
||||
}
|
||||
|
||||
// Green ANSI color applied to a string
|
||||
func Green(text string) string {
|
||||
return colorize(text, green)
|
||||
}
|
||||
|
||||
// Orange ANSI color applied to a string
|
||||
func Orange(text string) string {
|
||||
return colorize(text, orange)
|
||||
}
|
||||
|
||||
// Red ANSI color applied to a 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)
|
||||
}
|
46
pkg/logger/logger.go
Normal file
46
pkg/logger/logger.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
HookOutputEnabled = false
|
||||
RequestOutputEnabled = false
|
||||
)
|
||||
|
||||
// Configure logger
|
||||
func Configure(format, level string) {
|
||||
logLevel := slog.LevelDebug
|
||||
switch level {
|
||||
case "info":
|
||||
logLevel = slog.LevelInfo
|
||||
case "warn":
|
||||
logLevel = slog.LevelWarn
|
||||
case "error":
|
||||
logLevel = slog.LevelError
|
||||
}
|
||||
|
||||
opts := slog.HandlerOptions{
|
||||
Level: logLevel,
|
||||
AddSource: logLevel == slog.LevelDebug,
|
||||
}
|
||||
|
||||
var logger *slog.Logger
|
||||
if format == "json" {
|
||||
logger = slog.New(slog.NewJSONHandler(os.Stdout, &opts))
|
||||
} else {
|
||||
logger = slog.New(slog.NewTextHandler(os.Stdout, &opts))
|
||||
}
|
||||
|
||||
slog.SetDefault(logger)
|
||||
}
|
||||
|
||||
// LogIf writ log on condition
|
||||
func LogIf(condition bool, level slog.Level, msg string, args ...any) {
|
||||
if condition {
|
||||
slog.Log(context.Background(), level, msg, args...)
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// Debug level
|
||||
Debug *log.Logger
|
||||
// Info level
|
||||
Info *log.Logger
|
||||
// Warning level
|
||||
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, 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
|
||||
case "warn":
|
||||
debugHandle = ioutil.Discard
|
||||
infoHandle = ioutil.Discard
|
||||
case "error":
|
||||
debugHandle = ioutil.Discard
|
||||
infoHandle = ioutil.Discard
|
||||
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
|
||||
}
|
||||
|
||||
Debug = log.New(debugHandle, Gray("DBG "), commonFlags)
|
||||
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
|
||||
}
|
|
@ -2,6 +2,7 @@ package middleware
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -25,16 +26,18 @@ func Logger(next http.Handler) http.Handler {
|
|||
if !ok {
|
||||
requestID = "0"
|
||||
}
|
||||
logger.Info.Printf(
|
||||
"%s - - [%s] %q %d %d %q %q %q",
|
||||
getRequestIP(r),
|
||||
start.Format("02/Jan/2006:15:04:05 -0700"),
|
||||
logger.LogIf(
|
||||
logger.RequestOutputEnabled,
|
||||
slog.LevelInfo+1,
|
||||
fmt.Sprintf("%s %s %s", r.Method, r.URL, r.Proto),
|
||||
o.status,
|
||||
o.written,
|
||||
r.Referer(),
|
||||
r.UserAgent(),
|
||||
fmt.Sprintf("REQID=%s", requestID),
|
||||
"ip", getRequestIP(r),
|
||||
"time", start.Format("02/Jan/2006:15:04:05 -0700"),
|
||||
"duration", time.Since(start).Milliseconds(),
|
||||
"status", o.status,
|
||||
"bytes", o.written,
|
||||
"referer", r.Referer(),
|
||||
"ua", r.UserAgent(),
|
||||
"reqid", requestID,
|
||||
)
|
||||
}()
|
||||
next.ServeHTTP(o, r)
|
||||
|
|
|
@ -12,14 +12,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/middleware/signature"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
func TestEd5519Signature(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
|
||||
assert.Nil(t, err, "")
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/middleware/signature"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
@ -25,8 +24,6 @@ func assertSigner(t *testing.T) httpsig.Signer {
|
|||
}
|
||||
|
||||
func TestHTTPSignature(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
privkey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
assert.Nil(t, err, "")
|
||||
pubkey := &privkey.PublicKey
|
||||
|
|
|
@ -3,13 +3,13 @@ package http
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/helper"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/notification"
|
||||
)
|
||||
|
||||
|
@ -27,7 +27,7 @@ type httpNotifier struct {
|
|||
}
|
||||
|
||||
func newHTTPNotifier(uri *url.URL) (notification.Notifier, error) {
|
||||
logger.Info.Println("using HTTP notification system: ", uri.String())
|
||||
slog.Info("using HTTP notification system ", "üri", uri.Opaque)
|
||||
return &httpNotifier{
|
||||
URL: uri,
|
||||
PrefixFilter: helper.GetValueOrAlt(uri.Query(), "prefix", "notify:"),
|
||||
|
@ -65,7 +65,7 @@ func (n *httpNotifier) Notify(result notification.HookResult) error {
|
|||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
logger.Info.Printf("job %s#%d notification sent to %s\n", result.Name(), result.ID(), n.URL.String())
|
||||
slog.Info("notification sent", "hook", result.Name(), "id", result.ID(), "to", n.URL.Opaque)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package notification
|
||||
|
||||
import (
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Notifier is able to send a notification.
|
||||
|
@ -17,7 +17,7 @@ func Notify(result HookResult) {
|
|||
return
|
||||
}
|
||||
if err := notifier.Notify(result); err != nil {
|
||||
logger.Error.Printf("unable to send notification for webhook %s#%d: %v\n", result.Name(), result.ID(), err)
|
||||
slog.Error("unable to send notification", "webhook", result.Name(), "id", result.ID(), "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package smtp
|
|||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"net/url"
|
||||
|
@ -11,7 +12,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/helper"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/notification"
|
||||
)
|
||||
|
||||
|
@ -28,7 +28,7 @@ type smtpNotifier struct {
|
|||
}
|
||||
|
||||
func newSMTPNotifier(uri *url.URL) (notification.Notifier, error) {
|
||||
logger.Info.Println("using SMTP notification system:", uri.Opaque)
|
||||
slog.Info("using SMTP notification system", "uri", uri.Opaque)
|
||||
q := uri.Query()
|
||||
return &smtpNotifier{
|
||||
Host: helper.GetValueOrAlt(q, "smtp", "localhost:25"),
|
||||
|
@ -128,7 +128,7 @@ func (n *smtpNotifier) Notify(result notification.HookResult) error {
|
|||
return err
|
||||
}
|
||||
|
||||
logger.Info.Printf("job %s#%d notification sent to %s\n", result.Name(), result.ID(), n.To)
|
||||
slog.Info("notification sent", "hook", result.Name(), "id", result.ID(), "to", n.To)
|
||||
|
||||
// Send the QUIT command and close the connection.
|
||||
return client.Quit()
|
||||
|
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
|
@ -9,7 +10,6 @@ import (
|
|||
|
||||
"github.com/ncarlier/webhookd/pkg/api"
|
||||
"github.com/ncarlier/webhookd/pkg/config"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
@ -48,12 +48,13 @@ func (s *Server) Shutdown(ctx context.Context) error {
|
|||
|
||||
// NewServer create new HTTP(s) server
|
||||
func NewServer(cfg *config.Config) *Server {
|
||||
logger := slog.NewLogLogger(slog.Default().Handler(), slog.LevelError)
|
||||
server := &Server{
|
||||
tls: cfg.TLS,
|
||||
self: &http.Server{
|
||||
Addr: cfg.ListenAddr,
|
||||
Handler: api.NewRouter(cfg),
|
||||
ErrorLog: logger.Error,
|
||||
ErrorLog: logger,
|
||||
},
|
||||
}
|
||||
if server.tls {
|
||||
|
|
|
@ -2,14 +2,14 @@ package truststore
|
|||
|
||||
import (
|
||||
"crypto"
|
||||
"io/ioutil"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"golang.org/x/crypto/pkcs12"
|
||||
)
|
||||
|
||||
func newP12TrustStore(filename string) (TrustStore, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ func newP12TrustStore(filename string) (TrustStore, error) {
|
|||
|
||||
keyID := string(cert.Subject.CommonName)
|
||||
result.Keys[keyID] = cert.PublicKey
|
||||
logger.Debug.Printf("certificate \"%s\" loaded into the trustore", keyID)
|
||||
slog.Debug("certificate loaded into the trustore", "id", keyID)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
@ -5,13 +5,12 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
func newPEMTrustStore(filename string) (TrustStore, error) {
|
||||
raw, err := ioutil.ReadFile(filename)
|
||||
raw, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -37,7 +36,7 @@ func newPEMTrustStore(filename string) (TrustStore, error) {
|
|||
}
|
||||
|
||||
result.Keys[keyID] = key
|
||||
logger.Debug.Printf("public key \"%s\" loaded into the trustore", keyID)
|
||||
slog.Debug("public key loaded into the trustore", "id", keyID)
|
||||
case "CERTIFICATE":
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
|
@ -45,7 +44,7 @@ func newPEMTrustStore(filename string) (TrustStore, error) {
|
|||
}
|
||||
keyID := string(cert.Subject.CommonName)
|
||||
result.Keys[keyID] = cert.PublicKey
|
||||
logger.Debug.Printf("certificate \"%s\" loaded into the trustore", keyID)
|
||||
slog.Debug("certificate loaded into the trustore", "id", keyID)
|
||||
}
|
||||
raw = rest
|
||||
}
|
||||
|
|
|
@ -5,13 +5,11 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
func TestTrustStoreWithP12(t *testing.T) {
|
||||
t.Skip()
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test.p12")
|
||||
assert.Nil(t, err, "")
|
||||
|
|
|
@ -5,13 +5,10 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/assert"
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/truststore"
|
||||
)
|
||||
|
||||
func TestTrustStoreWithNoKeyID(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-key-01.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
|
@ -24,8 +21,6 @@ func TestTrustStoreWithNoKeyID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTrustStoreWithKeyID(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-key-02.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
|
@ -36,8 +31,6 @@ func TestTrustStoreWithKeyID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTrustStoreWithCertificate(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-cert.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
|
@ -48,8 +41,6 @@ func TestTrustStoreWithCertificate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTrustStoreWithMultipleEntries(t *testing.T) {
|
||||
logger.Init("warn")
|
||||
|
||||
ts, err := truststore.New("test-multi.pem")
|
||||
assert.Nil(t, err, "")
|
||||
assert.NotNil(t, ts, "")
|
||||
|
|
|
@ -3,9 +3,8 @@ package truststore
|
|||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
)
|
||||
|
||||
// TrustStore is a generic interface to retrieve a public key
|
||||
|
@ -31,7 +30,7 @@ func New(filename string) (store TrustStore, err error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
logger.Debug.Printf("loading trust store: %s", filename)
|
||||
slog.Debug("loading trust store...", "filname", filename)
|
||||
switch filepath.Ext(filename) {
|
||||
case ".pem":
|
||||
store, err = newPEMTrustStore(filename)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// WorkerQueue is the global queue of Workers
|
||||
|
@ -17,7 +17,7 @@ func StartDispatcher(nworkers int) {
|
|||
|
||||
// Now, create all of our workers.
|
||||
for i := 0; i < nworkers; i++ {
|
||||
logger.Debug.Printf("starting worker #%d ...\n", i+1)
|
||||
slog.Debug("starting worker...", "worker", i+1)
|
||||
worker := NewWorker(i+1, WorkerQueue)
|
||||
worker.Start()
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func StartDispatcher(nworkers int) {
|
|||
go func() {
|
||||
worker := <-WorkerQueue
|
||||
|
||||
logger.Debug.Printf("dispatching hook request: %s#%d", work.Name(), work.ID())
|
||||
slog.Debug("dispatching hook request", "hook", work.Name(), "id", work.ID())
|
||||
worker <- work
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ package worker
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/metric"
|
||||
|
||||
"github.com/ncarlier/webhookd/pkg/logger"
|
||||
"github.com/ncarlier/webhookd/pkg/notification"
|
||||
)
|
||||
|
||||
|
@ -41,7 +41,7 @@ func (w Worker) Start() {
|
|||
select {
|
||||
case work := <-w.Work:
|
||||
// Receive a work request.
|
||||
logger.Debug.Printf("worker #%d received hook request: %s#%d\n", w.ID, work.Name(), work.ID())
|
||||
slog.Debug("hook execution request received", "worker", w.ID, "hook", work.Name(), "id", work.ID())
|
||||
metric.Requests.Add(1)
|
||||
err := work.Run()
|
||||
if err != nil {
|
||||
|
@ -53,7 +53,7 @@ func (w Worker) Start() {
|
|||
|
||||
work.Close()
|
||||
case <-w.QuitChan:
|
||||
logger.Debug.Printf("stopping worker #%d...\n", w.ID)
|
||||
slog.Debug("stopping worker...", "worker", w.ID)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -45,7 +45,7 @@ func main() {
|
|||
log.Fatal("invalid target URL")
|
||||
}
|
||||
|
||||
keyBytes, err := ioutil.ReadFile(conf.KeyFile)
|
||||
keyBytes, err := os.ReadFile(conf.KeyFile)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func main() {
|
|||
var jsonBytes []byte
|
||||
if conf.JSON != "" {
|
||||
var err error
|
||||
jsonBytes, err = ioutil.ReadFile(conf.JSON)
|
||||
jsonBytes, err = os.ReadFile(conf.JSON)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user