My app runs on App Engine Standard and the Go runtime. I have this trace for my recent request:
There is a big gap between the "urlfetch" span and the "datastore_v3" span, because my app processes some CPU-bound computation for ~1000ms.
I would love to programmatically add my computation as a custom span into the Trace view, and get something like this:
Is there a way to do this in my app written in go? (source here)
It appears it might be possible. From Setting Up Stackdriver Trace for Go:
Alpha
This is an alpha release of the OpenCensus package for Go. These libraries might be changed in backward-incompatible ways and are not recommended for production use. They are not subject to any SLA or deprecation policy.
Stackdriver Trace can be used by Go applications using the OpenCensus package for Go.
Stackdriver Trace's Go support is provided by OpenCensus, a set of tracing and application metrics instrumentation libraries that work with multiple backends. The latest details about OpenCensus for Go, along with additional documentation and examples, can be found on its GitHub page.
Support is enabled by default in the flexible environment, however the docs make no mention about the standard environment (if that's your case I'd say just give it a try). From App Engine:
On Google App Engine flexible environment, the Stackdriver Trace API access scope is enabled by default, and the OpenCensus client library can be used without needing to provide credentials or a project ID.
An application code sample is provided on the same page.
I could make it work with the new AppEngine runtime for Go 1.11 (currently in beta) and OpenCensus with the Stackdriver exporter.
In order to attach my custom span to the main Trace of the request, I use this utility func:
// Start a new span "With Remote Parent"
func startSpanfWRT(r *http.Request, msg string, args ...interface{}) (c2 context.Context, endSpan func()) {
caption := fmt.Sprintf(msg, args...)
c := r.Context()
spanContext, ok := (&propagation.HTTPFormat{}).SpanContextFromRequest(r)
if !ok {
return c, func() {}
}
var span *trace.Span
c2, span = trace.StartSpanWithRemoteParent(c, caption, spanContext)
endSpan = func() {
span.End()
}
return c2, endSpan
}
Note that it requires the *http.Request
as argument (a context.Context
wouldn't be enough here).
Here is the sample app source code.
As a span needs to be started and then later stopped, the start func returns an "end" callback, and a new Context as well.
It is fine to call startSpanfWRT
multiple times, and they may overlap. It requires to pass the *http.Request
around, which is not super-convenient (usually we only pass Contexts around).
However, after a call to startSpanfWRT
, you may add children spans conveniently, just paying attention to the respective Contexts:
c2, childSpan := trace.StartSpan(c, caption)