blob: 88325155623d8c9bdac567971649bd7c8b8119f0 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package vtrace implements the Trace and Span interfaces in v.io/v23/vtrace.
// We also provide internal utilities for migrating trace information across
// RPC calls.
package vtrace
import (
"fmt"
"time"
"v.io/v23/context"
"v.io/v23/uniqueid"
"v.io/v23/vtrace"
"v.io/x/ref/lib/flags"
)
// A span represents an annotated period of time.
type span struct {
id uniqueid.Id
parent uniqueid.Id
name string
trace uniqueid.Id
start time.Time
store *Store
}
func newSpan(parent uniqueid.Id, name string, trace uniqueid.Id, store *Store) (*span, error) {
id, err := uniqueid.Random()
if err != nil {
return nil, fmt.Errorf("vtrace: Couldn't generate Span ID, debug data may be lost: %v", err)
}
s := &span{
id: id,
parent: parent,
name: name,
trace: trace,
start: time.Now(),
store: store,
}
store.start(s)
return s, nil
}
func (s *span) ID() uniqueid.Id {
// nologcall
return s.id
}
func (s *span) Parent() uniqueid.Id {
// nologcall
return s.parent
}
func (s *span) Name() string {
// nologcall
return s.name
}
func (s *span) Trace() uniqueid.Id {
// nologcall
return s.trace
}
func (s *span) Annotate(msg string) {
// nologcall
s.store.annotate(s, msg)
}
func (s *span) Annotatef(format string, a ...interface{}) {
// nologcall
s.store.annotate(s, fmt.Sprintf(format, a...))
}
func (s *span) Finish() {
// nologcall
s.store.finish(s)
}
func (s *span) flags() vtrace.TraceFlags {
return s.store.flags(s.trace)
}
type contextKey int
const (
storeKey = contextKey(iota)
spanKey
vtraceLevelKey
)
// Manager allows you to create new traces and spans and access the
// vtrace store.
type manager struct{}
// WithNewTrace creates a new vtrace context that is not the child of any
// other span. This is useful when starting operations that are
// disconnected from the activity ctx is performing. For example
// this might be used to start background tasks.
func (m manager) WithNewTrace(ctx *context.T) (*context.T, vtrace.Span) {
// nologcall
id, err := uniqueid.Random()
if err != nil {
ctx.Errorf("vtrace: Couldn't generate Trace Id, debug data may be lost: %v", err)
}
s, err := newSpan(id, "", id, getStore(ctx))
if err != nil {
ctx.Error(err)
}
return context.WithValue(ctx, spanKey, s), s
}
// WithContinuedTrace creates a span that represents a continuation of
// a trace from a remote server. name is the name of the new span and
// req contains the parameters needed to connect this span with it's
// trace.
func (m manager) WithContinuedTrace(ctx *context.T, name string, req vtrace.Request) (*context.T, vtrace.Span) {
// nologcall
st := getStore(ctx)
if req.Flags&vtrace.CollectInMemory != 0 {
st.ForceCollect(req.TraceId, int(req.LogLevel))
}
newSpan, err := newSpan(req.SpanId, name, req.TraceId, st)
if err != nil {
ctx.Error(err)
}
return context.WithValue(ctx, spanKey, newSpan), newSpan
}
// WithNewSpan derives a context with a new Span that can be used to
// trace and annotate operations across process boundaries.
func (m manager) WithNewSpan(ctx *context.T, name string) (*context.T, vtrace.Span) {
// nologcall
if curSpan := getSpan(ctx); curSpan != nil {
if curSpan.store == nil {
panic("nil store")
}
s, err := newSpan(curSpan.ID(), name, curSpan.trace, curSpan.store)
if err != nil {
ctx.Error(err)
}
return context.WithValue(ctx, spanKey, s), s
}
ctx.Error("vtrace: Creating a new child span from context with no existing span.")
return m.WithNewTrace(ctx)
}
// Span finds the currently active span.
func (m manager) GetSpan(ctx *context.T) vtrace.Span {
// nologcall
if span := getSpan(ctx); span != nil {
return span
}
return nil
}
// Request generates a vtrace.Request from the active Span.
func (m manager) GetRequest(ctx *context.T) vtrace.Request {
// nologcall
if span := getSpan(ctx); span != nil {
return vtrace.Request{
SpanId: span.id,
TraceId: span.trace,
Flags: span.flags(),
LogLevel: int32(GetVTraceLevel(ctx)),
}
}
return vtrace.Request{}
}
// Response captures the vtrace.Response for the active Span.
func (m manager) GetResponse(ctx *context.T) vtrace.Response {
// nologcall
if span := getSpan(ctx); span != nil {
return vtrace.Response{
Flags: span.flags(),
Trace: *span.store.TraceRecord(span.trace),
}
}
return vtrace.Response{}
}
// Store returns the current vtrace.Store.
func (m manager) GetStore(ctx *context.T) vtrace.Store {
// nologcall
if store := getStore(ctx); store != nil {
return store
}
return nil
}
// getSpan returns the internal span type.
func getSpan(ctx *context.T) *span {
span, _ := ctx.Value(spanKey).(*span)
return span
}
// GetStore returns the *Store attached to the context.
func getStore(ctx *context.T) *Store {
store, _ := ctx.Value(storeKey).(*Store)
return store
}
// SetVTraceLevel returns the vtrace level This value is used
// to determine which log messages are sent as part of the vtrace output.
func GetVTraceLevel(ctx *context.T) int {
store := getStore(ctx)
if store == nil {
return 0
}
span := getSpan(ctx)
if span == nil {
return 0
}
return store.logLevel(span.trace)
}
// Init initializes vtrace and attaches some state to the context.
// This should be called by the runtimes initialization function.
func Init(ctx *context.T, opts flags.VtraceFlags) (*context.T, error) {
nctx := vtrace.WithManager(ctx, manager{})
store, err := NewStore(opts)
if err != nil {
return ctx, err
}
return context.WithValue(nctx, storeKey, store), nil
}