diff --git a/CHANGELOG.md b/CHANGELOG.md index 34e6f464713..4aef3c1afb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added +- Added `ErrorHandlerFunc` to use a function as an `"go.opentelemetry.io/otel".ErrorHandler`. (#2149) + ### Changed ### Deprecated diff --git a/Makefile b/Makefile index 7df5424e256..939e2c136d6 100644 --- a/Makefile +++ b/Makefile @@ -40,8 +40,8 @@ $(TOOLS)/%: | $(TOOLS) cd $(TOOLS_MOD_DIR) && \ $(GO) build -o $@ $(PACKAGE) -SEMCONVGEN = $(TOOLS)/semconv-gen -$(TOOLS)/semconv-gen: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/semconv-gen +SEMCONVGEN = $(TOOLS)/semconvgen +$(TOOLS)/semconvgen: PACKAGE=go.opentelemetry.io/build-tools/semconvgen CROSSLINK = $(TOOLS)/crosslink $(TOOLS)/crosslink: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/crosslink diff --git a/RELEASING.md b/RELEASING.md index 215e5c872bd..730436672c8 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -3,8 +3,8 @@ ## Semantic Convention Generation If a new version of the OpenTelemetry Specification has been released it will be necessary to generate a new -semantic convention package from the YAML definitions in the specification repository. There is a utility in -`internal/tools/semconv-gen` that can be used to generate the a package with the name matching the specification +semantic convention package from the YAML definitions in the specification repository. There is a `semconvgen` utility +installed by `make tools` that can be used to generate the a package with the name matching the specification version number under the `semconv` package. This will ideally be done soon after the specification release is tagged. Make sure that the specification repo contains a checkout of the the latest tagged release so that the generated files match the released semantic conventions. @@ -12,9 +12,8 @@ generated files match the released semantic conventions. There are currently two categories of semantic conventions that must be generated, `resource` and `trace`. ``` -cd internal/tools/semconv-gen -go run generator.go -i /path/to/specification/repo/semantic_conventions/resource -go run generator.go -i /path/to/specification/repo/semantic_conventions/trace +.tools/semconvgen -i /path/to/specification/repo/semantic_conventions/resource -t semconv/template.j2 +.tools/semconvgen -i /path/to/specification/repo/semantic_conventions/trace -t semconv/template.j2 ``` Using default values for all options other than `input` will result in using the `template.j2` template to diff --git a/bridge/opencensus/test/bridge_test.go b/bridge/opencensus/test/bridge_test.go index 79e13233c94..4caa7e79025 100644 --- a/bridge/opencensus/test/bridge_test.go +++ b/bridge/opencensus/test/bridge_test.go @@ -267,27 +267,24 @@ func TestSetThings(t *testing.T) { if v := aeAttrs[attribute.Key("string")]; v.AsString() != "annotateval" { t.Errorf("Got annotateEvent.Attributes[string] = %v, expected annotateval", v.AsString()) } - - uncompressedKey := attribute.Key("uncompressed byte size") - compressedKey := attribute.Key("compressed byte size") seAttrs := attrsMap(sendEvent.Attributes) reAttrs := attrsMap(receiveEvent.Attributes) - if sendEvent.Name != "message send" { + if sendEvent.Name != internal.MessageSendEvent { t.Errorf("Got sendEvent.Name = %v, expected message send", sendEvent.Name) } - if v := seAttrs[uncompressedKey]; v.AsInt64() != 456 { + if v := seAttrs[internal.UncompressedKey]; v.AsInt64() != 456 { t.Errorf("Got sendEvent.Attributes[uncompressedKey] = %v, expected 456", v.AsInt64()) } - if v := seAttrs[compressedKey]; v.AsInt64() != 789 { + if v := seAttrs[internal.CompressedKey]; v.AsInt64() != 789 { t.Errorf("Got sendEvent.Attributes[compressedKey] = %v, expected 789", v.AsInt64()) } - if receiveEvent.Name != "message receive" { + if receiveEvent.Name != internal.MessageReceiveEvent { t.Errorf("Got receiveEvent.Name = %v, expected message receive", receiveEvent.Name) } - if v := reAttrs[uncompressedKey]; v.AsInt64() != 135 { + if v := reAttrs[internal.UncompressedKey]; v.AsInt64() != 135 { t.Errorf("Got receiveEvent.Attributes[uncompressedKey] = %v, expected 135", v.AsInt64()) } - if v := reAttrs[compressedKey]; v.AsInt64() != 369 { + if v := reAttrs[internal.CompressedKey]; v.AsInt64() != 369 { t.Errorf("Got receiveEvent.Attributes[compressedKey] = %v, expected 369", v.AsInt64()) } } diff --git a/error_handler.go b/error_handler.go index a51b2dee4e9..72fad85412b 100644 --- a/error_handler.go +++ b/error_handler.go @@ -25,3 +25,14 @@ type ErrorHandler interface { // DO NOT CHANGE: any modification will not be backwards compatible and // must never be done outside of a new major release. } + +// ErrorHandlerFunc is a convenience adapter to allow the use of a function +// as an ErrorHandler. +type ErrorHandlerFunc func(error) + +var _ ErrorHandler = ErrorHandlerFunc(nil) + +// Handle handles the irremediable error by calling the ErrorHandlerFunc itself. +func (f ErrorHandlerFunc) Handle(err error) { + f(err) +} diff --git a/internal/tools/go.mod b/internal/tools/go.mod index b792670e161..ec7626c9e01 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -7,8 +7,7 @@ require ( github.com/gogo/protobuf v1.3.2 github.com/golangci/golangci-lint v1.41.1 github.com/itchyny/gojq v0.12.4 - github.com/spf13/pflag v1.0.5 - golang.org/x/mod v0.4.2 + go.opentelemetry.io/build-tools/semconvgen v0.0.0-20210730171444-520d53fe242d golang.org/x/tools v0.1.5 ) diff --git a/internal/tools/go.sum b/internal/tools/go.sum index 0a5e01b6197..52808978d80 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -670,6 +670,10 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/build-tools v0.0.0-20210719163622-92017e64f35b h1:tFMjUqEDGM2F82663yYidqTluwEJmmihk/AXr19J7rI= +go.opentelemetry.io/build-tools v0.0.0-20210719163622-92017e64f35b/go.mod h1:zZRrJN8qdwDdPNkCEyww4SW54mM1Da0v9H3TyQet9T4= +go.opentelemetry.io/build-tools/semconvgen v0.0.0-20210730171444-520d53fe242d h1:EjYSijh2wWPht3w0Zfa6V7280z5iDBr1228TysQgurM= +go.opentelemetry.io/build-tools/semconvgen v0.0.0-20210730171444-520d53fe242d/go.mod h1:QJyAzHKDKGDl52AUIAZhWKx3nOPBZJ3dD44utso2FPE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= diff --git a/internal/tools/semconv-gen/generator.go b/internal/tools/semconv-gen/generator.go deleted file mode 100644 index c54b7f537bf..00000000000 --- a/internal/tools/semconv-gen/generator.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "log" - "os" - "os/exec" - "path" - "regexp" - "sort" - "strings" - - flag "github.com/spf13/pflag" - "golang.org/x/mod/semver" - - "go.opentelemetry.io/otel/internal/tools" -) - -func main() { - // Plain log output, no timestamps. - log.SetFlags(0) - - cfg := config{} - flag.StringVarP(&cfg.inputPath, "input", "i", "", "Path to semantic convention definition YAML. Should be a directory in the specification git repository.") - flag.StringVarP(&cfg.specVersion, "specver", "s", "", "Version of semantic convention to generate. Must be an existing version tag in the specification git repository.") - flag.StringVarP(&cfg.outputPath, "output", "o", "", "Path to output target. Must be either an absolute path or relative to the repository root. If unspecified will output to a sub-directory with the name matching the version number specified via --specver flag.") - flag.StringVarP(&cfg.containerImage, "container", "c", "otel/semconvgen", "Container image ID") - flag.StringVarP(&cfg.outputFilename, "filename", "f", "", "Filename for templated output. If not specified 'basename(inputPath).go' will be used.") - flag.StringVarP(&cfg.templateFilename, "template", "t", "template.j2", "Template filename") - flag.Parse() - - cfg, err := validateConfig(cfg) - if err != nil { - fmt.Println(err) - flag.Usage() - os.Exit(-1) - } - - err = render(cfg) - if err != nil { - panic(err) - } - - err = fixIdentifiers(cfg) - if err != nil { - panic(err) - } - - err = format(cfg.outputFilename) - if err != nil { - panic(err) - } -} - -type config struct { - inputPath string - outputPath string - outputFilename string - templateFilename string - containerImage string - specVersion string -} - -func validateConfig(cfg config) (config, error) { - if cfg.inputPath == "" { - return config{}, errors.New("input path must be provided") - } - - if cfg.outputFilename == "" { - cfg.outputFilename = fmt.Sprintf("%s.go", path.Base(cfg.inputPath)) - } - - if cfg.specVersion == "" { - // Find the latest version of the specification and use it for generation. - var err error - cfg.specVersion, err = findLatestSpecVersion(cfg) - if err != nil { - return config{}, err - } - } - - if cfg.outputPath == "" { - // If output path is unspecified put it under a sub-directory with a name matching - // the version of semantic convention under the semconv directory. - cfg.outputPath = path.Join("semconv", cfg.specVersion) - } - - if !path.IsAbs(cfg.outputPath) { - root, err := tools.FindRepoRoot() - if err != nil { - return config{}, err - } - cfg.outputPath = path.Join(root, cfg.outputPath) - } - - cfg.outputFilename = path.Join(cfg.outputPath, cfg.outputFilename) - - if !path.IsAbs(cfg.templateFilename) { - pwd, err := os.Getwd() - if err != nil { - return config{}, err - } - cfg.templateFilename = path.Join(pwd, cfg.templateFilename) - } - - return cfg, nil -} - -func render(cfg config) error { - tmpDir, err := os.MkdirTemp("", "otel_semconvgen") - if err != nil { - return fmt.Errorf("unable to create temporary directory: %w", err) - } - defer os.RemoveAll(tmpDir) - - specCheckoutPath := path.Join(tmpDir, "input") - err = os.Mkdir(specCheckoutPath, 0700) - if err != nil { - return fmt.Errorf("unable to create input directory: %w", err) - } - - outputPath := path.Join(tmpDir, "output") - err = os.Mkdir(outputPath, 0700) - if err != nil { - return fmt.Errorf("unable to create output directory: %w", err) - } - - // Checkout the specification repo to a temp dir. This will be the input - // for the generator. - doneFunc, err := checkoutSpecToDir(cfg, specCheckoutPath) - if err != nil { - return err - } - defer doneFunc() - - err = exec.Command("cp", cfg.templateFilename, tmpDir).Run() - if err != nil { - return fmt.Errorf("unable to copy template to temp directory: %w", err) - } - - cmd := exec.Command("docker", "run", "--rm", - "-v", fmt.Sprintf("%s:/data", tmpDir), - cfg.containerImage, - "--yaml-root", path.Join("/data/input/semantic_conventions/", path.Base(cfg.inputPath)), - "code", - "--template", path.Join("/data", path.Base(cfg.templateFilename)), - "--output", path.Join("/data/output", path.Base(cfg.outputFilename)), - ) - err = cmd.Run() - if err != nil { - return fmt.Errorf("unable to render template: %w", err) - } - - err = os.MkdirAll(cfg.outputPath, 0700) - if err != nil { - return fmt.Errorf("unable to create output directory %s: %w", cfg.outputPath, err) - } - err = exec.Command("cp", path.Join(tmpDir, "output", path.Base(cfg.outputFilename)), cfg.outputPath).Run() - if err != nil { - return fmt.Errorf("unable to copy result to target: %w", err) - } - - return nil -} - -type semVerSlice []string - -func (s semVerSlice) Len() int { - return len(s) -} - -func (s semVerSlice) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -func (s semVerSlice) Less(i, j int) bool { - return semver.Compare(s[i], s[j]) < 0 -} - -// findLatestSpecVersion finds the latest specification version number and checkouts -// that version in the repo's working directory. -func findLatestSpecVersion(cfg config) (string, error) { - // List all tags in the specification repo. All released version numbers are tags - // in the repo. - cmd := exec.Command("git", "tag") - // The specification repo is in cfg.inputPath. - cmd.Dir = cfg.inputPath - output, err := cmd.Output() - if err != nil { - return "", fmt.Errorf("unable to exec %s: %w", cmd.String(), err) - } - - // Split the output: each line is a tag. - lines := strings.Split(string(output), "\n") - - // Copy valid semver version numbers to a slice. - var versions semVerSlice - for _, line := range lines { - ver := line - if semver.IsValid(ver) { - versions = append(versions, ver) - } - } - - // Sort it according to semver rules. - sort.Sort(versions) - - if len(versions) == 0 { - return "", fmt.Errorf("no version tags found in the specification repo at %s", cfg.inputPath) - } - - // Use the latest version number. - lastVer := versions[len(versions)-1] - return lastVer, nil -} - -// checkoutSpecToDir checks out the specification repository to the toDir. -// Returned doneFunc should be called when the directory is no longer needed and can be -// cleaned up. -func checkoutSpecToDir(cfg config, toDir string) (doneFunc func(), err error) { - // Checkout the selected tag to make sure we use the correct version of semantic - // convention yaml files as the input. We will checkout the worktree to a temporary toDir. - cmd := exec.Command("git", "worktree", "add", toDir, cfg.specVersion) - // The specification repo is in cfg.inputPath. - cmd.Dir = cfg.inputPath - err = cmd.Run() - if err != nil { - return nil, fmt.Errorf("unable to exec %s: %w", cmd.String(), err) - } - - doneFunc = func() { - // Remove the worktree when it is no longer needed. - cmd := exec.Command("git", "worktree", "remove", "-f", toDir) - cmd.Dir = cfg.inputPath - err := cmd.Run() - if err != nil { - log.Printf("Could not cleanup spec repo worktree, unable to exec %s: %s\n", cmd.String(), err.Error()) - } - } - - return doneFunc, nil -} - -var capitalizations = []string{ - "ACL", - "AIX", - "AKS", - "AMD64", - "API", - "ARM32", - "ARM64", - "ARN", - "ARNs", - "ASCII", - "AWS", - "CPP", - "CPU", - "CSS", - "DB", - "DC", - "DNS", - "EC2", - "ECS", - "EDB", - "EKS", - "EOF", - "GCP", - "GRPC", - "GUID", - "HPUX", - "HSQLDB", - "HTML", - "HTTP", - "HTTPS", - "IA64", - "ID", - "IP", - "JDBC", - "JSON", - "K8S", - "LHS", - "MSSQL", - "OS", - "PHP", - "PID", - "PPC32", - "PPC64", - "QPS", - "QUIC", - "RAM", - "RHS", - "RPC", - "SDK", - "SLA", - "SMTP", - "SPDY", - "SQL", - "SSH", - "TCP", - "TLS", - "TTL", - "UDP", - "UID", - "UI", - "UUID", - "URI", - "URL", - "UTF8", - "VM", - "XML", - "XMPP", - "XSRF", - "XSS", - "ZOS", - "CronJob", - "WebEngine", - "MySQL", - "PostgreSQL", - "MariaDB", - "MaxDB", - "FirstSQL", - "InstantDB", - "HBase", - "MongoDB", - "CouchDB", - "CosmosDB", - "DynamoDB", - "HanaDB", - "FreeBSD", - "NetBSD", - "OpenBSD", - "DragonflyBSD", - "InProc", - "FaaS", -} - -// These are not simple capitalization fixes, but require string replacement. -// All occurrences of the key will be replaced with the corresponding value. -var replacements = map[string]string{ - "RedisDatabase": "RedisDB", - "IPTCP": "TCP", - "IPUDP": "UDP", - "Lineno": "LineNumber", -} - -func fixIdentifiers(cfg config) error { - data, err := ioutil.ReadFile(cfg.outputFilename) - if err != nil { - return fmt.Errorf("unable to read file: %w", err) - } - - for _, init := range capitalizations { - // Match the title-cased capitalization target, asserting that its followed by - // either a capital letter, whitespace, a digit, or the end of text. - // This is to avoid, e.g., turning "Identifier" into "IDentifier". - re := regexp.MustCompile(strings.Title(strings.ToLower(init)) + `([A-Z\s\d]|\b|$)`) - // RE2 does not support zero-width lookahead assertions, so we have to replace - // the last character that may have matched the first capture group in the - // expression constructed above. - data = re.ReplaceAll(data, []byte(init+`$1`)) - } - - for cur, repl := range replacements { - data = bytes.ReplaceAll(data, []byte(cur), []byte(repl)) - } - - // Inject the correct import path. - packageDir := path.Base(path.Dir(cfg.outputFilename)) - importPath := fmt.Sprintf(`"go.opentelemetry.io/otel/semconv/%s"`, packageDir) - data = bytes.ReplaceAll(data, []byte(`[[IMPORTPATH]]`), []byte(importPath)) - - err = ioutil.WriteFile(cfg.outputFilename, data, 0644) - if err != nil { - return fmt.Errorf("unable to write updated file: %w", err) - } - - return nil -} - -func format(fn string) error { - cmd := exec.Command("gofmt", "-w", "-s", fn) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - return fmt.Errorf("unable to format updated file: %w", err) - } - - return nil -} diff --git a/internal/tools/tools.go b/internal/tools/tools.go index c86ce2fdc90..873343bf4db 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -21,5 +21,6 @@ import ( _ "github.com/gogo/protobuf/protoc-gen-gogofast" _ "github.com/golangci/golangci-lint/cmd/golangci-lint" _ "github.com/itchyny/gojq" + _ "go.opentelemetry.io/build-tools/semconvgen" _ "golang.org/x/tools/cmd/stringer" ) diff --git a/internal/tools/semconv-gen/template.j2 b/semconv/template.j2 similarity index 100% rename from internal/tools/semconv-gen/template.j2 rename to semconv/template.j2 diff --git a/trace.go b/trace.go index b1e8e56bd78..28b4f5e4d82 100644 --- a/trace.go +++ b/trace.go @@ -31,9 +31,9 @@ func Tracer(name string, opts ...trace.TracerOption) trace.Tracer { // If none is registered then an instance of NoopTracerProvider is returned. // // Use the trace provider to create a named tracer. E.g. -// tracer := global.GetTracerProvider().Tracer("example.com/foo") +// tracer := otel.GetTracerProvider().Tracer("example.com/foo") // or -// tracer := global.Tracer("example.com/foo") +// tracer := otel.Tracer("example.com/foo") func GetTracerProvider() trace.TracerProvider { return global.TracerProvider() } diff --git a/trace/config.go b/trace/config.go index 0ecb4dcb891..87fb4839310 100644 --- a/trace/config.go +++ b/trace/config.go @@ -151,10 +151,10 @@ func (cfg *EventConfig) Timestamp() time.Time { return cfg.timestamp } -// NewEventConfig applies all the EventOptions to a returned SpanConfig. If no -// timestamp option is passed, the returned SpanConfig will have a Timestamp +// NewEventConfig applies all the EventOptions to a returned EventConfig. If no +// timestamp option is passed, the returned EventConfig will have a Timestamp // set to the call time, otherwise no validation is performed on the returned -// SpanConfig. +// EventConfig. func NewEventConfig(options ...EventOption) *EventConfig { c := new(EventConfig) for _, option := range options { diff --git a/version.go b/version.go index 69bb4673138..f8e0f3f8f0e 100644 --- a/version.go +++ b/version.go @@ -16,5 +16,5 @@ package otel // import "go.opentelemetry.io/otel" // Version is the current release version of OpenTelemetry in use. func Version() string { - return "1.0.0-RC1" + return "1.0.0-RC2" }