diff --git a/api/webhook.go b/api/webhook.go
index 676fd2cbc1..6297133da2 100644
--- a/api/webhook.go
+++ b/api/webhook.go
@@ -4,6 +4,7 @@
package api
import (
+ "io"
"net/http"
"strings"
@@ -373,14 +374,33 @@ func incomingWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
- var parsedRequest *model.IncomingWebhookRequest
+ var payload io.Reader
contentType := r.Header.Get("Content-Type")
if strings.Split(contentType, "; ")[0] == "application/x-www-form-urlencoded" {
- parsedRequest = model.IncomingWebhookRequestFromJson(strings.NewReader(r.FormValue("payload")))
+ payload = strings.NewReader(r.FormValue("payload"))
} else {
- parsedRequest = model.IncomingWebhookRequestFromJson(r.Body)
+ payload = r.Body
}
+ if utils.Cfg.LogSettings.EnableWebhookDebugging {
+ var err error
+ payload, err = utils.DebugReader(
+ payload,
+ utils.T("api.webhook.incoming.debug"),
+ )
+ if err != nil {
+ c.Err = model.NewLocAppError(
+ "incomingWebhook",
+ "api.webhook.incoming.debug.error",
+ nil,
+ err.Error(),
+ )
+ return
+ }
+ }
+
+ parsedRequest := model.IncomingWebhookRequestFromJson(payload)
+
if parsedRequest == nil {
c.Err = model.NewLocAppError("incomingWebhook", "web.incoming_webhook.parse.app_error", nil, "")
return
diff --git a/config/config.json b/config/config.json
index 726cb0d8cb..db62b0bac2 100644
--- a/config/config.json
+++ b/config/config.json
@@ -52,7 +52,8 @@
"EnableFile": true,
"FileLevel": "INFO",
"FileFormat": "",
- "FileLocation": ""
+ "FileLocation": "",
+ "EnableWebhookDebugging": true
},
"FileSettings": {
"MaxFileSize": 52428800,
diff --git a/i18n/en.json b/i18n/en.json
index f88e00d291..1589dd4acb 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1955,6 +1955,14 @@
"id": "api.webhook.get_outgoing.disabled.app_error",
"translation": "Outgoing webhooks have been disabled by the system admin."
},
+ {
+ "id": "api.webhook.incoming.debug",
+ "translation": "Incoming webhook received. Content="
+ },
+ {
+ "id": "api.webhook.incoming.debug.error",
+ "translation": "Could not read payload of incoming webhook."
+ },
{
"id": "api.webhook.init.debug",
"translation": "Initializing webhook api routes"
diff --git a/model/config.go b/model/config.go
index 7e810be02f..b9177204bd 100644
--- a/model/config.go
+++ b/model/config.go
@@ -83,12 +83,13 @@ type SqlSettings struct {
}
type LogSettings struct {
- EnableConsole bool
- ConsoleLevel string
- EnableFile bool
- FileLevel string
- FileFormat string
- FileLocation string
+ EnableConsole bool
+ ConsoleLevel string
+ EnableFile bool
+ FileLevel string
+ FileFormat string
+ FileLocation string
+ EnableWebhookDebugging bool
}
type FileSettings struct {
diff --git a/utils/log.go b/utils/log.go
new file mode 100644
index 0000000000..360c785d08
--- /dev/null
+++ b/utils/log.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package utils
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+
+ l4g "github.com/alecthomas/log4go"
+)
+
+// DebugReader logs the content of the io.Reader and returns a new io.Reader
+// with the same content as the received io.Reader.
+// If you pass reader by reference, it won't be re-created unless the loglevel
+// includes Debug.
+// If an error is returned, the reader is consumed an cannot be read again.
+func DebugReader(reader io.Reader, message string) (io.Reader, error) {
+ var err error
+ l4g.Debug(func() string {
+ content, err := ioutil.ReadAll(reader)
+ if err != nil {
+ return ""
+ }
+
+ reader = bytes.NewReader(content)
+
+ return message + string(content)
+ })
+
+ return reader, err
+}
diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx
index 0a69cbc165..238040b710 100644
--- a/webapp/components/admin_console/log_settings.jsx
+++ b/webapp/components/admin_console/log_settings.jsx
@@ -26,7 +26,8 @@ export default class LogSettings extends AdminSettings {
enableFile: props.config.LogSettings.EnableFile,
fileLevel: props.config.LogSettings.FileLevel,
fileLocation: props.config.LogSettings.FileLocation,
- fileFormat: props.config.LogSettings.FileFormat
+ fileFormat: props.config.LogSettings.FileFormat,
+ enableWebhookDebugging: props.config.LogSettings.EnableWebhookDebugging
});
}
@@ -37,6 +38,7 @@ export default class LogSettings extends AdminSettings {
config.LogSettings.FileLevel = this.state.fileLevel;
config.LogSettings.FileLocation = this.state.fileLocation;
config.LogSettings.FileFormat = this.state.fileFormat;
+ config.LogSettings.EnableWebhookDebugging = this.state.enableWebhookDebugging;
return config;
}
@@ -166,6 +168,23 @@ export default class LogSettings extends AdminSettings {
onChange={this.handleChange}
disabled={!this.state.enableFile}
/>
+
+ }
+ helpText={
+
+ }
+ value={this.state.enableWebhookDebugging}
+ onChange={this.handleChange}
+ />
);
}
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index 29269adeb7..1e0788ebe7 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -326,6 +326,8 @@
"admin.log.logSettings": "Log Settings",
"admin.logs.reload": "Reload",
"admin.logs.title": "Server Logs",
+ "admin.log.enableWebhookDebugging": "Enable Webhook Debugging:",
+ "admin.log.enableWebhookDebuggingDescription": "You can set this to false to disable the debug logging of all incoming webhook request bodies.",
"admin.nav.help": "Help",
"admin.nav.logout": "Logout",
"admin.nav.report": "Report a Problem",