Skip to content

Commit

Permalink
Restore jaegerthrifthttpexporter (open-telemetry#5666)
Browse files Browse the repository at this point in the history
* Close open-telemetry#4667: Restore jaegerthrifthttpexporter

In some cases, a Jaeger collector may be deployed to a platform
that does not support gRPC.  This restores the jaegerthrifthttpexporter
so that OpenTelemetry users who need to ship traces to a Jaeger
collector that cannot support gRPC are able to ship traces using
the Jaeger collector's [Thrift over HTTP API](https://www.jaegertracing.io/docs/1.27/apis/#thrift-over-http-stable).

* Add warning regarding usage of the Jaeger Thrift HTTP exporter

* Update exporter/jaegerthrifthttpexporter/README.md

Co-authored-by: Juraci Paixão Kröhling <[email protected]>

* Bump go version

* [WIP] Exporter refactor

This removes the protospan_to_jaegerthrift translator because we can
use the existing internal_to_jaegerproto translator to translate from
from internal spans to Jaeger domain spans.  The Jaeger domain spans
to Jaeger Thrift conversion is small enough that I think it makes more
sense as part of the exporter now.

That said, some tests may need to be added for the exporter to cover
the code that used to be in protospan_to_jaegerthrift.
I still need to convert from jaeger/model.Batch to thrift-gen/jaeger.Batch,
which will require a separate translator

* Fix lint errors

* Use HTTPClientSettings for configuration

* Use same version as other exporters in go.mod

* Update README to match new config requirements

* Wrap all errors returned by pushTraceData with consumererror.NewPermanent

* Include Jaeger Thrift exporter in exporters_test

* Only return an error from start if there's an error

* Fix exporters_test & error message, add exporter to versions.yaml

Co-authored-by: Juraci Paixão Kröhling <[email protected]>
  • Loading branch information
ctreatma and jpkrohling committed Nov 1, 2021
1 parent 1f4ab83 commit 8e5b3ac
Show file tree
Hide file tree
Showing 21 changed files with 2,163 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ exporter/elasticsearchexporter/ @open-telemetry/collector-c
exporter/f5cloudexporter/ @open-telemetry/collector-contrib-approvers @gramidt
exporter/honeycombexporter/ @open-telemetry/collector-contrib-approvers @paulosman @lizthegrey @MikeGoldsmith
exporter/humioexporter/ @open-telemetry/collector-contrib-approvers @xitric
exporter/jaegerthrifthttpexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling @pavolloffay
exporter/awskinesisexporter/ @open-telemetry/collector-contrib-approvers @owais @anuraaga
exporter/loadbalancingexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling
exporter/logzioexporter/ @open-telemetry/collector-contrib-approvers @jkowall @Doron-Bargo @yotamloe
Expand Down
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ updates:
directory: "/exporter/jaegerexporter"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/exporter/jaegerthrifthttpexporter"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/exporter/kafkaexporter"
schedule:
Expand Down
4 changes: 3 additions & 1 deletion examples/tracing/otel-collector-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ receivers:
zipkin:

exporters:
jaeger_thrift:
url: "http:https://jaeger:14268/api/traces"
logging:
zipkin:
endpoint: "http:https://zipkin:9411/api/v2/spans"
Expand All @@ -22,5 +24,5 @@ service:
pipelines:
traces:
receivers: [otlp, zipkin]
exporters: [zipkin, logging]
exporters: [zipkin, jaeger_thrift, logging]
processors: [batch]
1 change: 1 addition & 0 deletions exporter/jaegerthrifthttpexporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
37 changes: 37 additions & 0 deletions exporter/jaegerthrifthttpexporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Jaeger Thrift Exporter

This exporter supports sending trace data to [Jaeger](https://www.jaegertracing.io) over Thrift HTTP.

*WARNING:* The [Jaeger gRPC Exporter](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/jaegerexporter) is the recommended one for exporting traces from an OpenTelemetry Collector to Jaeger. This Jaeger Thrift Exporter should only be used to export traces to a Jaeger Collector that is unable to expose the [gRPC API](https://www.jaegertracing.io/docs/1.27/apis/#protobuf-via-grpc-stable).

Supported pipeline types: traces

## Configuration

The following settings are required:

- `endpoint` (no default): target to which the exporter is going to send Jaeger trace data,
using the Thrift HTTP protocol.

The following settings can be optionally configured:

- `timeout` (default = 5s): the maximum time to wait for a HTTP request to complete
- `headers` (no default): headers to be added to the HTTP request

Example:

```yaml
exporters:
jaeger_thrift:
endpoint: "http:https://jaeger.example.com/api/traces"
timeout: 2s
headers:
added-entry: "added value"
dot.test: test
```

The full list of settings exposed for this exporter are documented [here](config.go)
with detailed sample configurations [here](testdata/config.yaml).

This exporter also offers proxy support as documented
[here](https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter#proxy-support).
33 changes: 33 additions & 0 deletions exporter/jaegerthrifthttpexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http:https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package jaegerthrifthttpexporter

import (
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/config/confighttp"
)

// Config defines configuration for Jaeger Thrift over HTTP exporter.
type Config struct {
config.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
confighttp.HTTPClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
}

var _ config.Exporter = (*Config)(nil)

// Validate checks if the exporter configuration is valid
func (cfg *Config) Validate() error {
return nil
}
66 changes: 66 additions & 0 deletions exporter/jaegerthrifthttpexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http:https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package jaegerthrifthttpexporter

import (
"context"
"path"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configtest"
)

func TestLoadConfig(t *testing.T) {
factories, err := componenttest.NopFactories()
assert.Nil(t, err)

factory := NewFactory()
factories.Exporters[typeStr] = factory
cfg, err := configtest.LoadConfigAndValidate(path.Join(".", "testdata", "config.yaml"), factories)

require.NoError(t, err)
require.NotNil(t, cfg)

e0 := cfg.Exporters[config.NewComponentID(typeStr)]

// URL doesn't have a default value so set it directly.
defaultCfg := factory.CreateDefaultConfig().(*Config)
defaultCfg.Endpoint = "http:https://jaeger.example:14268/api/traces"
assert.Equal(t, defaultCfg, e0)

e1 := cfg.Exporters[config.NewComponentIDWithName(typeStr, "2")]
expectedCfg := Config{
ExporterSettings: config.NewExporterSettings(config.NewComponentIDWithName(typeStr, "2")),
HTTPClientSettings: confighttp.HTTPClientSettings{
Endpoint: "http:https://jaeger.example.com/api/traces",
Headers: map[string]string{
"added-entry": "added value",
"dot.test": "test",
},
Timeout: 2 * time.Second,
},
}
assert.Equal(t, &expectedCfg, e1)

te, err := factory.CreateTracesExporter(context.Background(), componenttest.NewNopExporterCreateSettings(), e1)
require.NoError(t, err)
require.NotNil(t, te)
}
17 changes: 17 additions & 0 deletions exporter/jaegerthrifthttpexporter/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http:https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package jaegerthrifthttpexporter implements an exporter that sends trace data
// to a Jaeger collector Thrift over HTTP endpoint.
package jaegerthrifthttpexporter
163 changes: 163 additions & 0 deletions exporter/jaegerthrifthttpexporter/exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http:https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package jaegerthrifthttpexporter

import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"net/http"

"github.com/apache/thrift/lib/go/thrift"
"github.com/jaegertracing/jaeger/model"
jaegerThriftConverter "github.com/jaegertracing/jaeger/model/converter/thrift/jaeger"
"github.com/jaegertracing/jaeger/thrift-gen/jaeger"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter/exporterhelper"
"go.opentelemetry.io/collector/model/pdata"

jaegertranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger"
)

func newTracesExporter(
config *Config,
params component.ExporterCreateSettings,
) (component.TracesExporter, error) {
s := &jaegerThriftHTTPSender{
config: config,
}

return exporterhelper.NewTracesExporter(
config,
params,
s.pushTraceData,
exporterhelper.WithStart(s.start),
)
}

// jaegerThriftHTTPSender forwards spans encoded in the jaeger thrift
// format to a http server.
type jaegerThriftHTTPSender struct {
config *Config
client *http.Client
}

// start starts the exporter
func (s *jaegerThriftHTTPSender) start(_ context.Context, host component.Host) (err error) {
s.client, err = s.config.HTTPClientSettings.ToClient(host.GetExtensions())

if err != nil {
return consumererror.NewPermanent(err)
}

return nil
}

func (s *jaegerThriftHTTPSender) pushTraceData(
ctx context.Context,
td pdata.Traces,
) error {
batches, err := jaegertranslator.InternalTracesToJaegerProto(td)
if err != nil {
return consumererror.NewPermanent(fmt.Errorf("failed to push trace data via Jaeger Thrift HTTP exporter: %w", err))
}

for i := 0; i < len(batches); i++ {
body, err := serializeThrift(ctx, batches[i])
if err != nil {
return consumererror.NewPermanent(err)
}

req, err := http.NewRequest("POST", s.config.HTTPClientSettings.Endpoint, body)
if err != nil {
return consumererror.NewPermanent(err)
}

req.Header.Set("Content-Type", "application/x-thrift")

resp, err := s.client.Do(req)
if err != nil {
return consumererror.NewPermanent(err)
}

io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()

if resp.StatusCode >= http.StatusBadRequest {
err = fmt.Errorf(
"HTTP %d %q",
resp.StatusCode,
http.StatusText(resp.StatusCode))
return consumererror.NewPermanent(err)
}
}

return nil
}

func serializeThrift(ctx context.Context, batch *model.Batch) (*bytes.Buffer, error) {
thriftSpans := jaegerThriftConverter.FromDomain(batch.GetSpans())
thriftProcess := jaeger.Process{
ServiceName: batch.GetProcess().GetServiceName(),
Tags: convertTagsToThrift(batch.GetProcess().GetTags()),
}
thriftBatch := jaeger.Batch{
Spans: thriftSpans,
Process: &thriftProcess,
}
t := thrift.NewTMemoryBuffer()
p := thrift.NewTBinaryProtocolConf(t, nil)
if err := thriftBatch.Write(ctx, p); err != nil {
return nil, err
}
return t.Buffer, nil
}

func convertTagsToThrift(tags []model.KeyValue) []*jaeger.Tag {
thriftTags := make([]*jaeger.Tag, 0, len(tags))

for i := 0; i < len(tags); i++ {
tag := tags[i]
thriftTag := &jaeger.Tag{Key: tag.GetKey()}
switch tag.GetVType() {
case model.ValueType_STRING:
str := tag.GetVStr()
thriftTag.VStr = &str
thriftTag.VType = jaeger.TagType_STRING
case model.ValueType_INT64:
i := tag.GetVInt64()
thriftTag.VLong = &i
thriftTag.VType = jaeger.TagType_LONG
case model.ValueType_BOOL:
b := tag.GetVBool()
thriftTag.VBool = &b
thriftTag.VType = jaeger.TagType_BOOL
case model.ValueType_FLOAT64:
d := tag.GetVFloat64()
thriftTag.VDouble = &d
thriftTag.VType = jaeger.TagType_DOUBLE
default:
str := "<Unknown tag type for key \"" + tag.GetKey() + "\">"
thriftTag.VStr = &str
thriftTag.VType = jaeger.TagType_STRING
}
thriftTags = append(thriftTags, thriftTag)
}

return thriftTags
}
Loading

0 comments on commit 8e5b3ac

Please sign in to comment.