Skip to content

Conversation

@minuk-dev
Copy link

@minuk-dev minuk-dev commented Nov 13, 2025

Summary

Question

  • I checked other plugins' directories, but is it a coding convention to keep everything within a single file? There's too much logic, so I want to split the file.

Local Test

  • You can test this feature using below config.
otel.yaml
# otel.yaml
receivers:
  otlp:
    protocols:
      http:
        # OTLP/HTTP standard port is 4318
        endpoint: "0.0.0.0:4318"

exporters:
  debug:

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: []
      exporters: [debug]

    metrics:
      receivers: [otlp]
      processors: []
      exporters: [debug]

    logs:
      receivers: [otlp]
      processors: []
      exporters: [debug]
docker run --rm -it -v "$(pwd)/otel.yaml":/etc/otel/config.yaml -p 4318:4318 --name otel-collector otel/opentelemetry-collector:latest --config /etc/otel/config.yaml
# telegraf.conf
# skip
[[outputs.opentelemetry]]
protocol = "http"
service_address = "http://localhost:4318/v1/metrics"
image

Checklist

Related issues

@telegraf-tiger
Copy link
Contributor

Thanks so much for the pull request!
🤝 ✒️ Just a reminder that the CLA has not yet been signed, and we'll need it before merging. Please sign the CLA when you get a chance, then post a comment here saying !signed-cla

@telegraf-tiger telegraf-tiger bot added feat Improvement on an existing feature such as adding a new setting/mode to an existing plugin plugin/output 1. Request for new output plugins 2. Issues/PRs that are related to out plugins labels Nov 13, 2025
@minuk-dev minuk-dev marked this pull request as ready for review November 13, 2025 19:42
Copy link
Member

@srebhan srebhan left a comment

Choose a reason for hiding this comment

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

Thanks @minuk-dev for your contribution! I do have some initial comments below. Let's first get the code structure right and then work on the details...

Comment on lines 41 to 44
// EncodingType is the encoding type to use for sending data to the OpenTelemetry Collector.
// It is used only when Protocol is set to "http".
// Supported encoding types are "application/x-protobuf" & "application/json". Defaults to "application/x-protobuf".
EncodingType string `toml:"encoding_type"`
Copy link
Member

Choose a reason for hiding this comment

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

Does it make sense to support protobuf via HTTP? If so, please name the option format and use json or protobuf as values instead of the full HTTP encoding type to not mislead users to think they can specify an arbitrary encoding. We also usually use encoding for compression settings...

@srebhan srebhan self-assigned this Nov 17, 2025
@srebhan srebhan changed the title feat(outputs.opentelemetry): support http protocol with protobuf & json encoding feat(outputs.opentelemetry): Support http protocol Nov 17, 2025
@minuk-dev minuk-dev marked this pull request as draft November 17, 2025 14:18
@minuk-dev minuk-dev marked this pull request as ready for review November 26, 2025 15:56
@telegraf-tiger
Copy link
Contributor

Download PR build artifacts for linux_amd64.tar.gz, darwin_arm64.tar.gz, and windows_amd64.zip.
Downloads for additional architectures and packages are available below.

⚠️ This pull request increases the Telegraf binary size by 1.31 % for linux amd64 (new size: 300.0 MB, nightly size 296.1 MB)

📦 Click here to get additional PR build artifacts

Artifact URLs

. DEB . RPM . TAR . GZ . ZIP
amd64.deb aarch64.rpm darwin_amd64.tar.gz windows_amd64.zip
arm64.deb armel.rpm darwin_arm64.tar.gz windows_arm64.zip
armel.deb armv6hl.rpm freebsd_amd64.tar.gz windows_i386.zip
armhf.deb i386.rpm freebsd_armv7.tar.gz
i386.deb ppc64le.rpm freebsd_i386.tar.gz
mips.deb riscv64.rpm linux_amd64.tar.gz
mipsel.deb s390x.rpm linux_arm64.tar.gz
ppc64el.deb x86_64.rpm linux_armel.tar.gz
riscv64.deb linux_armhf.tar.gz
s390x.deb linux_i386.tar.gz
linux_mips.tar.gz
linux_mipsel.tar.gz
linux_ppc64le.tar.gz
linux_riscv64.tar.gz
linux_s390x.tar.gz

