first commit
This commit is contained in:
114
plugins/otel/http.go
Normal file
114
plugins/otel/http.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package otel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
collectorpb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// OtelClientHTTP is the implementation of the OpenTelemetry client for HTTP
|
||||
type OtelClientHTTP struct {
|
||||
client *http.Client
|
||||
endpoint string
|
||||
headers map[string]string
|
||||
}
|
||||
|
||||
// NewOtelClientHTTP creates a new OpenTelemetry client for HTTP
|
||||
func NewOtelClientHTTP(endpoint string, headers map[string]string, tlsCACert string, insecureMode bool) (*OtelClientHTTP, error) {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.MaxIdleConns = 100
|
||||
transport.MaxIdleConnsPerHost = 10
|
||||
transport.IdleConnTimeout = 120 * time.Second
|
||||
|
||||
// TLS priority: custom CA > system roots > insecure
|
||||
if tlsCACert != "" {
|
||||
// Validate the CA cert path to prevent path traversal attacks
|
||||
if err := validateCACertPath(tlsCACert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCert, err := os.ReadFile(tlsCACert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to load provided CA cert: %w", err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCert) {
|
||||
return nil, fmt.Errorf("fail to add provided CA cert")
|
||||
}
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
} else if insecureMode {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: true, // #nosec G402
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
} else {
|
||||
// Use system root CAs with MinVersion
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
return &OtelClientHTTP{client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: transport,
|
||||
}, endpoint: endpoint, headers: headers}, nil
|
||||
}
|
||||
|
||||
// Emit sends a trace to the OpenTelemetry collector
|
||||
func (c *OtelClientHTTP) Emit(ctx context.Context, rs []*ResourceSpan) error {
|
||||
payload, err := proto.Marshal(&collectorpb.ExportTraceServiceRequest{ResourceSpans: rs})
|
||||
if err != nil {
|
||||
logger.Error("[otel] failed to marshal trace: %v", err)
|
||||
return err
|
||||
}
|
||||
var body bytes.Buffer
|
||||
body.Write(payload)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint, &body)
|
||||
if err != nil {
|
||||
logger.Error("[otel] failed to create request: %v", err)
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-protobuf")
|
||||
if c.headers != nil {
|
||||
for key, value := range c.headers {
|
||||
if strings.ToLower(key) == "content-type" {
|
||||
continue
|
||||
}
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
logger.Error("[otel] failed to send request to %s: %v", c.endpoint, err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode/100 != 2 {
|
||||
// Discard the body to avoid leaking memory
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
logger.Error("[otel] collector at %s returned status %s", c.endpoint, resp.Status)
|
||||
return fmt.Errorf("collector returned %s", resp.Status)
|
||||
}
|
||||
logger.Debug("[otel] successfully sent trace to %s, status: %s", c.endpoint, resp.Status)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the HTTP client
|
||||
func (c *OtelClientHTTP) Close() error {
|
||||
if c.client != nil {
|
||||
c.client.CloseIdleConnections()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user