Skip to content

Commit

Permalink
Merge pull request #189 from ymtdzzz/feature/remove_trace_id_from_log…
Browse files Browse the repository at this point in the history
…_table_in_timeline

Remove Trace ID from log table in timeline
  • Loading branch information
ymtdzzz authored Dec 22, 2024
2 parents 45a1465 + aaa7aba commit a16e90c
Show file tree
Hide file tree
Showing 12 changed files with 450 additions and 195 deletions.
Binary file modified docs/logs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/metrics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/spans.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/traces.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions screenshot.tape
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ Sleep 1s
Tab
Type "/recommend"
Enter
Sleep 10ms
Sleep 100ms
Down
Sleep 10ms
Sleep 100ms
Screenshot docs/logs.png
Sleep 1s

Expand Down
84 changes: 84 additions & 0 deletions tuiexporter/internal/telemetry/store.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package telemetry

import (
"fmt"
"strings"
"sync"
"time"

"github.com/icza/gox/timex"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
Expand All @@ -30,6 +32,26 @@ func (sd *SpanData) IsRoot() bool {
return sd.Span.ParentSpanID().IsEmpty()
}

func (sd *SpanData) GetServiceName() string {
if sname, ok := sd.ResourceSpan.Resource().Attributes().Get("service.name"); ok {
return sname.AsString()
}
return ""
}

func (sd *SpanData) GetDurationText() string {
duration := sd.Span.EndTimestamp().AsTime().Sub(sd.Span.StartTimestamp().AsTime())
return timex.Round(duration, 2).String()
}

func (sd *SpanData) GetReceivedAtText() string {
return sd.ReceivedAt.Local().Format("2006-01-02 15:04:05")
}

func (sd *SpanData) GetSpanName() string {
return sd.Span.Name()
}

// SvcSpans is a slice of service spans
// This is a slice of one span of a single service
type SvcSpans []*SpanData
Expand All @@ -56,6 +78,37 @@ func (md *MetricData) HasNumberDatapoints() bool {
return md.Metric.Type() == pmetric.MetricTypeGauge || md.Metric.Type() == pmetric.MetricTypeSum
}

func (md *MetricData) GetServiceName() string {
if sname, ok := md.ResourceMetric.Resource().Attributes().Get("service.name"); ok {
return sname.AsString()
}
return ""
}

func (md *MetricData) GetMetricName() string {
return md.Metric.Name()
}

func (md *MetricData) GetMetricTypeText() string {
return md.Metric.Type().String()
}

func (md *MetricData) GetDataPointNum() string {
switch md.Metric.Type() {
case pmetric.MetricTypeGauge:
return fmt.Sprintf("%d", md.Metric.Gauge().DataPoints().Len())
case pmetric.MetricTypeSum:
return fmt.Sprintf("%d", md.Metric.Sum().DataPoints().Len())
case pmetric.MetricTypeHistogram:
return fmt.Sprintf("%d", md.Metric.Histogram().DataPoints().Len())
case pmetric.MetricTypeExponentialHistogram:
return fmt.Sprintf("%d", md.Metric.ExponentialHistogram().DataPoints().Len())
case pmetric.MetricTypeSummary:
return fmt.Sprintf("%d", md.Metric.Summary().DataPoints().Len())
}
return ""
}

// LogData is a struct to represent a log
type LogData struct {
Log *plog.LogRecord
Expand All @@ -74,6 +127,37 @@ func (l *LogData) GetResolvedBody() string {
return b
}

func (l *LogData) GetTraceID() string {
return l.Log.TraceID().String()
}

func (l *LogData) GetServiceName() string {
if sname, ok := l.ResourceLog.Resource().Attributes().Get("service.name"); ok {
return sname.AsString()
}
return ""
}

func (l *LogData) GetTimestampText() string {
return l.Log.Timestamp().AsTime().Format("2006/01/02 15:04:05")
}

func (l *LogData) GetSeverity() string {
return l.Log.SeverityText()
}

func (l *LogData) GetEventName() string {
// see: https://github.com/open-telemetry/semantic-conventions/blob/a4fc971e0c7ffa4b9572654f075d3cb8560db770/docs/general/events.md#event-definition
if sname, ok := l.Log.Attributes().Get("event.name"); ok {
return sname.AsString()
}
return ""
}

func (l *LogData) GetRawData() string {
return l.Log.Body().AsString()
}

// Store is a store of trace spans
type Store struct {
mut sync.Mutex
Expand Down
135 changes: 95 additions & 40 deletions tuiexporter/internal/tui/component/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,114 @@ import (
"github.com/ymtdzzz/otel-tui/tuiexporter/internal/telemetry"
)

var logTableHeader = [6]string{
"Trace ID",
"Service Name",
"Timestamp",
"Severity",
"Event Name",
"RawData",
var defaultLogCellMappers = cellMappers[telemetry.LogData]{
0: {
header: "Trace ID",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetTraceID()
},
},
1: {
header: "Service Name",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetServiceName()
},
},
2: {
header: "Timestamp",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetTimestampText()
},
},
3: {
header: "Severity",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetSeverity()
},
},
4: {
header: "Event Name",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetEventName()
},
},
5: {
header: "RawData",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetRawData()
},
},
}

