We follow a number of conventions in our source base to help us create something cohesive and coherent, which makes the source base easier to create, maintain, and use.
Others docs you should look at:
Follow the general guidance from Effective Go
Go Code Review Comments provides a good collection of common code review comments so you can get your code up to snuff before asking others to look at it.
Comment your code.
Command-line flags should use dashes, not underscores
Naming
Please consider package name when selecting an interface name, and avoid redundancy. For example, use adapter.AspectConfig
instead of adapter.AdapterConfig
.
Must use lowercase for Go package names.
Must use camel case for Go types.
Use singular nouns for types unless they represent a collection. Use plural nouns for collections.
Please consider parent directory name when choosing a package name:
adapters/factMapper/tracker.go
should say package factMapper
not package factmapperadapter
.
Unless there's a good reason, package names should match the name of the directory in which the .go file exists.
Importers can use a different name if they need to disambiguate.
Istio code is expected to use the standard Istio logging package. When importing external package to Istio, if these packages send logs to the standard Go "log" package or to the global zap logger, the log data will automatically be captured by the Istio log package and merged into the primary log stream(s) produced for the component. If other logging packages are used, their output will uncoordinated and unrelated to the primary log output, which isn't great for users of the component.
The Istio logging package supports four logging levels:
By default, errors, warnings, and informational data is emitted, while debug is only enabled when a component is started with particular options.
Good user facing errors/warnings are:
This advice applies to all log statements, but is especially critical for any message a user is expected to see and act upon. Code reviewers should and approvers should pay attention that log messages and errors follow these guidelines.
In many cases, it's preferable to use the error dictionary because it is reviewed by working groups that have expertise in writing good logging messages. The error dictionary also improves style consistency and maintainability.
The log package supports structured logging through the WithLabels() function, available both at scope and global level. Structured logging is useful for situations where some persistent context is desired for a set of log statements, especially if log output is interleaved due to concurrency.
Example:
// assume some local scope has been defined already in the package e.g. "installer" var ( scope = log.RegisterScope("installer", "Messages related to installation and upgrade", 0) ) func MyHandler(userID, contextID string) { // WithLabels returns a copy of scope so other "installer" logs are not affected. scope = scope.WithLabels("userID", userID, "contextID", contextID) scope.Debug("Something happened") }
Output:
<timestamp> debug installer Something happened userID=<some value> contextID=<some other value>
or JSON:
{
...
"msg": "Something happened"
"userID": "<some value>"
"contextID": "<some other value>"
}
Note: WithLabels() is a direct replacement for zap.Field structured logging:
// no longer supported scope.Debug("Hello", zap.String("foo", "bar") // use WithLabels() instead scope.WithLabels("foo", "bar").Debug("Hello")))
The log package output functions (Fatal*, Error*, Warn* etc.) now accept an optional first parameter, a *structured.Error type. Defining a structured.Error entry in the dictionary file is appropriate for most user facing errors. First, fill out all applicable fields in a structured.Error entry in the dictionary file:
var ( OperatorFailedToGetObjectFromAPIServer = &structured.Error{ MoreInfo: "Failed to get an object from the Kubernetes API server, because of a transient " + "API server error or the object no longer exists.", Impact: OperatorImpactFailedToGetObjectFromAPIServer, LikelyCause: formatCauses(LikelyCauseAPIServer) + " " + TransiencePermanentForInstall, Action: "If the error is because the object was deleted, it can be safely ignored. Otherwise, if the " + "error persists, " + ActionCheckBugList, )
This file contains macros and definitions for many common scenarios to reduce repeating the same thing in slightly different ways. Rather than writing verbose errors in an inconsistent way, many errors can be composed with existing common text constants. The second step is to actually use the error in a logging statement:
scope.Warnf(errdict.OperatorFailedToGetObjectFromAPIServer, "action X error: %v", err)
In this case, the logging is at Warn level because the error may be transient. The call will output the full error string in the logs:
Failed to get an object from the Kubernetes API server, because of a transient API server error
or the object no longer exists. If the error is transient, the impact is low. If permanent, updates
for the objects cannot be processed leading to an out of sync control plane. The likely cause is a
problem with the Kubernetes API server. If the error occurred immediately after installation, it is
likely permanent. If the error is because the object was deleted, it can be safely ignored. If this
error persists, see https://istio.io/latest/about/bugs/ for possible solutions.
The logging handler can output the dictionary error entry in a variety of formats depending on configuration. A likely future format will be a URL with error code, pointing to a page with the above paragraph.
The *structured.Error implements both the error#Error and error#Unwrap interfaces and can be returned from any function while retaining full type semantics when it's actually logged. This is important because error context often originates deep in the stack and can be lost at the logging site when the error is logged. For example:
func MainFunction() { if err := calledFunction(); err != nil { var ie *structured.Error # logger must call As() to retrieve wrapped error errors.As(eWrap, &ie) s.Errorf(ie, "calledFunction returned an error: %v", err) } ... } func calledFunction() error { ... // NOTE: %w verb must be used with annotations. See https://blog.golang.org/go1.13-errors. if err := deeperCalledFuction(); err != nil { return fmt.Errorf("deeperCalledFunction returned an error: %w", err) } } func deeperCalledFuction() error { ... return errdict.OperatorFailedToGetObjectFromAPIServer }
The Istio logging package is built on top of the Zap logger and thus inherits its efficiency and flexibility. In any high performance paths, prefer to use the Error, Warn, Info, and Debug methods, as these are the most efficient. All other variations of these four calls end up triggering some memory allocations and have a considerably higher execution time.
If you need to do a fair bit of computation in order to produce data for logging, you should protect that code using the ErrorEnabled, WarnEnabled, InfoEnabled, and DebugEnabled methods.
Be careful not to introduce expensive computations as part of the evaluation of a log statement. For example:
log.Debug(fmt.Sprintf("%s %s %d", s1, s2, i1))
Even when debug-level logs are disabled, the fmt.Sprintf call will still execute. Instead, you should write:
if log.DebugEnabled() { log.Debug(fmt.Sprintf("%s %s %d", s1, s2, i1)) }
Note that Mixer adapters don't use the standard Istio logging package. Instead, they are expected to use the adapter logger interface.
For Go code, unit tests are written using the standard Go testing package.
All new packages and most new significant functionality must come with unit tests with code coverage >98%
Table-driven tests are preferred for testing multiple scenarios/inputs
Significant features should come with integration and/or end-to-end tests
Tests must be robust. In particular, don't assume that async operations will complete promptly just because it's a test. Put proper synchronization in place or if not possible put in place some retry logic.
A flaky test is a test that non-deterministically fails. Most of these are caused by timing sensitive tests. Avoiding code that makes assumptions like time.Sleep
in favor of more robust solutions, like polling or pushing.
If you see a flaky test, file an issue - or even better, a PR fixing the issue. Flaky tests have a large impact on developer and CI productivity.
To reproduce test flakes locally, use the stress tool. For example, to test the TestFoo
test:
$ go get -u golang.org/x/tools/cmd/stress $ go test -c -race -o stress.test ./my/test/package $ stress -p 256 ./stress.test -test.run=TestFoo
This will run the TestFoo test repeatedly with parallelism determined by the -p
flag.
To see how frequently a test has flaked, use https://eng.istio.io/flakes.
Directory and file conventionsAvoid package sprawl. Find an appropriate subdirectory for new packages.
Avoid general utility packages. Packages called "util" are suspect. Instead, derive a name that describes your desired function.
Use lowercase for file and directory names.
Use underscores for Go file names, e.g. type DeviceAllocator
must reside in device_allocator.go
.
All directory names should be singular unless required by existing frameworks. This is to avoid mixed singular and plural names in the full paths. NOTE: Traditional Unix directory names are often singular, such as "/usr/bin".
Third-party code
Go code for normal third-party dependencies is managed by the Go Dep.
Third-party code must carry licenses. This includes modified third-party code and excerpts.
When possible, avoid making changes that do not bring tangible benefit to Istio users or developers. For example, a change like this that reorders code with no benefit:
These types of changes are not harmless.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4