You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	* Add MetricAggregator.Merge() implementations * Update from feedback * Type * Ckpt * Ckpt * Add push controller * Ckpt * Add aggregator interfaces, stdout encoder * Modify basic main.go * Main is working * Batch stdout output * Sum udpate * Rename stdout * Add stateless/stateful Batcher options * Undo a for-loop in the example, remove a done TODO * Update imports * Add note * Rename defaultkeys * Support variable label encoder to speed OpenMetrics/Statsd export * Lint * Doc * Precommit/lint * Simplify Aggregator API * Record->Identifier * Remove export.Record a.k.a. Identifier * Checkpoint * Propagate errors to the SDK, remove a bunch of 'TODO warn' * Checkpoint * Introduce export.Labels * Comments in export/metric.go * Comment * More merge * More doc * Complete example * Lint fixes * Add a testable example * Lint * Let Export return an error * add a basic stdout exporter test * Add measure test; fix aggregator APIs * Use JSON numbers, not strings * Test stdout exporter error * Add a test for the call to RangeTest * Add error handler API to improve correctness test; return errors from RecordOne * Undo the previous -- do not expose errors * Add simple selector variations, test * Repair examples * Test push controller error handling * Add SDK label encoder tests * Add a defaultkeys batcher test * Add an ungrouped batcher test * Lint new tests * Respond to krnowak's feedback * Undo comment * Use concrete receivers for export records and labels, since the constructors return structs not pointers * Bug fix for stateful batchers; clone an aggregator for long term storage * Remove TODO addressed in #318 * Add errors to all aggregator interfaces * Handle ErrNoLastValue case in stdout exporter * Move aggregator API into sdk/export/metric/aggregator * Update all aggregator exported-method comments * Document the aggregator APIs * More aggregator comments * Add multiple updates to the ungrouped test * Fixes for feedback from Gustavo and Liz * Producer->CheckpointSet; add FinishedCollection * Process takes an export.Record * ReadCheckpoint->CheckpointSet * EncodeLabels->Encode * Format a better inconsistent type error; add more aggregator API tests * More RangeTest test coverage * Make benbjohnson/clock a test-only dependency * Handle ErrNoLastValue in stress_test
		
			
				
	
	
		
			215 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 stdout // import "go.opentelemetry.io/otel/exporter/metric/stdout"
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	export "go.opentelemetry.io/otel/sdk/export/metric"
 | |
| 	"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
 | |
| )
 | |
| 
 | |
| type Exporter struct {
 | |
| 	options Options
 | |
| }
 | |
| 
 | |
| var _ export.Exporter = &Exporter{}
 | |
| 
 | |
| // Options are the options to be used when initializing a stdout export.
 | |
| type Options struct {
 | |
| 	// File is the destination.  If not set, os.Stdout is used.
 | |
| 	File io.Writer
 | |
| 
 | |
| 	// PrettyPrint will pretty the json representation of the span,
 | |
| 	// making it print "pretty". Default is false.
 | |
| 	PrettyPrint bool
 | |
| 
 | |
| 	// DoNotPrintTime suppresses timestamp printing.  This is
 | |
| 	// useful to create deterministic test conditions.
 | |
| 	DoNotPrintTime bool
 | |
| 
 | |
| 	// Quantiles are the desired aggregation quantiles for measure
 | |
| 	// metric data, used when the configured aggregator supports
 | |
| 	// quantiles.
 | |
| 	//
 | |
| 	// Note: this exporter is meant as a demonstration; a real
 | |
| 	// exporter may wish to configure quantiles on a per-metric
 | |
| 	// basis.
 | |
| 	Quantiles []float64
 | |
| }
 | |
| 
 | |
| type expoBatch struct {
 | |
| 	Timestamp *time.Time `json:"time,omitempty"`
 | |
| 	Updates   []expoLine `json:"updates"`
 | |
| }
 | |
| 
 | |
| type expoLine struct {
 | |
| 	Name      string      `json:"name"`
 | |
| 	Max       interface{} `json:"max,omitempty"`
 | |
| 	Sum       interface{} `json:"sum,omitempty"`
 | |
| 	Count     interface{} `json:"count,omitempty"`
 | |
| 	LastValue interface{} `json:"last,omitempty"`
 | |
| 
 | |
| 	Quantiles interface{} `json:"quantiles,omitempty"`
 | |
| 
 | |
| 	// Note: this is a pointer because omitempty doesn't work when time.IsZero()
 | |
| 	Timestamp *time.Time `json:"time,omitempty"`
 | |
| }
 | |
| 
 | |
| type expoQuantile struct {
 | |
| 	Q interface{} `json:"q"`
 | |
| 	V interface{} `json:"v"`
 | |
| }
 | |
| 
 | |
