上下文传播

编辑

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

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

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

例如,考虑一个简单的 CRUD 类型 Web 服务,它通过 HTTP 接受请求,然后进行相应的数据库查询。对于每个传入请求,将自动启动一个事务并将其添加到请求上下文中。此上下文需要手动传递到处理程序中的方法调用中,以便在该事务中创建 Span,例如,测量 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()
}

上下文可以具有关联的截止时间,并且可以显式取消。在某些情况下,您可能希望将跟踪上下文(父事务/Span)传播到某些代码,而无需传播取消。例如,当客户端连接关闭时,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.
}