mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-03-23 21:19:35 +02:00
Golang opentelemetry-go draft API (incomplete) (#9)
* Work in progress from https://github.com/lightstep/opentelemetry-golang-prototype * Renames * Rename * Finish rename * Rename packages * README
This commit is contained in:
parent
1429272864
commit
e17f4468a6
20
README.md
Normal file
20
README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
This is a prototype *intended to be modified* into the opentelemetry-go implementation. The `api` directory here should be used as a starting point to introduce a new OpenTelemetry exporter, wherease the existing `exporter/observer` streaming model should be help verify the api
|
||||||
|
|
||||||
|
To run the examples, first build the stderr tracer plugin (requires Linux or OS X):
|
||||||
|
|
||||||
|
```
|
||||||
|
(cd ./exporter/stdout/plugin && make)
|
||||||
|
(cd ./exporter/spanlog/plugin && make)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set the `OPENTELEMETRY_LIB` environment variable to the .so file in that directory, e.g.,
|
||||||
|
|
||||||
|
```
|
||||||
|
OPENTELEMETRY_LIB=./exporter/stderr/plugin/stderr.so go run ./example/server/server.go
|
||||||
|
```
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
```
|
||||||
|
OPENTELEMETRY_LIB=./exporter/spanlog/plugin/spanlog.so go run ./example/client/client.go
|
||||||
|
```
|
179
api/core/core.go
Normal file
179
api/core/core.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/unit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ScopeID struct {
|
||||||
|
EventID
|
||||||
|
SpanContext
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanContext struct {
|
||||||
|
TraceIDHigh uint64
|
||||||
|
TraceIDLow uint64
|
||||||
|
SpanID uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
EventID uint64
|
||||||
|
|
||||||
|
BaseMeasure interface {
|
||||||
|
Name() string
|
||||||
|
Description() string
|
||||||
|
Unit() unit.Unit
|
||||||
|
|
||||||
|
DefinitionID() EventID
|
||||||
|
}
|
||||||
|
|
||||||
|
Measure interface {
|
||||||
|
BaseMeasure
|
||||||
|
|
||||||
|
M(float64) Measurement
|
||||||
|
V(float64) KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
Measurement struct {
|
||||||
|
// NOTE: If we add a ScopeID field this can carry
|
||||||
|
// pre-aggregated measures via the stats.Record API.
|
||||||
|
Measure Measure
|
||||||
|
Value float64
|
||||||
|
ScopeID ScopeID
|
||||||
|
}
|
||||||
|
|
||||||
|
Key interface {
|
||||||
|
BaseMeasure
|
||||||
|
|
||||||
|
Value(ctx context.Context) KeyValue
|
||||||
|
|
||||||
|
Bool(v bool) KeyValue
|
||||||
|
|
||||||
|
Int(v int) KeyValue
|
||||||
|
Int32(v int32) KeyValue
|
||||||
|
Int64(v int64) KeyValue
|
||||||
|
|
||||||
|
Uint(v uint) KeyValue
|
||||||
|
Uint32(v uint32) KeyValue
|
||||||
|
Uint64(v uint64) KeyValue
|
||||||
|
|
||||||
|
Float32(v float32) KeyValue
|
||||||
|
Float64(v float64) KeyValue
|
||||||
|
|
||||||
|
String(v string) KeyValue
|
||||||
|
Bytes(v []byte) KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValue struct {
|
||||||
|
Key Key
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueType int
|
||||||
|
|
||||||
|
Value struct {
|
||||||
|
Type ValueType
|
||||||
|
Bool bool
|
||||||
|
Int64 int64
|
||||||
|
Uint64 uint64
|
||||||
|
Float64 float64
|
||||||
|
String string
|
||||||
|
Bytes []byte
|
||||||
|
|
||||||
|
// TODO Lazy value type?
|
||||||
|
}
|
||||||
|
|
||||||
|
MutatorOp int
|
||||||
|
|
||||||
|
Mutator struct {
|
||||||
|
MutatorOp
|
||||||
|
KeyValue
|
||||||
|
MeasureMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
MeasureMetadata struct {
|
||||||
|
MaxHops int // -1 == infinite, 0 == do not propagate
|
||||||
|
|
||||||
|
// TODO time to live?
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
INVALID ValueType = iota
|
||||||
|
BOOL
|
||||||
|
INT32
|
||||||
|
INT64
|
||||||
|
UINT32
|
||||||
|
UINT64
|
||||||
|
FLOAT32
|
||||||
|
FLOAT64
|
||||||
|
STRING
|
||||||
|
BYTES
|
||||||
|
|
||||||
|
INSERT MutatorOp = iota
|
||||||
|
UPDATE
|
||||||
|
UPSERT
|
||||||
|
DELETE
|
||||||
|
)
|
||||||
|
|
||||||
|
func (sc SpanContext) HasTraceID() bool {
|
||||||
|
return sc.TraceIDHigh != 0 || sc.TraceIDLow != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc SpanContext) HasSpanID() bool {
|
||||||
|
return sc.SpanID != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc SpanContext) SpanIDString() string {
|
||||||
|
p := fmt.Sprintf("%.16x", sc.SpanID)
|
||||||
|
return p[0:3] + ".." + p[13:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc SpanContext) TraceIDString() string {
|
||||||
|
p1 := fmt.Sprintf("%.16x", sc.TraceIDHigh)
|
||||||
|
p2 := fmt.Sprintf("%.16x", sc.TraceIDLow)
|
||||||
|
return p1[0:3] + ".." + p2[13:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this a lazy one-time conversion.
|
||||||
|
func (v Value) Emit() string {
|
||||||
|
switch v.Type {
|
||||||
|
case BOOL:
|
||||||
|
return fmt.Sprint(v.Bool)
|
||||||
|
case INT32, INT64:
|
||||||
|
return fmt.Sprint(v.Int64)
|
||||||
|
case UINT32, UINT64:
|
||||||
|
return fmt.Sprint(v.Uint64)
|
||||||
|
case FLOAT32, FLOAT64:
|
||||||
|
return fmt.Sprint(v.Float64)
|
||||||
|
case STRING:
|
||||||
|
return v.String
|
||||||
|
case BYTES:
|
||||||
|
return string(v.Bytes)
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Mutator) WithMaxHops(hops int) Mutator {
|
||||||
|
m.MaxHops = hops
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e EventID) Scope() ScopeID {
|
||||||
|
return ScopeID{
|
||||||
|
EventID: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SpanContext) Scope() ScopeID {
|
||||||
|
return ScopeID{
|
||||||
|
SpanContext: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Measurement) With(id ScopeID) Measurement {
|
||||||
|
m.ScopeID = id
|
||||||
|
return m
|
||||||
|
}
|
52
api/log/log.go
Normal file
52
api/log/log.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/scope"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Interface interface {
|
||||||
|
Log(ctx context.Context, msg string, fields ...core.KeyValue)
|
||||||
|
Logf(ctx context.Context, fmt string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger struct {
|
||||||
|
scope.Scope
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func With(scope scope.Scope) Logger {
|
||||||
|
return Logger{scope}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Log(ctx context.Context, msg string, fields ...core.KeyValue) {
|
||||||
|
With(scope.Active(ctx)).Log(ctx, msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Logf(ctx context.Context, fmt string, args ...interface{}) {
|
||||||
|
With(scope.Active(ctx)).Logf(ctx, fmt, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Log(ctx context.Context, msg string, fields ...core.KeyValue) {
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.LOG_EVENT,
|
||||||
|
Scope: l.ScopeID(),
|
||||||
|
String: msg,
|
||||||
|
Attributes: fields,
|
||||||
|
Context: ctx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Logger) Logf(ctx context.Context, fmt string, args ...interface{}) {
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.LOGF_EVENT,
|
||||||
|
Scope: l.ScopeID(),
|
||||||
|
String: fmt,
|
||||||
|
Arguments: args,
|
||||||
|
Context: ctx,
|
||||||
|
})
|
||||||
|
}
|
60
api/metric/api.go
Normal file
60
api/metric/api.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/unit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Metric interface {
|
||||||
|
Measure() core.Measure
|
||||||
|
|
||||||
|
DefinitionID() core.EventID
|
||||||
|
|
||||||
|
Type() MetricType
|
||||||
|
Fields() []core.Key
|
||||||
|
Err() error
|
||||||
|
|
||||||
|
base() *baseMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
MetricType int
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Invalid MetricType = iota
|
||||||
|
GaugeInt64
|
||||||
|
GaugeFloat64
|
||||||
|
DerivedGaugeInt64
|
||||||
|
DerivedGaugeFloat64
|
||||||
|
CumulativeInt64
|
||||||
|
CumulativeFloat64
|
||||||
|
DerivedCumulativeInt64
|
||||||
|
DerivedCumulativeFloat64
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Option func(*baseMetric, *[]tag.Option)
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithDescription applies provided description.
|
||||||
|
func WithDescription(desc string) Option {
|
||||||
|
return func(_ *baseMetric, to *[]tag.Option) {
|
||||||
|
*to = append(*to, tag.WithDescription(desc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUnit applies provided unit.
|
||||||
|
func WithUnit(unit unit.Unit) Option {
|
||||||
|
return func(_ *baseMetric, to *[]tag.Option) {
|
||||||
|
*to = append(*to, tag.WithUnit(unit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKeys applies the provided dimension keys.
|
||||||
|
func WithKeys(keys ...core.Key) Option {
|
||||||
|
return func(bm *baseMetric, _ *[]tag.Option) {
|
||||||
|
bm.keys = keys
|
||||||
|
}
|
||||||
|
}
|
74
api/metric/common.go
Normal file
74
api/metric/common.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/scope"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
baseMetric struct {
|
||||||
|
measure core.Measure
|
||||||
|
|
||||||
|
mtype MetricType
|
||||||
|
keys []core.Key
|
||||||
|
eventID core.EventID
|
||||||
|
status error // Indicates registry conflict
|
||||||
|
}
|
||||||
|
|
||||||
|
baseEntry struct {
|
||||||
|
base *baseMetric
|
||||||
|
metric Metric
|
||||||
|
eventID core.EventID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func initBaseMetric(name string, mtype MetricType, opts []Option, init Metric) Metric {
|
||||||
|
var tagOpts []tag.Option
|
||||||
|
bm := init.base()
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(bm, &tagOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
bm.measure = tag.NewMeasure(name, tagOpts...)
|
||||||
|
bm.mtype = mtype
|
||||||
|
|
||||||
|
bm.eventID = observer.Record(observer.Event{
|
||||||
|
Type: observer.NEW_METRIC,
|
||||||
|
Scope: bm.measure.DefinitionID().Scope(),
|
||||||
|
})
|
||||||
|
|
||||||
|
other, err := GetRegistry().RegisterMetric(init)
|
||||||
|
if err != nil {
|
||||||
|
bm.status = err
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *baseMetric) base() *baseMetric {
|
||||||
|
return bm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *baseMetric) Measure() core.Measure {
|
||||||
|
return bm.measure
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *baseMetric) Type() MetricType {
|
||||||
|
return bm.mtype
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *baseMetric) Fields() []core.Key {
|
||||||
|
return bm.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm *baseMetric) Err() error {
|
||||||
|
return bm.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *baseEntry) init(m Metric, values []core.KeyValue) {
|
||||||
|
e.base = m.base()
|
||||||
|
e.metric = m
|
||||||
|
e.eventID = scope.New(core.ScopeID{}, values...).ScopeID().EventID
|
||||||
|
}
|
39
api/metric/gauge.go
Normal file
39
api/metric/gauge.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Float64Gauge struct {
|
||||||
|
baseMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
Float64Entry struct {
|
||||||
|
baseEntry
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFloat64Gauge(name string, mos ...Option) *Float64Gauge {
|
||||||
|
m := initBaseMetric(name, GaugeFloat64, mos, &Float64Gauge{}).(*Float64Gauge)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Float64Gauge) Gauge(values ...core.KeyValue) Float64Entry {
|
||||||
|
var entry Float64Entry
|
||||||
|
entry.init(g, values)
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Float64Gauge) DefinitionID() core.EventID {
|
||||||
|
return g.eventID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g Float64Entry) Set(ctx context.Context, val float64) {
|
||||||
|
stats.Record(ctx, g.base.measure.M(val).With(core.ScopeID{
|
||||||
|
EventID: g.eventID,
|
||||||
|
}))
|
||||||
|
}
|
78
api/metric/registry.go
Normal file
78
api/metric/registry.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2018, OpenCensus 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 metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Registry is a mechanism for avoiding duplicate registration
|
||||||
|
// of different-type pre-aggregated metrics (in one process).
|
||||||
|
Registry interface {
|
||||||
|
RegisterMetric(Metric) (Metric, error)
|
||||||
|
ForeachMetric(func(string, Metric))
|
||||||
|
}
|
||||||
|
|
||||||
|
registry struct {
|
||||||
|
nameType sync.Map // map[string]Metric
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
registryLock sync.Mutex
|
||||||
|
registryGlobal Registry = ®istry{}
|
||||||
|
|
||||||
|
errDuplicateMetricTypeConflict = errors.New("Duplicate metric registration with conflicting type")
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetRegistry may be used to reset the global metric registry, which should not be
|
||||||
|
// needed unless for testing purposes.
|
||||||
|
func SetRegistry(r Registry) {
|
||||||
|
registryLock.Lock()
|
||||||
|
defer registryLock.Unlock()
|
||||||
|
registryGlobal = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegistry may be used to access a global list of metric definitions.
|
||||||
|
func GetRegistry() Registry {
|
||||||
|
registryLock.Lock()
|
||||||
|
defer registryLock.Unlock()
|
||||||
|
return registryGlobal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registry) RegisterMetric(newMet Metric) (Metric, error) {
|
||||||
|
name := newMet.Measure().Name()
|
||||||
|
has, ok := r.nameType.Load(name)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
m := has.(Metric)
|
||||||
|
if m.Type() != newMet.Type() {
|
||||||
|
return nil, errDuplicateMetricTypeConflict
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.nameType.Store(name, newMet)
|
||||||
|
return newMet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registry) ForeachMetric(f func(string, Metric)) {
|
||||||
|
r.nameType.Range(func(key, value interface{}) bool {
|
||||||
|
f(key.(string), value.(Metric))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
67
api/scope/scope.go
Normal file
67
api/scope/scope.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package scope
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Scope interface {
|
||||||
|
ScopeID() core.ScopeID
|
||||||
|
}
|
||||||
|
|
||||||
|
Mutable interface {
|
||||||
|
Scope
|
||||||
|
|
||||||
|
SetAttribute(core.KeyValue)
|
||||||
|
SetAttributes(...core.KeyValue)
|
||||||
|
|
||||||
|
ModifyAttribute(core.Mutator)
|
||||||
|
ModifyAttributes(...core.Mutator)
|
||||||
|
}
|
||||||
|
|
||||||
|
scopeIdent struct {
|
||||||
|
id core.ScopeID
|
||||||
|
}
|
||||||
|
|
||||||
|
scopeKeyType struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
scopeKey = &scopeKeyType{}
|
||||||
|
emptyScope = &scopeIdent{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetActive(ctx context.Context, scope Scope) context.Context {
|
||||||
|
return context.WithValue(ctx, scopeKey, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Active(ctx context.Context) Scope {
|
||||||
|
if scope, has := ctx.Value(scopeKey).(Scope); has {
|
||||||
|
return scope
|
||||||
|
}
|
||||||
|
return emptyScope
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scopeIdent) ScopeID() core.ScopeID {
|
||||||
|
if s == nil {
|
||||||
|
return core.ScopeID{}
|
||||||
|
}
|
||||||
|
return s.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(parent core.ScopeID, attributes ...core.KeyValue) Scope {
|
||||||
|
eventID := observer.Record(observer.Event{
|
||||||
|
Type: observer.NEW_SCOPE,
|
||||||
|
Scope: parent,
|
||||||
|
Attributes: attributes,
|
||||||
|
})
|
||||||
|
return &scopeIdent{
|
||||||
|
id: core.ScopeID{
|
||||||
|
EventID: eventID,
|
||||||
|
SpanContext: parent.SpanContext,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
50
api/stats/stats.go
Normal file
50
api/stats/stats.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package stats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/scope"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Interface interface {
|
||||||
|
Record(ctx context.Context, m ...core.Measurement)
|
||||||
|
RecordSingle(ctx context.Context, m core.Measurement)
|
||||||
|
}
|
||||||
|
|
||||||
|
Recorder struct {
|
||||||
|
core.ScopeID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func With(scope scope.Scope) Recorder {
|
||||||
|
return Recorder{scope.ScopeID()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Record(ctx context.Context, m ...core.Measurement) {
|
||||||
|
With(scope.Active(ctx)).Record(ctx, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecordSingle(ctx context.Context, m core.Measurement) {
|
||||||
|
With(scope.Active(ctx)).RecordSingle(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Recorder) Record(ctx context.Context, m ...core.Measurement) {
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.RECORD_STATS,
|
||||||
|
Scope: r.ScopeID,
|
||||||
|
Context: ctx,
|
||||||
|
Stats: m,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Recorder) RecordSingle(ctx context.Context, m core.Measurement) {
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.RECORD_STATS,
|
||||||
|
Scope: r.ScopeID,
|
||||||
|
Context: ctx,
|
||||||
|
Stat: m,
|
||||||
|
})
|
||||||
|
}
|
99
api/tag/api.go
Normal file
99
api/tag/api.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/unit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Map interface {
|
||||||
|
// TODO combine these four into a struct
|
||||||
|
Apply(a1 core.KeyValue, attributes []core.KeyValue, m1 core.Mutator, mutators []core.Mutator) Map
|
||||||
|
|
||||||
|
Value(core.Key) (core.Value, bool)
|
||||||
|
HasValue(core.Key) bool
|
||||||
|
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
Foreach(func(kv core.KeyValue) bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
Option func(*registeredKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
EmptyMap = NewMap(core.KeyValue{}, nil, core.Mutator{}, nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(name string, opts ...Option) core.Key { // TODO rename NewKey?
|
||||||
|
return register(name, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMeasure(name string, opts ...Option) core.Measure {
|
||||||
|
return measure{
|
||||||
|
rk: register(name, opts),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMap(a1 core.KeyValue, attributes []core.KeyValue, m1 core.Mutator, mutators []core.Mutator) Map {
|
||||||
|
var t tagMap
|
||||||
|
return t.Apply(a1, attributes, m1, mutators)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (input tagMap) Apply(a1 core.KeyValue, attributes []core.KeyValue, m1 core.Mutator, mutators []core.Mutator) Map {
|
||||||
|
m := make(tagMap, len(input)+len(attributes)+len(mutators))
|
||||||
|
for k, v := range input {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
if a1.Key != nil {
|
||||||
|
m[a1.Key] = tagContent{
|
||||||
|
value: a1.Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kv := range attributes {
|
||||||
|
m[kv.Key] = tagContent{
|
||||||
|
value: kv.Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m1.KeyValue.Key != nil {
|
||||||
|
m.apply(m1)
|
||||||
|
}
|
||||||
|
for _, mutator := range mutators {
|
||||||
|
m.apply(mutator)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMap(ctx context.Context, m Map) context.Context {
|
||||||
|
return context.WithValue(ctx, ctxTagsKey, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContext(ctx context.Context, mutators ...core.Mutator) context.Context {
|
||||||
|
return WithMap(ctx, FromContext(ctx).Apply(
|
||||||
|
core.KeyValue{}, nil,
|
||||||
|
core.Mutator{}, mutators,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromContext(ctx context.Context) Map {
|
||||||
|
if m, ok := ctx.Value(ctxTagsKey).(Map); ok {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return tagMap{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDescription applies provided description.
|
||||||
|
func WithDescription(desc string) Option {
|
||||||
|
return func(rk *registeredKey) {
|
||||||
|
rk.desc = desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUnit applies provided unit.
|
||||||
|
func WithUnit(unit unit.Unit) Option {
|
||||||
|
return func(rk *registeredKey) {
|
||||||
|
rk.unit = unit
|
||||||
|
}
|
||||||
|
}
|
111
api/tag/map.go
Normal file
111
api/tag/map.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
tagMap map[core.Key]tagContent
|
||||||
|
|
||||||
|
tagContent struct {
|
||||||
|
value core.Value
|
||||||
|
meta core.MeasureMetadata
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m tagMap) HasValue(k core.Key) bool {
|
||||||
|
_, has := m.Value(k)
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tagMap) Value(k core.Key) (core.Value, bool) {
|
||||||
|
entry, ok := m[k]
|
||||||
|
if !ok {
|
||||||
|
entry.value.Type = core.INVALID
|
||||||
|
}
|
||||||
|
return entry.value, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tagMap) apply(mutator core.Mutator) {
|
||||||
|
if m == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key := mutator.KeyValue.Key
|
||||||
|
content := tagContent{
|
||||||
|
value: mutator.KeyValue.Value,
|
||||||
|
meta: mutator.MeasureMetadata,
|
||||||
|
}
|
||||||
|
switch mutator.MutatorOp {
|
||||||
|
case core.INSERT:
|
||||||
|
if _, ok := m[key]; !ok {
|
||||||
|
m[key] = content
|
||||||
|
}
|
||||||
|
case core.UPDATE:
|
||||||
|
if _, ok := m[key]; ok {
|
||||||
|
m[key] = content
|
||||||
|
}
|
||||||
|
case core.UPSERT:
|
||||||
|
m[key] = content
|
||||||
|
case core.DELETE:
|
||||||
|
delete(m, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Insert(kv core.KeyValue) core.Mutator {
|
||||||
|
return core.Mutator{
|
||||||
|
MutatorOp: core.INSERT,
|
||||||
|
KeyValue: kv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Update(kv core.KeyValue) core.Mutator {
|
||||||
|
return core.Mutator{
|
||||||
|
MutatorOp: core.UPDATE,
|
||||||
|
KeyValue: kv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Upsert(kv core.KeyValue) core.Mutator {
|
||||||
|
return core.Mutator{
|
||||||
|
MutatorOp: core.UPSERT,
|
||||||
|
KeyValue: kv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delete(k core.Key) core.Mutator {
|
||||||
|
return core.Mutator{
|
||||||
|
MutatorOp: core.DELETE,
|
||||||
|
KeyValue: core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: the golang pprof.Do API forces this memory allocation, we
|
||||||
|
// should file an issue about that. (There's a TODO in the source.)
|
||||||
|
func Do(ctx context.Context, f func(ctx context.Context)) {
|
||||||
|
m := FromContext(ctx).(tagMap)
|
||||||
|
keyvals := make([]string, 0, 2*len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
keyvals = append(keyvals, k.Name(), v.value.Emit())
|
||||||
|
}
|
||||||
|
pprof.Do(ctx, pprof.Labels(keyvals...), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tagMap) Foreach(f func(kv core.KeyValue) bool) {
|
||||||
|
for k, v := range m {
|
||||||
|
if !f(core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: v.value,
|
||||||
|
}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tagMap) Len() int {
|
||||||
|
return len(m)
|
||||||
|
}
|
200
api/tag/tag.go
Normal file
200
api/tag/tag.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/unit"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
registeredKey struct {
|
||||||
|
name string
|
||||||
|
desc string
|
||||||
|
unit unit.Unit
|
||||||
|
eventID core.EventID
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxTagsType struct{}
|
||||||
|
|
||||||
|
measure struct {
|
||||||
|
rk *registeredKey
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctxTagsKey = &ctxTagsType{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func register(name string, opts []Option) *registeredKey {
|
||||||
|
rk := ®isteredKey{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
for _, of := range opts {
|
||||||
|
of(rk)
|
||||||
|
}
|
||||||
|
rk.eventID = observer.Record(observer.Event{
|
||||||
|
Type: observer.NEW_MEASURE,
|
||||||
|
String: name,
|
||||||
|
// TODO desc, unit
|
||||||
|
})
|
||||||
|
return rk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Name() string {
|
||||||
|
if k == nil {
|
||||||
|
return "unregistered"
|
||||||
|
}
|
||||||
|
return k.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Description() string {
|
||||||
|
if k == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return k.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Unit() unit.Unit {
|
||||||
|
if k == nil {
|
||||||
|
return unit.Dimensionless
|
||||||
|
}
|
||||||
|
return k.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) DefinitionID() core.EventID {
|
||||||
|
if k == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return k.eventID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Bool(v bool) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.BOOL,
|
||||||
|
Bool: v,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Int64(v int64) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.INT64,
|
||||||
|
Int64: v,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Uint64(v uint64) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.UINT64,
|
||||||
|
Uint64: v,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Float64(v float64) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.FLOAT64,
|
||||||
|
Float64: v,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Int32(v int32) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.INT32,
|
||||||
|
Int64: int64(v),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Uint32(v uint32) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.UINT32,
|
||||||
|
Uint64: uint64(v),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Float32(v float32) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.FLOAT32,
|
||||||
|
Float64: float64(v),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) String(v string) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.STRING,
|
||||||
|
String: v,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Bytes(v []byte) core.KeyValue {
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: core.Value{
|
||||||
|
Type: core.BYTES,
|
||||||
|
Bytes: v,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Int(v int) core.KeyValue {
|
||||||
|
if unsafe.Sizeof(v) == 4 {
|
||||||
|
return k.Int32(int32(v))
|
||||||
|
}
|
||||||
|
return k.Int64(int64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Uint(v uint) core.KeyValue {
|
||||||
|
if unsafe.Sizeof(v) == 4 {
|
||||||
|
return k.Uint32(uint32(v))
|
||||||
|
}
|
||||||
|
return k.Uint64(uint64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *registeredKey) Value(ctx context.Context) core.KeyValue {
|
||||||
|
v, _ := FromContext(ctx).Value(k)
|
||||||
|
return core.KeyValue{
|
||||||
|
Key: k,
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m measure) M(v float64) core.Measurement {
|
||||||
|
return core.Measurement{
|
||||||
|
Measure: m,
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m measure) V(v float64) core.KeyValue {
|
||||||
|
return m.rk.Float64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m measure) Name() string { return m.rk.Name() }
|
||||||
|
func (m measure) Description() string { return m.rk.Description() }
|
||||||
|
func (m measure) Unit() unit.Unit { return m.rk.Unit() }
|
||||||
|
func (m measure) DefinitionID() core.EventID { return m.rk.DefinitionID() }
|
144
api/trace/api.go
Normal file
144
api/trace/api.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/log"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/scope"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/stats"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Tracer interface {
|
||||||
|
Start(context.Context, string, ...Option) (context.Context, Span)
|
||||||
|
|
||||||
|
WithSpan(
|
||||||
|
ctx context.Context,
|
||||||
|
operation string,
|
||||||
|
body func(ctx context.Context) error,
|
||||||
|
) error
|
||||||
|
|
||||||
|
WithService(name string) Tracer
|
||||||
|
WithComponent(name string) Tracer
|
||||||
|
WithResources(res ...core.KeyValue) Tracer
|
||||||
|
|
||||||
|
// Note: see https://github.com/opentracing/opentracing-go/issues/127
|
||||||
|
Inject(context.Context, Span, Injector)
|
||||||
|
|
||||||
|
// ScopeID returns the resource scope of this tracer.
|
||||||
|
scope.Scope
|
||||||
|
}
|
||||||
|
|
||||||
|
Span interface {
|
||||||
|
scope.Mutable
|
||||||
|
|
||||||
|
log.Interface
|
||||||
|
|
||||||
|
stats.Interface
|
||||||
|
|
||||||
|
SetError(bool)
|
||||||
|
|
||||||
|
Tracer() Tracer
|
||||||
|
|
||||||
|
Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
Injector interface {
|
||||||
|
Inject(core.SpanContext, tag.Map)
|
||||||
|
}
|
||||||
|
|
||||||
|
Option struct {
|
||||||
|
attribute core.KeyValue
|
||||||
|
attributes []core.KeyValue
|
||||||
|
startTime time.Time
|
||||||
|
reference Reference
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference struct {
|
||||||
|
core.SpanContext
|
||||||
|
RelationshipType
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationshipType int
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChildOfRelationship RelationshipType = iota
|
||||||
|
FollowsFromRelationship
|
||||||
|
)
|
||||||
|
|
||||||
|
func GlobalTracer() Tracer {
|
||||||
|
if t := global.Load(); t != nil {
|
||||||
|
return t.(Tracer)
|
||||||
|
}
|
||||||
|
return empty
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetGlobalTracer(t Tracer) {
|
||||||
|
global.Store(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(ctx context.Context, name string, opts ...Option) (context.Context, Span) {
|
||||||
|
return GlobalTracer().Start(ctx, name, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Active(ctx context.Context) Span {
|
||||||
|
span, _ := scope.Active(ctx).(*span)
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSpan(ctx context.Context, name string, body func(context.Context) error) error {
|
||||||
|
return GlobalTracer().WithSpan(ctx, name, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetError(ctx context.Context, v bool) {
|
||||||
|
Active(ctx).SetError(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Inject(ctx context.Context, injector Injector) {
|
||||||
|
span := Active(ctx)
|
||||||
|
if span == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
span.Tracer().Inject(ctx, span, injector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithStartTime(t time.Time) Option {
|
||||||
|
return Option{
|
||||||
|
startTime: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAttributes(attrs ...core.KeyValue) Option {
|
||||||
|
return Option{
|
||||||
|
attributes: attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAttribute(attr core.KeyValue) Option {
|
||||||
|
return Option{
|
||||||
|
attribute: attr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChildOf(sc core.SpanContext) Option {
|
||||||
|
return Option{
|
||||||
|
reference: Reference{
|
||||||
|
SpanContext: sc,
|
||||||
|
RelationshipType: ChildOfRelationship,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FollowsFrom(sc core.SpanContext) Option {
|
||||||
|
return Option{
|
||||||
|
reference: Reference{
|
||||||
|
SpanContext: sc,
|
||||||
|
RelationshipType: FollowsFromRelationship,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
138
api/trace/span.go
Normal file
138
api/trace/span.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/log"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/stats"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (sp *span) ScopeID() core.ScopeID {
|
||||||
|
if sp == nil {
|
||||||
|
return core.ScopeID{}
|
||||||
|
}
|
||||||
|
sp.lock.Lock()
|
||||||
|
sid := core.ScopeID{
|
||||||
|
EventID: sp.eventID,
|
||||||
|
SpanContext: sp.spanContext,
|
||||||
|
}
|
||||||
|
sp.lock.Unlock()
|
||||||
|
return sid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) updateScope() (core.ScopeID, core.EventID) {
|
||||||
|
next := observer.NextEventID()
|
||||||
|
|
||||||
|
sp.lock.Lock()
|
||||||
|
sid := core.ScopeID{
|
||||||
|
EventID: sp.eventID,
|
||||||
|
SpanContext: sp.spanContext,
|
||||||
|
}
|
||||||
|
sp.eventID = next
|
||||||
|
sp.lock.Unlock()
|
||||||
|
|
||||||
|
return sid, next
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) SetError(v bool) {
|
||||||
|
sp.SetAttribute(ErrorKey.Bool(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) SetAttribute(attribute core.KeyValue) {
|
||||||
|
if sp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid, next := sp.updateScope()
|
||||||
|
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.MODIFY_ATTR,
|
||||||
|
Scope: sid,
|
||||||
|
Sequence: next,
|
||||||
|
Attribute: attribute,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) SetAttributes(attributes ...core.KeyValue) {
|
||||||
|
if sp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid, next := sp.updateScope()
|
||||||
|
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.MODIFY_ATTR,
|
||||||
|
Scope: sid,
|
||||||
|
Sequence: next,
|
||||||
|
Attributes: attributes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) ModifyAttribute(mutator core.Mutator) {
|
||||||
|
if sp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid, next := sp.updateScope()
|
||||||
|
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.MODIFY_ATTR,
|
||||||
|
Scope: sid,
|
||||||
|
Sequence: next,
|
||||||
|
Mutator: mutator,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) ModifyAttributes(mutators ...core.Mutator) {
|
||||||
|
if sp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid, next := sp.updateScope()
|
||||||
|
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.MODIFY_ATTR,
|
||||||
|
Scope: sid,
|
||||||
|
Sequence: next,
|
||||||
|
Mutators: mutators,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) Finish() {
|
||||||
|
if sp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recovered := recover()
|
||||||
|
sp.finishOnce.Do(func() {
|
||||||
|
observer.Record(observer.Event{
|
||||||
|
Type: observer.FINISH_SPAN,
|
||||||
|
Scope: sp.ScopeID(),
|
||||||
|
Recovered: recovered,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if recovered != nil {
|
||||||
|
panic(recovered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) Tracer() Tracer {
|
||||||
|
return sp.tracer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) Log(ctx context.Context, msg string, args ...core.KeyValue) {
|
||||||
|
log.With(sp).Log(ctx, msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) Logf(ctx context.Context, fmt string, args ...interface{}) {
|
||||||
|
log.With(sp).Logf(ctx, fmt, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) Record(ctx context.Context, m ...core.Measurement) {
|
||||||
|
stats.With(sp).Record(ctx, m...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *span) RecordSingle(ctx context.Context, m core.Measurement) {
|
||||||
|
stats.With(sp).RecordSingle(ctx, m)
|
||||||
|
}
|
145
api/trace/trace.go
Normal file
145
api/trace/trace.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/log"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/scope"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
span struct {
|
||||||
|
tracer *tracer
|
||||||
|
spanContext core.SpanContext
|
||||||
|
lock sync.Mutex
|
||||||
|
eventID core.EventID
|
||||||
|
finishOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
tracer struct {
|
||||||
|
resources core.EventID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ServiceKey = tag.New("service")
|
||||||
|
ComponentKey = tag.New("component")
|
||||||
|
ErrorKey = tag.New("error")
|
||||||
|
SpanIDKey = tag.New("span_id")
|
||||||
|
TraceIDKey = tag.New("trace_id")
|
||||||
|
ParentSpanIDKey = tag.New("parent_span_id")
|
||||||
|
MessageKey = tag.New("message",
|
||||||
|
tag.WithDescription("message text: info, error, etc"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// The process global tracer could have process-wide resource
|
||||||
|
// tags applied directly, or we can have a SetGlobal tracer to
|
||||||
|
// install a default tracer w/ resources.
|
||||||
|
global atomic.Value
|
||||||
|
empty = &tracer{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *tracer) ScopeID() core.ScopeID {
|
||||||
|
return t.resources.Scope()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) WithResources(attributes ...core.KeyValue) Tracer {
|
||||||
|
s := scope.New(t.resources.Scope(), attributes...)
|
||||||
|
return &tracer{
|
||||||
|
resources: s.ScopeID().EventID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *tracer) WithComponent(name string) Tracer {
|
||||||
|
return g.WithResources(ComponentKey.String(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *tracer) WithService(name string) Tracer {
|
||||||
|
return g.WithResources(ServiceKey.String(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) WithSpan(ctx context.Context, name string, body func(context.Context) error) error {
|
||||||
|
// TODO: use runtime/trace.WithRegion for execution tracer support
|
||||||
|
// TODO: use runtime/pprof.Do for profile tags support
|
||||||
|
ctx, span := t.Start(ctx, name)
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
if err := body(ctx); err != nil {
|
||||||
|
span.SetAttribute(ErrorKey.Bool(true))
|
||||||
|
log.Log(ctx, "span error", MessageKey.String(err.Error()))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) Start(ctx context.Context, name string, opts ...Option) (context.Context, Span) {
|
||||||
|
var child core.SpanContext
|
||||||
|
|
||||||
|
child.SpanID = rand.Uint64()
|
||||||
|
|
||||||
|
var startTime time.Time
|
||||||
|
var attributes []core.KeyValue
|
||||||
|
var reference Reference
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
if !opt.startTime.IsZero() {
|
||||||
|
startTime = opt.startTime
|
||||||
|
}
|
||||||
|
if len(opt.attributes) != 0 {
|
||||||
|
attributes = append(opt.attributes, attributes...)
|
||||||
|
}
|
||||||
|
if opt.attribute.Key != nil {
|
||||||
|
attributes = append(attributes, opt.attribute)
|
||||||
|
}
|
||||||
|
if opt.reference.HasTraceID() {
|
||||||
|
reference = opt.reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentScope core.ScopeID
|
||||||
|
|
||||||
|
if reference.HasTraceID() {
|
||||||
|
parentScope = reference.Scope()
|
||||||
|
} else {
|
||||||
|
parentScope = Active(ctx).ScopeID()
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentScope.HasTraceID() {
|
||||||
|
parent := parentScope.SpanContext
|
||||||
|
child.TraceIDHigh = parent.TraceIDHigh
|
||||||
|
child.TraceIDLow = parent.TraceIDLow
|
||||||
|
} else {
|
||||||
|
child.TraceIDHigh = rand.Uint64()
|
||||||
|
child.TraceIDLow = rand.Uint64()
|
||||||
|
}
|
||||||
|
|
||||||
|
childScope := core.ScopeID{
|
||||||
|
SpanContext: child,
|
||||||
|
EventID: t.resources,
|
||||||
|
}
|
||||||
|
|
||||||
|
span := &span{
|
||||||
|
spanContext: child,
|
||||||
|
tracer: t,
|
||||||
|
eventID: observer.Record(observer.Event{
|
||||||
|
Time: startTime,
|
||||||
|
Type: observer.START_SPAN,
|
||||||
|
Scope: scope.New(childScope, attributes...).ScopeID(),
|
||||||
|
Context: ctx,
|
||||||
|
Parent: parentScope,
|
||||||
|
String: name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
return scope.SetActive(ctx, span), span
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tracer) Inject(ctx context.Context, span Span, injector Injector) {
|
||||||
|
injector.Inject(span.ScopeID().SpanContext, tag.FromContext(ctx))
|
||||||
|
}
|
11
api/unit/unit.go
Normal file
11
api/unit/unit.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package unit
|
||||||
|
|
||||||
|
type (
|
||||||
|
Unit string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Dimensionless Unit = "1"
|
||||||
|
Bytes Unit = "By"
|
||||||
|
Milliseconds Unit = "ms"
|
||||||
|
)
|
78
example/example.go
Normal file
78
example/example.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/log"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/metric"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/stats"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tracer = trace.GlobalTracer().
|
||||||
|
WithComponent("example").
|
||||||
|
WithResources(
|
||||||
|
tag.New("whatevs").String("yesss"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fooKey = tag.New("ex.com/foo", tag.WithDescription("A Foo var"))
|
||||||
|
barKey = tag.New("ex.com/bar", tag.WithDescription("A Bar var"))
|
||||||
|
lemonsKey = tag.New("ex.com/lemons", tag.WithDescription("A Lemons var"))
|
||||||
|
anotherKey = tag.New("ex.com/another")
|
||||||
|
|
||||||
|
oneMetric = metric.NewFloat64Gauge("ex.com/one",
|
||||||
|
metric.WithKeys(fooKey, barKey, lemonsKey),
|
||||||
|
metric.WithDescription("A gauge set to 1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
measureTwo = tag.NewMeasure("ex.com/two")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ctx = tag.NewContext(ctx,
|
||||||
|
tag.Insert(fooKey.String("foo1")),
|
||||||
|
tag.Insert(barKey.String("bar1")),
|
||||||
|
)
|
||||||
|
|
||||||
|
gauge := oneMetric.Gauge(
|
||||||
|
fooKey.Value(ctx),
|
||||||
|
barKey.Value(ctx),
|
||||||
|
lemonsKey.Int(10),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
|
||||||
|
|
||||||
|
trace.SetError(ctx, true)
|
||||||
|
|
||||||
|
log.Log(ctx, "Nice operation!", tag.New("bogons").Int(100))
|
||||||
|
|
||||||
|
trace.Active(ctx).SetAttributes(anotherKey.String("yes"))
|
||||||
|
|
||||||
|
gauge.Set(ctx, 1)
|
||||||
|
|
||||||
|
return tracer.WithSpan(
|
||||||
|
ctx,
|
||||||
|
"Sub operation...",
|
||||||
|
func(ctx context.Context) error {
|
||||||
|
trace.Active(ctx).SetAttribute(lemonsKey.String("five"))
|
||||||
|
|
||||||
|
log.Logf(ctx, "Format schmormat %d!", 100)
|
||||||
|
|
||||||
|
stats.Record(ctx, measureTwo.M(1.3))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.Flush()
|
||||||
|
}
|
57
example/http/client/client.go
Normal file
57
example/http/client/client.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/plugin/httptrace"
|
||||||
|
|
||||||
|
_ "github.com/open-telemetry/opentelemetry-go/exporter/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tracer = trace.GlobalTracer().
|
||||||
|
WithService("client").
|
||||||
|
WithComponent("main").
|
||||||
|
WithResources(
|
||||||
|
tag.New("whatevs").String("yesss"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := http.DefaultClient
|
||||||
|
|
||||||
|
ctx := tag.NewContext(context.Background(),
|
||||||
|
tag.Insert(tag.New("username").String("donuts")),
|
||||||
|
)
|
||||||
|
|
||||||
|
var body []byte
|
||||||
|
|
||||||
|
err := tracer.WithSpan(ctx, "say hello",
|
||||||
|
func(ctx context.Context) error {
|
||||||
|
req, _ := http.NewRequest("GET", "http://localhost:7777/hello", nil)
|
||||||
|
|
||||||
|
ctx, req, inj := httptrace.W3C(ctx, req)
|
||||||
|
|
||||||
|
trace.Inject(ctx, inj)
|
||||||
|
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s", body)
|
||||||
|
}
|
7
example/http/server/modd.conf
Normal file
7
example/http/server/modd.conf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# A basic modd.conf file for Go development.
|
||||||
|
|
||||||
|
# Run go test on ALL modules on startup, and subsequently only on modules
|
||||||
|
# containing changes.
|
||||||
|
server.go {
|
||||||
|
daemon +sigterm: go run server.go
|
||||||
|
}
|
49
example/http/server/server.go
Normal file
49
example/http/server/server.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/log"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/plugin/httptrace"
|
||||||
|
|
||||||
|
_ "github.com/open-telemetry/opentelemetry-go/exporter/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tracer = trace.GlobalTracer().
|
||||||
|
WithService("server").
|
||||||
|
WithComponent("main").
|
||||||
|
WithResources(
|
||||||
|
tag.New("whatevs").String("nooooo"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
helloHandler := func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
attrs, tags, spanCtx := httptrace.Extract(req)
|
||||||
|
|
||||||
|
req = req.WithContext(tag.WithMap(req.Context(), tag.NewMap(core.KeyValue{}, tags, core.Mutator{}, nil)))
|
||||||
|
|
||||||
|
ctx, span := tracer.Start(
|
||||||
|
req.Context(),
|
||||||
|
"hello",
|
||||||
|
trace.WithAttributes(attrs...),
|
||||||
|
trace.ChildOf(spanCtx),
|
||||||
|
)
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
log.Log(ctx, "handling this...")
|
||||||
|
|
||||||
|
io.WriteString(w, "Hello, world!\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/hello", helloHandler)
|
||||||
|
err := http.ListenAndServe(":7777", nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
62
exporter/buffer/buffer.go
Normal file
62
exporter/buffer/buffer.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Buffer struct {
|
||||||
|
observers []observer.Observer
|
||||||
|
events chan observer.Event
|
||||||
|
dropped uint64
|
||||||
|
wait sync.WaitGroup
|
||||||
|
close chan struct{}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBuffer(size int, observers ...observer.Observer) *Buffer {
|
||||||
|
b := &Buffer{
|
||||||
|
observers: observers,
|
||||||
|
events: make(chan observer.Event, size),
|
||||||
|
close: make(chan struct{}),
|
||||||
|
}
|
||||||
|
b.wait.Add(1)
|
||||||
|
go b.run()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) Observe(data observer.Event) {
|
||||||
|
select {
|
||||||
|
case b.events <- data:
|
||||||
|
default:
|
||||||
|
atomic.AddUint64(&b.dropped, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) Close() {
|
||||||
|
close(b.close)
|
||||||
|
b.wait.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) run() {
|
||||||
|
defer func() {
|
||||||
|
_ = recover()
|
||||||
|
b.wait.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-b.close:
|
||||||
|
return
|
||||||
|
case ev := <-b.events:
|
||||||
|
// TODO: This has to ensure ordered arrival,
|
||||||
|
// e.g., put into a heap and delay observations.
|
||||||
|
for _, obs := range b.observers {
|
||||||
|
obs.Observe(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
exporter/loader/loader.go
Normal file
42
exporter/loader/loader.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package loader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"plugin"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO add buffer support directly, eliminate stdout
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pluginName := os.Getenv("OPENTELEMETRY_LIB")
|
||||||
|
if pluginName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sharedObj, err := plugin.Open(pluginName)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Open failed", pluginName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obsPlugin, err := sharedObj.Lookup("Observer")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Observer not found", pluginName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obs, ok := obsPlugin.(*observer.Observer)
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf("Observer not valid\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
observer.RegisterObserver(*obs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Flush() {
|
||||||
|
// TODO implement for exporter/{stdout,stderr,buffer}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
32
exporter/observer/eventtype_string.go
Normal file
32
exporter/observer/eventtype_string.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by "stringer -type=EventType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package observer
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[INVALID-0]
|
||||||
|
_ = x[START_SPAN-1]
|
||||||
|
_ = x[FINISH_SPAN-2]
|
||||||
|
_ = x[LOG_EVENT-3]
|
||||||
|
_ = x[LOGF_EVENT-4]
|
||||||
|
_ = x[NEW_SCOPE-5]
|
||||||
|
_ = x[NEW_MEASURE-6]
|
||||||
|
_ = x[NEW_METRIC-7]
|
||||||
|
_ = x[MODIFY_ATTR-8]
|
||||||
|
_ = x[RECORD_STATS-9]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _EventType_name = "INVALIDSTART_SPANFINISH_SPANLOG_EVENTLOGF_EVENTNEW_SCOPENEW_MEASURENEW_METRICMODIFY_ATTRRECORD_STATS"
|
||||||
|
|
||||||
|
var _EventType_index = [...]uint8{0, 7, 17, 28, 37, 47, 56, 67, 77, 88, 100}
|
||||||
|
|
||||||
|
func (i EventType) String() string {
|
||||||
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
return "EventType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _EventType_name[_EventType_index[i]:_EventType_index[i+1]]
|
||||||
|
}
|
126
exporter/observer/observer.go
Normal file
126
exporter/observer/observer.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package observer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
EventType int
|
||||||
|
|
||||||
|
Event struct {
|
||||||
|
// Automatic fields
|
||||||
|
Sequence core.EventID // Auto-filled
|
||||||
|
Time time.Time // Auto-filled
|
||||||
|
|
||||||
|
// Type, Scope, Context
|
||||||
|
Type EventType // All events
|
||||||
|
Scope core.ScopeID // All events
|
||||||
|
Context context.Context // core.FromContext() and scope.Active()
|
||||||
|
|
||||||
|
// Arguments (type-specific)
|
||||||
|
Attribute core.KeyValue // SET_ATTRIBUTE
|
||||||
|
Attributes []core.KeyValue // SET_ATTRIBUTES, LOG_EVENT
|
||||||
|
Mutator core.Mutator // SET_ATTRIBUTE
|
||||||
|
Mutators []core.Mutator // SET_ATTRIBUTES
|
||||||
|
Arguments []interface{} // LOGF_EVENT
|
||||||
|
Recovered interface{} // FINISH_SPAN
|
||||||
|
|
||||||
|
// Values
|
||||||
|
String string // START_SPAN, EVENT, ...
|
||||||
|
Float64 float64
|
||||||
|
Parent core.ScopeID // START_SPAN
|
||||||
|
Stats []core.Measurement
|
||||||
|
Stat core.Measurement
|
||||||
|
}
|
||||||
|
|
||||||
|
Observer interface {
|
||||||
|
Observe(data Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
observersMap map[Observer]struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate stringer -type=EventType
|
||||||
|
const (
|
||||||
|
// TODO: rename these NOUN_VERB
|
||||||
|
INVALID EventType = iota
|
||||||
|
START_SPAN
|
||||||
|
FINISH_SPAN
|
||||||
|
LOG_EVENT
|
||||||
|
LOGF_EVENT
|
||||||
|
NEW_SCOPE
|
||||||
|
NEW_MEASURE
|
||||||
|
NEW_METRIC
|
||||||
|
MODIFY_ATTR
|
||||||
|
RECORD_STATS
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
observerMu sync.Mutex
|
||||||
|
observers atomic.Value
|
||||||
|
|
||||||
|
sequenceNum uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
func NextEventID() core.EventID {
|
||||||
|
return core.EventID(atomic.AddUint64(&sequenceNum, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterObserver adds to the list of Observers that will receive sampled
|
||||||
|
// trace spans.
|
||||||
|
//
|
||||||
|
// Binaries can register observers, libraries shouldn't register observers.
|
||||||
|
func RegisterObserver(e Observer) {
|
||||||
|
observerMu.Lock()
|
||||||
|
new := make(observersMap)
|
||||||
|
if old, ok := observers.Load().(observersMap); ok {
|
||||||
|
for k, v := range old {
|
||||||
|
new[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new[e] = struct{}{}
|
||||||
|
observers.Store(new)
|
||||||
|
observerMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterObserver removes from the list of Observers the Observer that was
|
||||||
|
// registered with the given name.
|
||||||
|
func UnregisterObserver(e Observer) {
|
||||||
|
observerMu.Lock()
|
||||||
|
new := make(observersMap)
|
||||||
|
if old, ok := observers.Load().(observersMap); ok {
|
||||||
|
for k, v := range old {
|
||||||
|
new[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(new, e)
|
||||||
|
observers.Store(new)
|
||||||
|
observerMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Record(event Event) core.EventID {
|
||||||
|
if event.Sequence == 0 {
|
||||||
|
event.Sequence = NextEventID()
|
||||||
|
}
|
||||||
|
if event.Time.IsZero() {
|
||||||
|
event.Time = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
observers, _ := observers.Load().(observersMap)
|
||||||
|
for observer, _ := range observers {
|
||||||
|
observer.Observe(event)
|
||||||
|
}
|
||||||
|
return event.Sequence
|
||||||
|
}
|
||||||
|
|
||||||
|
func Foreach(f func(Observer)) {
|
||||||
|
observers, _ := observers.Load().(observersMap)
|
||||||
|
for observer, _ := range observers {
|
||||||
|
f(observer)
|
||||||
|
}
|
||||||
|
}
|
107
exporter/reader/format/format.go
Normal file
107
exporter/reader/format/format.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/reader"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AppendEvent(buf *strings.Builder, data reader.Event) {
|
||||||
|
|
||||||
|
f := func(skipIf bool) func(kv core.KeyValue) bool {
|
||||||
|
return func(kv core.KeyValue) bool {
|
||||||
|
if skipIf && data.Attributes.HasValue(kv.Key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
buf.WriteString(" " + kv.Key.Name() + "=" + kv.Value.Emit())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(data.Time.Format("2006/01/02 15-04-05.000000"))
|
||||||
|
buf.WriteString(" ")
|
||||||
|
|
||||||
|
switch data.Type {
|
||||||
|
case reader.START_SPAN:
|
||||||
|
buf.WriteString("start ")
|
||||||
|
buf.WriteString(data.Name)
|
||||||
|
|
||||||
|
if !data.Parent.HasSpanID() {
|
||||||
|
buf.WriteString(", a root span")
|
||||||
|
} else {
|
||||||
|
buf.WriteString(" <")
|
||||||
|
if data.Parent.HasSpanID() {
|
||||||
|
f(false)(trace.ParentSpanIDKey.String(data.SpanContext.SpanIDString()))
|
||||||
|
}
|
||||||
|
if data.ParentAttributes != nil {
|
||||||
|
data.ParentAttributes.Foreach(f(false))
|
||||||
|
}
|
||||||
|
buf.WriteString(" >")
|
||||||
|
}
|
||||||
|
|
||||||
|
case reader.FINISH_SPAN:
|
||||||
|
buf.WriteString("finish ")
|
||||||
|
buf.WriteString(data.Name)
|
||||||
|
|
||||||
|
buf.WriteString(" (")
|
||||||
|
buf.WriteString(data.Duration.String())
|
||||||
|
buf.WriteString(")")
|
||||||
|
|
||||||
|
case reader.LOG_EVENT:
|
||||||
|
buf.WriteString(data.Message)
|
||||||
|
|
||||||
|
case reader.LOGF_EVENT:
|
||||||
|
buf.WriteString(data.Message)
|
||||||
|
|
||||||
|
case reader.MODIFY_ATTR:
|
||||||
|
buf.WriteString("modify attr")
|
||||||
|
case reader.RECORD_STATS:
|
||||||
|
buf.WriteString("record")
|
||||||
|
|
||||||
|
for _, s := range data.Stats {
|
||||||
|
f(false)(s.Measure.V(s.Value))
|
||||||
|
|
||||||
|
buf.WriteString(" {")
|
||||||
|
i := 0
|
||||||
|
s.Tags.Foreach(func(kv core.KeyValue) bool {
|
||||||
|
if i != 0 {
|
||||||
|
buf.WriteString(",")
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
buf.WriteString(kv.Key.Name())
|
||||||
|
buf.WriteString("=")
|
||||||
|
buf.WriteString(kv.Value.Emit())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
buf.WriteString("}")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
buf.WriteString(fmt.Sprintf("WAT? %d", data.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the scope (span) attributes and context tags.
|
||||||
|
buf.WriteString(" [")
|
||||||
|
if data.Attributes != nil {
|
||||||
|
data.Attributes.Foreach(f(false))
|
||||||
|
}
|
||||||
|
if data.Tags != nil {
|
||||||
|
data.Tags.Foreach(f(true))
|
||||||
|
}
|
||||||
|
if data.SpanContext.HasSpanID() {
|
||||||
|
f(false)(trace.SpanIDKey.String(data.SpanContext.SpanIDString()))
|
||||||
|
}
|
||||||
|
if data.SpanContext.HasTraceID() {
|
||||||
|
f(false)(trace.TraceIDKey.String(data.SpanContext.TraceIDString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(" ]\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventToString(data reader.Event) string {
|
||||||
|
var buf strings.Builder
|
||||||
|
AppendEvent(&buf, data)
|
||||||
|
return buf.String()
|
||||||
|
}
|
332
exporter/reader/reader.go
Normal file
332
exporter/reader/reader.go
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
package reader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/metric"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/unit"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Reader interface {
|
||||||
|
Read(Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
EventType int
|
||||||
|
|
||||||
|
Event struct {
|
||||||
|
Type EventType
|
||||||
|
Time time.Time
|
||||||
|
Sequence core.EventID
|
||||||
|
SpanContext core.SpanContext
|
||||||
|
Tags tag.Map
|
||||||
|
Attributes tag.Map
|
||||||
|
Stats []Measurement
|
||||||
|
|
||||||
|
Parent core.SpanContext
|
||||||
|
ParentAttributes tag.Map
|
||||||
|
|
||||||
|
Duration time.Duration
|
||||||
|
Name string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
Measurement struct {
|
||||||
|
Measure core.Measure
|
||||||
|
Value float64
|
||||||
|
Tags tag.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
readerObserver struct {
|
||||||
|
readers []Reader
|
||||||
|
|
||||||
|
// core.EventID -> *readerSpan or *readerScope
|
||||||
|
scopes sync.Map
|
||||||
|
|
||||||
|
// core.EventID -> *readerMeasure
|
||||||
|
measures sync.Map
|
||||||
|
|
||||||
|
// core.EventID -> *readerMetric
|
||||||
|
metrics sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
readerSpan struct {
|
||||||
|
name string
|
||||||
|
start time.Time
|
||||||
|
startTags tag.Map
|
||||||
|
spanContext core.SpanContext
|
||||||
|
|
||||||
|
*readerScope
|
||||||
|
}
|
||||||
|
|
||||||
|
readerMeasure struct {
|
||||||
|
name string
|
||||||
|
desc string
|
||||||
|
unit unit.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
readerMetric struct {
|
||||||
|
*readerMeasure
|
||||||
|
mtype metric.MetricType
|
||||||
|
fields []core.Measure
|
||||||
|
}
|
||||||
|
|
||||||
|
readerScope struct {
|
||||||
|
span *readerSpan
|
||||||
|
parent core.EventID
|
||||||
|
attributes tag.Map
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
INVALID EventType = iota
|
||||||
|
START_SPAN
|
||||||
|
FINISH_SPAN
|
||||||
|
LOG_EVENT
|
||||||
|
LOGF_EVENT
|
||||||
|
MODIFY_ATTR
|
||||||
|
RECORD_STATS
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewReaderObserver returns an implementation that computes the
|
||||||
|
// necessary state needed by a reader to process events in memory.
|
||||||
|
// Practically, this means tracking live metric handles and scope
|
||||||
|
// attribute sets.
|
||||||
|
func NewReaderObserver(readers ...Reader) observer.Observer {
|
||||||
|
return &readerObserver{
|
||||||
|
readers: readers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ro *readerObserver) Observe(event observer.Event) {
|
||||||
|
read := Event{
|
||||||
|
Time: event.Time,
|
||||||
|
Sequence: event.Sequence,
|
||||||
|
Attributes: tag.EmptyMap,
|
||||||
|
Tags: tag.EmptyMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Context != nil {
|
||||||
|
read.Tags = tag.FromContext(event.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch event.Type {
|
||||||
|
case observer.START_SPAN:
|
||||||
|
// Save the span context tags, initial attributes, start time, and name.
|
||||||
|
span := &readerSpan{
|
||||||
|
name: event.String,
|
||||||
|
start: event.Time,
|
||||||
|
startTags: tag.FromContext(event.Context),
|
||||||
|
spanContext: event.Scope.SpanContext,
|
||||||
|
readerScope: &readerScope{},
|
||||||
|
}
|
||||||
|
|
||||||
|
rattrs, _ := ro.readScope(event.Scope)
|
||||||
|
|
||||||
|
span.readerScope.span = span
|
||||||
|
span.readerScope.attributes = rattrs
|
||||||
|
|
||||||
|
read.Name = span.name
|
||||||
|
read.Type = START_SPAN
|
||||||
|
read.SpanContext = span.spanContext
|
||||||
|
read.Attributes = rattrs
|
||||||
|
|
||||||
|
if event.Parent.EventID == 0 && event.Parent.HasTraceID() {
|
||||||
|
// Remote parent
|
||||||
|
read.Parent = event.Parent.SpanContext
|
||||||
|
|
||||||
|
// Note: No parent attributes in the event for remote parents.
|
||||||
|
} else {
|
||||||
|
pattrs, pspan := ro.readScope(event.Parent)
|
||||||
|
|
||||||
|
if pspan != nil {
|
||||||
|
// Local parent
|
||||||
|
read.Parent = pspan.spanContext
|
||||||
|
read.ParentAttributes = pattrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ro.scopes.Store(event.Sequence, span)
|
||||||
|
|
||||||
|
case observer.FINISH_SPAN:
|
||||||
|
attrs, span := ro.readScope(event.Scope)
|
||||||
|
if span == nil {
|
||||||
|
panic("span not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
read.Name = span.name
|
||||||
|
read.Type = FINISH_SPAN
|
||||||
|
|
||||||
|
read.Attributes = attrs
|
||||||
|
read.Duration = event.Time.Sub(span.start)
|
||||||
|
read.Tags = span.startTags
|
||||||
|
read.SpanContext = span.spanContext
|
||||||
|
|
||||||
|
// TODO: recovered
|
||||||
|
|
||||||
|
case observer.NEW_SCOPE, observer.MODIFY_ATTR:
|
||||||
|
var span *readerSpan
|
||||||
|
var m tag.Map
|
||||||
|
var sid core.ScopeID
|
||||||
|
|
||||||
|
if event.Scope.EventID == 0 {
|
||||||
|
// TODO: This is racey. Do this at the call
|
||||||
|
// site via Resources.
|
||||||
|
sid = trace.GlobalTracer().ScopeID()
|
||||||
|
} else {
|
||||||
|
sid = event.Scope
|
||||||
|
}
|
||||||
|
if sid.EventID == 0 {
|
||||||
|
m = tag.EmptyMap
|
||||||
|
} else {
|
||||||
|
parentI, has := ro.scopes.Load(sid.EventID)
|
||||||
|
if !has {
|
||||||
|
panic("parent scope not found")
|
||||||
|
}
|
||||||
|
if parent, ok := parentI.(*readerScope); ok {
|
||||||
|
m = parent.attributes
|
||||||
|
span = parent.span
|
||||||
|
} else if parent, ok := parentI.(*readerSpan); ok {
|
||||||
|
m = parent.attributes
|
||||||
|
span = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := &readerScope{
|
||||||
|
span: span,
|
||||||
|
parent: sid.EventID,
|
||||||
|
attributes: m.Apply(
|
||||||
|
event.Attribute,
|
||||||
|
event.Attributes,
|
||||||
|
event.Mutator,
|
||||||
|
event.Mutators,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
ro.scopes.Store(event.Sequence, sc)
|
||||||
|
|
||||||
|
if event.Type == observer.NEW_SCOPE {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
read.Type = MODIFY_ATTR
|
||||||
|
read.Attributes = sc.attributes
|
||||||
|
|
||||||
|
if span != nil {
|
||||||
|
read.SpanContext = span.spanContext
|
||||||
|
read.Tags = span.startTags
|
||||||
|
}
|
||||||
|
|
||||||
|
case observer.NEW_MEASURE:
|
||||||
|
measure := &readerMeasure{
|
||||||
|
name: event.String,
|
||||||
|
}
|
||||||
|
ro.measures.Store(event.Sequence, measure)
|
||||||
|
return
|
||||||
|
|
||||||
|
case observer.NEW_METRIC:
|
||||||
|
measureI, has := ro.measures.Load(event.Scope.EventID)
|
||||||
|
if !has {
|
||||||
|
panic("metric measure not found")
|
||||||
|
}
|
||||||
|
metric := &readerMetric{
|
||||||
|
readerMeasure: measureI.(*readerMeasure),
|
||||||
|
}
|
||||||
|
ro.metrics.Store(event.Sequence, metric)
|
||||||
|
return
|
||||||
|
|
||||||
|
case observer.LOG_EVENT:
|
||||||
|
read.Type = LOG_EVENT
|
||||||
|
|
||||||
|
read.Message = event.String
|
||||||
|
|
||||||
|
attrs, span := ro.readScope(event.Scope)
|
||||||
|
read.Attributes = attrs.Apply(core.KeyValue{}, event.Attributes, core.Mutator{}, nil)
|
||||||
|
if span != nil {
|
||||||
|
read.SpanContext = span.spanContext
|
||||||
|
}
|
||||||
|
|
||||||
|
case observer.LOGF_EVENT:
|
||||||
|
// TODO: this can't be done lazily, must be done before Record()
|
||||||
|
read.Message = fmt.Sprintf(event.String, event.Arguments...)
|
||||||
|
|
||||||
|
read.Type = LOGF_EVENT
|
||||||
|
attrs, span := ro.readScope(event.Scope)
|
||||||
|
read.Attributes = attrs
|
||||||
|
if span != nil {
|
||||||
|
read.SpanContext = span.spanContext
|
||||||
|
}
|
||||||
|
|
||||||
|
case observer.RECORD_STATS:
|
||||||
|
read.Type = RECORD_STATS
|
||||||
|
|
||||||
|
_, span := ro.readScope(event.Scope)
|
||||||
|
if span != nil {
|
||||||
|
read.SpanContext = span.spanContext
|
||||||
|
}
|
||||||
|
for _, es := range event.Stats {
|
||||||
|
ro.addMeasurement(&read, es)
|
||||||
|
}
|
||||||
|
if event.Stat.Measure != nil {
|
||||||
|
ro.addMeasurement(&read, event.Stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprint("Unhandled case: ", event.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, reader := range ro.readers {
|
||||||
|
reader.Read(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Type == observer.FINISH_SPAN {
|
||||||
|
ro.cleanupSpan(event.Scope.EventID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ro *readerObserver) addMeasurement(e *Event, m core.Measurement) {
|
||||||
|
attrs, _ := ro.readScope(m.ScopeID)
|
||||||
|
e.Stats = append(e.Stats, Measurement{
|
||||||
|
Measure: m.Measure,
|
||||||
|
Value: m.Value,
|
||||||
|
Tags: attrs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ro *readerObserver) readScope(id core.ScopeID) (tag.Map, *readerSpan) {
|
||||||
|
if id.EventID == 0 {
|
||||||
|
return tag.EmptyMap, nil
|
||||||
|
}
|
||||||
|
ev, has := ro.scopes.Load(id.EventID)
|
||||||
|
if !has {
|
||||||
|
panic(fmt.Sprintln("scope not found", id.EventID))
|
||||||
|
}
|
||||||
|
if sp, ok := ev.(*readerScope); ok {
|
||||||
|
return sp.attributes, sp.span
|
||||||
|
} else if sp, ok := ev.(*readerSpan); ok {
|
||||||
|
return sp.attributes, sp
|
||||||
|
}
|
||||||
|
return tag.EmptyMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ro *readerObserver) cleanupSpan(id core.EventID) {
|
||||||
|
for id != 0 {
|
||||||
|
ev, has := ro.scopes.Load(id)
|
||||||
|
if !has {
|
||||||
|
panic(fmt.Sprintln("scope not found", id))
|
||||||
|
}
|
||||||
|
ro.scopes.Delete(id)
|
||||||
|
|
||||||
|
if sp, ok := ev.(*readerScope); ok {
|
||||||
|
id = sp.parent
|
||||||
|
} else if sp, ok := ev.(*readerSpan); ok {
|
||||||
|
id = sp.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
exporter/spandata/format/format.go
Normal file
20
exporter/spandata/format/format.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/reader/format"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/spandata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AppendSpan(buf *strings.Builder, data *spandata.Span) {
|
||||||
|
for _, event := range data.Events {
|
||||||
|
format.AppendEvent(buf, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpanToString(data *spandata.Span) string {
|
||||||
|
var buf strings.Builder
|
||||||
|
AppendSpan(&buf, data)
|
||||||
|
return buf.String()
|
||||||
|
}
|
55
exporter/spandata/spandata.go
Normal file
55
exporter/spandata/spandata.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package spandata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/reader"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Reader interface {
|
||||||
|
Read(*Span)
|
||||||
|
}
|
||||||
|
|
||||||
|
Span struct {
|
||||||
|
Events []reader.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
spanReader struct {
|
||||||
|
spans map[core.SpanContext]*Span
|
||||||
|
readers []Reader
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewReaderObserver(readers ...Reader) observer.Observer {
|
||||||
|
return reader.NewReaderObserver(&spanReader{
|
||||||
|
spans: map[core.SpanContext]*Span{},
|
||||||
|
readers: readers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *spanReader) Read(data reader.Event) {
|
||||||
|
if !data.SpanContext.HasSpanID() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var span *Span
|
||||||
|
if data.Type == reader.START_SPAN {
|
||||||
|
span = &Span{Events: make([]reader.Event, 0, 4)}
|
||||||
|
s.spans[data.SpanContext] = span
|
||||||
|
} else {
|
||||||
|
span = s.spans[data.SpanContext]
|
||||||
|
if span == nil {
|
||||||
|
// TODO count and report this.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.Events = append(span.Events, data)
|
||||||
|
|
||||||
|
if data.Type == reader.FINISH_SPAN {
|
||||||
|
for _, r := range s.readers {
|
||||||
|
r.Read(span)
|
||||||
|
}
|
||||||
|
delete(s.spans, data.SpanContext)
|
||||||
|
}
|
||||||
|
}
|
16
exporter/spanlog/install/package.go
Normal file
16
exporter/spanlog/install/package.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package install
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/spanlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use this import:
|
||||||
|
//
|
||||||
|
// import _ "github.com/open-telemetry/opentelemetry-go/exporter/spanlog/install"
|
||||||
|
//
|
||||||
|
// to include the spanlog exporter by default.
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
observer.RegisterObserver(spanlog.New())
|
||||||
|
}
|
4
exporter/spanlog/plugin/Makefile
Normal file
4
exporter/spanlog/plugin/Makefile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.PHONY: module
|
||||||
|
|
||||||
|
module:
|
||||||
|
go build -buildmode=plugin -o spanlog.so package.go
|
10
exporter/spanlog/plugin/package.go
Normal file
10
exporter/spanlog/plugin/package.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/open-telemetry/opentelemetry-go/exporter/spanlog"
|
||||||
|
|
||||||
|
var (
|
||||||
|
Observer = spanlog.New()
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
26
exporter/spanlog/spanlog.go
Normal file
26
exporter/spanlog/spanlog.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package spanlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/buffer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/spandata"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/spandata/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
spanLog struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() observer.Observer {
|
||||||
|
return buffer.NewBuffer(1000, spandata.NewReaderObserver(&spanLog{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *spanLog) Read(data *spandata.Span) {
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.WriteString("----------------------------------------------------------------------\n")
|
||||||
|
format.AppendSpan(&buf, data)
|
||||||
|
os.Stdout.WriteString(buf.String())
|
||||||
|
}
|
16
exporter/stderr/install/package.go
Normal file
16
exporter/stderr/install/package.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package install
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/stderr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use this import:
|
||||||
|
//
|
||||||
|
// import _ "github.com/open-telemetry/opentelemetry-go/exporter/stderr/install"
|
||||||
|
//
|
||||||
|
// to include the stderr exporter by default.
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
observer.RegisterObserver(stderr.New())
|
||||||
|
}
|
4
exporter/stderr/plugin/Makefile
Normal file
4
exporter/stderr/plugin/Makefile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.PHONY: module
|
||||||
|
|
||||||
|
module:
|
||||||
|
go build -buildmode=plugin -o stderr.so package.go
|
10
exporter/stderr/plugin/package.go
Normal file
10
exporter/stderr/plugin/package.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/open-telemetry/opentelemetry-go/exporter/stderr"
|
||||||
|
|
||||||
|
var (
|
||||||
|
Observer = stderr.New()
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
21
exporter/stderr/stderr.go
Normal file
21
exporter/stderr/stderr.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package stderr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/reader"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/reader/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
stderrLog struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() observer.Observer {
|
||||||
|
return reader.NewReaderObserver(&stderrLog{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stderrLog) Read(data reader.Event) {
|
||||||
|
os.Stderr.WriteString(format.EventToString(data))
|
||||||
|
}
|
16
exporter/stdout/install/package.go
Normal file
16
exporter/stdout/install/package.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package install
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/stdout"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use this import:
|
||||||
|
//
|
||||||
|
// import _ "github.com/open-telemetry/opentelemetry-go/exporter/stdout/install"
|
||||||
|
//
|
||||||
|
// to include the stderr exporter by default.
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
observer.RegisterObserver(stdout.New())
|
||||||
|
}
|
4
exporter/stdout/plugin/Makefile
Normal file
4
exporter/stdout/plugin/Makefile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.PHONY: module
|
||||||
|
|
||||||
|
module:
|
||||||
|
go build -buildmode=plugin -o stdout.so package.go
|
10
exporter/stdout/plugin/package.go
Normal file
10
exporter/stdout/plugin/package.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/open-telemetry/opentelemetry-go/exporter/stdout"
|
||||||
|
|
||||||
|
var (
|
||||||
|
Observer = stdout.New()
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
21
exporter/stdout/stdout.go
Normal file
21
exporter/stdout/stdout.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package stdout
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/observer"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/reader"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/exporter/reader/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
stdoutLog struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() observer.Observer {
|
||||||
|
return reader.NewReaderObserver(&stdoutLog{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stdoutLog) Read(data reader.Event) {
|
||||||
|
os.Stdout.WriteString(format.EventToString(data))
|
||||||
|
}
|
35
plugin/httptrace/api.go
Normal file
35
plugin/httptrace/api.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package httptrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client
|
||||||
|
func W3C(ctx context.Context, req *http.Request) (context.Context, *http.Request, trace.Injector) {
|
||||||
|
t := newClientTracer(ctx)
|
||||||
|
|
||||||
|
t.GetConn = t.getConn
|
||||||
|
t.GotConn = t.gotConn
|
||||||
|
t.PutIdleConn = t.putIdleConn
|
||||||
|
t.GotFirstResponseByte = t.gotFirstResponseByte
|
||||||
|
t.Got100Continue = t.got100Continue
|
||||||
|
t.Got1xxResponse = t.got1xxResponse
|
||||||
|
t.DNSStart = t.dnsStart
|
||||||
|
t.DNSDone = t.dnsDone
|
||||||
|
t.ConnectStart = t.connectStart
|
||||||
|
t.ConnectDone = t.connectDone
|
||||||
|
t.TLSHandshakeStart = t.tlsHandshakeStart
|
||||||
|
t.TLSHandshakeDone = t.tlsHandshakeDone
|
||||||
|
t.WroteHeaderField = t.wroteHeaderField
|
||||||
|
t.WroteHeaders = t.wroteHeaders
|
||||||
|
t.Wait100Continue = t.wait100Continue
|
||||||
|
t.WroteRequest = t.wroteRequest
|
||||||
|
|
||||||
|
ctx = httptrace.WithClientTrace(ctx, &t.ClientTrace)
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
return ctx, req, hinjector{req}
|
||||||
|
}
|
174
plugin/httptrace/clienttrace.go
Normal file
174
plugin/httptrace/clienttrace.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package httptrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http/httptrace"
|
||||||
|
"net/textproto"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
clientLevel struct {
|
||||||
|
trace.Span
|
||||||
|
ident string
|
||||||
|
}
|
||||||
|
|
||||||
|
clientTracer struct {
|
||||||
|
context.Context
|
||||||
|
httptrace.ClientTrace
|
||||||
|
|
||||||
|
levels []clientLevel
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
HTTPStatus = tag.New("http.status")
|
||||||
|
HTTPHeaderMIME = tag.New("http.mime")
|
||||||
|
HTTPRemoteAddr = tag.New("http.remote")
|
||||||
|
HTTPLocalAddr = tag.New("http.local")
|
||||||
|
)
|
||||||
|
|
||||||
|
func newClientTracer(ctx context.Context) *clientTracer {
|
||||||
|
ct := &clientTracer{
|
||||||
|
Context: ctx,
|
||||||
|
}
|
||||||
|
ct.open("http.request")
|
||||||
|
return ct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) open(name string, attrs ...core.KeyValue) {
|
||||||
|
_, sp := trace.Start(ct.Context, name, trace.WithAttributes(attrs...))
|
||||||
|
ct.levels = append(ct.levels, clientLevel{
|
||||||
|
Span: sp,
|
||||||
|
ident: name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) close(name string) {
|
||||||
|
if len(ct.levels) == 0 {
|
||||||
|
panic("remove me")
|
||||||
|
}
|
||||||
|
l := len(ct.levels)
|
||||||
|
ct.levels[l-1].Finish()
|
||||||
|
ct.levels = ct.levels[0 : l-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) current() trace.Span {
|
||||||
|
return ct.levels[len(ct.levels)-1].Span
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) currentName() string {
|
||||||
|
if len(ct.levels) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ct.levels[len(ct.levels)-1].ident
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) getConn(host string) {
|
||||||
|
ct.open("http.getconn", HostKey.String(host))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) {
|
||||||
|
ct.current().SetAttribute(HTTPRemoteAddr.String(info.Conn.RemoteAddr().String()))
|
||||||
|
ct.current().SetAttribute(HTTPLocalAddr.String(info.Conn.LocalAddr().String()))
|
||||||
|
|
||||||
|
ct.close("http.getconn")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) putIdleConn(err error) {
|
||||||
|
if err != nil {
|
||||||
|
ct.current().SetAttribute(trace.MessageKey.String(err.Error()))
|
||||||
|
ct.current().SetError(true)
|
||||||
|
}
|
||||||
|
ct.close("http.receive")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) gotFirstResponseByte() {
|
||||||
|
ct.open("http.receive")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) dnsStart(httptrace.DNSStartInfo) {
|
||||||
|
ct.open("http.dns")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) dnsDone(httptrace.DNSDoneInfo) {
|
||||||
|
ct.close("http.dns")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) connectStart(network, addr string) {
|
||||||
|
ct.open("http.connect")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) connectDone(network, addr string, err error) {
|
||||||
|
ct.close("http.connect")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) tlsHandshakeStart() {
|
||||||
|
ct.open("http.tls")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) tlsHandshakeDone(tls.ConnectionState, error) {
|
||||||
|
ct.close("http.tls")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) wroteHeaderField(key string, value []string) {
|
||||||
|
if ct.currentName() != "http.headers" {
|
||||||
|
ct.open("http.headers")
|
||||||
|
}
|
||||||
|
ct.levels[0].SetAttribute(tag.New("http." + strings.ToLower(key)).String(sa2s(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) wroteHeaders() {
|
||||||
|
ct.open("http.send")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) {
|
||||||
|
if info.Err != nil {
|
||||||
|
ct.levels[0].SetAttribute(trace.MessageKey.String(info.Err.Error()))
|
||||||
|
ct.levels[0].SetError(true)
|
||||||
|
}
|
||||||
|
ct.close("http.send")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) got100Continue() {
|
||||||
|
ct.current().Log(ct.Context, "GOT 100 - Continue")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) wait100Continue() {
|
||||||
|
ct.current().Log(ct.Context, "GOT 100 - Wait")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error {
|
||||||
|
ct.current().Log(ct.Context, "GOT 1xx",
|
||||||
|
HTTPStatus.Int(code),
|
||||||
|
HTTPHeaderMIME.String(sm2s(header)),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sa2s(value []string) string {
|
||||||
|
if len(value) == 1 {
|
||||||
|
return value[0]
|
||||||
|
} else if len(value) == 0 {
|
||||||
|
return "undefined"
|
||||||
|
}
|
||||||
|
return strings.Join(value, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func sm2s(value map[string][]string) string {
|
||||||
|
var buf strings.Builder
|
||||||
|
for k, v := range value {
|
||||||
|
if buf.Len() != 0 {
|
||||||
|
buf.WriteString(",")
|
||||||
|
}
|
||||||
|
buf.WriteString(k)
|
||||||
|
buf.WriteString("=")
|
||||||
|
buf.WriteString(sa2s(v))
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
87
plugin/httptrace/httptrace.go
Normal file
87
plugin/httptrace/httptrace.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package httptrace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/core"
|
||||||
|
"github.com/open-telemetry/opentelemetry-go/api/tag"
|
||||||
|
"github.com/lightstep/tracecontext.go"
|
||||||
|
"github.com/lightstep/tracecontext.go/tracestate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Vendor = "ot"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
hinjector struct {
|
||||||
|
*http.Request
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
HostKey = tag.New("http.host")
|
||||||
|
URLKey = tag.New("http.url")
|
||||||
|
|
||||||
|
encoding = binary.BigEndian
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns the Attributes, Context Tags, and SpanContext that were encoded by Inject.
|
||||||
|
func Extract(req *http.Request) ([]core.KeyValue, []core.KeyValue, core.SpanContext) {
|
||||||
|
tc, err := tracecontext.FromHeaders(req.Header)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, core.SpanContext{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sc core.SpanContext
|
||||||
|
sc.SpanID = encoding.Uint64(tc.TraceParent.SpanID[0:8])
|
||||||
|
sc.TraceIDHigh = encoding.Uint64(tc.TraceParent.TraceID[0:8])
|
||||||
|
sc.TraceIDLow = encoding.Uint64(tc.TraceParent.TraceID[8:16])
|
||||||
|
|
||||||
|
attrs := []core.KeyValue{
|
||||||
|
URLKey.String(req.URL.String()),
|
||||||
|
// Etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags []core.KeyValue
|
||||||
|
|
||||||
|
for _, ts := range tc.TraceState {
|
||||||
|
if ts.Vendor != Vendor {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: max-hops, type conversion questions answered,
|
||||||
|
// case-conversion questions.
|
||||||
|
tags = append(tags, tag.New(ts.Tenant).String(ts.Value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs, tags, sc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h hinjector) Inject(sc core.SpanContext, tags tag.Map) {
|
||||||
|
var tc tracecontext.TraceContext
|
||||||
|
var sid [8]byte
|
||||||
|
var tid [16]byte
|
||||||
|
|
||||||
|
encoding.PutUint64(sid[0:8], sc.SpanID)
|
||||||
|
encoding.PutUint64(tid[0:8], sc.TraceIDHigh)
|
||||||
|
encoding.PutUint64(tid[8:16], sc.TraceIDLow)
|
||||||
|
|
||||||
|
tc.TraceParent.Version = tracecontext.Version
|
||||||
|
tc.TraceParent.TraceID = tid
|
||||||
|
tc.TraceParent.SpanID = sid
|
||||||
|
tc.TraceParent.Flags.Recorded = true // Note: not implemented.
|
||||||
|
|
||||||
|
tags.Foreach(func(kv core.KeyValue) bool {
|
||||||
|
// TODO: implement MaxHops
|
||||||
|
tc.TraceState = append(tc.TraceState, tracestate.Member{
|
||||||
|
Vendor: Vendor,
|
||||||
|
Tenant: kv.Key.Name(),
|
||||||
|
Value: kv.Value.Emit(),
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
tc.SetHeaders(h.Header)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user