Context
If you are not familiar with context.Context
, please read this and that first.
From the context documentation package:
Since we are building a gRPC web service, it’s part of the design.
Everything gRPC related already has a context.Context
as the first argument.
-
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error
-
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
-
Also, all the generated server endpoints have
context.Context
as the first argument.
Storage
One of the “drawbacks” in Go is a lack of storage per go routine. You can “solve” this drawback by:
- Having a sync
map[goroutine-id]storage
, but it’s probably not a practical idea. - Propagate
context.Context
as the first parameter for every public function.
Actually, the second option is largely encouraged and has become a kind of a standard.
- Tracing uses context to store a
traceId
,spanId
and other information. - grpc-gateway reverse proxy injects selected
http.Request
headers into the context.
In Mortar, most interfaces have a way to leverage on context.Context
, and depending on the package there are different ContextExtractor
definitions.
Let’s look at Logging as an example.
Logging Example
All Logger methods have a context.Context
as their first argument:
Debug(ctx context.Context, format string, args ...interface{})
Info(ctx context.Context, format string, args ...interface{})
Meaning that every log entry has a context…
Internally, just before we send the log to the implementing library, we iterate on every provided ContextExtractor
and add all the extracted key->value pairs to the log entry.
func (c *contextAwareLogEntry) enrich(ctx context.Context) (logger log.Fields) {
defer func() {
if r := recover(); r != nil {
c.innerLogger.WithField("__panic__", r).Error(ctx, "one of the context extractors panicked")
logger = c.innerLogger
}
}()
logger = c.innerLogger
for _, extractor := range c.contextExtractors {
for k, v := range extractor(ctx) {
logger = logger.WithField(k, v)
}
}
return
}
One of such ContextExtractor
is provided by the bjaeger wrapper.
func extractFromSpanContext(ctx jaeger.SpanContext) map[string]interface{} {
var output = make(map[string]interface{}, 4)
output[SpanIDKey] = ctx.SpanID().String()
output[ParentSpanIDKey] = ctx.ParentID().String()
output[SampledKey] = ctx.IsSampled()
if traceID := ctx.TraceID(); traceID.IsValid() {
output[TraceIDKey] = traceID.String()
}
return output
}
If there is a Trace Span within the Context, we will include its information in the log entry.
For additional information about Logging, Context and Telemetry read here.