| func New(options Options) (*Exporter, error) {
 | |
| 	if options.File == nil {
 | |
| 		options.File = os.Stdout
 | |
| 	}
 | |
| 	if options.Quantiles == nil {
 | |
| 		options.Quantiles = []float64{0.5, 0.9, 0.99}
 | |
| 	} else {
 | |
| 		for _, q := range options.Quantiles {
 | |
| 			if q < 0 || q > 1 {
 | |
| 				return nil, aggregator.ErrInvalidQuantile
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return &Exporter{
 | |
| 		options: options,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
 | |
| 	// N.B. Only return one aggError, if any occur. They're likely
 | |
| 	// to be duplicates of the same error.
 | |
| 	var aggError error
 | |
| 	var batch expoBatch
 | |
| 	if !e.options.DoNotPrintTime {
 | |
| 		ts := time.Now()
 | |
| 		batch.Timestamp = &ts
 | |
| 	}
 | |
| 	checkpointSet.ForEach(func(record export.Record) {
 | |
| 		desc := record.Descriptor()
 | |
| 		agg := record.Aggregator()
 | |
| 		kind := desc.NumberKind()
 | |
| 
 | |
| 		var expose expoLine
 | |
| 
 | |
| 		if sum, ok := agg.(aggregator.Sum); ok {
 | |
| 			if value, err := sum.Sum(); err != nil {
 | |
| 				aggError = err
 | |
| 				expose.Sum = "NaN"
 | |
| 			} else {
 | |
| 				expose.Sum = value.AsInterface(kind)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if msc, ok := agg.(aggregator.MaxSumCount); ok {
 | |
| 			if count, err := msc.Count(); err != nil {
 | |
| 				aggError = err
 | |
| 				expose.Count = "NaN"
 | |
| 			} else {
 | |
| 				expose.Count = count
 | |
| 			}
 | |
| 
 | |
| 			// TODO: Should tolerate ErrEmptyDataSet here,
 | |
| 			// just like ErrNoLastValue below, since
 | |
| 			// there's a race condition between creating
 | |
| 			// the Aggregator and updating the first
 | |
| 			// value.
 | |
| 
 | |
| 			if max, err := msc.Max(); err != nil {
 | |
| 				aggError = err
 | |
| 				expose.Max = "NaN"
 | |
| 			} else {
 | |
| 				expose.Max = max.AsInterface(kind)
 | |
| 			}
 | |
| 
 | |
| 			if dist, ok := agg.(aggregator.Distribution); ok && len(e.options.Quantiles) != 0 {
 | |
| 				summary := make([]expoQuantile, len(e.options.Quantiles))
 | |
| 				expose.Quantiles = summary
 | |
| 
 | |
| 				for i, q := range e.options.Quantiles {
 | |
| 					var vstr interface{}
 | |
| 					if value, err := dist.Quantile(q); err != nil {
 | |
| 						aggError = err
 | |
| 						vstr = "NaN"
 | |
| 					} else {
 | |
| 						vstr = value.AsInterface(kind)
 | |
| 					}
 | |
| 					summary[i] = expoQuantile{
 | |
| 						Q: q,
 | |
| 						V: vstr,
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		} else if lv, ok := agg.(aggregator.LastValue); ok {
 | |
| 			if value, timestamp, err := lv.LastValue(); err != nil {
 | |
| 				if err == aggregator.ErrNoLastValue {
 | |
| 					// This is a special case, indicates an aggregator that
 | |
| 					// was checkpointed before its first value was set.
 | |
| 					return
 | |
| 				}
 | |
| 
 | |
| 				aggError = err
 | |
| 				expose.LastValue = "NaN"
 | |
| 			} else {
 | |
| 				expose.LastValue = value.AsInterface(kind)
 | |
| 
 | |
| 				if !e.options.DoNotPrintTime {
 | |
| 					expose.Timestamp = ×tamp
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		var sb strings.Builder
 | |
| 
 | |
| 		sb.WriteString(desc.Name())
 | |
| 
 | |
| 		if labels := record.Labels(); labels.Len() > 0 {
 | |
| 			sb.WriteRune('{')
 | |
| 			sb.WriteString(labels.Encoded())
 | |
| 			sb.WriteRune('}')
 | |
| 		}
 | |
| 
 | |
| 		expose.Name = sb.String()
 | |
| 
 | |
| 		batch.Updates = append(batch.Updates, expose)
 | |
| 	})
 | |
| 
 | |
| 	var data []byte
 | |
| 	var err error
 | |
| 	if e.options.PrettyPrint {
 | |
| 		data, err = json.MarshalIndent(batch, "", "\t")
 | |
| 	} else {
 | |
| 		data, err = json.Marshal(batch)
 | |
| 	}
 | |
| 
 | |
| 	if err == nil {
 | |
| 		fmt.Fprintln(e.options.File, string(data))
 | |
| 	} else {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return aggError
 | |
| }
 |