Copy link
Member

@srebhan srebhan left a comment

Choose a reason for hiding this comment

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

Thanks @minuk-dev for the update! Here is round two of my comments... ;-)

Comment on lines +19 to +30
type gRPCClient struct {
grpcClientConn *grpc.ClientConn
metricsServiceClient pmetricotlp.GRPCClient
callOptions []grpc.CallOption
}

type httpClient struct {
httpClient *http.Client
url string
encodingType string
compress string
}
Copy link
Member

Choose a reason for hiding this comment

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

What I meant previously is to have a client_grpc.go and a client_http.go with the respective struct etc. Sorry for being not clear!

}

func (*httpClient) Close() error {
// No persistent connections to close for HTTP client
Copy link
Member

Choose a reason for hiding this comment

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

I think you need to CloseIdleConnections here.

Comment on lines +215 to +217
const (
maxHTTPResponseReadBytes = 64 * 1024 // 64 KB
)
Copy link
Member

Choose a reason for hiding this comment

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

Don't declare this const but just use the value!

Comment on lines +111 to +113
if err != nil {
t.Error(err)
}
Copy link
Member

Choose a reason for hiding this comment

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

You need to return as t.Error will not stop the processing here! Same below...

Suggested change
if err != nil {
t.Error(err)
}
if err != nil {
t.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

Comment on lines +116 to +119
err = req.UnmarshalProto(body)
if err != nil {
t.Error(err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Please limit the scope of the variables! Same below.

Suggested change
err = req.UnmarshalProto(body)
if err != nil {
t.Error(err)
}
if err := req.UnmarshalProto(body); err != nil {
t.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

Comment on lines +151 to +152
err = plugin.Connect()
require.NoError(t, err)
Copy link
Member

Choose a reason for hiding this comment

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

Please use

Suggested change
err = plugin.Connect()
require.NoError(t, err)
require.NoError(t, plugin.Connect())

as it is shorter and avoids an explicit err... Same for all other instances below.

map[string]interface{}{
"gauge": 87.332,
},
time.Unix(0, 1622848686000000000))
Copy link
Member

Choose a reason for hiding this comment

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

Just a matter of taste but I like

Suggested change
time.Unix(0, 1622848686000000000))
time.Unix(0, 1622848686000000000),
)

to better see the closing bracket.

Comment on lines 1 to 13
# Send OpenTelemetry metrics over gRPC
[[outputs.opentelemetry]]
## Override the default (grpc) protocol
## grpc, http
# protocol = "grpc"
## Override the default (localhost:4317) OpenTelemetry gRPC service
## address:port
## When the protocol is grpc, address:port
## When the protocol is http, http(s)://address:port/path
# service_address = "localhost:4317"
## Override the default (application/x-protobuf) encodingType when Protocol is http
## application/x-protobuf, application/json
# encoding_type = "application/x-protobuf"

Copy link
Member

Choose a reason for hiding this comment

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

Please update!

}
if o.EncodingType == "" {
o.EncodingType = "application/x-protobuf"
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we please check the encoding type!? What if some uses application/pdf? I guess we are only support JSON and protobuf for now, don't we?

Use a switch statement like

	switch o.EncodingType {
	case "":
		o.EncodingType = "application/x-protobuf"
	case "application/json", "application/x-protobuf":
		// Do nothing, those are valid
	default:
		return fmt.Errorf("invalid encoding %q", o.EncodingType)
	}

to be honest I would even prefer

	switch o.EncodingType {
	case "", "protobuf":
		o.EncodingType = "application/x-protobuf"
	case "json":
		o.EncodingType = "application/json"
	default:
		return fmt.Errorf("invalid encoding %q", o.EncodingType)
	}

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 plugin/output 1. Request for new output plugins 2. Issues/PRs that are related to out plugins

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants