User:Giuseppe Lavagetto/Golang for SRE

From Wikitech
Jump to navigation Jump to search

This page is supposed to be a draft of a simple set of general rules for writing applications in golang for SRE usage.

This is not:

  • A style guide
  • A resource on how to write software in go

Consider it an attempt at standardizing how we structure our projects.

In the reminder, we will refer to an example application, xkcli - a cli tool (and library!) to find XKCD comic strips about a topic

Application scaffolding using cobra

  1. Use cobra to generate your application:
$ go get
$ cobra init --pkg-name --author "Giuseppe Lavagetto" --license Apache

This will generate a basic directory tree for you:

├── cmd
│   └── root.go
└── main.go

You can now add subcommands, if you like. We want to support three commands:

  • search [--lucky] [--idOnly] <search_term> to search the archive,
  • refresh to refresh the archive,
  • print [--url|--image|--text] <xkcd n> to print the desired part of the comic strip to stdout

We thus run cobra add <cmd>:

$ cobra add search
search created at /home/joe/Code/xkcli

This creates the subcommands:

├── cmd
│   ├── print.go
│   ├── refresh.go
│   ├── root.go
│   └── search.go
└── main.go

Now we'll need to add the appropriate flags to each command. Please refer to the cobra readme for an introduction.

Note that an application setup with cobra also comes with the awesome configuration manager called viper, that allows to read configurations in various formats, and from different sources (including etcd and consul), and to handle watching the configuration for changes.

Setup logging

Use Uber's zap which combines good structured and unstructured logs support, and performs very well. It should be conventional to use --debug as a command-line parameter for all commands, and make logging work in the same way.

So we will have to add a flag, and a simple helper function to our main.go

var debugLog bool
// Helper function
func setupLogging(isDebug bool) *zap.Logger {
	var log *zap.Logger
	var err error
	if isDebug {
		// level = debug, format human-readable
		log, err = zap.NewDevelopment()
	} else {
		// level = info, json
		log, err = zap.NewProduction()
	if err != nil {
	return log

func init() {
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.xkcli.yaml)")
	rootCmd.PersistentFlags().BoolVar(&debugLog, "debug", false, "Show more logs.")

And then call it from the subcommands' Run function:

var refreshCmd = &cobra.Command{
	Use:   "refresh",
	Short: "Download the info about the missing strips.",
	Long: `xkcli refresh will refresh the local database of strips, 
fetching all the  relative metadata.`,
	Run: func(cmd *cobra.Command, args []string) {
		logger := setupLogging(debugLog)
		defer logger.Sync()
		log := logger.Sugar()
		log.Debug("Showing logs at debug level")

go mod configuration

Once we've added our flags to one command, it's time to add the go mod configuration and vendorize your dependencies:

xkcli$ go mod init
xkcli$ go mod vendor
go: finding module for package

Please note: having go.mod configured in your project is useful even if you decide to build the application using the dreaded Debian conventions in production.

In that case:

  • Add the vendor directory to .gitignore
  • Pin the packages versions to what is available in Debian
  • Package all your missing dependencies (ahah)


The importance of testing early in your development process (or - just do TDD) cannot be stressed enough in go. If you don't do that, you might end up with mostly untestable code pretty quickly, in particular if you're not an experienced go programmer (or at least - it happened over and over to me!).

Use the builtin testing library, although the additions from testify, like assert, require and mock are nice, and something you're probably used to in other languages.