Skip to content

getoutreach/gobox

Repository files navigation

gobox

go.dev reference Generated via Bootstrap Coverage Status

A collection of libraries that are useful for implementing Go services, libraries, and more.

Contributing

Please read the CONTRIBUTING.md document for guidelines on developing and contributing changes.

High-level Overview

Please see individual packages in the generated documentation for overviews on each.

Go idioms

Standard idioms

Please see Code review comments, go proverbs and Idiomatic Go.

Log errors with events.NewErrorInfo

When logging errors, use log.Debug(ctx, "some debug event", events.NewErrorInfo(err)) instead of using log.F{"error": err}. NewErrorInfo logs errors using outreach naming conventions and logs stack traces.

Do not use context.WithValue

Context is often abused for thread-local state. There are very few legitimate uses for this (tracing is one of those).

Do not use fmt.PrintXXX or the standard log package

Prefer the gobox log package. This logs data in a structured format suitable for outreach Go services.

Do not use non-literal messages with log

Do not use the following pattern:

   message := fmt.Sprintf("working on org: %s", model.Org.ShortName)
   log.Info(ctx, message, modelInfo)

The first arg of log.XXX calls should be a literal string so we can quickly find out where a log message comes from. The rest of the args can hold any structured data we want. The events package exposes a few common logging structures.

Use code generation for stringifying enums

See go generate:

For example, given this snippet,

package painkiller

//go:generate ./scripts/gobin.sh golang.org/x/tools/cmd/stringer@v0.1.0 -type=Pill
type Pill int

const (
  Placebo Pill = iota
  Aspirin
  Ibuprofen
  Paracetamol
  Acetaminophen = Paracetamol
)

running go generate ./... from the root of the repo will create the file pill_string.go, in package painkiller, containing a definition of func (Pill) String() string which can be used to get the string representation of the enum.

A suggested workflow is to run go generate ./... from the root of the repo before sending PRs out.

Use events.Org for logging org tenancy info.

Org information can be logged with standard naming conventions using:

   orgInfo := events.Org{Bento: xyz, DatabaseHost: ...}
   log.Debug(ctx, "doing xyz", orgInfo)

In most cases, though you probably have some other model struct which has this info. In those cases, the preferred route is to make those model types themselves loggable:

type Model struct {...}

func (m *Model) MarshalLog(addField func(key string, value interface{}) {
     m.Org().MarshalLog(addField)
     ... add any custom fields you want: addField("myCustomField", m.CustomInfo)...
}

func (m *Model) Org() events.Org {
     return events.Org{...}
}

Now Model can be used in logs like so:

   var myModel m
   log.Debug(ctx, "doing xyz", myModel)

Better still is to generate the MarshalLog function using struct tags