上下文传播编辑

在 Go 中,context 用于在调用链中传播请求范围的值,可能跨越 goroutine 和进程。对于基于 net/http 的服务器,每个请求都包含一个独立的上下文对象,这允许添加特定于该特定请求的值。

当您启动事务时,可以使用 apm.ContextWithTransaction 将其添加到上下文对象中。此上下文对象稍后可以传递到 apm.TransactionFromContext 以获取事务,或传递到 apm.StartSpan 以启动跨度。

创建和传播跨度的最简单方法是使用 apm.StartSpan,它接受一个上下文并返回一个跨度。该跨度将被创建为最近添加到此上下文的跨度的子级,或如上所述添加到上下文的交易。如果上下文既不包含事务也不包含跨度,则该跨度将被丢弃(即不会报告给 APM 服务器)。

例如,以一个简单的 CRUD 类型 Web 服务为例,它通过 HTTP 接受请求,然后执行相应的数据库查询。对于每个传入请求,将自动启动一个事务并将其添加到请求上下文中。此上下文需要手动传递到处理程序中的方法调用中,以便在该事务中创建跨度,例如,测量 SQL 查询的持续时间。

import (
	"net/http"

	"go.elastic.co/apm/v2"
	"go.elastic.co/apm/module/apmhttp/v2"
	"go.elastic.co/apm/module/apmsql/v2"
	_ "go.elastic.co/apm/module/apmsql/v2/pq"
)

var db *sql.DB

func init() {
	// apmsql.Open wraps sql.Open, in order
	// to add tracing to database operations.
	db, _ = apmsql.Open("postgres", "")
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", handleList)

	// apmhttp.Wrap instruments an http.Handler, in order
	// to report any request to this handler as a transaction,
	// and to store the transaction in the request's context.
	handler := apmhttp.Wrap(mux)
	http.ListenAndServe(":8080", handler)
}

func handleList(w http.ResponseWriter, req *http.Request) {
	// By passing the request context down to getList, getList can add spans to it.
	ctx := req.Context()
	getList(ctx)
	...
}

func getList(ctx context.Context) (
	// When getList is called with a context containing a transaction or span,
	// StartSpan creates a child span. In this example, getList is always called
	// with a context containing a transaction for the handler, so we should
	// expect to see something like:
	//
	//     Transaction: handleList
	//         Span: getList
	//             Span: SELECT FROM items
	//
	span, ctx := apm.StartSpan(ctx, "getList", "custom")
	defer span.End()

	// NOTE: The context object ctx returned by StartSpan above contains
	// the current span now, so subsequent calls to StartSpan create new
	// child spans.

	// db was opened with apmsql, so queries will be reported as
	// spans when using the context methods.
	rows, err := db.QueryContext(ctx, "SELECT * FROM items")
	...
	rows.Close()
}

上下文可以具有关联的截止日期,并且可以显式取消。在某些情况下,您可能希望将跟踪上下文(父事务/跨度)传播到某些代码,而无需传播取消。例如,HTTP 请求的上下文将在客户端连接关闭时取消。您可能希望在请求处理程序中执行某些操作,而不会因客户端连接关闭而取消,例如在即发即弃操作中。为了处理这种情况,我们提供了函数 apm.DetachedContext

func handleRequest(w http.ResponseWriter, req *http.Request) {
	go fireAndForget(apm.DetachedContext(req.Context()))

	// After handleRequest returns, req.Context() will be canceled,
	// but the "detached context" passed into fireAndForget will not.
	// Any spans created by fireAndForget will still be joined to
	// the handleRequest transaction.
}