Skip to content
This repository was archived by the owner on Nov 7, 2022. It is now read-only.

Commit f019968

Browse files
committed
exporters: enable AWS X-Ray Trace exporter
Added AWS X-Ray as a Trace exporter. It shards exporters by service-name that's retrieved by each Trace batch's Node. With this sample configuration YAML file: ```yaml receivers: opencensus: address: "localhost:55678" exporters: aws-xray: default_service_name: "ocagent" version: "latest" buffer_size: 200 ``` and obviously AWS credentials in your environment, it will export to AWS X-Ray. Fixes #241
1 parent 0a982f4 commit f019968

7 files changed

Lines changed: 309 additions & 1 deletion

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ exporters:
169169

170170
zipkin:
171171
endpoint: "http://127.0.0.1:9411/api/v2/spans"
172+
173+
aws-xray:
174+
region: "us-west-2"
175+
default_service_name: "verifiability_agent"
176+
version: "latest"
177+
buffer_size: 200
172178
```
173179
174180
### <a name="config-diagnostics"></a>Diagnostics

example/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"log"
2222
"math/rand"
23+
"os"
2324
"time"
2425

2526
"contrib.go.opencensus.io/exporter/ocagent"
@@ -32,7 +33,7 @@ import (
3233
func main() {
3334
oce, err := ocagent.NewExporter(
3435
ocagent.WithInsecure(),
35-
ocagent.WithServiceName("example-go"))
36+
ocagent.WithServiceName(fmt.Sprintf("example-go-%d", os.Getpid())))
3637
if err != nil {
3738
log.Fatalf("Failed to create ocagent-exporter: %v", err)
3839
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright 2019, OpenCensus 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 exporterparser
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"regexp"
21+
"sync"
22+
"time"
23+
24+
xray "contrib.go.opencensus.io/exporter/aws"
25+
"go.opencensus.io/trace"
26+
27+
"github.com/spf13/viper"
28+
29+
"github.com/census-instrumentation/opencensus-service/data"
30+
"github.com/census-instrumentation/opencensus-service/exporter"
31+
)
32+
33+
const defaultVersionForAWSXRayApplications = "latest"
34+
35+
type awsXRayConfig struct {
36+
Version string `mapstructure:"version"`
37+
BlacklistRegexes []string `mapstructure:"blacklist_regexes"`
38+
BufferSize int `mapstructure:"buffer_size"`
39+
BufferPeriod time.Duration `mapstructure:"buffer_period"`
40+
DefaultServiceName string `mapstructure:"default_service_name"`
41+
42+
// DestinationRegion is an optional field that if set defines
43+
// the region to which the X-Ray payloads will be sent.
44+
DestinationRegion string `mapstructure:"destination_region"`
45+
}
46+
47+
type awsXRayExporter struct {
48+
mu sync.RWMutex
49+
50+
// exportersByServiceName shards AWS X-Ray OpenCensus-Go
51+
// Trace exporters by serviceName that's derived
52+
// from each Node of spans that this exporter receives.
53+
exportersByServiceName map[string]*xray.Exporter
54+
55+
defaultServiceName string
56+
defaultOptions []xray.Option
57+
}
58+
59+
var _ exporter.TraceExporter = (*awsXRayExporter)(nil)
60+
61+
// AWSXRayTraceExportersFromViper unmarshals the viper and returns an exporter.TraceExporter targeting
62+
// AWS X-Ray according to the configuration settings.
63+
func AWSXRayTraceExportersFromViper(v *viper.Viper) (tes []exporter.TraceExporter, mes []exporter.MetricsExporter, doneFns []func() error, err error) {
64+
var cfg struct {
65+
AWSXRay *awsXRayConfig `mapstructure:"aws-xray"`
66+
}
67+
if err := v.Unmarshal(&cfg); err != nil {
68+
return nil, nil, nil, err
69+
}
70+
xc := cfg.AWSXRay
71+
if xc == nil {
72+
return nil, nil, nil, nil
73+
}
74+
75+
defaultOptions, err := transformConfigToXRayOptions(xc)
76+
if err != nil {
77+
return nil, nil, nil, fmt.Errorf("AWS-Xray: converting configuration to options: %v", err)
78+
}
79+
80+
axe := &awsXRayExporter{
81+
exportersByServiceName: make(map[string]*xray.Exporter),
82+
defaultOptions: defaultOptions,
83+
defaultServiceName: xc.DefaultServiceName,
84+
}
85+
86+
tes = append(tes, axe)
87+
doneFns = append(doneFns, func() error {
88+
axe.Flush()
89+
return nil
90+
})
91+
return tes, mes, doneFns, nil
92+
}
93+
94+
// Flush invokes .Flush() for every one of its underlying exporters.
95+
func (axe *awsXRayExporter) Flush() {
96+
axe.mu.RLock()
97+
defer axe.mu.RUnlock()
98+
99+
for _, exp := range axe.exportersByServiceName {
100+
if exp != nil {
101+
exp.Flush()
102+
}
103+
}
104+
}
105+
106+
func transformConfigToXRayOptions(axrCfg *awsXRayConfig) (xopts []xray.Option, err error) {
107+
if axrCfg == nil {
108+
return nil, nil
109+
}
110+
111+
// Compile any blacklist regexes.
112+
var blacklistRegexes []*regexp.Regexp
113+
for _, blacklistRegexStr := range axrCfg.BlacklistRegexes {
114+
blacklistRegex, err := regexp.Compile(blacklistRegexStr)
115+
if err != nil {
116+
return nil, fmt.Errorf("compiling %q error: %v", blacklistRegexStr, err)
117+
}
118+
blacklistRegexes = append(blacklistRegexes, blacklistRegex)
119+
}
120+
if len(blacklistRegexes) > 0 {
121+
xopts = append(xopts, xray.WithBlacklist(blacklistRegexes))
122+
}
123+
124+
// Handle the buffer-size option.
125+
if axrCfg.BufferSize > 0 {
126+
xopts = append(xopts, xray.WithBufferSize(axrCfg.BufferSize))
127+
}
128+
129+
if axrCfg.BufferPeriod > 0 {
130+
xopts = append(xopts, xray.WithInterval(axrCfg.BufferPeriod))
131+
}
132+
133+
if axrCfg.DestinationRegion != "" {
134+
xopts = append(xopts, xray.WithRegion(axrCfg.DestinationRegion))
135+
}
136+
137+
version := axrCfg.Version
138+
if version == "" {
139+
version = defaultVersionForAWSXRayApplications
140+
}
141+
xopts = append(xopts, xray.WithVersion(version))
142+
143+
return xopts, nil
144+
}
145+
146+
// ExportSpans is the method that translates OpenCensus-Proto Traces into AWS X-Ray spans.
147+
// It uniquely maintains
148+
func (axe *awsXRayExporter) ExportSpans(ctx context.Context, td data.TraceData) (xerr error) {
149+
ctx, span := trace.StartSpan(ctx,
150+
"opencensus.service.exporter.aws_xray.ExportSpans",
151+
trace.WithSampler(trace.NeverSample()))
152+
153+
defer func() {
154+
if xerr != nil {
155+
span.SetStatus(trace.Status{
156+
Code: int32(trace.StatusCodeUnknown),
157+
Message: xerr.Error(),
158+
})
159+
}
160+
span.End()
161+
}()
162+
163+
serviceName := td.Node.GetServiceInfo().GetName()
164+
if serviceName == "" {
165+
serviceName = axe.defaultServiceName
166+
}
167+
span.Annotate([]trace.Attribute{
168+
trace.StringAttribute("service_name", serviceName),
169+
}, "")
170+
171+
exp, err := axe.getOrMakeExporterByServiceName(serviceName)
172+
if err != nil {
173+
return err
174+
}
175+
return exportSpans(ctx, "aws-xray", exp, td)
176+
}
177+
178+
func (axe *awsXRayExporter) getOrMakeExporterByServiceName(serviceName string) (*xray.Exporter, error) {
179+
axe.mu.Lock()
180+
defer axe.mu.Unlock()
181+
182+
exp, ok := axe.exportersByServiceName[serviceName]
183+
if ok && exp != nil {
184+
return exp, nil
185+
}
186+
187+
// Otherwise, this is the our first time creating this exporter,
188+
// so create it with the default options but finally the prescribed serviceName.
189+
opts := append(axe.defaultOptions, xray.WithServiceName(serviceName))
190+
exp, err := xray.NewExporter(opts...)
191+
if err != nil {
192+
return nil, err
193+
}
194+
195+
// Now memoize the newly created AWS X-Ray exporter, for later lookups.
196+
axe.exportersByServiceName[serviceName] = exp
197+
198+
return exp, nil
199+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2019, OpenCensus 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 exporterparser
16+
17+
import (
18+
"fmt"
19+
"regexp"
20+
"strings"
21+
"testing"
22+
"time"
23+
24+
xray "contrib.go.opencensus.io/exporter/aws"
25+
)
26+
27+
func TestTransformConfigToXRayOptions(t *testing.T) {
28+
testCases := []struct {
29+
config *awsXRayConfig
30+
want []xray.Option
31+
wantErr string
32+
}{
33+
{
34+
config: nil,
35+
want: nil,
36+
},
37+
{
38+
config: &awsXRayConfig{
39+
Version: "0.12.9",
40+
BlacklistRegexes: []string{"foo.+", "[a-z]+"},
41+
BufferSize: 10,
42+
BufferPeriod: 379 * time.Millisecond,
43+
DestinationRegion: "us-west-2",
44+
},
45+
want: []xray.Option{
46+
xray.WithBlacklist([]*regexp.Regexp{
47+
regexp.MustCompile("foo.+"), regexp.MustCompile("[a-z]+"),
48+
}),
49+
xray.WithBufferSize(10),
50+
xray.WithInterval(379 * time.Millisecond),
51+
xray.WithRegion("us-west-2"),
52+
xray.WithVersion("0.12.9"),
53+
},
54+
},
55+
{
56+
config: &awsXRayConfig{
57+
Version: "0.12.9",
58+
BlacklistRegexes: []string{"+"},
59+
},
60+
wantErr: "error parsing regexp: missing argument to repetition operator: `+`",
61+
},
62+
}
63+
64+
for i, tt := range testCases {
65+
got, err := transformConfigToXRayOptions(tt.config)
66+
if tt.wantErr != "" {
67+
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
68+
t.Errorf("#%d:\nGot: %q\nWant substring: %q\n", i, err, tt.wantErr)
69+
}
70+
continue
71+
}
72+
if err != nil {
73+
t.Errorf("#%d: Unexpected error: %v", i, err)
74+
continue
75+
}
76+
77+
ng, nw := len(got), len(tt.want)
78+
if ng != nw {
79+
t.Errorf("#%d: got len(options)=%d want len(options)=%d", i, ng, nw)
80+
continue
81+
}
82+
83+
// Comparing the slice of options. This is a horrendous
84+
// comparison but the closest that we can do for now.
85+
addrStr := func(v interface{}) string { return fmt.Sprintf("%p", v) }
86+
87+
for j := 0; j < ng; j++ {
88+
sg, sw := addrStr(got[j]), addrStr(tt.want[j])
89+
// Since these are unexported options that could be anything,
90+
// the best that we could do is compare pointer addresses.
91+
if sg != sw {
92+
t.Errorf("#%d:\nGot:\n%v\nWant:\n%v\n", i, sg, sw)
93+
}
94+
}
95+
}
96+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module github.com/census-instrumentation/opencensus-service
22

33
require (
44
cloud.google.com/go v0.32.0 // indirect
5+
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0
56
contrib.go.opencensus.io/exporter/ocagent v0.4.4
67
contrib.go.opencensus.io/exporter/stackdriver v0.9.1
78
git.apache.org/thrift.git v0.0.0-20181101003639-92be4f312b88 // indirect

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
44
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
55
cloud.google.com/go v0.32.0 h1:DSt59WoyNcfAInilEpfvm2ugq8zvNyaHAm9MkzOwRQ4=
66
cloud.google.com/go v0.32.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
7+
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0 h1:YsbWYxDZkC7x2OxlsDEYvvEXZ3cBI3qBgUK5BqkZvRw=
8+
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
79
contrib.go.opencensus.io/exporter/ocagent v0.4.3 h1:QjNm697iO7CZ09IxxSiCUzOhALENIsLsixdPwjV1yGs=
810
contrib.go.opencensus.io/exporter/ocagent v0.4.3/go.mod h1:YuG83h+XWwqWjvCqn7vK4KSyLKhThY3+gNGQ37iS2V0=
911
contrib.go.opencensus.io/exporter/ocagent v0.4.4 h1:6v7OlUmiBDhvbYcHPWp8LHO8wh9jJY8f4mO34J4GJiA=
@@ -47,6 +49,7 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZ
4749
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
4850
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
4951
github.com/aws/aws-sdk-go v0.0.0-20180507225419-00862f899353/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
52+
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
5053
github.com/aws/aws-sdk-go v1.15.31/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
5154
github.com/aws/aws-sdk-go v1.15.68 h1:2+CJhKkxU/ldztVaJ7V5adR1/ZnYl+oc7ufq2Bl18BQ=
5255
github.com/aws/aws-sdk-go v1.15.68/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=

internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ func eqLocalHost(host string) bool {
390390
// + kafka
391391
// + opencensus
392392
// + prometheus
393+
// + aws-xray
393394
func ExportersFromViperConfig(logger *zap.Logger, v *viper.Viper) ([]exporter.TraceExporter, []exporter.MetricsExporter, []func() error, error) {
394395
parseFns := []struct {
395396
name string
@@ -402,6 +403,7 @@ func ExportersFromViperConfig(logger *zap.Logger, v *viper.Viper) ([]exporter.Tr
402403
{name: "kafka", fn: exporterparser.KafkaExportersFromViper},
403404
{name: "opencensus", fn: exporterparser.OpenCensusTraceExportersFromViper},
404405
{name: "prometheus", fn: exporterparser.PrometheusExportersFromViper},
406+
{name: "aws-xray", fn: exporterparser.AWSXRayTraceExportersFromViper},
405407
}
406408

407409
var traceExporters []exporter.TraceExporter

0 commit comments

Comments
 (0)