blob: 3b9159dd13f7ae40f76c0d50e274b5464393f674 [file] [log] [blame]
// 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()
}
}
}