Go standards and style guidelines
This document describes various guidelines and best practices for GitLab projects using the Go language.
GitLab is built on top of Ruby on Rails, but we're also using Go for projects where it makes sense. Go is a very powerful language, with many advantages, and is best suited for projects with a lot of IO (disk/network access), HTTP requests, parallel processing, etc. Since we have both Ruby on Rails and Go at GitLab, we should evaluate carefully which of the two is best for the job.
This page aims to define and organize our Go guidelines, based on our various
experiences. Several projects were started with different standards and they
can still have specifics. They will be described in their respective
We follow the common principles of Go Code Review Comments.
Reviewers and maintainers should pay attention to:
deferfunctions: ensure the presence when needed, and after
- Inject dependencies as parameters.
- Void structs when marshaling to JSON (generates
Security is our top priority at GitLab. During code reviews, we must take care of possible security breaches in our code:
- XSS when using text/template
- CSRF Protection using Gorilla
- Use a Go version without known vulnerabilities
- Don't leak secret tokens
- SQL injections
Web servers can take advantages of middlewares like Secure.
Finding a reviewer
Many of our projects are too small to have full-time maintainers. That's why we have a shared pool of Go reviewers at GitLab. To find a reviewer, use the Engineering Projects page in the handbook. "GitLab Community Edition (CE)" and "GitLab Community Edition (EE)" both have a "Go" section with its list of reviewers.
To add yourself to this list, add the following to your profile in the team.yml file and ask your manager to review and merge.
projects: gitlab-ee: reviewer go gitlab-ce: reviewer go
Code style and format
- Avoid global variables, even in packages. By doing so you will introduce side effects if the package is included multiple times.
go fmtbefore committing (Gofmt is a tool that automatically formats Go source code).
All Go projects should include these GitLab CI/CD jobs:
go lint: image: golang:1.11 script: - go get -u golang.org/x/lint/golint - golint -set_exit_status
Dependencies should be kept to the minimum. The introduction of a new dependency should be argued in the merge request, as per our Approval Guidelines. Both License Management [ULTIMATE] and Dependency Scanning [ULTIMATE] should be activated on all projects to ensure new dependencies security status and license compatibility.
Since Go 1.11, a standard dependency system is available behind the name Go Modules. It provides a way to define and lock dependencies for reproducible builds. It should be used whenever possible.
There was a bug on modules
checksums in Go < v1.11.4, so make
sure to use at least this version to avoid
checksum mismatch errors.
We don't use object-relational mapping libraries (ORMs) at GitLab (except ActiveRecord in Ruby on Rails). Projects can be structured with services to avoid them. PQ should be enough to interact with PostgreSQL databases.
In the rare event of managing a hosted database, it's necessary to use a
migration system like ActiveRecord is providing. A simple library like
Journey, designed to be used in
postgres containers, can be deployed as long-running pods. New versions will
deploy a new pod, migrating the data automatically.
We should not use any specific library or framework for testing, as the standard library provides already everything to get started. For example, some external dependencies might be worth considering in case we decide to use a specific library or framework:
Use subtests whenever possible to improve code readability and test output.
Programs handling a lot of IO or complex operations should always include benchmarks, to ensure performance consistency over time.
Every Go program is launched from the command line.
cli is a convenient package to create command
line apps. It should be used whether the project is a daemon or a simple cli
tool. Flags can be mapped to environment
which documents and centralizes at the same time all the possible command line
interactions with the program. Don't use
os.GetEnv, it hides variables deep
in the code.
The usage of a logging library is strongly recommended for daemons. Even
though there is a
log package in the standard library, we generally use
Logrus. Its plugin ("hooks") system
makes it a powerful logging library, with the ability to add notifiers and
formatters at the logger level directly.
Structured (JSON) logging
Every binary ideally must have structured (JSON) logging in place as it helps with searching and filtering the logs. At GitLab we use structured logging in JSON format, as all our infrastructure assumes that. When using Logrus you can turn on structured logging simply by using the build in JSON formatter. This follows the same logging type we use in our Ruby applications.
How to use Logrus
There are a few guidelines one should follow when using the Logrus package:
- When printing an error use
logrus.WithError(err).Error("Failed to do something").
- Since we use structured logging we can log
fields in the context of that code path, such as the URI of the request using
WithFields. For example,
logrus.WithField("file", "/app/go).Info("Opening dir"). If you have to log multiple keys, always use
WithFieldsinstead of calling
WithFieldmore than once.
Tracing and Correlation
LabKit is a place to keep common libraries for Go services. Currently it's vendored into two projects: Workhorse and Gitaly, and it exports two main (but related) pieces of functionality:
gitlab.com/gitlab-org/labkit/correlation: for propagating and extracting correlation ids between services.
gitlab.com/gitlab-org/labkit/tracing: for instrumenting Go libraries for distributed tracing.
This gives us a thin abstraction over underlying implementations that is
consistent across Workhorse, Gitaly, and, in future, other Go servers. For
example, in the case of
gitlab.com/gitlab-org/labkit/tracing we can switch
from using Opentracing directly to using Zipkin or Gokit's own tracing wrapper
without changes to the application code, while still keeping the same
consistent configuration mechanism (i.e. the
Since daemons are long-running applications, they should have mechanisms to manage cancellations, and avoid unnecessary resources consumption (which could lead to DDOS vulnerabilities). Go Context should be used in functions that can block and passed as the first parameter.
Every project should have a
Dockerfile at the root of their repository, to
build and run the project. Since Go program are static binaries, they should
not require any external dependency, and shells in the final image are useless.
We encourage Multistage
- They let the user build the project with the right Go version and dependencies.
- They generate a small, self-contained image, derived from
Generated docker images should have the program at their
Entrypoint to create
portable commands. That way, anyone can run the image, and without parameters
it will display its help message (if
cli has been used).