This documentation is written for ACT3 developers who are creating and maintaining the Data Tool software.
Logging is done as JSONL so it is complete but harder for a human to easily parse. To convert them on the fly to colored and formatted text run it with 2> >(jq -j -f log.jq) at the end. The log.jq filter can be used with jq to pretty print the logs. So run ace-dt like so (in bash)
ace-dt bottle commit 2> >(jq -j -f log.jq)The Data team uses git.act3-ace.com/ace/go-common/pkg/logger, which uses slog internally.
- Logs should be JSONL formatted but other formats can be supported. They are not meant to be consumed by a user but rather by a developer or operator.
- The logging system is not part of the UX.
- If returning an error there is no need to call
log.Error()but you might want to add context withfmt.Errorf(). If handling an error or ignoring an error then uselog.Error(). - All errors from external packages (i.e., not ACT3 developed) should use
fmt.Errorf()to wrap the error so that the code point is easily located in our code base. The format string should not contain the verbs "error", "failed", "cannot", ... since these are implied. We want concise error messages. - At the creation of errors, the error should be logged (if a logger is available) with
log.Info(). - Use
cmd.Printx()to output text to the user if thecobra.Commandis available otherwise pass aio.Writerthen use it withfmt.FprintX(out, ...). Do not use the log to convey information to the user. - Do not use
fmt.Printx()(see the alternatives above). - We only use structured logging. This means that values are explicit and that the first argument to
log.Info()must always be a string literal (not a formatted string). The key values pairs follow. The keys should be string literals as well. The values are often variables. - We use
context.Contextto store the logger however we try to only pass the root logger (for this sub-system) in the context. We do not want many loggers stacked up in the context. - Logging levels are not predefined, and are considered relative to log level 0. No log is output at log level 0, but an API user may provide a higher base logging level, which means the log levels cannot be considered fixed. As a guideline, ace-dt's logging levels are generally organized in the following way:
- LevelError = 0
- LevelWarn = 4
- LevelInfo = 8
- LevelDebug = 12
- Logging levels are intended to be relative to a caller, for instance a consumer of the ace-dt code as an API may provide a logger with a higher level than 0. For cases where calls within ace-dt should have relative log levels, it is permissable to increment the log level and pass the updated logger through the context. In order to avoid unnecessary resource usage, this technique is not used commonly within ace-dt (As it causes a growing linked list in the context chain that must be traversed, and creates multiple instances of the logger object in memory)
- Use
log.WithName()sparingly. It should only be used for creating a new logger for a sub-system. - Avoid the use of
logr.NewContext()unless you really need to add a new logger to the context. Instead let the exiting logger be passed on through to the caller.
Example 1:
import (
"context"
"git.act3-ace.com/ace/go-common/pkg/logger"
)
func DoWork(ctx context.Context, a string) error {
log := logger.FromContext(ctx).With("a", a)
}Example 2:
This example shows logging with name; use sparingly
import (
"context"
"git.act3-ace.com/ace/go-common/pkg/logger"
)
func DoWork(ctx context.Context) error {
log := logger.FromContext(ctx).WithGroup("foxtrot")
}Example 3:
This example is from the CSI driver code base using the ace/data/tool code base
// logger is the root logger
// csiLog is used in csi-driver
csiLog := logger.WithName("csi-driver")
// aceDTLog has a name is one level less verbose
aceDTLog = logger.WithName("ace-dt").V(1)
// pull func from pkg/pull in ace-dt which takes a context and some pull args
pull.Pull(logr.NewContext(ctx, aceDTLog), ...pullargs)
// use csiLog for CSI related workTo run all the unit tests:
make test-goTo run coverage:
make coverTo test ace-dt by performing operations on a registry and ASCE Telemetry:
make test-functionalTo run functional tests manually:
make start-servicesThen, set -a; . test.env; set +a.
In the same shell, you will have the necessary environment variables exported to run the tests with:
go test ./...To enable debugging or running from within VSCode use the test.env file by adding the following to .vscode/settings.json:
{
"go.testEnvFile": "${workspaceFolder}/.env.test"
}To run the functional tests manually from the command line (e.g., go test ./...) it is helpful to install direnv then create a file called .envrc with the following:
dotenv_if_exists .env.test
PATH_bin binTo allow the .envrc file to be used, in the project's root run:
direnv allowThen, run:
make start-servicesThis updates the .env.test and direnv and automatically sets the TEST_REGISTRY and TEST_TELEMETRY in your current shell.
It also puts the projects bin directory first on the PATH so if you try to use ace-dt it will be using bin/ace-dt.
There are two options for integration testing.
Use make:
make test-goUse the ACT3 pipeline, which does integration testing for Data Tools interaction with ASCE Telemetry and registry (with auth) in the telemetry test job.
Integration testing for ace-dev to ACE Telemetry is done in the ace-dev test job.
Go Report Card provides Go language code quality reports. It can be run locally or as a web application.
Developers using this option for code quality reports should use the local option documented in the project's GitHub repository.
Using a local GitLab runner locally is useful for pre-merge testing.
Follow the GitLab documentation corresponding to your operating system to install a local runner.
The GitLab runner requires an executor; Data Tool uses Docker
For pre-merge testing, the following commands/steps are most useful:
- For functional test, use:
gitlab-runner exec docker 'functional test' - For unit test, use:
gitlab-runner exec docker 'unit test' - For golang-ci lint, use:
gitlab-runner exec docker 'golangci-lint'
Tips:
- The runner may fail if your repository has a shallow clone
- Avoid errors by running
git fetch --unshallow
- Avoid errors by running
- The runner fails when objects tracked by
git-lfsare not available locally- Avoid errors by installing
git-lfsand runninggit lfs pull
- Avoid errors by installing
Dependencies can be enforced with depguard.
Example usage:
To list the imports of a package (first-level dependencies) run:
go list -f '{{ join .Imports "\n" }}' ./pkg/transfer/To list the transitive dependencies of a package (all dependencies) run:
go list -f '{{ join .Deps "\n" }}' ./pkg/transfer/To initiate a release and create a release tag, run the CI pipeline, setting a variable DO_RELEASE to true. Ensure that at least one commit message has a fix or feat prefix, in order to trigger the semantic release process. For major version updates, at least one commit message must have BREAKING CHANGE in the commit message.