var logCellMappersForTimeline = cellMappers[telemetry.LogData]{
0: {
header: "Service Name",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetServiceName()
},
},
1: {
header: "Timestamp",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetTimestampText()
},
},
2: {
header: "Severity",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetSeverity()
},
},
3: {
header: "Event Name",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetEventName()
},
},
4: {
header: "RawData",
getTextRowFn: func(log *telemetry.LogData) string {
return log.GetRawData()
},
},
}

// LogDataForTable is a wrapper for logs to be displayed in a table
type LogDataForTable struct {
tview.TableContentReadOnly
logs *[]*telemetry.LogData
logs *[]*telemetry.LogData
mapper cellMappers[telemetry.LogData]
}

// NewLogDataForTable creates a new LogDataForTable.
func NewLogDataForTable(logs *[]*telemetry.LogData) LogDataForTable {
return LogDataForTable{
logs: logs,
logs: logs,
mapper: defaultLogCellMappers,
}
}

// NewLogDataForTableForTimeline creates a new LogDataForTable for timeline page.
func NewLogDataForTableForTimeline(logs *[]*telemetry.LogData) LogDataForTable {
return LogDataForTable{
logs: logs,
mapper: logCellMappersForTimeline,
}
}

// implementation for tableModalMapper interface
func (l *LogDataForTable) GetColumnIdx() int {
return len(logTableHeader) - 1
return len(l.mapper) - 1
}

// implementations for tview Virtual Table
// see: https://github.com/rivo/tview/wiki/VirtualTable
func (l LogDataForTable) GetCell(row, column int) *tview.TableCell {
if row == 0 {
sortType := telemetry.SORT_TYPE_NONE
return getHeaderCell(logTableHeader[:], column, &sortType)
return l.getHeaderCell(column)
}
if row > 0 && row <= len(*l.logs) {
return getCellFromLog((*l.logs)[row-1], column)
return getCellFromData(l.mapper, (*l.logs)[row-1], column)
}
return tview.NewTableCell("N/A")
}
Expand All @@ -53,37 +125,20 @@ func (l LogDataForTable) GetRowCount() int {
}

func (l LogDataForTable) GetColumnCount() int {
return len(logTableHeader)
return len(l.mapper)
}

// getCellFromLog returns a table cell for the given log and column.
func getCellFromLog(log *telemetry.LogData, column int) *tview.TableCell {
text := "N/A"

switch column {
case 0:
text = log.Log.TraceID().String()
case 1:
sname, _ := log.ResourceLog.Resource().Attributes().Get("service.name")
text = sname.AsString()
case 2:
text = log.Log.Timestamp().AsTime().Format("2006/01/02 15:04:05")
case 3:
text = log.Log.SeverityText()
case 4:
// see: https://github.com/open-telemetry/semantic-conventions/blob/a4fc971e0c7ffa4b9572654f075d3cb8560db770/docs/general/events.md#event-definition
if ename, ok := log.Log.Attributes().Get("event.name"); ok {
text = ename.AsString()
}
case 5:
text = log.Log.Body().AsString()
}

if text == "" {
text = "N/A"
func (l LogDataForTable) getHeaderCell(column int) *tview.TableCell {
cell := tview.NewTableCell("N/A").
SetSelectable(false).
SetTextColor(tcell.ColorYellow)
h, ok := l.mapper[column]
if !ok {
return cell
}
cell.SetText(h.header)

return tview.NewTableCell(text)
return cell
}

func getLogInfoTree(commands *tview.TextView, showModalFn showModalFunc, hideModalFn hideModalFunc, l *telemetry.LogData, tcache *telemetry.TraceCache, drawTimelineFn func(traceID string)) *tview.TreeView {
Expand Down
Loading

0 comments on commit a16e90c

Please sign in to comment.