Skip to content
This repository has been archived by the owner on Jul 31, 2023. It is now read-only.

Commit

Permalink
Add log exporter. (#1126)
Browse files Browse the repository at this point in the history
* Add log exporter.

* close log files when program terminates.

* split stats into multiple lines.

* fix review comments.

* one more comment.
  • Loading branch information
rghetia committed Apr 25, 2019
1 parent 0ac3701 commit 295a4b8
Showing 1 changed file with 199 additions and 0 deletions.
199 changes: 199 additions & 0 deletions examples/exporter/logexporter.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit 295a4b8

Please sign in to comment.