Jettison is a library providing structured logs and errors in a way that's
interoperable with gRPC. It does this under the hood by serialising message
details to protobuf messages and attaching them to gRPC Status objects -
see the gRPC docs for
details of how this works. Jettison is also compatible with the Go 2 error
spec, which can be found in the draft design page.
Jettison is in alpha, but the following features are planned:
- [✓] Simple, gRPC-compatible utilities for building up structured errors/logs.
 - [✓] Support for error identification and unwrapping as per the Go 2 spec.
 - [✓] Structured error/log formatting utilities for both machines (JSON) and humans.
 - [✕] Key/value pair type support for compatibility with Elasticsearch.
 
The jettison/errors package provides functions for creating and working with
gRPC-compatible error values. You can attach arbitrary metadata to jettison
errors, decorate them with source/stacktrace information and wrap errors to
form a chain as the stack unwinds. Passing jettison errors over gRPC preserves
all metadata structure, in contrast to other error types (which get marshalled
to a string by default).
Jettison also provides gRPC middleware that automatically groups the errors in a chain by the gRPC server (or "hop") they originated from.
See the jettison/example package for a more complete usage example, including
a gRPC server/client passing jettison errors over the wire.
import (
    "github.com/luno/jettison"
    "github.com/luno/jettison/errors"
)
func ExampleNew() {
	// Construct your error as usual, with additional metadata.
	err := errors.New("something went wrong",
		jettison.WithKeyValueString("key", "value"),
		jettison.WithSource("Example()"))
	// Wrap errors with additional metadata as they get passed down the stack.
	err = errors.Wrap(err, "something else went wrong",
		jettison.WithKeyValueString("another_key", "another_value"))
	// Pass it around - including over gRPC - like you would any other error.
}The jettison/log package provides structured JSON logging, with additional
utilities for logging jettison errors. You can attach metadata to logs in the
same manner as you attach metadata to errors.
import (
    "context"
    "github.com/luno/jettison"
    "github.com/luno/jettison/errors"
    "github.com/luno/jettison/log"
)
func ExampleInfo() {
	ctx := context.Background()
	// You can log general info as you normally would.
	log.Info(ctx, "entering the example function",
		jettison.WithKeyValueString("key", "value"))
}
func ExampleError() {
	ctx := context.Background()
	err := errors.New("a jettison error",
		jettison.WithKeyValueString("key", "value"),
		jettison.WithSource("Example()"))
	// Errors can be logged separately, with metadata marshalled to JSON in
	// a machine-friendly manner.
	log.Error(ctx, err,
		jettison.WithKeyValueString("another_key", "another_value"))
}An example log written via log.Info:
{
  "message": "entering the example function",
  "source": "jettison/example/example.go:9",
  "level": "info",
  "parameters": [
    {
      "key": "key",
      "value": "value"
    }
  ]
}An example log written via log.Error:
{
  "message": "a jettison error",
  "source": "jettison/example/example.go:18",
  "level": "error",
  "hops": [
    {
      "binary": "example",
      "errors": [
        {
          "message": "a jettison error",
          "source": "jettison/example/example.go:14",
          "parameters": [
            {
              "key": "key",
              "value": "value"
            }
          ]
        }
      ]
    }
  ],
  "parameters": [
    {
      "key": "key",
      "value": "value"
    },
    {
      "key": "another_key",
      "value": "another_value"
    }
  ]
}The jettison/j package contains aliases for common jettison options,
saving you a couple of keystrokes:
import (
    "github.com/luno/jettison/errors"
    "github.com/luno/jettison/j"
)
func ExampleKS() {
	err := errors.New("using j.KS",
		j.KS("string_key", "value"))
	fmt.Printf("%%+v: %+v\n", err)
	fmt.Printf("%%#v: %#v\n", err)
}
func ExampleKV() {
	err := errors.New("using j.KV",
		j.KV("int_key", 1))
	fmt.Printf("%%+v: %+v\n", err)
	fmt.Printf("%%#v: %#v\n", err)
}