From 295a4b8b79f868eed42df5700d1f63fd4d295ae0 Mon Sep 17 00:00:00 2001 From: rghetia Date: Tue, 23 Apr 2019 11:44:16 -0700 Subject: [PATCH] Add log exporter. (#1126) * Add log exporter. * close log files when program terminates. * split stats into multiple lines. * fix review comments. * one more comment. --- examples/exporter/logexporter.go | 199 +++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 examples/exporter/logexporter.go diff --git a/examples/exporter/logexporter.go b/examples/exporter/logexporter.go new file mode 100644 index 000000000..d868b5a65 --- /dev/null +++ b/examples/exporter/logexporter.go @@ -0,0 +1,199 @@ +// Copyright 2019, 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 exporter contains a log exporter that supports exporting +// OpenCensus metrics and spans to a logging framework. +package exporter // import "go.opencensus.io/examples/exporter" + +import ( + "context" + "encoding/hex" + "fmt" + "log" + "os" + "sync" + "time" + + "go.opencensus.io/metric/metricdata" + "go.opencensus.io/metric/metricexport" + "go.opencensus.io/trace" +) + +// LogExporter exports metrics and span to log file +type LogExporter struct { + reader *metricexport.Reader + ir *metricexport.IntervalReader + initReaderOnce sync.Once + o Options + tFile *os.File + mFile *os.File + tLogger *log.Logger + mLogger *log.Logger +} + +// Options provides options for LogExporter +type Options struct { + // ReportingInterval is a time interval between two successive metrics + // export. + ReportingInterval time.Duration + + // MetricsLogFile is path where exported metrics are logged. + // If it is nil then the metrics are logged on console + MetricsLogFile string + + // TracesLogFile is path where exported span data are logged. + // If it is nil then the span data are logged on console + TracesLogFile string +} + +func getLogger(filepath string) (*log.Logger, *os.File, error) { + if filepath == "" { + return log.New(os.Stdout, "", 0), nil, nil + } + f, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + return nil, nil, err + } + return log.New(f, "", 0), f, nil +} + +// NewLogExporter creates new log exporter. +func NewLogExporter(options Options) (*LogExporter, error) { + e := &LogExporter{reader: metricexport.NewReader(), + o: options} + var err error + e.tLogger, e.tFile, err = getLogger(options.TracesLogFile) + if err != nil { + return nil, err + } + e.mLogger, e.mFile, err = getLogger(options.MetricsLogFile) + if err != nil { + return nil, err + } + return e, nil +} + +func printMetricDescriptor(metric *metricdata.Metric) string { + d := metric.Descriptor + return fmt.Sprintf("name: %s, type: %s, unit: %s ", + d.Name, d.Type, d.Unit) +} + +func printLabels(metric *metricdata.Metric, values []metricdata.LabelValue) string { + d := metric.Descriptor + kv := []string{} + for i, k := range d.LabelKeys { + kv = append(kv, fmt.Sprintf("%s=%v", k, values[i])) + } + return fmt.Sprintf("%v", kv) +} + +func printPoint(point metricdata.Point) string { + switch v := point.Value.(type) { + case *metricdata.Distribution: + dv := v + return fmt.Sprintf("count=%v sum=%v sum_sq_dev=%v, buckets=%v", dv.Count, + dv.Sum, dv.SumOfSquaredDeviation, dv.Buckets) + default: + return fmt.Sprintf("value=%v", point.Value) + } +} + +// Start starts the metric and span data exporter. +func (e *LogExporter) Start() error { + trace.RegisterExporter(e) + e.initReaderOnce.Do(func() { + e.ir, _ = metricexport.NewIntervalReader(&metricexport.Reader{}, e) + }) + e.ir.ReportingInterval = e.o.ReportingInterval + return e.ir.Start() +} + +// Stop stops the metric and span data exporter. +func (e *LogExporter) Stop() { + trace.UnregisterExporter(e) + e.ir.Stop() +} + +// Close closes any files that were opened for logging. +func (e *LogExporter) Close() { + if e.tFile != nil { + e.tFile.Close() + e.tFile = nil + } + if e.mFile != nil { + e.mFile.Close() + e.mFile = nil + } +} + +// ExportMetrics exports to log. +func (e *LogExporter) ExportMetrics(ctx context.Context, metrics []*metricdata.Metric) error { + for _, metric := range metrics { + for _, ts := range metric.TimeSeries { + for _, point := range ts.Points { + e.mLogger.Println("#----------------------------------------------") + e.mLogger.Println() + e.mLogger.Printf("Metric: %s\n Labels: %s\n Value : %s\n", + printMetricDescriptor(metric), + printLabels(metric, ts.LabelValues), + printPoint(point)) + e.mLogger.Println() + } + } + } + return nil +} + +// ExportSpan exports a SpanData to log +func (e *LogExporter) ExportSpan(sd *trace.SpanData) { + var ( + traceID = hex.EncodeToString(sd.SpanContext.TraceID[:]) + spanID = hex.EncodeToString(sd.SpanContext.SpanID[:]) + parentSpanID = hex.EncodeToString(sd.ParentSpanID[:]) + ) + e.tLogger.Println() + e.tLogger.Println("#----------------------------------------------") + e.tLogger.Println() + e.tLogger.Println("TraceID: ", traceID) + e.tLogger.Println("SpanID: ", spanID) + if !reZero.MatchString(parentSpanID) { + e.tLogger.Println("ParentSpanID:", parentSpanID) + } + + e.tLogger.Println() + e.tLogger.Printf("Span: %v\n", sd.Name) + e.tLogger.Printf("Status: %v [%v]\n", sd.Status.Message, sd.Status.Code) + e.tLogger.Printf("Elapsed: %v\n", sd.EndTime.Sub(sd.StartTime).Round(time.Millisecond)) + + if len(sd.Annotations) > 0 { + e.tLogger.Println() + e.tLogger.Println("Annotations:") + for _, item := range sd.Annotations { + e.tLogger.Print(indent, item.Message) + for k, v := range item.Attributes { + e.tLogger.Printf(" %v=%v", k, v) + } + e.tLogger.Println() + } + } + + if len(sd.Attributes) > 0 { + e.tLogger.Println() + e.tLogger.Println("Attributes:") + for k, v := range sd.Attributes { + e.tLogger.Printf("%v- %v=%v\n", indent, k, v) + } + } +}