Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AWS X-Ray Exporter #41

Merged
merged 68 commits into from
Dec 16, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
c0d6c5b
initial def of aws xray exporter
kbrockhoff Nov 8, 2019
f5079de
initial dev of xray data structures and converters
kbrockhoff Nov 13, 2019
b0b6901
initial dev of xray data structures and converters
kbrockhoff Nov 14, 2019
72a0683
initial dev of xray data structures and converters
kbrockhoff Nov 14, 2019
9dee5a1
initial dev of xray data structures and converters
kbrockhoff Nov 15, 2019
5e22689
initial dev of xray data structures and converters
kbrockhoff Nov 16, 2019
2dc3c2c
initial dev of xray data structures and converters
kbrockhoff Nov 18, 2019
e98cd2e
initial dev of xray data structures and converters
kbrockhoff Nov 19, 2019
31e123e
initial dev of export handler
kbrockhoff Nov 20, 2019
bccb369
Merge remote-tracking branch 'upstream/master'
kbrockhoff Nov 20, 2019
7bea1a5
fix formatting and lint errors
kbrockhoff Nov 20, 2019
e50934c
initial dev of export handler
kbrockhoff Nov 20, 2019
7548f34
added to component list
kbrockhoff Nov 20, 2019
1e3e047
fix issues raised during code review
kbrockhoff Nov 21, 2019
c92085c
switch user attribute name to constant
kbrockhoff Nov 21, 2019
7d62e94
Merge remote-tracking branch 'upstream/master'
kbrockhoff Nov 21, 2019
27d9962
fixed additional code review issues
kbrockhoff Nov 26, 2019
dfa1427
Merge remote-tracking branch 'upstream/master'
kbrockhoff Nov 27, 2019
b201406
fixed additional code review issues
kbrockhoff Nov 27, 2019
c1c8b9c
temporarily change package name
kbrockhoff Nov 27, 2019
338caca
temporarily change package name
kbrockhoff Nov 27, 2019
a47e06b
revert temporarily change package name
kbrockhoff Nov 27, 2019
3f4154a
fixed additional code review issues
kbrockhoff Nov 27, 2019
87c63cb
Merge remote-tracking branch 'upstream/master'
kbrockhoff Dec 4, 2019
7d0c7df
switched to constants defined in collector
kbrockhoff Dec 4, 2019
aba55dc
Merge remote-tracking branch 'upstream/master'
kbrockhoff Dec 5, 2019
d0f2748
switched to status conversion functions defined in collector
kbrockhoff Dec 5, 2019
f41e865
Merge remote-tracking branch 'upstream/master'
kbrockhoff Dec 7, 2019
c7abdda
fix latest code review issues
kbrockhoff Dec 7, 2019
7e732c0
Merge remote-tracking branch 'upstream/master'
kbrockhoff Dec 11, 2019
9189a5a
fix new static check issues
kbrockhoff Dec 11, 2019
851184f
Merge remote-tracking branch 'upstream/master'
kbrockhoff Dec 11, 2019
e8ac424
fix test that breaks if no valid aws session available
kbrockhoff Dec 12, 2019
e900d2d
Update Collector Core dependency to latest `master` (#61)
tigrannajaryan Dec 12, 2019
af8a37b
Ported kinesis exporter from Omnition (#60)
owais Dec 13, 2019
9d176f5
fix pull request issues
kbrockhoff Dec 13, 2019
4d8548a
Updated SAPM exporter dependencies (#59)
owais Dec 13, 2019
55b8658
Add E2E test for SAPM Receiver (#63)
tigrannajaryan Dec 13, 2019
e747a7e
Updated component dependencies (#64)
owais Dec 13, 2019
0d77b22
Fix build for go 1.13 (#65)
rghetia Dec 13, 2019
7f4c30a
Add E2E test for SAPM Exporter (#66)
tigrannajaryan Dec 13, 2019
4f69d2c
initial def of aws xray exporter
kbrockhoff Nov 8, 2019
65c5a41
initial dev of xray data structures and converters
kbrockhoff Nov 13, 2019
30506d5
initial dev of xray data structures and converters
kbrockhoff Nov 14, 2019
029eba8
initial dev of xray data structures and converters
kbrockhoff Nov 14, 2019
22c2ae6
initial dev of xray data structures and converters
kbrockhoff Nov 15, 2019
73e2541
initial dev of xray data structures and converters
kbrockhoff Nov 16, 2019
b6b1198
initial dev of xray data structures and converters
kbrockhoff Nov 18, 2019
42435c8
initial dev of xray data structures and converters
kbrockhoff Nov 19, 2019
b6c606b
initial dev of export handler
kbrockhoff Nov 20, 2019
4f0086a
fix formatting and lint errors
kbrockhoff Nov 20, 2019
3fb89a0
initial dev of export handler
kbrockhoff Nov 20, 2019
56623ed
added to component list
kbrockhoff Nov 20, 2019
42a3d06
fix issues raised during code review
kbrockhoff Nov 21, 2019
34442e6
switch user attribute name to constant
kbrockhoff Nov 21, 2019
2b6a513
fixed additional code review issues
kbrockhoff Nov 26, 2019
30566de
fixed additional code review issues
kbrockhoff Nov 27, 2019
142de99
temporarily change package name
kbrockhoff Nov 27, 2019
101ea0e
temporarily change package name
kbrockhoff Nov 27, 2019
5989f2b
revert temporarily change package name
kbrockhoff Nov 27, 2019
935e60c
fixed additional code review issues
kbrockhoff Nov 27, 2019
6f86a33
switched to constants defined in collector
kbrockhoff Dec 4, 2019
47c88e2
switched to status conversion functions defined in collector
kbrockhoff Dec 5, 2019
3915e0f
fix latest code review issues
kbrockhoff Dec 7, 2019
ddc4777
fix new static check issues
kbrockhoff Dec 11, 2019
e76baa4
fix test that breaks if no valid aws session available
kbrockhoff Dec 12, 2019
2ae696b
fix pull request issues
kbrockhoff Dec 13, 2019
ef18288
Merge branch 'master' of github.com:kbrockhoff/opentelemetry-collecto…
kbrockhoff Dec 14, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions exporter/awsxrayexporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
63 changes: 63 additions & 0 deletions exporter/awsxrayexporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# AWS X-Ray Tracing Exporter for OpenTelemetry Collector

This exporter converts OpenTelemetry spans to
[AWS X-Ray Segment Documents](https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html)
and then sends them directly to X-Ray using the
[PutTraceSegements](https://docs.aws.amazon.com/xray/latest/api/API_PutTraceSegments.html) API.

## Data Conversion

Trace IDs and Span IDs are expected to be originally generated by either AWS API Gateway or AWS ALB and
propagated by them using the `X-Amzn-Trace-Id` HTTP header. However, other generation sources are
supported by replacing Trace IDs where necessary. For consistency, you may want to consider using the
X-Ray approach if generating Trace IDs within the application.

> AWS X-Ray IDs are the same size as W3C Trace Context IDs but differ in that the first 32 bits of a Trace ID
> is the Unix epoch time when the trace was started. Since X-Ray only allows submission of Trace IDs from the
> past 30 days, received Trace IDs are checked. If outside the allowed range, a replacement is generated using
> the current time.

The `http` object is populated when the `component` attribute value is `grpc` as well as `http`. Other
synchronous call types should also result in the `http` object being populated.

## AWS Specific Attributes

The following AWS-specific Span attributes are supported in addition to the standard names and values
defined in the OpenTelemetry Semantic Conventions.

| Attribute name | Notes and examples | Required? |
| :--------------- | :--------------------------------------------------------------------- | --------- |
| `aws.operation` | The name of the API action invoked against an AWS service or resource. | No |
| `aws.account_id` | The AWS account number if accessing resource in different account. | No |
| `aws.region` | The AWS region if accessing resource in different region from app. | No |
| `aws.request_id` | AWS-generated unique identifier for the request. | No |
| `aws.queue_url` | For operations on an Amazon SQS queue, the queue's URL. | No |
| `aws.table_name` | For operations on a DynamoDB table, the name of the table. | No |

Any of these values supplied are used to populate the `aws` object in addition to any relevant data supplied
by the Span Resource object. X-Ray uses this data to generate inferred segments for the remote APIs.

## Exporter Configuration

The following exporter configuration parameters are supported. They mirror and have the same affect as the
comparable AWS X-Ray Daemon configuration values.

| Name | Description | Default |
| :---------------- | :--------------------------------------------------------------------- | ------- |
| `num_workers` | Maximum number of concurrent calls to AWS X-Ray to upload documents. | 8 |
| `endpoint` | Optionally override the default X-Ray service endpoint. | |
| `request_timeout` | Number of seconds before timing out a request. | 30 |
| `no_verify_ssl` | Enable or disable TLS certificate verification. | false |
| `proxy_address` | Upload segments to AWS X-Ray through a proxy. | |
| `region` | Send segments to AWS X-Ray service in a specific region. | |
| `local_mode` | Local mode to skip EC2 instance metadata check. | false |
| `resource_arn` | Amazon Resource Name (ARN) of the AWS resource running the collector. | |
| `role_arn` | IAM role to upload segments to a different account. | |

## AWS Credential Configuration

This exporter follows default credential resolution for the
[aws-sdk-go](https://docs.aws.amazon.com/sdk-for-go/api/index.html).

Follow the [guidelines](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html) for the
credential configuration.
77 changes: 77 additions & 0 deletions exporter/awsxrayexporter/awsxray.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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://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 awsxrayexporter

import (
"context"

"github.com/aws/aws-sdk-go/service/xray"
"github.com/open-telemetry/opentelemetry-collector/config/configmodels"
"github.com/open-telemetry/opentelemetry-collector/consumer/consumerdata"
"github.com/open-telemetry/opentelemetry-collector/exporter"
"github.com/open-telemetry/opentelemetry-collector/exporter/exporterhelper"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter/translator"
)

// NewTraceExporter creates an exporter.TraceExporter that just drops the
// received data and logs debugging messages.
func NewTraceExporter(config configmodels.Exporter, logger *zap.Logger, cn connAttr) (exporter.TraceExporter, error) {
typeLog := zap.String("type", config.Type())
nameLog := zap.String("name", config.Name())
awsConfig, session, err := GetAWSConfigSession(logger, cn, config.(*Config))
if err != nil {
return nil, err
}
xrayClient := NewXRay(logger, awsConfig, session)
return exporterhelper.NewTraceExporter(
config,
func(ctx context.Context, td consumerdata.TraceData) (int, error) {
logger.Debug("TraceExporter", typeLog, nameLog, zap.Int("#spans", len(td.Spans)))
droppedSpans, input := assembleRequest(td, logger)
logger.Debug("request: " + input.String())
output, err := xrayClient.PutTraceSegments(input)
logger.Debug("response: " + output.String())
if output != nil && output.UnprocessedTraceSegments != nil {
droppedSpans += len(output.UnprocessedTraceSegments)
}
return droppedSpans, err
},
exporterhelper.WithTracing(true),
exporterhelper.WithMetrics(false),
exporterhelper.WithShutdown(logger.Sync),
)
}

func assembleRequest(td consumerdata.TraceData, logger *zap.Logger) (int, *xray.PutTraceSegmentsInput) {
documents := make([]*string, len(td.Spans))
droppedSpans := int(0)
for i, span := range td.Spans {
if span == nil || span.Name == nil {
droppedSpans++
continue
}
spanName := span.Name.Value
jsonStr, err := translator.MakeSegmentDocumentString(spanName, span)
if err != nil {
droppedSpans++
logger.Warn("Unable to convert span", zap.Error(err))
}
logger.Debug(jsonStr)
documents[i] = &jsonStr
}
return droppedSpans, &xray.PutTraceSegmentsInput{TraceSegmentDocuments: documents}
}
195 changes: 195 additions & 0 deletions exporter/awsxrayexporter/awsxray_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// 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://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 awsxrayexporter

import (
"context"
"fmt"
"reflect"
"testing"
"time"

resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/open-telemetry/opentelemetry-collector/consumer/consumerdata"
"github.com/open-telemetry/opentelemetry-collector/exporter"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter/translator"
)

func TestTraceExport(t *testing.T) {
traceExporter := initializeTraceExporter()
ctx := context.Background()
td := constructSpanData()
err := traceExporter.ConsumeTraceData(ctx, td)
assert.Nil(t, err)
}

func initializeTraceExporter() exporter.TraceExporter {
logger := zap.NewNop()
factory := Factory{}
config := factory.CreateDefaultConfig()
config.(*Config).Region = "us-east-1"
config.(*Config).LocalMode = true
mconn := new(mockConn)
mconn.sn, _ = getDefaultSession(logger)
traceExporter, err := NewTraceExporter(config, logger, mconn)
if err != nil {
panic(err)
}
return traceExporter
}

func constructSpanData() consumerdata.TraceData {
resource := constructResource()
spans := make([]*tracepb.Span, 2)
spans[0] = constructHTTPClientSpan()
spans[0].Resource = resource
spans[1] = constructHTTPServerSpan()
spans[1].Resource = resource
return consumerdata.TraceData{
Node: nil,
Resource: resource,
Spans: spans,
SourceFormat: "oc",
}
}

func constructResource() *resourcepb.Resource {
labels := make(map[string]string)
labels[translator.ServiceNameAttribute] = "signup_aggregator"
labels[translator.ServiceVersionAttribute] = "1.1.12"
labels[translator.ContainerNameAttribute] = "signup_aggregator"
labels[translator.ContainerImageAttribute] = "otel/signupaggregator"
labels[translator.ContainerTagAttribute] = "v1"
labels[translator.CloudProviderAttribute] = "aws"
labels[translator.CloudAccountAttribute] = "999999998"
labels[translator.CloudRegionAttribute] = "us-west-2"
labels[translator.CloudZoneAttribute] = "us-west-1b"
return &resourcepb.Resource{
Type: "container",
Labels: labels,
}
}

func constructHTTPClientSpan() *tracepb.Span {
attributes := make(map[string]interface{})
attributes[translator.ComponentAttribute] = translator.HTTPComponentType
attributes[translator.MethodAttribute] = "GET"
attributes[translator.URLAttribute] = "https://api.example.com/users/junit"
attributes[translator.StatusCodeAttribute] = 200
endTime := time.Now().Round(time.Second)
startTime := endTime.Add(-90 * time.Second)
spanAttributes := constructSpanAttributes(attributes)

return &tracepb.Span{
TraceId: translator.NewTraceID(),
SpanId: translator.NewSegmentID(),
ParentSpanId: translator.NewSegmentID(),
Name: &tracepb.TruncatableString{Value: "/users/junit"},
Kind: tracepb.Span_CLIENT,
StartTime: convertTimeToTimestamp(startTime),
EndTime: convertTimeToTimestamp(endTime),
Status: &tracepb.Status{
Code: 0,
Message: "OK",
},
SameProcessAsParentSpan: &wrappers.BoolValue{Value: false},
Tracestate: &tracepb.Span_Tracestate{
Entries: []*tracepb.Span_Tracestate_Entry{
{Key: "foo", Value: "bar"},
{Key: "a", Value: "b"},
},
},
Attributes: &tracepb.Span_Attributes{
AttributeMap: spanAttributes,
},
}
}

func constructHTTPServerSpan() *tracepb.Span {
attributes := make(map[string]interface{})
attributes[translator.ComponentAttribute] = translator.HTTPComponentType
attributes[translator.MethodAttribute] = "GET"
attributes[translator.URLAttribute] = "https://api.example.com/users/junit"
attributes[translator.UserAgentAttribute] = "PostmanRuntime/7.16.3"
attributes[translator.ClientIPAttribute] = "192.168.15.32"
attributes[translator.StatusCodeAttribute] = 200
endTime := time.Now().Round(time.Second)
startTime := endTime.Add(-90 * time.Second)
spanAttributes := constructSpanAttributes(attributes)

return &tracepb.Span{
TraceId: translator.NewTraceID(),
SpanId: translator.NewSegmentID(),
ParentSpanId: translator.NewSegmentID(),
Name: &tracepb.TruncatableString{Value: "/users/junit"},
Kind: tracepb.Span_SERVER,
StartTime: convertTimeToTimestamp(startTime),
EndTime: convertTimeToTimestamp(endTime),
Status: &tracepb.Status{
Code: 0,
Message: "OK",
},
SameProcessAsParentSpan: &wrappers.BoolValue{Value: false},
Tracestate: &tracepb.Span_Tracestate{
Entries: []*tracepb.Span_Tracestate_Entry{
{Key: "foo", Value: "bar"},
{Key: "a", Value: "b"},
},
},
Attributes: &tracepb.Span_Attributes{
AttributeMap: spanAttributes,
},
}
}

func convertTimeToTimestamp(t time.Time) *timestamp.Timestamp {
if t.IsZero() {
return nil
}
nanoTime := t.UnixNano()
return &timestamp.Timestamp{
Seconds: nanoTime / 1e9,
Nanos: int32(nanoTime % 1e9),
}
}

func constructSpanAttributes(attributes map[string]interface{}) map[string]*tracepb.AttributeValue {
attrs := make(map[string]*tracepb.AttributeValue)
for key, value := range attributes {
valType := reflect.TypeOf(value)
var attrVal tracepb.AttributeValue
if valType.Kind() == reflect.Int {
attrVal = tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{
IntValue: int64(value.(int)),
}}
} else if valType.Kind() == reflect.Int64 {
attrVal = tracepb.AttributeValue{Value: &tracepb.AttributeValue_IntValue{
IntValue: value.(int64),
}}
} else {
attrVal = tracepb.AttributeValue{Value: &tracepb.AttributeValue_StringValue{
StringValue: &tracepb.TruncatableString{Value: fmt.Sprintf("%v", value)},
}}
}
attrs[key] = &attrVal
}
return attrs
}
42 changes: 42 additions & 0 deletions exporter/awsxrayexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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://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 awsxrayexporter

import "github.com/open-telemetry/opentelemetry-collector/config/configmodels"

// Config defines configuration for AWS X-Ray exporter.
type Config struct {
configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
// Maximum number of concurrent calls to AWS X-Ray to upload documents.
Concurrency int `mapstructure:"num_workers"`
kbrockhoff marked this conversation as resolved.
Show resolved Hide resolved
// X-Ray service endpoint to which the collector sends segment documents.
Endpoint string `mapstructure:"endpoint"`
// Number of seconds before timing out a request.
RequestTimeoutSeconds int `mapstructure:"request_timeout_seconds"`
// Maximum number of retries before abandoning an attempt to post data.
MaxRetries int `mapstructure:"max_retries"`
// Enable or disable TLS certificate verification.
NoVerifySSL bool `mapstructure:"no_verify_ssl"`
// Upload segments to AWS X-Ray through a proxy.
ProxyAddress string `mapstructure:"proxy_address"`
kbrockhoff marked this conversation as resolved.
Show resolved Hide resolved
// Send segments to AWS X-Ray service in a specific region.
Region string `mapstructure:"region"`
// Local mode to skip EC2 instance metadata check.
LocalMode bool `mapstructure:"local_mode"`
// Amazon Resource Name (ARN) of the AWS resource running the collector.
ResourceARN string `mapstructure:"resource_arn"`
// IAM role to upload segments to a different account.
RoleARN string `mapstructure:"role_arn"`
}
Loading