-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HTTP output plugin #2491
HTTP output plugin #2491
Conversation
@sparrc I modified the code to use []byte buffer and use newline. |
👍 |
I think you are missing the change in plugins/outputs/all/all.go. You still have to import the plugin for it to be properly registered. |
plugins/outputs/http/http.go
Outdated
// Writes metrics over HTTP POST | ||
func (h *Http) Write(metrics []telegraf.Metric) error { | ||
if err := validate(h); err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'validate' is called on every write even though we don't expect the checked values to change once set (because they are read from the config on startup).
It may be a good idea to add a 'Validate() err' method to the output plugin interfaces (and others). That function could be called in https://github.com/influxdata/telegraf/blob/master/internal/config/config.go#L760 after unmarshaling the toml values. Any wrong configuration could be detected and acted upon there (fail). Then we could get rid of this call on every Write.
Obviously changing the interface would result in quite some code churn because the Validate method would have to be added to all (output) plugins. Adding a stub that just returns nil shouldn't be too much work though, if people don't want to implement proper configuration checking (yet).
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Shugyousha Oh, It's interesting. You are right.
Every time the write function is called, the validation check is done continuously since the beginning.
If the output interface has Validate, check the required option in the individual Outputs in advance, and then it will be very good if the setting is wrong.
Until then, I will remove the corresponding check.
Thanks a lot. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please review again.
Thanks.
…tput interface later through the Validate() err function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every time the write function is called, the validation check is done continuously since the beginning.
If the output interface has Validate, check the required option in the individual Outputs in advance, and then it will be very good if the setting is wrong.
Until then, I will remove the corresponding check.
Thanks a lot. 👍
plugins/outputs/http/http.go
Outdated
// Writes metrics over HTTP POST | ||
func (h *Http) Write(metrics []telegraf.Metric) error { | ||
if err := validate(h); err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please review again.
Thanks.
This looks good to me. Is there anything else to be changed before this can be merged? If I can be reasonably confident that adding a Validate method to the output interface will be accepted, I can start working on it. Since we are using this plugin in production already, for now I will have to fork and apply this PR until this has been merged upstream. |
I do not know how to add the Validate method to the output interface. P.S. Do you have any idea how to appeal this code to merge? |
@Dark0096 I'll try to review soon |
@danielnelson Any chance this could get a bump? Would be really appreciated. |
plugins/outputs/http/README.md
Outdated
## The value is written as a delimiter(:). | ||
## Content-Type is required http header in http plugin. | ||
## so content-type of HTTP specification (plain/text, application/json, etc...) must be filled out. | ||
http_headers = [ "Content-Type:application/json" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should do this in the same format as the httpjson input:
[outputs.http]
[outputs.http.headers]
Content-Type = "application/json"
This can also be written as an inline table, which is similar to the array version:
[outputs.http]
headers = {Content-Type = "application/json"}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed to map[string]string.
awesome! :)
plugins/outputs/http/README.md
Outdated
## Configure response header timeout in seconds. Default : 3 | ||
response_header_timeout = 3 | ||
## Configure dial timeout in seconds. Default : 3 | ||
dial_timeout = 3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's start with only a timeout that covers the entire request (http.Client.Timeout
). I'm not sure we need timeouts that cover only parts of the request but we can add them in later if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got it. I remained only timeout configuration.
[[outputs.http]] | ||
## It requires a url name. | ||
## Will be transmitted telegraf metrics to the HTTP Server using the below URL. | ||
## Note that not support the HTTPS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add support for the regular HTTPS options once this is merged, but this code will support connecting to a HTTPS url as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to do additional work on HTTPS after you first merge it? I have not worked on this part.
plugins/outputs/http/README.md
Outdated
http_headers = [ "Content-Type:application/json" ] | ||
## With this HTTP status code, the http plugin checks that the HTTP request is completed normally. | ||
## As a result, any status code that is not a specified status code is considered to be an error condition and processed. | ||
expected_status_codes = [ 200, 204 ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this success_status_codes
and include 201 in the default list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got it. I have to add the 201 status code and rename expected_status_codes => success_status_codes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the code like below.
if resp.StatusCode < 200 || resp.StatusCode > 209
// Connect to the Output | ||
func (h *Http) Connect() error { | ||
h.client = http.Client{ | ||
Transport: &http.Transport{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add Proxy: ProxyFromEnvironment
to transport.
plugins/outputs/http/http.go
Outdated
return err | ||
} | ||
|
||
res.Body.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the status code does not match the expected set the function returns but the body is not closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added below code
defer resp.Body.Close()
plugins/outputs/http/http.go
Outdated
arrayJsonObj = append(arrayJsonObj, []byte("[")...) | ||
arrayJsonObj = append(arrayJsonObj, reqBodyBuf...) | ||
arrayJsonObj = append(arrayJsonObj, []byte("]")...) | ||
return bytes.Replace(arrayJsonObj, []byte("\n"), []byte(","), mCount-1), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand why you did this since the Serializer interface does not provide a way to process a list of metrics, but I don't think it is robust enough. I will update the Serializer interface to take a []telegraf.Metrics
and then we can do this in a better way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a code that processes each individual metric using a serializer and creates a Body according to the Content-Type. This section has been modified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see 92 line about serializer.
plugins/outputs/http/http.go
Outdated
outputs.Add("http", func() telegraf.Output { | ||
return &Http{ | ||
ResHeaderTimeout: DEFAULT_RESPONSE_HEADER_TIMEOUT, | ||
DialTimeOut: DEFAULT_DIAL_TIME_OUT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also set default status codes here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code has been modified to recognize that the 2xx status code is unconditionally normal.
@danielnelson thanks for the review! Now it shouldn't take much longer until this PR can be merged, I hope. |
@danielnelson Thank you for taking a look at my code. |
@danielnelson Open source review is first time to my life. Are you fixing my code? or Can I fix the code? Thanks. |
@Dark0096 can you do it for me? |
Anything we can do to help to get this plugin pushed though into core? |
@danielnelson Sure. It is an honour. @JonoB |
@Dark0096 - sounds good! For reference, the datadog output module uses http (I think), so it may be a good source of reference for you. https://github.com/influxdata/telegraf/tree/master/plugins/outputs/datadog |
plugins/outputs/http/http.go
Outdated
if h.SuccessStatusCode == nil { | ||
h.SuccessStatusCode = make(map[int]bool) | ||
|
||
for _, expectedStatusCode := range h.SuccessStatusCodes { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that any 2xx status code should be considered accepted. https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success
This is also how the Datadog plugin works: https://github.com/influxdata/telegraf/blob/master/plugins/outputs/datadog/datadog.go#L123
If agreed, then this could be simplified as follows:
if resp.StatusCode < 200 || resp.StatusCode > 209 {
return fmt.Errorf("received bad status code, %d\n", resp.StatusCode)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good idea.
I agree that.
I'll fix the code.
- Accept status code 2xx.
@danielnelson Could you review the code? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've finished fixing the code for the parts you commented on. Could you review the code?
func (h *Http) Connect() error { | ||
h.client = http.Client{ | ||
Transport: &http.Transport{ | ||
Proxy: http.ProxyFromEnvironment, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added the Proxy: ProxyFromEnvironment to transport.
## Content-Type is required http header in http plugin. | ||
## so content-type of HTTP specification (plain/text, application/json, etc...) must be filled out. | ||
[outputs.http.headers] | ||
Content-Type = "plain/text" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check this configuration format.
@JonoB Thanks a lot. |
@Dark0096 - you're welcome, but you're the one doing all the hard work ! |
@danielnelson - please let us know if you have any further comments. Thanks! |
I think we need to address the batch json metrics in a better way. The current interface only provides for serialization of a single metric, which works out for line based formats since we can just concat them all together. I would like to add a method for serializing a list of metrics along with an option to enable it for JSON for backwards compatibility. I wrote up some details in #3856 and I would like to get this implemented before this pull request, so that we can adjust how the JSON output is handled. |
Good idea. I strongly agree. |
@danielnelson It is my honor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We modified the code for the http output plugin to use SerializeBatch. In addition, we did code-wide refactoring.
I'm going to make a quick follow up change to give Content-Type a default value of text/plain. This way you will only need to be required to set it for application/json. I'll add TLS support as well. |
HTTP output plugin
HTTP is one of the most commonly used protocols. So, I want to add http plugin to telegraf.
The features are as follows.
Feature
The metrics collected from telegraf are transmitted through the HTTP Post method.
Specify a code that can determine that the HTTP status code is not an error. (expected_status_codes)
Set the timeout. (dial_timeout, response_header_timeout)
Test Code
Wrote the test code using the httptest package.
Required for all PRs: