Skip to content
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

Merged
merged 20 commits into from
May 15, 2018
Merged

HTTP output plugin #2491

merged 20 commits into from
May 15, 2018

Conversation

Dark0096
Copy link
Contributor

@Dark0096 Dark0096 commented Mar 4, 2017

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:

  • CHANGELOG.md updated (we recommend not updating this until the PR has been approved by a maintainer)
  • Sign CLA (if not already signed)
  • README.md updated (if adding a new plugin)

@Dark0096 Dark0096 changed the title The metrics collected from telegraf are transmitted through the HTTP … HTTP output plugin Mar 4, 2017
@Dark0096 Dark0096 mentioned this pull request Mar 4, 2017
3 tasks
@Dark0096
Copy link
Contributor Author

@sparrc I modified the code to use []byte buffer and use newline.
I have also removed the unnecessary max_bulk_limit option.
Can you take a look at my code?
Thanks.

@lin-credible
Copy link

👍

@Shugyousha
Copy link

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.

// Writes metrics over HTTP POST
func (h *Http) Write(metrics []telegraf.Metric) error {
if err := validate(h); err != nil {
return err

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?

Copy link
Contributor Author

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. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review again.
Thanks.

Copy link
Contributor Author

@Dark0096 Dark0096 left a 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. 👍

// Writes metrics over HTTP POST
func (h *Http) Write(metrics []telegraf.Metric) error {
if err := validate(h); err != nil {
return err
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review again.
Thanks.

@Shugyousha
Copy link

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.

@Dark0096
Copy link
Contributor Author

I do not know how to add the Validate method to the output interface.
This is the part where each output implementation can be changed due to the code change of the output interface.
However, it is an honor to use this code for you.
Thanks. :)

P.S. Do you have any idea how to appeal this code to merge?

@danielnelson
Copy link
Contributor

@Dark0096 I'll try to review soon

@danielnelson danielnelson added the feat Improvement on an existing feature such as adding a new setting/mode to an existing plugin label Aug 24, 2017
@drunz
Copy link

drunz commented Oct 22, 2017

@danielnelson Any chance this could get a bump? Would be really appreciated.

## 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" ]
Copy link
Contributor

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"}

Copy link
Contributor Author

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! :)

## Configure response header timeout in seconds. Default : 3
response_header_timeout = 3
## Configure dial timeout in seconds. Default : 3
dial_timeout = 3
Copy link
Contributor

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.

Copy link
Contributor Author

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.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

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 ]
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@Dark0096 Dark0096 Mar 4, 2018

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{
Copy link
Contributor

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.

return err
}

res.Body.Close()
Copy link
Contributor

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.

Copy link
Contributor Author

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()

arrayJsonObj = append(arrayJsonObj, []byte("[")...)
arrayJsonObj = append(arrayJsonObj, reqBodyBuf...)
arrayJsonObj = append(arrayJsonObj, []byte("]")...)
return bytes.Replace(arrayJsonObj, []byte("\n"), []byte(","), mCount-1), nil
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

outputs.Add("http", func() telegraf.Output {
return &Http{
ResHeaderTimeout: DEFAULT_RESPONSE_HEADER_TIMEOUT,
DialTimeOut: DEFAULT_DIAL_TIME_OUT,
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@Shugyousha
Copy link

@danielnelson thanks for the review! Now it shouldn't take much longer until this PR can be merged, I hope.

@Dark0096
Copy link
Contributor Author

@danielnelson Thank you for taking a look at my code.
Please tell me if I can help you :)

@Dark0096
Copy link
Contributor Author

@danielnelson Open source review is first time to my life.
So, I am not understand about process.
I have a some question about your review.

Are you fixing my code? or Can I fix the code?

Thanks.

@danielnelson
Copy link
Contributor

@Dark0096 can you do it for me?

@JonoB
Copy link

JonoB commented Feb 25, 2018

Anything we can do to help to get this plugin pushed though into core?

@Dark0096
Copy link
Contributor Author

@danielnelson Sure. It is an honour.

@JonoB
Tomorrow is a Korean holiday.
I have a lot of time, so let's work on the code to merge the code.

@JonoB
Copy link

JonoB commented Feb 28, 2018

@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

if h.SuccessStatusCode == nil {
h.SuccessStatusCode = make(map[int]bool)

for _, expectedStatusCode := range h.SuccessStatusCodes {
Copy link

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)
}

Copy link
Contributor Author

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.

@Dark0096
Copy link
Contributor Author

Dark0096 commented Mar 4, 2018

@danielnelson Could you review the code?

Copy link
Contributor Author

@Dark0096 Dark0096 left a 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,
Copy link
Contributor Author

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"
Copy link
Contributor Author

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.

@Dark0096
Copy link
Contributor Author

Dark0096 commented Mar 5, 2018

@JonoB Thanks a lot.
Your advice has been very helpful.

@JonoB
Copy link

JonoB commented Mar 5, 2018

@Dark0096 - you're welcome, but you're the one doing all the hard work !

@JonoB
Copy link

JonoB commented Mar 5, 2018

@danielnelson - please let us know if you have any further comments. Thanks!

@danielnelson
Copy link
Contributor

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.

@danielnelson danielnelson added this to the 1.7.0 milestone Mar 5, 2018
@Dark0096 Dark0096 closed this Mar 16, 2018
@Dark0096 Dark0096 reopened this Mar 16, 2018
@Dark0096
Copy link
Contributor Author

Good idea. I strongly agree.

@danielnelson
Copy link
Contributor

@Dark0096 I have added support for SerializeBatch in #4107, can you update this pull request to use this method for serialization?

@Dark0096
Copy link
Contributor Author

Dark0096 commented May 6, 2018

@danielnelson It is my honor.
I will tell you after the code work.

Copy link
Contributor Author

@Dark0096 Dark0096 left a 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.

@danielnelson danielnelson merged commit 190a412 into influxdata:master May 15, 2018
@danielnelson
Copy link
Contributor

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.

arkady-emelyanov pushed a commit to arkady-emelyanov/telegraf that referenced this pull request May 18, 2018
leodido pushed a commit that referenced this pull request May 22, 2018
maxunt pushed a commit that referenced this pull request Jun 26, 2018
otherpirate pushed a commit to otherpirate/telegraf that referenced this pull request Mar 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat Improvement on an existing feature such as adding a new setting/mode to an existing plugin new plugin
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants