Skip to content

Commit 8e5b3ac

Browse files
ctreatmajpkrohling
andauthored
Restore jaegerthrifthttpexporter (#5666)
* Close #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]>
1 parent 1f4ab83 commit 8e5b3ac

21 files changed

+2163
-1
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ exporter/elasticsearchexporter/ @open-telemetry/collector-c
2727
exporter/f5cloudexporter/ @open-telemetry/collector-contrib-approvers @gramidt
2828
exporter/honeycombexporter/ @open-telemetry/collector-contrib-approvers @paulosman @lizthegrey @MikeGoldsmith
2929
exporter/humioexporter/ @open-telemetry/collector-contrib-approvers @xitric
30+
exporter/jaegerthrifthttpexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling @pavolloffay
3031
exporter/awskinesisexporter/ @open-telemetry/collector-contrib-approvers @owais @anuraaga
3132
exporter/loadbalancingexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling
3233
exporter/logzioexporter/ @open-telemetry/collector-contrib-approvers @jkowall @Doron-Bargo @yotamloe

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ updates:
101101
directory: "/exporter/jaegerexporter"
102102
schedule:
103103
interval: "weekly"
104+
- package-ecosystem: "gomod"
105+
directory: "/exporter/jaegerthrifthttpexporter"
106+
schedule:
107+
interval: "weekly"
104108
- package-ecosystem: "gomod"
105109
directory: "/exporter/kafkaexporter"
106110
schedule:

examples/tracing/otel-collector-config.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ receivers:
55
zipkin:
66

77
exporters:
8+
jaeger_thrift:
9+
url: "http://jaeger:14268/api/traces"
810
logging:
911
zipkin:
1012
endpoint: "http://zipkin:9411/api/v2/spans"
@@ -22,5 +24,5 @@ service:
2224
pipelines:
2325
traces:
2426
receivers: [otlp, zipkin]
25-
exporters: [zipkin, logging]
27+
exporters: [zipkin, jaeger_thrift, logging]
2628
processors: [batch]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../../Makefile.Common
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Jaeger Thrift Exporter
2+
3+
This exporter supports sending trace data to [Jaeger](https://www.jaegertracing.io) over Thrift HTTP.
4+
5+
*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).
6+
7+
Supported pipeline types: traces
8+
9+
## Configuration
10+
11+
The following settings are required:
12+
13+
- `endpoint` (no default): target to which the exporter is going to send Jaeger trace data,
14+
using the Thrift HTTP protocol.
15+
16+
The following settings can be optionally configured:
17+
18+
- `timeout` (default = 5s): the maximum time to wait for a HTTP request to complete
19+
- `headers` (no default): headers to be added to the HTTP request
20+
21+
Example:
22+
23+
```yaml
24+
exporters:
25+
jaeger_thrift:
26+
endpoint: "http://jaeger.example.com/api/traces"
27+
timeout: 2s
28+
headers:
29+
added-entry: "added value"
30+
dot.test: test
31+
```
32+
33+
The full list of settings exposed for this exporter are documented [here](config.go)
34+
with detailed sample configurations [here](testdata/config.yaml).
35+
36+
This exporter also offers proxy support as documented
37+
[here](https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter#proxy-support).
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2019, OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package jaegerthrifthttpexporter
16+
17+
import (
18+
"go.opentelemetry.io/collector/config"
19+
"go.opentelemetry.io/collector/config/confighttp"
20+
)
21+
22+
// Config defines configuration for Jaeger Thrift over HTTP exporter.
23+
type Config struct {
24+
config.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
25+
confighttp.HTTPClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
26+
}
27+
28+
var _ config.Exporter = (*Config)(nil)
29+
30+
// Validate checks if the exporter configuration is valid
31+
func (cfg *Config) Validate() error {
32+
return nil
33+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2019, OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package jaegerthrifthttpexporter
16+
17+
import (
18+
"context"
19+
"path"
20+
"testing"
21+
"time"
22+
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
"go.opentelemetry.io/collector/component/componenttest"
26+
"go.opentelemetry.io/collector/config"
27+
"go.opentelemetry.io/collector/config/confighttp"
28+
"go.opentelemetry.io/collector/config/configtest"
29+
)
30+
31+
func TestLoadConfig(t *testing.T) {
32+
factories, err := componenttest.NopFactories()
33+
assert.Nil(t, err)
34+
35+
factory := NewFactory()
36+
factories.Exporters[typeStr] = factory
37+
cfg, err := configtest.LoadConfigAndValidate(path.Join(".", "testdata", "config.yaml"), factories)
38+
39+
require.NoError(t, err)
40+
require.NotNil(t, cfg)
41+
42+
e0 := cfg.Exporters[config.NewComponentID(typeStr)]
43+
44+
// URL doesn't have a default value so set it directly.
45+
defaultCfg := factory.CreateDefaultConfig().(*Config)
46+
defaultCfg.Endpoint = "http://jaeger.example:14268/api/traces"
47+
assert.Equal(t, defaultCfg, e0)
48+
49+
e1 := cfg.Exporters[config.NewComponentIDWithName(typeStr, "2")]
50+
expectedCfg := Config{
51+
ExporterSettings: config.NewExporterSettings(config.NewComponentIDWithName(typeStr, "2")),
52+
HTTPClientSettings: confighttp.HTTPClientSettings{
53+
Endpoint: "http://jaeger.example.com/api/traces",
54+
Headers: map[string]string{
55+
"added-entry": "added value",
56+
"dot.test": "test",
57+
},
58+
Timeout: 2 * time.Second,
59+
},
60+
}
61+
assert.Equal(t, &expectedCfg, e1)
62+
63+
te, err := factory.CreateTracesExporter(context.Background(), componenttest.NewNopExporterCreateSettings(), e1)
64+
require.NoError(t, err)
65+
require.NotNil(t, te)
66+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2019, OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package jaegerthrifthttpexporter implements an exporter that sends trace data
16+
// to a Jaeger collector Thrift over HTTP endpoint.
17+
package jaegerthrifthttpexporter
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2019, OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package jaegerthrifthttpexporter
16+
17+
import (
18+
"bytes"
19+
"context"
20+
"fmt"
21+
"io"
22+
"io/ioutil"
23+
"net/http"
24+
25+
"github.com/apache/thrift/lib/go/thrift"
26+
"github.com/jaegertracing/jaeger/model"
27+
jaegerThriftConverter "github.com/jaegertracing/jaeger/model/converter/thrift/jaeger"
28+
"github.com/jaegertracing/jaeger/thrift-gen/jaeger"
29+
"go.opentelemetry.io/collector/component"
30+
"go.opentelemetry.io/collector/consumer/consumererror"
31+
"go.opentelemetry.io/collector/exporter/exporterhelper"
32+
"go.opentelemetry.io/collector/model/pdata"
33+
34+
jaegertranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger"
35+
)
36+
37+
func newTracesExporter(
38+
config *Config,
39+
params component.ExporterCreateSettings,
40+
) (component.TracesExporter, error) {
41+
s := &jaegerThriftHTTPSender{
42+
config: config,
43+
}
44+
45+
return exporterhelper.NewTracesExporter(
46+
config,
47+
params,
48+
s.pushTraceData,
49+
exporterhelper.WithStart(s.start),
50+
)
51+
}
52+
53+
// jaegerThriftHTTPSender forwards spans encoded in the jaeger thrift
54+
// format to a http server.
55+
type jaegerThriftHTTPSender struct {
56+
config *Config
57+
client *http.Client
58+
}
59+
60+
// start starts the exporter
61+
func (s *jaegerThriftHTTPSender) start(_ context.Context, host component.Host) (err error) {
62+
s.client, err = s.config.HTTPClientSettings.ToClient(host.GetExtensions())
63+
64+
if err != nil {
65+
return consumererror.NewPermanent(err)
66+
}
67+
68+
return nil
69+
}
70+
71+
func (s *jaegerThriftHTTPSender) pushTraceData(
72+
ctx context.Context,
73+
td pdata.Traces,
74+
) error {
75+
batches, err := jaegertranslator.InternalTracesToJaegerProto(td)
76+
if err != nil {
77+
return consumererror.NewPermanent(fmt.Errorf("failed to push trace data via Jaeger Thrift HTTP exporter: %w", err))
78+
}
79+
80+
for i := 0; i < len(batches); i++ {
81+
body, err := serializeThrift(ctx, batches[i])
82+
if err != nil {
83+
return consumererror.NewPermanent(err)
84+
}
85+
86+
req, err := http.NewRequest("POST", s.config.HTTPClientSettings.Endpoint, body)
87+
if err != nil {
88+
return consumererror.NewPermanent(err)
89+
}
90+
91+
req.Header.Set("Content-Type", "application/x-thrift")
92+
93+
resp, err := s.client.Do(req)
94+
if err != nil {
95+
return consumererror.NewPermanent(err)
96+
}
97+
98+
io.Copy(ioutil.Discard, resp.Body)
99+
resp.Body.Close()
100+
101+
if resp.StatusCode >= http.StatusBadRequest {
102+
err = fmt.Errorf(
103+
"HTTP %d %q",
104+
resp.StatusCode,
105+
http.StatusText(resp.StatusCode))
106+
return consumererror.NewPermanent(err)
107+
}
108+
}
109+
110+
return nil
111+
}
112+
113+
func serializeThrift(ctx context.Context, batch *model.Batch) (*bytes.Buffer, error) {
114+
thriftSpans := jaegerThriftConverter.FromDomain(batch.GetSpans())
115+
thriftProcess := jaeger.Process{
116+
ServiceName: batch.GetProcess().GetServiceName(),
117+
Tags: convertTagsToThrift(batch.GetProcess().GetTags()),
118+
}
119+
thriftBatch := jaeger.Batch{
120+
Spans: thriftSpans,
121+
Process: &thriftProcess,
122+
}
123+
t := thrift.NewTMemoryBuffer()
124+
p := thrift.NewTBinaryProtocolConf(t, nil)
125+
if err := thriftBatch.Write(ctx, p); err != nil {
126+
return nil, err
127+
}
128+
return t.Buffer, nil
129+
}
130+
131+
func convertTagsToThrift(tags []model.KeyValue) []*jaeger.Tag {
132+
thriftTags := make([]*jaeger.Tag, 0, len(tags))
133+
134+
for i := 0; i < len(tags); i++ {
135+
tag := tags[i]
136+
thriftTag := &jaeger.Tag{Key: tag.GetKey()}
137+
switch tag.GetVType() {
138+
case model.ValueType_STRING:
139+
str := tag.GetVStr()
140+
thriftTag.VStr = &str
141+
thriftTag.VType = jaeger.TagType_STRING
142+
case model.ValueType_INT64:
143+
i := tag.GetVInt64()
144+
thriftTag.VLong = &i
145+
thriftTag.VType = jaeger.TagType_LONG
146+
case model.ValueType_BOOL:
147+
b := tag.GetVBool()
148+
thriftTag.VBool = &b
149+
thriftTag.VType = jaeger.TagType_BOOL
150+
case model.ValueType_FLOAT64:
151+
d := tag.GetVFloat64()
152+
thriftTag.VDouble = &d
153+
thriftTag.VType = jaeger.TagType_DOUBLE
154+
default:
155+
str := "<Unknown tag type for key \"" + tag.GetKey() + "\">"
156+
thriftTag.VStr = &str
157+
thriftTag.VType = jaeger.TagType_STRING
158+
}
159+
thriftTags = append(thriftTags, thriftTag)
160+
}
161+
162+
return thriftTags
163+
}

0 commit comments

Comments
 (0)