2019-09-09 23:59:39 +02:00
|
|
|
// 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 jaeger
|
|
|
|
|
|
|
|
import (
|
|
|
|
"log"
|
|
|
|
|
|
|
|
"google.golang.org/api/support/bundler"
|
2019-09-25 22:22:33 +02:00
|
|
|
"google.golang.org/grpc/codes"
|
2019-09-09 23:59:39 +02:00
|
|
|
|
2019-09-25 22:22:33 +02:00
|
|
|
"go.opentelemetry.io/api/core"
|
2019-09-09 23:59:39 +02:00
|
|
|
gen "go.opentelemetry.io/exporter/trace/jaeger/internal/gen-go/jaeger"
|
|
|
|
"go.opentelemetry.io/sdk/trace"
|
|
|
|
)
|
|
|
|
|
|
|
|
const defaultServiceName = "OpenTelemetry"
|
|
|
|
|
2019-10-04 21:07:42 +02:00
|
|
|
type Option func(*options)
|
2019-09-09 23:59:39 +02:00
|
|
|
|
2019-10-04 21:07:42 +02:00
|
|
|
// options are the options to be used when initializing a Jaeger exporter.
|
|
|
|
type options struct {
|
2019-09-09 23:59:39 +02:00
|
|
|
// OnError is the hook to be called when there is
|
2019-10-04 21:07:42 +02:00
|
|
|
// an error occurred when uploading the span data.
|
2019-09-09 23:59:39 +02:00
|
|
|
// If no custom hook is set, errors are logged.
|
|
|
|
OnError func(err error)
|
|
|
|
|
|
|
|
// Process contains the information about the exporting process.
|
|
|
|
Process Process
|
|
|
|
|
|
|
|
//BufferMaxCount defines the total number of traces that can be buffered in memory
|
|
|
|
BufferMaxCount int
|
|
|
|
}
|
|
|
|
|
2019-10-04 21:07:42 +02:00
|
|
|
// WithOnError sets the hook to be called when there is
|
|
|
|
// an error occurred when uploading the span data.
|
|
|
|
// If no custom hook is set, errors are logged.
|
|
|
|
func WithOnError(onError func(err error)) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.OnError = onError
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithProcess sets the process with the information about the exporting process.
|
|
|
|
func WithProcess(process Process) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.Process = process
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//WithBufferMaxCount defines the total number of traces that can be buffered in memory
|
|
|
|
func WithBufferMaxCount(bufferMaxCount int) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.BufferMaxCount = bufferMaxCount
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-09 23:59:39 +02:00
|
|
|
// NewExporter returns a trace.Exporter implementation that exports
|
|
|
|
// the collected spans to Jaeger.
|
2019-10-04 21:07:42 +02:00
|
|
|
func NewExporter(endpointOption EndpointOption, opts ...Option) (*Exporter, error) {
|
|
|
|
uploader, err := endpointOption()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-09-09 23:59:39 +02:00
|
|
|
}
|
|
|
|
|
2019-10-04 21:07:42 +02:00
|
|
|
o := options{}
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(&o)
|
2019-09-09 23:59:39 +02:00
|
|
|
}
|
2019-10-04 21:07:42 +02:00
|
|
|
|
2019-09-09 23:59:39 +02:00
|
|
|
onError := func(err error) {
|
|
|
|
if o.OnError != nil {
|
|
|
|
o.OnError(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("Error when uploading spans to Jaeger: %v", err)
|
|
|
|
}
|
|
|
|
service := o.Process.ServiceName
|
|
|
|
if service == "" {
|
|
|
|
service = defaultServiceName
|
|
|
|
}
|
|
|
|
tags := make([]*gen.Tag, len(o.Process.Tags))
|
|
|
|
for i, tag := range o.Process.Tags {
|
|
|
|
tags[i] = attributeToTag(tag.key, tag.value)
|
|
|
|
}
|
|
|
|
e := &Exporter{
|
2019-10-04 21:07:42 +02:00
|
|
|
uploader: uploader,
|
2019-09-09 23:59:39 +02:00
|
|
|
process: &gen.Process{
|
|
|
|
ServiceName: service,
|
|
|
|
Tags: tags,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
bundler := bundler.NewBundler((*gen.Span)(nil), func(bundle interface{}) {
|
|
|
|
if err := e.upload(bundle.([]*gen.Span)); err != nil {
|
|
|
|
onError(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Set BufferedByteLimit with the total number of spans that are permissible to be held in memory.
|
|
|
|
// This needs to be done since the size of messages is always set to 1. Failing to set this would allow
|
|
|
|
// 1G messages to be held in memory since that is the default value of BufferedByteLimit.
|
|
|
|
if o.BufferMaxCount != 0 {
|
|
|
|
bundler.BufferedByteLimit = o.BufferMaxCount
|
|
|
|
}
|
|
|
|
|
|
|
|
e.bundler = bundler
|
|
|
|
return e, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process contains the information exported to jaeger about the source
|
|
|
|
// of the trace data.
|
|
|
|
type Process struct {
|
|
|
|
// ServiceName is the Jaeger service name.
|
|
|
|
ServiceName string
|
|
|
|
|
|
|
|
// Tags are added to Jaeger Process exports
|
|
|
|
Tags []Tag
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tag defines a key-value pair
|
|
|
|
// It is limited to the possible conversions to *jaeger.Tag by attributeToTag
|
|
|
|
type Tag struct {
|
|
|
|
key string
|
|
|
|
value interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exporter is an implementation of trace.Exporter that uploads spans to Jaeger.
|
|
|
|
type Exporter struct {
|
2019-10-04 21:07:42 +02:00
|
|
|
process *gen.Process
|
|
|
|
bundler *bundler.Bundler
|
|
|
|
uploader batchUploader
|
2019-09-09 23:59:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ trace.Exporter = (*Exporter)(nil)
|
|
|
|
|
|
|
|
// ExportSpan exports a SpanData to Jaeger.
|
|
|
|
func (e *Exporter) ExportSpan(data *trace.SpanData) {
|
|
|
|
_ = e.bundler.Add(spanDataToThrift(data), 1)
|
|
|
|
// TODO(jbd): Handle oversized bundlers.
|
|
|
|
}
|
|
|
|
|
|
|
|
func spanDataToThrift(data *trace.SpanData) *gen.Span {
|
|
|
|
tags := make([]*gen.Tag, 0, len(data.Attributes))
|
2019-09-25 22:22:33 +02:00
|
|
|
for _, kv := range data.Attributes {
|
|
|
|
tag := coreAttributeToTag(kv)
|
|
|
|
tags = append(tags, tag)
|
2019-09-09 23:59:39 +02:00
|
|
|
}
|
|
|
|
|
2019-09-25 22:22:33 +02:00
|
|
|
tags = append(tags, getInt64Tag("status.code", int64(data.Status)),
|
|
|
|
getStringTag("status.message", data.Status.String()),
|
2019-09-09 23:59:39 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Ensure that if Status.Code is not OK, that we set the "error" tag on the Jaeger span.
|
|
|
|
// See Issue https://github.com/census-instrumentation/opencensus-go/issues/1041
|
|
|
|
if data.Status != codes.OK {
|
2019-09-25 22:22:33 +02:00
|
|
|
tags = append(tags, getBoolTag("error", true))
|
2019-09-09 23:59:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var logs []*gen.Log
|
|
|
|
for _, a := range data.MessageEvents {
|
|
|
|
fields := make([]*gen.Tag, 0, len(a.Attributes))
|
|
|
|
for _, kv := range a.Attributes {
|
2019-09-25 22:22:33 +02:00
|
|
|
tag := coreAttributeToTag(kv)
|
2019-09-09 23:59:39 +02:00
|
|
|
if tag != nil {
|
|
|
|
fields = append(fields, tag)
|
|
|
|
}
|
|
|
|
}
|
2019-09-25 22:22:33 +02:00
|
|
|
fields = append(fields, getStringTag("message", a.Message))
|
2019-09-09 23:59:39 +02:00
|
|
|
logs = append(logs, &gen.Log{
|
2019-09-25 22:22:33 +02:00
|
|
|
Timestamp: a.Time.UnixNano() / 1000,
|
2019-09-09 23:59:39 +02:00
|
|
|
Fields: fields,
|
|
|
|
})
|
|
|
|
}
|
2019-10-07 18:40:36 +02:00
|
|
|
|
|
|
|
var refs []*gen.SpanRef
|
|
|
|
for _, link := range data.Links {
|
|
|
|
refs = append(refs, &gen.SpanRef{
|
|
|
|
TraceIdHigh: int64(link.TraceID.High),
|
|
|
|
TraceIdLow: int64(link.TraceID.Low),
|
|
|
|
SpanId: int64(link.SpanID),
|
|
|
|
// TODO(paivagustavo): properly set the reference type when specs are defined
|
|
|
|
// see https://github.com/open-telemetry/opentelemetry-specification/issues/65
|
|
|
|
RefType: gen.SpanRefType_CHILD_OF,
|
|
|
|
})
|
|
|
|
}
|
2019-09-09 23:59:39 +02:00
|
|
|
|
|
|
|
return &gen.Span{
|
|
|
|
TraceIdHigh: int64(data.SpanContext.TraceID.High),
|
|
|
|
TraceIdLow: int64(data.SpanContext.TraceID.Low),
|
|
|
|
SpanId: int64(data.SpanContext.SpanID),
|
|
|
|
ParentSpanId: int64(data.ParentSpanID),
|
|
|
|
OperationName: data.Name, // TODO: if span kind is added then add prefix "Sent"/"Recv"
|
2019-09-25 23:37:36 +02:00
|
|
|
Flags: int32(data.SpanContext.TraceFlags),
|
2019-09-09 23:59:39 +02:00
|
|
|
StartTime: data.StartTime.UnixNano() / 1000,
|
|
|
|
Duration: data.EndTime.Sub(data.StartTime).Nanoseconds() / 1000,
|
|
|
|
Tags: tags,
|
|
|
|
Logs: logs,
|
2019-10-07 18:40:36 +02:00
|
|
|
References: refs,
|
2019-09-09 23:59:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-25 22:22:33 +02:00
|
|
|
func coreAttributeToTag(kv core.KeyValue) *gen.Tag {
|
|
|
|
var tag *gen.Tag
|
|
|
|
switch kv.Value.Type {
|
|
|
|
case core.STRING:
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: kv.Key.Name,
|
|
|
|
VStr: &kv.Value.String,
|
|
|
|
VType: gen.TagType_STRING,
|
|
|
|
}
|
|
|
|
case core.BOOL:
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: kv.Key.Name,
|
|
|
|
VBool: &kv.Value.Bool,
|
|
|
|
VType: gen.TagType_BOOL,
|
|
|
|
}
|
|
|
|
case core.INT32, core.INT64:
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: kv.Key.Name,
|
|
|
|
VLong: &kv.Value.Int64,
|
|
|
|
VType: gen.TagType_LONG,
|
|
|
|
}
|
|
|
|
case core.FLOAT32, core.FLOAT64:
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: kv.Key.Name,
|
|
|
|
VDouble: &kv.Value.Float64,
|
|
|
|
VType: gen.TagType_DOUBLE,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tag
|
|
|
|
}
|
|
|
|
|
|
|
|
func getInt64Tag(k string, i int64) *gen.Tag {
|
|
|
|
return &gen.Tag{
|
|
|
|
Key: k,
|
|
|
|
VLong: &i,
|
|
|
|
VType: gen.TagType_LONG,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getStringTag(k, s string) *gen.Tag {
|
|
|
|
return &gen.Tag{
|
|
|
|
Key: k,
|
|
|
|
VStr: &s,
|
|
|
|
VType: gen.TagType_STRING,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getBoolTag(k string, b bool) *gen.Tag {
|
|
|
|
return &gen.Tag{
|
|
|
|
Key: k,
|
|
|
|
VBool: &b,
|
|
|
|
VType: gen.TagType_BOOL,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-09 23:59:39 +02:00
|
|
|
// TODO(rghetia): remove interface{}. see https://github.com/open-telemetry/opentelemetry-go/pull/112/files#r321444786
|
|
|
|
func attributeToTag(key string, a interface{}) *gen.Tag {
|
|
|
|
var tag *gen.Tag
|
|
|
|
switch value := a.(type) {
|
|
|
|
case bool:
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: key,
|
|
|
|
VBool: &value,
|
|
|
|
VType: gen.TagType_BOOL,
|
|
|
|
}
|
|
|
|
case string:
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: key,
|
|
|
|
VStr: &value,
|
|
|
|
VType: gen.TagType_STRING,
|
|
|
|
}
|
|
|
|
case int64:
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: key,
|
|
|
|
VLong: &value,
|
|
|
|
VType: gen.TagType_LONG,
|
|
|
|
}
|
|
|
|
case int32:
|
|
|
|
v := int64(value)
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: key,
|
|
|
|
VLong: &v,
|
|
|
|
VType: gen.TagType_LONG,
|
|
|
|
}
|
|
|
|
case float64:
|
|
|
|
v := float64(value)
|
|
|
|
tag = &gen.Tag{
|
|
|
|
Key: key,
|
|
|
|
VDouble: &v,
|
|
|
|
VType: gen.TagType_DOUBLE,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tag
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flush waits for exported trace spans to be uploaded.
|
|
|
|
//
|
|
|
|
// This is useful if your program is ending and you do not want to lose recent spans.
|
|
|
|
func (e *Exporter) Flush() {
|
|
|
|
e.bundler.Flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Exporter) upload(spans []*gen.Span) error {
|
|
|
|
batch := &gen.Batch{
|
|
|
|
Spans: spans,
|
|
|
|
Process: e.process,
|
|
|
|
}
|
|
|
|
|
2019-10-04 21:07:42 +02:00
|
|
|
return e.uploader.upload(batch)
|
2019-09-09 23:59:39 +02:00
|
|
|
}
|