package simplelog import ( "bytes" "context" "encoding/json" "fmt" "log/slog" "net/http" "net/url" ) // WebhookHandler POSTs each log record as JSON to a configured webhook // URL. type WebhookHandler struct { webhookURL string } // NewWebhookHandler returns a WebhookHandler that delivers records to // the given URL, validating the URL first. func NewWebhookHandler(webhookURL string) (*WebhookHandler, error) { _, err := url.ParseRequestURI(webhookURL) if err != nil { return nil, fmt.Errorf("invalid webhook URL: %w", err) } return &WebhookHandler{webhookURL: webhookURL}, nil } // Enabled reports whether the handler processes records at the given // level; it always returns true. func (w *WebhookHandler) Enabled(_ context.Context, _ slog.Level) bool { return true } // WithAttrs returns the handler unchanged; attributes are not rendered. func (w *WebhookHandler) WithAttrs(_ []slog.Attr) slog.Handler { return w } // WithGroup returns the handler unchanged; groups are not rendered. func (w *WebhookHandler) WithGroup(_ string) slog.Handler { return w } // Handle marshals the record to JSON and POSTs it to the webhook URL. func (w *WebhookHandler) Handle(ctx context.Context, record slog.Record) error { jsonData, err := json.Marshal(record) if err != nil { return fmt.Errorf("error marshaling event: %w", err) } request, err := http.NewRequestWithContext( ctx, http.MethodPost, w.webhookURL, bytes.NewReader(jsonData), ) if err != nil { return fmt.Errorf("error creating webhook request: %w", err) } request.Header.Set("Content-Type", "application/json") response, err := http.DefaultClient.Do(request) if err != nil { return err } defer func() { _ = response.Body.Close() }() return nil }