// 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)) spanKinds := map[int]string{ 1: "Server", 2: "Client", } if spanKind, ok := spanKinds[sd.SpanKind]; ok { e.tLogger.Printf("SpanKind: %s\n", spanKind) } 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) } } if len(sd.MessageEvents) > 0 { eventTypes := map[trace.MessageEventType]string{ trace.MessageEventTypeSent: "Sent", trace.MessageEventTypeRecv: "Received", } e.tLogger.Println() e.tLogger.Println("MessageEvents:") for _, item := range sd.MessageEvents { if eventType, ok := eventTypes[item.EventType]; ok { e.tLogger.Print(eventType) } e.tLogger.Printf("UncompressedByteSize: %v", item.UncompressedByteSize) e.tLogger.Printf("CompressedByteSize: %v", item.CompressedByteSize) e.tLogger.Println() } } }