Abstraction above the Uber Zap logging library so we aren't locked into a particular logger. Provides some standard logging practices across all Nullify services.
To make the most use of this library, you have to ensure that your service passes context down within each function call.
One big change is that the logger is no longer a global variable. It is now linked to the context, so you need to ensure that you pass the context.
logger.L(ctx).Info("this is a log message")First, declare the background context in your main function, and then inject the logger into the context.
func main() {
ctx := context.Background()
ctx, err := logger.ConfigureProductionLogger(ctx, "info") // inject logger into context
// There is no longer a global logger. It is always linked to the context, to ensure that log messages have the appropriate span and trace IDs linked to them.
defer logger.L(ctx).Sync() // defer the sync of the logger to ensure all logs are written
// Initiate a new span, and defer ending the span to record the end of this piece of work.
ctx, span := tracer.FromContext(ctx).Start(ctx, "main")
defer span.End()
// You can add more contextual event information to spans through the span.AddEvent method. This is analogous to adding a log message, and has an associated timestamp that is recorded.
span.AddEvent("a piece of work is starting within the span")
// You can also set attributes to the span to provide more context to the span, typically as key-value pairs.
span.SetAttributes(
attribute.String("customer-id", "018e4fc1-c079-70ce-84a0-9591295d96aa"),
attribute.Int64("request-count", 80),
)
// You need to ensure that you pass context to other functions so that the logger and tracer are available to them.
anotherFunction(ctx)
}
func anotherFunction(ctx context.Context) {
// Start a child span - the parent span is automatically linked via context.
ctx, span := tracer.FromContext(ctx).Start(ctx, "some-other-work")
defer span.End()
// To record errors, use logger.L(ctx).Error(). This automatically sets the span to errored, so it is highlighted in Grafana.
err := errors.New("this is an error")
logger.L(ctx).Error("this is an error", logger.Err(err))
}Spans are created via the tracer sub-package. Both the tracer and meter are automatically injected into context by ConfigureProductionLogger / ConfigureDevelopmentLogger.
// Start a child span (inherits parent from context)
ctx, span := tracer.StartNewSpan(ctx, "span-name")
defer span.End()
// Start a root span (no parent, e.g. at the entry point of an API handler)
ctx, span := tracer.StartNewRootSpan(ctx, "request-handler")
defer span.End()
// With options
ctx, span := tracer.StartNewSpan(ctx, "span-name",
trace.WithAttributes(attribute.String("key", "value")),
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()
// Flush traces before shutdown (e.g. in Lambda handlers)
tracer.ForceFlush(ctx)Metrics are created via the meter sub-package. The meter is retrieved from context.
m := meter.FromContext(ctx)
// Create instruments
counter, _ := m.Int64Counter("requests.total")
histogram, _ := m.Float64Histogram("request.duration_ms")
// Record measurements
counter.Add(ctx, 1, metric.WithAttributes(attribute.String("method", "GET")))
histogram.Record(ctx, 42.5)
// Flush metrics before shutdown
meter.ForceFlush(ctx)To actually have your traces exported, you need to set a few environment variables in your service:
OTEL_EXPORTER_OTLP_PROTOCOL: typically set tohttp/protobuf; this is the protocol that the traces are sent over.OTEL_EXPORTER_OTLP_ENDPOINT: the endpoint that the traces are sent to.OTEL_EXPORTER_OTLP_HEADERS_NAME: the name of the parameter in aws parameter store that contains the headers for the OTLP exporter.OTEL_RESOURCE_ATTRIBUTES: comma-separatedkey=valueattributes associated with the service (e.g.deployment.environment=production). These are propagated to traces, metrics, and as default log fields.OTEL_SERVICE_NAME: the name of the service. Propagated to traces, metrics, and as a defaultservice.namelog field.
Dependencies:
- make
- golang
Install code dependencies
go mod download
Compile the code locally into the bin directory
make
make unit # without coverage
make cov # with coverage
Run golangci-lint with the following tools:
- gofmt
- stylecheck
- gosec
make lint