update OpenTelemetry dependencies and grpc

This update dropped the otelgrpc → cloud.google.com/go/compute dependency,
among others. This dropped out because genproto cleaned up it's dependencies
on google cloud libraries, and otel updated - details in #113366.

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
Co-Authored-By: David Ashpole <dashpole@google.com>
This commit is contained in:
Davanum Srinivas
2024-07-05 12:10:07 -04:00
parent 5837de2796
commit ff7942be83
449 changed files with 32426 additions and 11880 deletions

View File

@@ -1,4 +1,4 @@
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Coverage Status][coveralls image]][coveralls]
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
@@ -21,8 +21,6 @@ Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
[travis]: https://travis-ci.org/cenkalti/backoff
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master

View File

@@ -71,6 +71,9 @@ type Clock interface {
Now() time.Time
}
// ExponentialBackOffOpts is a function type used to configure ExponentialBackOff options.
type ExponentialBackOffOpts func(*ExponentialBackOff)
// Default values for ExponentialBackOff.
const (
DefaultInitialInterval = 500 * time.Millisecond
@@ -81,7 +84,7 @@ const (
)
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
func NewExponentialBackOff() *ExponentialBackOff {
func NewExponentialBackOff(opts ...ExponentialBackOffOpts) *ExponentialBackOff {
b := &ExponentialBackOff{
InitialInterval: DefaultInitialInterval,
RandomizationFactor: DefaultRandomizationFactor,
@@ -91,10 +94,62 @@ func NewExponentialBackOff() *ExponentialBackOff {
Stop: Stop,
Clock: SystemClock,
}
for _, fn := range opts {
fn(b)
}
b.Reset()
return b
}
// WithInitialInterval sets the initial interval between retries.
func WithInitialInterval(duration time.Duration) ExponentialBackOffOpts {
return func(ebo *ExponentialBackOff) {
ebo.InitialInterval = duration
}
}
// WithRandomizationFactor sets the randomization factor to add jitter to intervals.
func WithRandomizationFactor(randomizationFactor float64) ExponentialBackOffOpts {
return func(ebo *ExponentialBackOff) {
ebo.RandomizationFactor = randomizationFactor
}
}
// WithMultiplier sets the multiplier for increasing the interval after each retry.
func WithMultiplier(multiplier float64) ExponentialBackOffOpts {
return func(ebo *ExponentialBackOff) {
ebo.Multiplier = multiplier
}
}
// WithMaxInterval sets the maximum interval between retries.
func WithMaxInterval(duration time.Duration) ExponentialBackOffOpts {
return func(ebo *ExponentialBackOff) {
ebo.MaxInterval = duration
}
}
// WithMaxElapsedTime sets the maximum total time for retries.
func WithMaxElapsedTime(duration time.Duration) ExponentialBackOffOpts {
return func(ebo *ExponentialBackOff) {
ebo.MaxElapsedTime = duration
}
}
// WithRetryStopDuration sets the duration after which retries should stop.
func WithRetryStopDuration(duration time.Duration) ExponentialBackOffOpts {
return func(ebo *ExponentialBackOff) {
ebo.Stop = duration
}
}
// WithClockProvider sets the clock used to measure time.
func WithClockProvider(clock Clock) ExponentialBackOffOpts {
return func(ebo *ExponentialBackOff) {
ebo.Clock = clock
}
}
type systemClock struct{}
func (t systemClock) Now() time.Time {

View File

@@ -70,3 +70,5 @@ benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
- [Ristretto](https://github.com/dgraph-io/ristretto)
- [Badger](https://github.com/dgraph-io/badger)

View File

@@ -19,10 +19,13 @@ const (
// Store the primes in an array as well.
//
// The consts are used when possible in Go code to avoid MOVs but we need a
// contiguous array of the assembly code.
// contiguous array for the assembly code.
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
// Digest implements hash.Hash64.
//
// Note that a zero-valued Digest is not ready to receive writes.
// Call Reset or create a Digest using New before calling other methods.
type Digest struct {
v1 uint64
v2 uint64
@@ -33,19 +36,31 @@ type Digest struct {
n int // how much of mem is used
}
// New creates a new Digest that computes the 64-bit xxHash algorithm.
// New creates a new Digest with a zero seed.
func New() *Digest {
return NewWithSeed(0)
}
// NewWithSeed creates a new Digest with the given seed.
func NewWithSeed(seed uint64) *Digest {
var d Digest
d.Reset()
d.ResetWithSeed(seed)
return &d
}
// Reset clears the Digest's state so that it can be reused.
// It uses a seed value of zero.
func (d *Digest) Reset() {
d.v1 = primes[0] + prime2
d.v2 = prime2
d.v3 = 0
d.v4 = -primes[0]
d.ResetWithSeed(0)
}
// ResetWithSeed clears the Digest's state so that it can be reused.
// It uses the given seed to initialize the state.
func (d *Digest) ResetWithSeed(seed uint64) {
d.v1 = seed + prime1 + prime2
d.v2 = seed + prime2
d.v3 = seed
d.v4 = seed - prime1
d.total = 0
d.n = 0
}

View File

@@ -6,7 +6,7 @@
package xxhash
// Sum64 computes the 64-bit xxHash digest of b.
// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
//
//go:noescape
func Sum64(b []byte) uint64

View File

@@ -3,7 +3,7 @@
package xxhash
// Sum64 computes the 64-bit xxHash digest of b.
// Sum64 computes the 64-bit xxHash digest of b with a zero seed.
func Sum64(b []byte) uint64 {
// A simpler version would be
// d := New()

View File

@@ -5,7 +5,7 @@
package xxhash
// Sum64String computes the 64-bit xxHash digest of s.
// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
func Sum64String(s string) uint64 {
return Sum64([]byte(s))
}

View File

@@ -33,7 +33,7 @@ import (
//
// See https://github.com/golang/go/issues/42739 for discussion.
// Sum64String computes the 64-bit xxHash digest of s.
// Sum64String computes the 64-bit xxHash digest of s with a zero seed.
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 {
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))

View File

@@ -1,6 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8

View File

@@ -1,7 +1,7 @@
.PHONY: ci generate clean
ci: clean generate
go test -v ./...
go test -race -v ./...
generate:
go generate .

View File

@@ -7,8 +7,8 @@ http.Handlers.
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
which is also exposed for users interested in a more low-level API.
[![GoDoc](https://godoc.org/github.com/felixge/httpsnoop?status.svg)](https://godoc.org/github.com/felixge/httpsnoop)
[![Build Status](https://travis-ci.org/felixge/httpsnoop.svg?branch=master)](https://travis-ci.org/felixge/httpsnoop)
[![Go Reference](https://pkg.go.dev/badge/github.com/felixge/httpsnoop.svg)](https://pkg.go.dev/github.com/felixge/httpsnoop)
[![Build Status](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml/badge.svg)](https://github.com/felixge/httpsnoop/actions/workflows/main.yaml)
## Usage Example

View File

@@ -52,7 +52,7 @@ func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWri
return func(code int) {
next(code)
if !headerWritten {
if !(code >= 100 && code <= 199) && !headerWritten {
m.Code = code
headerWritten = true
}

View File

@@ -1,5 +1,5 @@
// +build go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
package httpsnoop

View File

@@ -1,5 +1,5 @@
// +build !go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
// Code generated by "httpsnoop/codegen"; DO NOT EDIT.
package httpsnoop

View File

@@ -1,6 +1,7 @@
# A minimal logging API for Go
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-logr/logr)](https://goreportcard.com/report/github.com/go-logr/logr)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-logr/logr/badge)](https://securityscorecards.dev/viewer/?platform=github.com&org=go-logr&repo=logr)
logr offers an(other) opinion on how Go programs and libraries can do logging

View File

@@ -236,15 +236,14 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
// implementation. It should be constructed with NewFormatter. Some of
// its methods directly implement logr.LogSink.
type Formatter struct {
outputFormat outputFormat
prefix string
values []any
valuesStr string
parentValuesStr string
depth int
opts *Options
group string // for slog groups
groupDepth int
outputFormat outputFormat
prefix string
values []any
valuesStr string
depth int
opts *Options
groupName string // for slog groups
groups []groupDef
}
// outputFormat indicates which outputFormat to use.
@@ -257,6 +256,13 @@ const (
outputJSON
)
// groupDef represents a saved group. The values may be empty, but we don't
// know if we need to render the group until the final record is rendered.
type groupDef struct {
name string
values string
}
// PseudoStruct is a list of key-value pairs that gets logged as a struct.
type PseudoStruct []any
@@ -264,76 +270,102 @@ type PseudoStruct []any
func (f Formatter) render(builtins, args []any) string {
// Empirically bytes.Buffer is faster than strings.Builder for this.
buf := bytes.NewBuffer(make([]byte, 0, 1024))
if f.outputFormat == outputJSON {
buf.WriteByte('{') // for the whole line
buf.WriteByte('{') // for the whole record
}
// Render builtins
vals := builtins
if hook := f.opts.RenderBuiltinsHook; hook != nil {
vals = hook(f.sanitize(vals))
}
f.flatten(buf, vals, false, false) // keys are ours, no need to escape
f.flatten(buf, vals, false) // keys are ours, no need to escape
continuing := len(builtins) > 0
if f.parentValuesStr != "" {
// Turn the inner-most group into a string
argsStr := func() string {
buf := bytes.NewBuffer(make([]byte, 0, 1024))
vals = args
if hook := f.opts.RenderArgsHook; hook != nil {
vals = hook(f.sanitize(vals))
}
f.flatten(buf, vals, true) // escape user-provided keys
return buf.String()
}()
// Render the stack of groups from the inside out.
bodyStr := f.renderGroup(f.groupName, f.valuesStr, argsStr)
for i := len(f.groups) - 1; i >= 0; i-- {
grp := &f.groups[i]
if grp.values == "" && bodyStr == "" {
// no contents, so we must elide the whole group
continue
}
bodyStr = f.renderGroup(grp.name, grp.values, bodyStr)
}
if bodyStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.parentValuesStr)
continuing = true
}
groupDepth := f.groupDepth
if f.group != "" {
if f.valuesStr != "" || len(args) != 0 {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
} else {
// The group was empty
groupDepth--
}
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
continuing = true
}
vals = args
if hook := f.opts.RenderArgsHook; hook != nil {
vals = hook(f.sanitize(vals))
}
f.flatten(buf, vals, continuing, true) // escape user-provided keys
for i := 0; i < groupDepth; i++ {
buf.WriteByte('}') // for the groups
buf.WriteString(bodyStr)
}
if f.outputFormat == outputJSON {
buf.WriteByte('}') // for the whole line
buf.WriteByte('}') // for the whole record
}
return buf.String()
}
// flatten renders a list of key-value pairs into a buffer. If continuing is
// true, it assumes that the buffer has previous values and will emit a
// separator (which depends on the output format) before the first pair it
// writes. If escapeKeys is true, the keys are assumed to have
// non-JSON-compatible characters in them and must be evaluated for escapes.
// renderGroup returns a string representation of the named group with rendered
// values and args. If the name is empty, this will return the values and args,
// joined. If the name is not empty, this will return a single key-value pair,
// where the value is a grouping of the values and args. If the values and
// args are both empty, this will return an empty string, even if the name was
// specified.
func (f Formatter) renderGroup(name string, values string, args string) string {
buf := bytes.NewBuffer(make([]byte, 0, 1024))
needClosingBrace := false
if name != "" && (values != "" || args != "") {
buf.WriteString(f.quoted(name, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{')
needClosingBrace = true
}
continuing := false
if values != "" {
buf.WriteString(values)
continuing = true
}
if args != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(args)
}
if needClosingBrace {
buf.WriteByte('}')
}
return buf.String()
}
// flatten renders a list of key-value pairs into a buffer. If escapeKeys is
// true, the keys are assumed to have non-JSON-compatible characters in them
// and must be evaluated for escapes.
//
// This function returns a potentially modified version of kvList, which
// ensures that there is a value for every key (adding a value if needed) and
// that each key is a string (substituting a key if needed).
func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, escapeKeys bool) []any {
func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any {
// This logic overlaps with sanitize() but saves one type-cast per key,
// which can be measurable.
if len(kvList)%2 != 0 {
@@ -354,7 +386,7 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
}
v := kvList[i+1]
if i > 0 || continuing {
if i > 0 {
if f.outputFormat == outputJSON {
buf.WriteByte(f.comma())
} else {
@@ -766,46 +798,17 @@ func (f Formatter) sanitize(kvList []any) []any {
// startGroup opens a new group scope (basically a sub-struct), which locks all
// the current saved values and starts them anew. This is needed to satisfy
// slog.
func (f *Formatter) startGroup(group string) {
func (f *Formatter) startGroup(name string) {
// Unnamed groups are just inlined.
if group == "" {
if name == "" {
return
}
// Any saved values can no longer be changed.
buf := bytes.NewBuffer(make([]byte, 0, 1024))
continuing := false
if f.parentValuesStr != "" {
buf.WriteString(f.parentValuesStr)
continuing = true
}
if f.group != "" && f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
buf.WriteByte(f.colon())
buf.WriteByte('{') // for the group
continuing = false
}
if f.valuesStr != "" {
if continuing {
buf.WriteByte(f.comma())
}
buf.WriteString(f.valuesStr)
}
// NOTE: We don't close the scope here - that's done later, when a log line
// is actually rendered (because we have N scopes to close).
f.parentValuesStr = buf.String()
n := len(f.groups)
f.groups = append(f.groups[:n:n], groupDef{f.groupName, f.valuesStr})
// Start collecting new values.
f.group = group
f.groupDepth++
f.groupName = name
f.valuesStr = ""
f.values = nil
}
@@ -900,7 +903,7 @@ func (f *Formatter) AddValues(kvList []any) {
// Pre-render values, so we don't have to do it on each Info/Error call.
buf := bytes.NewBuffer(make([]byte, 0, 1024))
f.flatten(buf, vals, false, true) // escape user-provided keys
f.flatten(buf, vals, true) // escape user-provided keys
f.valuesStr = buf.String()
}

View File

@@ -1,5 +1,36 @@
# Changelog
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
### Features
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
### Bug Fixes
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
### Features
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
### Features
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
### Fixes
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)

View File

@@ -11,7 +11,7 @@ please explain why in the pull request description.
### Releasing
Commits that would precipitate a SemVer change, as desrcibed in the Conventional
Commits that would precipitate a SemVer change, as described in the Conventional
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
to create a release candidate pull request. Once submitted, `release-please`
will create a release.

View File

@@ -17,6 +17,12 @@ var (
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
Nil UUID // empty UUID, all zeros
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
Max = UUID{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
}
)
// NewHash returns a new UUID derived from the hash of space concatenated with

View File

@@ -108,12 +108,23 @@ func setClockSequence(seq int) {
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. The time is only defined for version 1 and 2 UUIDs.
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
func (uuid UUID) Time() Time {
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time)
var t Time
switch uuid.Version() {
case 6:
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
t = Time(time)
case 7:
time := binary.BigEndian.Uint64(uuid[:8])
t = Time((time>>16)*10000 + g1582ns100)
default: // forward compatible
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
t = Time(time)
}
return t
}
// ClockSequence returns the clock sequence encoded in uuid.

View File

@@ -56,11 +56,15 @@ func IsInvalidLengthError(err error) bool {
return ok
}
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
// the standard UUID forms defined in RFC 4122
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
// Parse accepts non-standard strings such as the raw hex encoding
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
// examined in the latter case. Parse should not be used to validate strings as
// it parses non-standard encodings as indicated above.
func Parse(s string) (UUID, error) {
var uuid UUID
switch len(s) {
@@ -182,6 +186,59 @@ func Must(uuid UUID, err error) UUID {
return uuid
}
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
// It returns an error if the format is invalid, otherwise nil.
func Validate(s string) error {
switch len(s) {
// Standard UUID format
case 36:
// UUID with "urn:uuid:" prefix
case 36 + 9:
if !strings.EqualFold(s[:9], "urn:uuid:") {
return fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
// UUID enclosed in braces
case 36 + 2:
if s[0] != '{' || s[len(s)-1] != '}' {
return fmt.Errorf("invalid bracketed UUID format")
}
s = s[1 : len(s)-1]
// UUID without hyphens
case 32:
for i := 0; i < len(s); i += 2 {
_, ok := xtob(s[i], s[i+1])
if !ok {
return errors.New("invalid UUID format")
}
}
default:
return invalidLengthError{len(s)}
}
// Check for standard UUID format
if len(s) == 36 {
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return errors.New("invalid UUID format")
}
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
if _, ok := xtob(s[x], s[x+1]); !ok {
return errors.New("invalid UUID format")
}
}
}
return nil
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
@@ -294,3 +351,15 @@ func DisableRandPool() {
poolMu.Lock()
poolPos = randPoolSize
}
// UUIDs is a slice of UUID types.
type UUIDs []UUID
// Strings returns a string slice containing the string form of each UUID in uuids.
func (uuids UUIDs) Strings() []string {
var uuidStrs = make([]string, len(uuids))
for i, uuid := range uuids {
uuidStrs[i] = uuid.String()
}
return uuidStrs
}

56
vendor/github.com/google/uuid/version6.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 2023 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "encoding/binary"
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
//
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
//
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewV6 returns Nil and an error.
func NewV6() (UUID, error) {
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_mid | time_low_and_version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|clk_seq_hi_res | clk_seq_low | node (0-1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| node (2-5) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
binary.BigEndian.PutUint16(uuid[8:], seq)
uuid[6] = 0x60 | (uuid[6] & 0x0F)
uuid[8] = 0x80 | (uuid[8] & 0x3F)
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
copy(uuid[10:], nodeID[:])
nodeMu.Unlock()
return uuid, nil
}

104
vendor/github.com/google/uuid/version7.go generated vendored Normal file
View File

@@ -0,0 +1,104 @@
// Copyright 2023 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// UUID version 7 features a time-ordered value field derived from the widely
// implemented and well known Unix Epoch timestamp source,
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
// As well as improved entropy characteristics over versions 1 or 6.
//
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
//
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
//
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
// Uses the randomness pool if it was enabled with EnableRandPool.
// On error, NewV7 returns Nil and an error
func NewV7() (UUID, error) {
uuid, err := NewRandom()
if err != nil {
return uuid, err
}
makeV7(uuid[:])
return uuid, nil
}
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
// it use NewRandomFromReader fill random bits.
// On error, NewV7FromReader returns Nil and an error.
func NewV7FromReader(r io.Reader) (UUID, error) {
uuid, err := NewRandomFromReader(r)
if err != nil {
return uuid, err
}
makeV7(uuid[:])
return uuid, nil
}
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
// uuid[8] already has the right version number (Variant is 10)
// see function NewV7 and NewV7FromReader
func makeV7(uuid []byte) {
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a (12 bit seq) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
_ = uuid[15] // bounds check
t, s := getV7Time()
uuid[0] = byte(t >> 40)
uuid[1] = byte(t >> 32)
uuid[2] = byte(t >> 24)
uuid[3] = byte(t >> 16)
uuid[4] = byte(t >> 8)
uuid[5] = byte(t)
uuid[6] = 0x70 | (0x0F & byte(s>>8))
uuid[7] = byte(s)
}
// lastV7time is the last time we returned stored as:
//
// 52 bits of time in milliseconds since epoch
// 12 bits of (fractional nanoseconds) >> 8
var lastV7time int64
const nanoPerMilli = 1000000
// getV7Time returns the time in milliseconds and nanoseconds / 256.
// The returned (milli << 12 + seq) is guarenteed to be greater than
// (milli << 12 + seq) returned by any previous call to getV7Time.
func getV7Time() (milli, seq int64) {
timeMu.Lock()
defer timeMu.Unlock()
nano := timeNow().UnixNano()
milli = nano / nanoPerMilli
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
seq = (nano - milli*nanoPerMilli) >> 8
now := milli<<12 + seq
if now <= lastV7time {
now = lastV7time + 1
milli = now >> 12
seq = now & 0xfff
}
lastV7time = now
return milli, seq
}

View File

@@ -24,7 +24,7 @@ go_test(
embed = [":httprule"],
deps = [
"//utilities",
"@com_github_golang_glog//:glog",
"@org_golang_google_grpc//grpclog",
],
)

View File

@@ -26,7 +26,7 @@ go_library(
deps = [
"//internal/httprule",
"//utilities",
"@go_googleapis//google/api:httpbody_go_proto",
"@org_golang_google_genproto_googleapis_api//httpbody",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//grpclog",
"@org_golang_google_grpc//health/grpc_health_v1",
@@ -70,9 +70,9 @@ go_test(
"//utilities",
"@com_github_google_go_cmp//cmp",
"@com_github_google_go_cmp//cmp/cmpopts",
"@go_googleapis//google/api:httpbody_go_proto",
"@go_googleapis//google/rpc:errdetails_go_proto",
"@go_googleapis//google/rpc:status_go_proto",
"@org_golang_google_genproto_googleapis_api//httpbody",
"@org_golang_google_genproto_googleapis_rpc//errdetails",
"@org_golang_google_genproto_googleapis_rpc//status",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//health/grpc_health_v1",

View File

@@ -148,6 +148,12 @@ func annotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcM
var pairs []string
for key, vals := range req.Header {
key = textproto.CanonicalMIMEHeaderKey(key)
switch key {
case xForwardedFor, xForwardedHost:
// Handled separately below
continue
}
for _, val := range vals {
// For backwards-compatibility, pass through 'authorization' header with no prefix.
if key == "Authorization" {
@@ -181,18 +187,17 @@ func annotateContext(ctx context.Context, mux *ServeMux, req *http.Request, rpcM
pairs = append(pairs, strings.ToLower(xForwardedHost), req.Host)
}
xff := req.Header.Values(xForwardedFor)
if addr := req.RemoteAddr; addr != "" {
if remoteIP, _, err := net.SplitHostPort(addr); err == nil {
if fwd := req.Header.Get(xForwardedFor); fwd == "" {
pairs = append(pairs, strings.ToLower(xForwardedFor), remoteIP)
} else {
pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP))
}
xff = append(xff, remoteIP)
}
}
if len(xff) > 0 {
pairs = append(pairs, strings.ToLower(xForwardedFor), strings.Join(xff, ", "))
}
if timeout != 0 {
//nolint:govet // The context outlives this function
ctx, _ = context.WithTimeout(ctx, timeout)
}
if len(pairs) == 0 {

View File

@@ -71,7 +71,7 @@ func HTTPStatusFromCode(code codes.Code) int {
case codes.DataLoss:
return http.StatusInternalServerError
default:
grpclog.Infof("Unknown gRPC error code: %v", code)
grpclog.Warningf("Unknown gRPC error code: %v", code)
return http.StatusInternalServerError
}
}
@@ -114,17 +114,17 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
buf, merr := marshaler.Marshal(pb)
if merr != nil {
grpclog.Infof("Failed to marshal error message %q: %v", s, merr)
grpclog.Errorf("Failed to marshal error message %q: %v", s, merr)
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallback); err != nil {
grpclog.Infof("Failed to write response: %v", err)
grpclog.Errorf("Failed to write response: %v", err)
}
return
}
md, ok := ServerMetadataFromContext(ctx)
if !ok {
grpclog.Infof("Failed to extract ServerMetadata from context")
grpclog.Error("Failed to extract ServerMetadata from context")
}
handleForwardResponseServerMetadata(w, mux, md)
@@ -137,7 +137,7 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
doForwardTrailers := requestAcceptsTrailers(r)
if doForwardTrailers {
handleForwardResponseTrailerHeader(w, md)
handleForwardResponseTrailerHeader(w, mux, md)
w.Header().Set("Transfer-Encoding", "chunked")
}
@@ -148,11 +148,11 @@ func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marsh
w.WriteHeader(st)
if _, err := w.Write(buf); err != nil {
grpclog.Infof("Failed to write response: %v", err)
grpclog.Errorf("Failed to write response: %v", err)
}
if doForwardTrailers {
handleForwardResponseTrailer(w, md)
handleForwardResponseTrailer(w, mux, md)
}
}

View File

@@ -27,7 +27,7 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*field_mask.Field
var root interface{}
if err := json.NewDecoder(r).Decode(&root); err != nil {
if err == io.EOF {
if errors.Is(err, io.EOF) {
return fm, nil
}
return nil, err
@@ -41,7 +41,7 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*field_mask.Field
m, ok := item.node.(map[string]interface{})
switch {
case ok:
case ok && len(m) > 0:
// if the item is an object, then enqueue all of its children
for k, v := range m {
if item.msg == nil {
@@ -96,6 +96,8 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*field_mask.Field
queue = append(queue, child)
}
}
case ok && len(m) == 0:
fallthrough
case len(item.path) > 0:
// otherwise, it's a leaf node so print its path
fm.Paths = append(fm.Paths, item.path)

View File

@@ -2,10 +2,11 @@ package runtime
import (
"context"
"fmt"
"errors"
"io"
"net/http"
"net/textproto"
"strconv"
"strings"
"google.golang.org/genproto/googleapis/api/httpbody"
@@ -17,16 +18,10 @@ import (
// ForwardResponseStream forwards the stream from gRPC server to REST client.
func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
f, ok := w.(http.Flusher)
if !ok {
grpclog.Infof("Flush not supported in %T", w)
http.Error(w, "unexpected type of web server", http.StatusInternalServerError)
return
}
rc := http.NewResponseController(w)
md, ok := ServerMetadataFromContext(ctx)
if !ok {
grpclog.Infof("Failed to extract ServerMetadata from context")
grpclog.Error("Failed to extract ServerMetadata from context")
http.Error(w, "unexpected error", http.StatusInternalServerError)
return
}
@@ -48,7 +43,7 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
var wroteHeader bool
for {
resp, err := recv()
if err == io.EOF {
if errors.Is(err, io.EOF) {
return
}
if err != nil {
@@ -81,20 +76,29 @@ func ForwardResponseStream(ctx context.Context, mux *ServeMux, marshaler Marshal
}
if err != nil {
grpclog.Infof("Failed to marshal response chunk: %v", err)
grpclog.Errorf("Failed to marshal response chunk: %v", err)
handleForwardResponseStreamError(ctx, wroteHeader, marshaler, w, req, mux, err, delimiter)
return
}
if _, err := w.Write(buf); err != nil {
grpclog.Infof("Failed to send response chunk: %v", err)
grpclog.Errorf("Failed to send response chunk: %v", err)
return
}
wroteHeader = true
if _, err := w.Write(delimiter); err != nil {
grpclog.Infof("Failed to send delimiter chunk: %v", err)
grpclog.Errorf("Failed to send delimiter chunk: %v", err)
return
}
err = rc.Flush()
if err != nil {
if errors.Is(err, http.ErrNotSupported) {
grpclog.Errorf("Flush not supported in %T", w)
http.Error(w, "unexpected type of web server", http.StatusInternalServerError)
return
}
grpclog.Errorf("Failed to flush response to client: %v", err)
return
}
f.Flush()
}
}
@@ -108,18 +112,20 @@ func handleForwardResponseServerMetadata(w http.ResponseWriter, mux *ServeMux, m
}
}
func handleForwardResponseTrailerHeader(w http.ResponseWriter, md ServerMetadata) {
func handleForwardResponseTrailerHeader(w http.ResponseWriter, mux *ServeMux, md ServerMetadata) {
for k := range md.TrailerMD {
tKey := textproto.CanonicalMIMEHeaderKey(fmt.Sprintf("%s%s", MetadataTrailerPrefix, k))
w.Header().Add("Trailer", tKey)
if h, ok := mux.outgoingTrailerMatcher(k); ok {
w.Header().Add("Trailer", textproto.CanonicalMIMEHeaderKey(h))
}
}
}
func handleForwardResponseTrailer(w http.ResponseWriter, md ServerMetadata) {
func handleForwardResponseTrailer(w http.ResponseWriter, mux *ServeMux, md ServerMetadata) {
for k, vs := range md.TrailerMD {
tKey := fmt.Sprintf("%s%s", MetadataTrailerPrefix, k)
for _, v := range vs {
w.Header().Add(tKey, v)
if h, ok := mux.outgoingTrailerMatcher(k); ok {
for _, v := range vs {
w.Header().Add(h, v)
}
}
}
}
@@ -134,7 +140,7 @@ type responseBody interface {
func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
md, ok := ServerMetadataFromContext(ctx)
if !ok {
grpclog.Infof("Failed to extract ServerMetadata from context")
grpclog.Error("Failed to extract ServerMetadata from context")
}
handleForwardResponseServerMetadata(w, mux, md)
@@ -147,12 +153,10 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
doForwardTrailers := requestAcceptsTrailers(req)
if doForwardTrailers {
handleForwardResponseTrailerHeader(w, md)
handleForwardResponseTrailerHeader(w, mux, md)
w.Header().Set("Transfer-Encoding", "chunked")
}
handleForwardResponseTrailerHeader(w, md)
contentType := marshaler.ContentType(resp)
w.Header().Set("Content-Type", contentType)
@@ -168,17 +172,21 @@ func ForwardResponseMessage(ctx context.Context, mux *ServeMux, marshaler Marsha
buf, err = marshaler.Marshal(resp)
}
if err != nil {
grpclog.Infof("Marshal error: %v", err)
grpclog.Errorf("Marshal error: %v", err)
HTTPError(ctx, mux, marshaler, w, req, err)
return
}
if !doForwardTrailers {
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
}
if _, err = w.Write(buf); err != nil {
grpclog.Infof("Failed to write response: %v", err)
grpclog.Errorf("Failed to write response: %v", err)
}
if doForwardTrailers {
handleForwardResponseTrailer(w, md)
handleForwardResponseTrailer(w, mux, md)
}
}
@@ -193,7 +201,7 @@ func handleForwardResponseOptions(ctx context.Context, w http.ResponseWriter, re
}
for _, opt := range opts {
if err := opt(ctx, w, resp); err != nil {
grpclog.Infof("Error handling ForwardResponseOptions: %v", err)
grpclog.Errorf("Error handling ForwardResponseOptions: %v", err)
return err
}
}
@@ -209,15 +217,15 @@ func handleForwardResponseStreamError(ctx context.Context, wroteHeader bool, mar
}
buf, err := marshaler.Marshal(msg)
if err != nil {
grpclog.Infof("Failed to marshal an error: %v", err)
grpclog.Errorf("Failed to marshal an error: %v", err)
return
}
if _, err := w.Write(buf); err != nil {
grpclog.Infof("Failed to notify error to client: %v", err)
grpclog.Errorf("Failed to notify error to client: %v", err)
return
}
if _, err := w.Write(delimiter); err != nil {
grpclog.Infof("Failed to send delimiter chunk: %v", err)
grpclog.Errorf("Failed to send delimiter chunk: %v", err)
return
}
}

View File

@@ -26,7 +26,7 @@ func (h *HTTPBodyMarshaler) ContentType(v interface{}) string {
// google.api.HttpBody message, otherwise it falls back to the default Marshaler.
func (h *HTTPBodyMarshaler) Marshal(v interface{}) ([]byte, error) {
if httpBody, ok := v.(*httpbody.HttpBody); ok {
return httpBody.Data, nil
return httpBody.GetData(), nil
}
return h.Marshaler.Marshal(v)
}

View File

@@ -24,6 +24,11 @@ func (j *JSONBuiltin) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
// MarshalIndent is like Marshal but applies Indent to format the output
func (j *JSONBuiltin) MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
return json.MarshalIndent(v, prefix, indent)
}
// Unmarshal unmarshals JSON data into "v".
func (j *JSONBuiltin) Unmarshal(data []byte, v interface{}) error {
return json.Unmarshal(data, v)

View File

@@ -30,10 +30,6 @@ func (*JSONPb) ContentType(_ interface{}) string {
// Marshal marshals "v" into JSON.
func (j *JSONPb) Marshal(v interface{}) ([]byte, error) {
if _, ok := v.(proto.Message); !ok {
return j.marshalNonProtoField(v)
}
var buf bytes.Buffer
if err := j.marshalTo(&buf, v); err != nil {
return nil, err
@@ -48,9 +44,17 @@ func (j *JSONPb) marshalTo(w io.Writer, v interface{}) error {
if err != nil {
return err
}
if j.Indent != "" {
b := &bytes.Buffer{}
if err := json.Indent(b, buf, "", j.Indent); err != nil {
return err
}
buf = b.Bytes()
}
_, err = w.Write(buf)
return err
}
b, err := j.MarshalOptions.Marshal(p)
if err != nil {
return err
@@ -150,9 +154,6 @@ func (j *JSONPb) marshalNonProtoField(v interface{}) ([]byte, error) {
}
m[fmt.Sprintf("%v", k.Interface())] = (*json.RawMessage)(&buf)
}
if j.Indent != "" {
return json.MarshalIndent(m, "", j.Indent)
}
return json.Marshal(m)
}
if enum, ok := rv.Interface().(protoEnum); ok && !j.UseEnumNumbers {

View File

@@ -46,7 +46,7 @@ func MarshalerForRequest(mux *ServeMux, r *http.Request) (inbound Marshaler, out
for _, contentTypeVal := range r.Header[contentTypeHeader] {
contentType, _, err := mime.ParseMediaType(contentTypeVal)
if err != nil {
grpclog.Infof("Failed to parse Content-Type %s: %v", contentTypeVal, err)
grpclog.Errorf("Failed to parse Content-Type %s: %v", contentTypeVal, err)
continue
}
if m, ok := mux.marshalers.mimeMap[contentType]; ok {

View File

@@ -57,6 +57,7 @@ type ServeMux struct {
marshalers marshalerRegistry
incomingHeaderMatcher HeaderMatcherFunc
outgoingHeaderMatcher HeaderMatcherFunc
outgoingTrailerMatcher HeaderMatcherFunc
metadataAnnotators []func(context.Context, *http.Request) metadata.MD
errorHandler ErrorHandlerFunc
streamErrorHandler StreamErrorHandlerFunc
@@ -114,10 +115,18 @@ func DefaultHeaderMatcher(key string) (string, bool) {
return "", false
}
func defaultOutgoingHeaderMatcher(key string) (string, bool) {
return fmt.Sprintf("%s%s", MetadataHeaderPrefix, key), true
}
func defaultOutgoingTrailerMatcher(key string) (string, bool) {
return fmt.Sprintf("%s%s", MetadataTrailerPrefix, key), true
}
// WithIncomingHeaderMatcher returns a ServeMuxOption representing a headerMatcher for incoming request to gateway.
//
// This matcher will be called with each header in http.Request. If matcher returns true, that header will be
// passed to gRPC context. To transform the header before passing to gRPC context, matcher should return modified header.
// passed to gRPC context. To transform the header before passing to gRPC context, matcher should return the modified header.
func WithIncomingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption {
for _, header := range fn.matchedMalformedHeaders() {
grpclog.Warningf("The configured forwarding filter would allow %q to be sent to the gRPC server, which will likely cause errors. See https://github.com/grpc/grpc-go/pull/4803#issuecomment-986093310 for more information.", header)
@@ -147,13 +156,24 @@ func (fn HeaderMatcherFunc) matchedMalformedHeaders() []string {
//
// This matcher will be called with each header in response header metadata. If matcher returns true, that header will be
// passed to http response returned from gateway. To transform the header before passing to response,
// matcher should return modified header.
// matcher should return the modified header.
func WithOutgoingHeaderMatcher(fn HeaderMatcherFunc) ServeMuxOption {
return func(mux *ServeMux) {
mux.outgoingHeaderMatcher = fn
}
}
// WithOutgoingTrailerMatcher returns a ServeMuxOption representing a headerMatcher for outgoing response from gateway.
//
// This matcher will be called with each header in response trailer metadata. If matcher returns true, that header will be
// passed to http response returned from gateway. To transform the header before passing to response,
// matcher should return the modified header.
func WithOutgoingTrailerMatcher(fn HeaderMatcherFunc) ServeMuxOption {
return func(mux *ServeMux) {
mux.outgoingTrailerMatcher = fn
}
}
// WithMetadata returns a ServeMuxOption for passing metadata to a gRPC context.
//
// This can be used by services that need to read from http.Request and modify gRPC context. A common use case
@@ -273,11 +293,11 @@ func NewServeMux(opts ...ServeMuxOption) *ServeMux {
if serveMux.incomingHeaderMatcher == nil {
serveMux.incomingHeaderMatcher = DefaultHeaderMatcher
}
if serveMux.outgoingHeaderMatcher == nil {
serveMux.outgoingHeaderMatcher = func(key string) (string, bool) {
return fmt.Sprintf("%s%s", MetadataHeaderPrefix, key), true
}
serveMux.outgoingHeaderMatcher = defaultOutgoingHeaderMatcher
}
if serveMux.outgoingTrailerMatcher == nil {
serveMux.outgoingTrailerMatcher = defaultOutgoingTrailerMatcher
}
return serveMux
@@ -321,13 +341,13 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) {
r.Method = strings.ToUpper(override)
if err := r.ParseForm(); err != nil {
_, outboundMarshaler := MarshalerForRequest(s, r)
sterr := status.Error(codes.InvalidArgument, err.Error())
s.errorHandler(ctx, s, outboundMarshaler, w, r, sterr)
return
}
r.Method = strings.ToUpper(override)
}
var pathComponents []string

View File

@@ -52,13 +52,13 @@ type Pattern struct {
// It returns an error if the given definition is invalid.
func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
if version != 1 {
grpclog.Infof("unsupported version: %d", version)
grpclog.Errorf("unsupported version: %d", version)
return Pattern{}, ErrInvalidPattern
}
l := len(ops)
if l%2 != 0 {
grpclog.Infof("odd number of ops codes: %d", l)
grpclog.Errorf("odd number of ops codes: %d", l)
return Pattern{}, ErrInvalidPattern
}
@@ -81,14 +81,14 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
stack++
case utilities.OpPushM:
if pushMSeen {
grpclog.Infof("pushM appears twice")
grpclog.Error("pushM appears twice")
return Pattern{}, ErrInvalidPattern
}
pushMSeen = true
stack++
case utilities.OpLitPush:
if op.operand < 0 || len(pool) <= op.operand {
grpclog.Infof("negative literal index: %d", op.operand)
grpclog.Errorf("negative literal index: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
if pushMSeen {
@@ -97,18 +97,18 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
stack++
case utilities.OpConcatN:
if op.operand <= 0 {
grpclog.Infof("negative concat size: %d", op.operand)
grpclog.Errorf("negative concat size: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
stack -= op.operand
if stack < 0 {
grpclog.Info("stack underflow")
grpclog.Error("stack underflow")
return Pattern{}, ErrInvalidPattern
}
stack++
case utilities.OpCapture:
if op.operand < 0 || len(pool) <= op.operand {
grpclog.Infof("variable name index out of bound: %d", op.operand)
grpclog.Errorf("variable name index out of bound: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
v := pool[op.operand]
@@ -116,11 +116,11 @@ func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, er
vars = append(vars, v)
stack--
if stack < 0 {
grpclog.Infof("stack underflow")
grpclog.Error("stack underflow")
return Pattern{}, ErrInvalidPattern
}
default:
grpclog.Infof("invalid opcode: %d", op.code)
grpclog.Errorf("invalid opcode: %d", op.code)
return Pattern{}, ErrInvalidPattern
}

View File

@@ -51,11 +51,13 @@ func (*DefaultQueryParser) Parse(msg proto.Message, values url.Values, filter *u
key = match[1]
values = append([]string{match[2]}, values...)
}
fieldPath := strings.Split(key, ".")
msgValue := msg.ProtoReflect()
fieldPath := normalizeFieldPath(msgValue, strings.Split(key, "."))
if filter.HasCommonPrefix(fieldPath) {
continue
}
if err := populateFieldValueFromPath(msg.ProtoReflect(), fieldPath, values); err != nil {
if err := populateFieldValueFromPath(msgValue, fieldPath, values); err != nil {
return err
}
}
@@ -68,6 +70,38 @@ func PopulateFieldFromPath(msg proto.Message, fieldPathString string, value stri
return populateFieldValueFromPath(msg.ProtoReflect(), fieldPath, []string{value})
}
func normalizeFieldPath(msgValue protoreflect.Message, fieldPath []string) []string {
newFieldPath := make([]string, 0, len(fieldPath))
for i, fieldName := range fieldPath {
fields := msgValue.Descriptor().Fields()
fieldDesc := fields.ByTextName(fieldName)
if fieldDesc == nil {
fieldDesc = fields.ByJSONName(fieldName)
}
if fieldDesc == nil {
// return initial field path values if no matching message field was found
return fieldPath
}
newFieldPath = append(newFieldPath, string(fieldDesc.Name()))
// If this is the last element, we're done
if i == len(fieldPath)-1 {
break
}
// Only singular message fields are allowed
if fieldDesc.Message() == nil || fieldDesc.Cardinality() == protoreflect.Repeated {
return fieldPath
}
// Get the nested message
msgValue = msgValue.Get(fieldDesc).Message()
}
return newFieldPath
}
func populateFieldValueFromPath(msgValue protoreflect.Message, fieldPath []string, values []string) error {
if len(fieldPath) < 1 {
return errors.New("no field path")

View File

@@ -1,23 +1,15 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
import (
"google.golang.org/grpc/stats"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
@@ -30,18 +22,26 @@ const (
GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code")
)
// Filter is a predicate used to determine whether a given request in
// interceptor info should be traced. A Filter must return true if
// InterceptorFilter is a predicate used to determine whether a given request in
// interceptor info should be instrumented. A InterceptorFilter must return true if
// the request should be traced.
type Filter func(*InterceptorInfo) bool
//
// Deprecated: Use stats handlers instead.
type InterceptorFilter func(*InterceptorInfo) bool
// Filter is a predicate used to determine whether a given request in
// should be instrumented by the attached RPC tag info.
// A Filter must return true if the request should be instrumented.
type Filter func(*stats.RPCTagInfo) bool
// config is a group of options for this instrumentation.
type config struct {
Filter Filter
Propagators propagation.TextMapPropagator
TracerProvider trace.TracerProvider
MeterProvider metric.MeterProvider
SpanStartOptions []trace.SpanStartOption
Filter Filter
InterceptorFilter InterceptorFilter
Propagators propagation.TextMapPropagator
TracerProvider trace.TracerProvider
MeterProvider metric.MeterProvider
SpanStartOptions []trace.SpanStartOption
ReceivedEvent bool
SentEvent bool
@@ -89,6 +89,9 @@ func newConfig(opts []Option, role string) *config {
metric.WithUnit("ms"))
if err != nil {
otel.Handle(err)
if c.rpcDuration == nil {
c.rpcDuration = noop.Float64Histogram{}
}
}
c.rpcRequestSize, err = c.meter.Int64Histogram("rpc."+role+".request.size",
@@ -96,6 +99,9 @@ func newConfig(opts []Option, role string) *config {
metric.WithUnit("By"))
if err != nil {
otel.Handle(err)
if c.rpcRequestSize == nil {
c.rpcRequestSize = noop.Int64Histogram{}
}
}
c.rpcResponseSize, err = c.meter.Int64Histogram("rpc."+role+".response.size",
@@ -103,6 +109,9 @@ func newConfig(opts []Option, role string) *config {
metric.WithUnit("By"))
if err != nil {
otel.Handle(err)
if c.rpcResponseSize == nil {
c.rpcResponseSize = noop.Int64Histogram{}
}
}
c.rpcRequestsPerRPC, err = c.meter.Int64Histogram("rpc."+role+".requests_per_rpc",
@@ -110,6 +119,9 @@ func newConfig(opts []Option, role string) *config {
metric.WithUnit("{count}"))
if err != nil {
otel.Handle(err)
if c.rpcRequestsPerRPC == nil {
c.rpcRequestsPerRPC = noop.Int64Histogram{}
}
}
c.rpcResponsesPerRPC, err = c.meter.Int64Histogram("rpc."+role+".responses_per_rpc",
@@ -117,6 +129,9 @@ func newConfig(opts []Option, role string) *config {
metric.WithUnit("{count}"))
if err != nil {
otel.Handle(err)
if c.rpcResponsesPerRPC == nil {
c.rpcResponsesPerRPC = noop.Int64Histogram{}
}
}
return c
@@ -147,15 +162,30 @@ func (o tracerProviderOption) apply(c *config) {
// WithInterceptorFilter returns an Option to use the request filter.
//
// Deprecated: Use stats handlers instead.
func WithInterceptorFilter(f Filter) Option {
func WithInterceptorFilter(f InterceptorFilter) Option {
return interceptorFilterOption{f: f}
}
type interceptorFilterOption struct {
f Filter
f InterceptorFilter
}
func (o interceptorFilterOption) apply(c *config) {
if o.f != nil {
c.InterceptorFilter = o.f
}
}
// WithFilter returns an Option to use the request filter.
func WithFilter(f Filter) Option {
return filterOption{f: f}
}
type filterOption struct {
f Filter
}
func (o filterOption) apply(c *config) {
if o.f != nil {
c.Filter = o.f
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
/*
Package otelgrpc is the instrumentation library for [google.golang.org/grpc].

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
@@ -18,6 +7,7 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md
import (
"context"
"errors"
"io"
"net"
"strconv"
@@ -59,7 +49,7 @@ var (
)
// UnaryClientInterceptor returns a grpc.UnaryClientInterceptor suitable
// for use in a grpc.Dial call.
// for use in a grpc.NewClient call.
//
// Deprecated: Use [NewClientHandler] instead.
func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
@@ -81,7 +71,7 @@ func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
Method: method,
Type: UnaryClient,
}
if cfg.Filter != nil && !cfg.Filter(i) {
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
return invoker(ctx, method, req, reply, cc, callOpts...)
}
@@ -125,27 +115,13 @@ func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
}
}
type streamEventType int
type streamEvent struct {
Type streamEventType
Err error
}
const (
receiveEndEvent streamEventType = iota
errorEvent
)
// clientStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and
// SendMsg method call.
type clientStream struct {
grpc.ClientStream
desc *grpc.StreamDesc
desc *grpc.StreamDesc
events chan streamEvent
eventsDone chan struct{}
finished chan error
span trace.Span
receivedEvent bool
sentEvent bool
@@ -160,11 +136,11 @@ func (w *clientStream) RecvMsg(m interface{}) error {
err := w.ClientStream.RecvMsg(m)
if err == nil && !w.desc.ServerStreams {
w.sendStreamEvent(receiveEndEvent, nil)
} else if err == io.EOF {
w.sendStreamEvent(receiveEndEvent, nil)
w.endSpan(nil)
} else if errors.Is(err, io.EOF) {
w.endSpan(nil)
} else if err != nil {
w.sendStreamEvent(errorEvent, err)
w.endSpan(err)
} else {
w.receivedMessageID++
@@ -186,7 +162,7 @@ func (w *clientStream) SendMsg(m interface{}) error {
}
if err != nil {
w.sendStreamEvent(errorEvent, err)
w.endSpan(err)
}
return err
@@ -195,7 +171,7 @@ func (w *clientStream) SendMsg(m interface{}) error {
func (w *clientStream) Header() (metadata.MD, error) {
md, err := w.ClientStream.Header()
if err != nil {
w.sendStreamEvent(errorEvent, err)
w.endSpan(err)
}
return md, err
@@ -204,58 +180,36 @@ func (w *clientStream) Header() (metadata.MD, error) {
func (w *clientStream) CloseSend() error {
err := w.ClientStream.CloseSend()
if err != nil {
w.sendStreamEvent(errorEvent, err)
w.endSpan(err)
}
return err
}
func wrapClientStream(ctx context.Context, s grpc.ClientStream, desc *grpc.StreamDesc, cfg *config) *clientStream {
events := make(chan streamEvent)
eventsDone := make(chan struct{})
finished := make(chan error)
go func() {
defer close(eventsDone)
for {
select {
case event := <-events:
switch event.Type {
case receiveEndEvent:
finished <- nil
return
case errorEvent:
finished <- event.Err
return
}
case <-ctx.Done():
finished <- ctx.Err()
return
}
}
}()
func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc, span trace.Span, cfg *config) *clientStream {
return &clientStream{
ClientStream: s,
span: span,
desc: desc,
events: events,
eventsDone: eventsDone,
finished: finished,
receivedEvent: cfg.ReceivedEvent,
sentEvent: cfg.SentEvent,
}
}
func (w *clientStream) sendStreamEvent(eventType streamEventType, err error) {
select {
case <-w.eventsDone:
case w.events <- streamEvent{Type: eventType, Err: err}:
func (w *clientStream) endSpan(err error) {
if err != nil {
s, _ := status.FromError(err)
w.span.SetStatus(codes.Error, s.Message())
w.span.SetAttributes(statusCodeAttr(s.Code()))
} else {
w.span.SetAttributes(statusCodeAttr(grpc_codes.OK))
}
w.span.End()
}
// StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable
// for use in a grpc.Dial call.
// for use in a grpc.NewClient call.
//
// Deprecated: Use [NewClientHandler] instead.
func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
@@ -277,7 +231,7 @@ func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
Method: method,
Type: StreamClient,
}
if cfg.Filter != nil && !cfg.Filter(i) {
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
return streamer(ctx, desc, cc, method, callOpts...)
}
@@ -306,22 +260,7 @@ func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
span.End()
return s, err
}
stream := wrapClientStream(ctx, s, desc, cfg)
go func() {
err := <-stream.finished
if err != nil {
s, _ := status.FromError(err)
span.SetStatus(codes.Error, s.Message())
span.SetAttributes(statusCodeAttr(s.Code()))
} else {
span.SetAttributes(statusCodeAttr(grpc_codes.OK))
}
span.End()
}()
stream := wrapClientStream(s, desc, span, cfg)
return stream, nil
}
}
@@ -347,7 +286,7 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
UnaryServerInfo: info,
Type: UnaryServer,
}
if cfg.Filter != nil && !cfg.Filter(i) {
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
return handler(ctx, req)
}
@@ -391,9 +330,11 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
grpcStatusCodeAttr := statusCodeAttr(s.Code())
span.SetAttributes(grpcStatusCodeAttr)
elapsedTime := time.Since(before).Milliseconds()
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedTime := float64(time.Since(before)) / float64(time.Millisecond)
metricAttrs = append(metricAttrs, grpcStatusCodeAttr)
cfg.rpcDuration.Record(ctx, float64(elapsedTime), metric.WithAttributes(metricAttrs...))
cfg.rpcDuration.Record(ctx, elapsedTime, metric.WithAttributeSet(attribute.NewSet(metricAttrs...)))
return resp, err
}
@@ -471,7 +412,7 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
StreamServerInfo: info,
Type: StreamServer,
}
if cfg.Filter != nil && !cfg.Filter(i) {
if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) {
return handler(srv, wrapServerStream(ctx, ss, cfg))
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
@@ -20,6 +9,7 @@ import (
"time"
grpc_codes "google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/stats"
"google.golang.org/grpc/status"
@@ -37,13 +27,14 @@ type gRPCContext struct {
messagesReceived int64
messagesSent int64
metricAttrs []attribute.KeyValue
record bool
}
type serverHandler struct {
*config
}
// NewServerHandler creates a stats.Handler for gRPC server.
// NewServerHandler creates a stats.Handler for a gRPC server.
func NewServerHandler(opts ...Option) stats.Handler {
h := &serverHandler{
config: newConfig(opts, "server"),
@@ -54,9 +45,6 @@ func NewServerHandler(opts ...Option) stats.Handler {
// TagConn can attach some information to the given context.
func (h *serverHandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
span := trace.SpanFromContext(ctx)
attrs := peerAttr(peerFromCtx(ctx))
span.SetAttributes(attrs...)
return ctx
}
@@ -79,20 +67,25 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
gctx := gRPCContext{
metricAttrs: attrs,
record: true,
}
if h.config.Filter != nil {
gctx.record = h.config.Filter(info)
}
return context.WithValue(ctx, gRPCContextKey{}, &gctx)
}
// HandleRPC processes the RPC stats.
func (h *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
h.handleRPC(ctx, rs)
isServer := true
h.handleRPC(ctx, rs, isServer)
}
type clientHandler struct {
*config
}
// NewClientHandler creates a stats.Handler for gRPC client.
// NewClientHandler creates a stats.Handler for a gRPC client.
func NewClientHandler(opts ...Option) stats.Handler {
h := &clientHandler{
config: newConfig(opts, "client"),
@@ -114,6 +107,10 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
gctx := gRPCContext{
metricAttrs: attrs,
record: true,
}
if h.config.Filter != nil {
gctx.record = h.config.Filter(info)
}
return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.config.Propagators)
@@ -121,14 +118,12 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
// HandleRPC processes the RPC stats.
func (h *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
h.handleRPC(ctx, rs)
isServer := false
h.handleRPC(ctx, rs, isServer)
}
// TagConn can attach some information to the given context.
func (h *clientHandler) TagConn(ctx context.Context, cti *stats.ConnTagInfo) context.Context {
span := trace.SpanFromContext(ctx)
attrs := peerAttr(cti.RemoteAddr.String())
span.SetAttributes(attrs...)
func (h *clientHandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
return ctx
}
@@ -137,20 +132,26 @@ func (h *clientHandler) HandleConn(context.Context, stats.ConnStats) {
// no-op
}
func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats) {
func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool) { // nolint: revive // isServer is not a control flag.
span := trace.SpanFromContext(ctx)
gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext)
var metricAttrs []attribute.KeyValue
var messageId int64
metricAttrs := make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
wctx := withoutCancel(ctx)
gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext)
if gctx != nil {
if !gctx.record {
return
}
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
}
switch rs := rs.(type) {
case *stats.Begin:
case *stats.InPayload:
if gctx != nil {
messageId = atomic.AddInt64(&gctx.messagesReceived, 1)
c.rpcRequestSize.Record(wctx, int64(rs.Length), metric.WithAttributes(metricAttrs...))
c.rpcRequestSize.Record(ctx, int64(rs.Length), metric.WithAttributeSet(attribute.NewSet(metricAttrs...)))
}
if c.ReceivedEvent {
@@ -166,7 +167,7 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats) {
case *stats.OutPayload:
if gctx != nil {
messageId = atomic.AddInt64(&gctx.messagesSent, 1)
c.rpcResponseSize.Record(wctx, int64(rs.Length), metric.WithAttributes(metricAttrs...))
c.rpcResponseSize.Record(ctx, int64(rs.Length), metric.WithAttributeSet(attribute.NewSet(metricAttrs...)))
}
if c.SentEvent {
@@ -180,12 +181,21 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats) {
)
}
case *stats.OutTrailer:
case *stats.OutHeader:
if p, ok := peer.FromContext(ctx); ok {
span.SetAttributes(peerAttr(p.Addr.String())...)
}
case *stats.End:
var rpcStatusAttr attribute.KeyValue
if rs.Error != nil {
s, _ := status.FromError(rs.Error)
span.SetStatus(codes.Error, s.Message())
if isServer {
statusCode, msg := serverStatus(s)
span.SetStatus(statusCode, msg)
} else {
span.SetStatus(codes.Error, s.Message())
}
rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(s.Code()))
} else {
rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(grpc_codes.OK))
@@ -194,42 +204,19 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats) {
span.End()
metricAttrs = append(metricAttrs, rpcStatusAttr)
c.rpcDuration.Record(wctx, float64(rs.EndTime.Sub(rs.BeginTime)), metric.WithAttributes(metricAttrs...))
c.rpcRequestsPerRPC.Record(wctx, gctx.messagesReceived, metric.WithAttributes(metricAttrs...))
c.rpcResponsesPerRPC.Record(wctx, gctx.messagesSent, metric.WithAttributes(metricAttrs...))
// Allocate vararg slice once.
recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))}
// Use floating point division here for higher precision (instead of Millisecond method).
// Measure right before calling Record() to capture as much elapsed time as possible.
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Millisecond)
c.rpcDuration.Record(ctx, elapsedTime, recordOpts...)
if gctx != nil {
c.rpcRequestsPerRPC.Record(ctx, atomic.LoadInt64(&gctx.messagesReceived), recordOpts...)
c.rpcResponsesPerRPC.Record(ctx, atomic.LoadInt64(&gctx.messagesSent), recordOpts...)
}
default:
return
}
}
func withoutCancel(parent context.Context) context.Context {
if parent == nil {
panic("cannot create context from nil parent")
}
return withoutCancelCtx{parent}
}
type withoutCancelCtx struct {
c context.Context
}
func (withoutCancelCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (withoutCancelCtx) Done() <-chan struct{} {
return nil
}
func (withoutCancelCtx) Err() error {
return nil
}
func (w withoutCancelCtx) Value(key any) any {
return w.c.Value(key)
}
func (w withoutCancelCtx) String() string {
return "withoutCancel"
}

View File

@@ -1,22 +1,11 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
// Version is the current release version of the gRPC instrumentation.
func Version() string {
return "0.46.0"
return "0.53.0"
// This string is updated by the pre_release.sh script during release
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -23,7 +12,7 @@ import (
)
// DefaultClient is the default Client and is used by Get, Head, Post and PostForm.
// Please be careful of intitialization order - for example, if you change
// Please be careful of initialization order - for example, if you change
// the global propagator, the DefaultClient might still be using the old one.
var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -31,10 +20,16 @@ const (
// Server HTTP metrics.
const (
RequestCount = "http.server.request_count" // Incoming request count total
RequestContentLength = "http.server.request_content_length" // Incoming request bytes total
ResponseContentLength = "http.server.response_content_length" // Incoming response bytes total
ServerLatency = "http.server.duration" // Incoming end to end duration, microseconds
serverRequestSize = "http.server.request.size" // Incoming request bytes total
serverResponseSize = "http.server.response.size" // Incoming response bytes total
serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds
)
// Client HTTP metrics.
const (
clientRequestSize = "http.client.request.size" // Outgoing request bytes total
clientResponseSize = "http.client.response.size" // Outgoing response bytes total
clientDuration = "http.client.duration" // Outgoing end to end duration, milliseconds
)
// Filter is a predicate used to determine whether a given http.request should
@@ -42,5 +37,5 @@ const (
type Filter func(*http.Request) bool
func newTracer(tp trace.TracerProvider) trace.Tracer {
return tp.Tracer(instrumentationName, trace.WithInstrumentationVersion(Version()))
return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version()))
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -25,9 +14,8 @@ import (
"go.opentelemetry.io/otel/trace"
)
const (
instrumentationName = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
// ScopeName is the instrumentation scope name.
const ScopeName = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
// config represents the configuration options available for the http.Handler
// and http.Transport types.
@@ -76,7 +64,7 @@ func newConfig(opts ...Option) *config {
}
c.Meter = c.MeterProvider.Meter(
instrumentationName,
ScopeName,
metric.WithInstrumentationVersion(Version()),
)
@@ -112,7 +100,7 @@ func WithPublicEndpoint() Option {
})
}
// WithPublicEndpointFn runs with every request, and allows conditionnally
// WithPublicEndpointFn runs with every request, and allows conditionally
// configuring the Handler to link the span with an incoming span context. If
// this option is not provided or returns false, then the association is a
// child association instead of a link.

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
// Package otelhttp provides an http.Handler and functions that are intended
// to be used to add tracing by wrapping existing handlers (with Handler) and

View File

@@ -1,32 +1,20 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
import (
"io"
"net/http"
"time"
"github.com/felixge/httpsnoop"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
)
@@ -43,10 +31,13 @@ type middleware struct {
writeEvent bool
filters []Filter
spanNameFormatter func(string, *http.Request) string
counters map[string]metric.Int64Counter
valueRecorders map[string]metric.Float64Histogram
publicEndpoint bool
publicEndpointFn func(*http.Request) bool
traceSemconv semconv.HTTPServer
requestBytesCounter metric.Int64Counter
responseBytesCounter metric.Int64Counter
serverLatencyMeasure metric.Float64Histogram
}
func defaultHandlerFormatter(operation string, _ *http.Request) string {
@@ -65,6 +56,8 @@ func NewHandler(handler http.Handler, operation string, opts ...Option) http.Han
func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler {
h := middleware{
operation: operation,
traceSemconv: semconv.NewHTTPServer(),
}
defaultOpts := []Option{
@@ -104,21 +97,27 @@ func handleErr(err error) {
}
func (h *middleware) createMeasures() {
h.counters = make(map[string]metric.Int64Counter)
h.valueRecorders = make(map[string]metric.Float64Histogram)
requestBytesCounter, err := h.meter.Int64Counter(RequestContentLength)
var err error
h.requestBytesCounter, err = h.meter.Int64Counter(
serverRequestSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP request messages."),
)
handleErr(err)
responseBytesCounter, err := h.meter.Int64Counter(ResponseContentLength)
h.responseBytesCounter, err = h.meter.Int64Counter(
serverResponseSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP response messages."),
)
handleErr(err)
serverLatencyMeasure, err := h.meter.Float64Histogram(ServerLatency)
h.serverLatencyMeasure, err = h.meter.Float64Histogram(
serverDuration,
metric.WithUnit("ms"),
metric.WithDescription("Measures the duration of inbound HTTP requests."),
)
handleErr(err)
h.counters[RequestContentLength] = requestBytesCounter
h.counters[ResponseContentLength] = responseBytesCounter
h.valueRecorders[ServerLatency] = serverLatencyMeasure
}
// serveHTTP sets up tracing and calls the given next http.Handler with the span
@@ -135,12 +134,9 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
opts := []trace.SpanStartOption{
trace.WithAttributes(semconvutil.HTTPServerRequest(h.server, r)...),
}
if h.server != "" {
hostAttr := semconv.NetHostName(h.server)
opts = append(opts, trace.WithAttributes(hostAttr))
trace.WithAttributes(h.traceSemconv.RequestTraceAttrs(h.server, r)...),
}
opts = append(opts, h.spanStartOptions...)
if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) {
opts = append(opts, trace.WithNewRoot())
@@ -209,61 +205,48 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
return rww.WriteHeader
},
Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc {
return rww.Flush
},
})
labeler := &Labeler{}
ctx = injectLabeler(ctx, labeler)
labeler, found := LabelerFromContext(ctx)
if !found {
ctx = ContextWithLabeler(ctx, labeler)
}
next.ServeHTTP(w, r.WithContext(ctx))
setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
span.SetStatus(semconv.ServerStatus(rww.statusCode))
span.SetAttributes(h.traceSemconv.ResponseTraceAttrs(semconv.ResponseTelemetry{
StatusCode: rww.statusCode,
ReadBytes: bw.read.Load(),
ReadError: bw.err,
WriteBytes: rww.written,
WriteError: rww.err,
})...)
// Add metrics
attributes := append(labeler.Get(), semconvutil.HTTPServerRequestMetrics(h.server, r)...)
if rww.statusCode > 0 {
attributes = append(attributes, semconv.HTTPStatusCode(rww.statusCode))
}
o := metric.WithAttributes(attributes...)
h.counters[RequestContentLength].Add(ctx, bw.read, o)
h.counters[ResponseContentLength].Add(ctx, rww.written, o)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
addOpts := []metric.AddOption{o} // Allocate vararg slice once.
h.requestBytesCounter.Add(ctx, bw.read.Load(), addOpts...)
h.responseBytesCounter.Add(ctx, rww.written, addOpts...)
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
h.valueRecorders[ServerLatency].Record(ctx, elapsedTime, o)
}
func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) {
attributes := []attribute.KeyValue{}
// TODO: Consider adding an event after each read and write, possibly as an
// option (defaulting to off), so as to not create needlessly verbose spans.
if read > 0 {
attributes = append(attributes, ReadBytesKey.Int64(read))
}
if rerr != nil && rerr != io.EOF {
attributes = append(attributes, ReadErrorKey.String(rerr.Error()))
}
if wrote > 0 {
attributes = append(attributes, WroteBytesKey.Int64(wrote))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
}
span.SetStatus(semconvutil.HTTPServerStatus(statusCode))
if werr != nil && werr != io.EOF {
attributes = append(attributes, WriteErrorKey.String(werr.Error()))
}
span.SetAttributes(attributes...)
h.serverLatencyMeasure.Record(ctx, elapsedTime, o)
}
// WithRouteTag annotates spans and metrics with the provided route name
// with HTTP route attribute.
func WithRouteTag(route string, h http.Handler) http.Handler {
attr := semconv.NewHTTPServer().Route(route)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
attr := semconv.HTTPRouteKey.String(route)
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attr)

View File

@@ -0,0 +1,82 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"fmt"
"net/http"
"os"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)
type ResponseTelemetry struct {
StatusCode int
ReadBytes int64
ReadError error
WriteBytes int64
WriteError error
}
type HTTPServer struct {
duplicate bool
}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
if s.duplicate {
return append(oldHTTPServer{}.RequestTraceAttrs(server, req), newHTTPServer{}.RequestTraceAttrs(server, req)...)
}
return oldHTTPServer{}.RequestTraceAttrs(server, req)
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
if s.duplicate {
return append(oldHTTPServer{}.ResponseTraceAttrs(resp), newHTTPServer{}.ResponseTraceAttrs(resp)...)
}
return oldHTTPServer{}.ResponseTraceAttrs(resp)
}
// Route returns the attribute for the route.
func (s HTTPServer) Route(route string) attribute.KeyValue {
return oldHTTPServer{}.Route(route)
}
func NewHTTPServer() HTTPServer {
env := strings.ToLower(os.Getenv("OTEL_HTTP_CLIENT_COMPATIBILITY_MODE"))
return HTTPServer{duplicate: env == "http/dup"}
}
// ServerStatus returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func ServerStatus(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}

View File

@@ -0,0 +1,91 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"net"
"net/http"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.24.0"
)
// splitHostPort splits a network address hostport of the form "host",
// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port",
// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and
// port.
//
// An empty host is returned if it is not provided or unparsable. A negative
// port is returned if it is not provided or unparsable.
func splitHostPort(hostport string) (host string, port int) {
port = -1
if strings.HasPrefix(hostport, "[") {
addrEnd := strings.LastIndex(hostport, "]")
if addrEnd < 0 {
// Invalid hostport.
return
}
if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 {
host = hostport[1:addrEnd]
return
}
} else {
if i := strings.LastIndex(hostport, ":"); i < 0 {
host = hostport
return
}
}
host, pStr, err := net.SplitHostPort(hostport)
if err != nil {
return
}
p, err := strconv.ParseUint(pStr, 10, 16)
if err != nil {
return
}
return host, int(p)
}
func requiredHTTPPort(https bool, port int) int { // nolint:revive
if https {
if port > 0 && port != 443 {
return port
}
} else {
if port > 0 && port != 80 {
return port
}
}
return -1
}
func serverClientIP(xForwardedFor string) string {
if idx := strings.Index(xForwardedFor, ","); idx >= 0 {
xForwardedFor = xForwardedFor[:idx]
}
return xForwardedFor
}
func netProtocol(proto string) (name string, version string) {
name, version, _ = strings.Cut(proto, "/")
name = strings.ToLower(name)
return name, version
}
var methodLookup = map[string]attribute.KeyValue{
http.MethodConnect: semconvNew.HTTPRequestMethodConnect,
http.MethodDelete: semconvNew.HTTPRequestMethodDelete,
http.MethodGet: semconvNew.HTTPRequestMethodGet,
http.MethodHead: semconvNew.HTTPRequestMethodHead,
http.MethodOptions: semconvNew.HTTPRequestMethodOptions,
http.MethodPatch: semconvNew.HTTPRequestMethodPatch,
http.MethodPost: semconvNew.HTTPRequestMethodPost,
http.MethodPut: semconvNew.HTTPRequestMethodPut,
http.MethodTrace: semconvNew.HTTPRequestMethodTrace,
}

View File

@@ -0,0 +1,74 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"errors"
"io"
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)
type oldHTTPServer struct{}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (o oldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
return semconvutil.HTTPServerRequest(server, req)
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (o oldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
attributes := []attribute.KeyValue{}
if resp.ReadBytes > 0 {
attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes)))
}
if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) {
// This is not in the semantic conventions, but is historically provided
attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error()))
}
if resp.WriteBytes > 0 {
attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes)))
}
if resp.StatusCode > 0 {
attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode))
}
if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) {
// This is not in the semantic conventions, but is historically provided
attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error()))
}
return attributes
}
// Route returns the attribute for the route.
func (o oldHTTPServer) Route(route string) attribute.KeyValue {
return semconv.HTTPRoute(route)
}
// HTTPStatusCode returns the attribute for the HTTP status code.
// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added.
func HTTPStatusCode(status int) attribute.KeyValue {
return semconv.HTTPStatusCode(status)
}

View File

@@ -0,0 +1,197 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"net/http"
"strings"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.24.0"
)
type newHTTPServer struct{}
// TraceRequest returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (n newHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
count := 3 // ServerAddress, Method, Scheme
var host string
var p int
if server == "" {
host, p = splitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = splitHostPort(server)
if p < 0 {
_, p = splitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
count++
}
method, methodOriginal := n.method(req.Method)
if methodOriginal != (attribute.KeyValue{}) {
count++
}
scheme := n.scheme(req.TLS != nil)
if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
count++
if peerPort > 0 {
count++
}
}
useragent := req.UserAgent()
if useragent != "" {
count++
}
clientIP := serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP != "" {
count++
}
if req.URL != nil && req.URL.Path != "" {
count++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
count++
}
if protoVersion != "" {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
attrs = append(attrs,
semconvNew.ServerAddress(host),
method,
scheme,
)
if hostPort > 0 {
attrs = append(attrs, semconvNew.ServerPort(hostPort))
}
if methodOriginal != (attribute.KeyValue{}) {
attrs = append(attrs, methodOriginal)
}
if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
attrs = append(attrs, semconvNew.NetworkPeerAddress(peer))
if peerPort > 0 {
attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort))
}
}
if useragent := req.UserAgent(); useragent != "" {
attrs = append(attrs, semconvNew.UserAgentOriginal(useragent))
}
if clientIP != "" {
attrs = append(attrs, semconvNew.ClientAddress(clientIP))
}
if req.URL != nil && req.URL.Path != "" {
attrs = append(attrs, semconvNew.URLPath(req.URL.Path))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}
return attrs
}
func (n newHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconvNew.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconvNew.HTTPRequestMethodGet, orig
}
func (n newHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
if https {
return semconvNew.URLScheme("https")
}
return semconvNew.URLScheme("http")
}
// TraceResponse returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (n newHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
var count int
if resp.ReadBytes > 0 {
count++
}
if resp.WriteBytes > 0 {
count++
}
if resp.StatusCode > 0 {
count++
}
attributes := make([]attribute.KeyValue, 0, count)
if resp.ReadBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)),
)
}
if resp.WriteBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)),
)
}
if resp.StatusCode > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseStatusCode(resp.StatusCode),
)
}
return attributes
}
// Route returns the attribute for the route.
func (n newHTTPServer) Route(route string) attribute.KeyValue {
return semconvNew.HTTPRoute(route)
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"

View File

@@ -2,18 +2,7 @@
// source: internal/shared/semconvutil/httpconv.go.tmpl
// 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.
// SPDX-License-Identifier: Apache-2.0
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
@@ -24,7 +13,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)
// HTTPClientResponse returns trace attributes for an HTTP response received by a
@@ -43,14 +32,22 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue {
}
// HTTPClientRequest returns trace attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.url", "http.flavor",
// "http.method", "net.peer.name". The following attributes are returned if the
// related values are defined in req: "net.peer.port", "http.user_agent",
// "http.request_content_length", "enduser.id".
// The following attributes are always returned: "http.url", "http.method",
// "net.peer.name". The following attributes are returned if the related values
// are defined in req: "net.peer.port", "user_agent.original",
// "http.request_content_length".
func HTTPClientRequest(req *http.Request) []attribute.KeyValue {
return hc.ClientRequest(req)
}
// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the
// related values are defined in req: "net.peer.port".
func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue {
return hc.ClientRequestMetrics(req)
}
// HTTPClientStatus returns a span status code and message for an HTTP status code
// value received by a client.
func HTTPClientStatus(code int) (codes.Code, string) {
@@ -75,10 +72,9 @@ func HTTPClientStatus(code int) (codes.Code, string) {
// The req Host will be used to determine the server instead.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "http.flavor", "http.target", "net.host.name". The following attributes are
// returned if they related values are defined in req: "net.host.port",
// "net.sock.peer.addr", "net.sock.peer.port", "http.user_agent", "enduser.id",
// "http.client_ip".
// "http.target", "net.host.name". The following attributes are returned if
// they related values are defined in req: "net.host.port", "net.sock.peer.addr",
// "net.sock.peer.port", "user_agent.original", "http.client_ip".
func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue {
return hc.ServerRequest(server, req)
}
@@ -101,8 +97,8 @@ func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue {
// The req Host will be used to determine the server instead.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "http.flavor", "net.host.name". The following attributes are
// returned if they related values are defined in req: "net.host.port".
// "net.host.name". The following attributes are returned if they related
// values are defined in req: "net.host.port".
func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue {
return hc.ServerRequestMetrics(server, req)
}
@@ -114,44 +110,12 @@ func HTTPServerStatus(code int) (codes.Code, string) {
return hc.ServerStatus(code)
}
// HTTPRequestHeader returns the contents of h as attributes.
//
// Instrumentation should require an explicit configuration of which headers to
// captured and then prune what they pass here. Including all headers can be a
// security risk - explicit configuration helps avoid leaking sensitive
// information.
//
// The User-Agent header is already captured in the http.user_agent attribute
// from ClientRequest and ServerRequest. Instrumentation may provide an option
// to capture that header here even though it is not recommended. Otherwise,
// instrumentation should filter that out of what is passed.
func HTTPRequestHeader(h http.Header) []attribute.KeyValue {
return hc.RequestHeader(h)
}
// HTTPResponseHeader returns the contents of h as attributes.
//
// Instrumentation should require an explicit configuration of which headers to
// captured and then prune what they pass here. Including all headers can be a
// security risk - explicit configuration helps avoid leaking sensitive
// information.
//
// The User-Agent header is already captured in the http.user_agent attribute
// from ClientRequest and ServerRequest. Instrumentation may provide an option
// to capture that header here even though it is not recommended. Otherwise,
// instrumentation should filter that out of what is passed.
func HTTPResponseHeader(h http.Header) []attribute.KeyValue {
return hc.ResponseHeader(h)
}
// httpConv are the HTTP semantic convention attributes defined for a version
// of the OpenTelemetry specification.
type httpConv struct {
NetConv *netConv
EnduserIDKey attribute.Key
HTTPClientIPKey attribute.Key
HTTPFlavorKey attribute.Key
HTTPMethodKey attribute.Key
HTTPRequestContentLengthKey attribute.Key
HTTPResponseContentLengthKey attribute.Key
@@ -161,15 +125,13 @@ type httpConv struct {
HTTPStatusCodeKey attribute.Key
HTTPTargetKey attribute.Key
HTTPURLKey attribute.Key
HTTPUserAgentKey attribute.Key
UserAgentOriginalKey attribute.Key
}
var hc = &httpConv{
NetConv: nc,
EnduserIDKey: semconv.EnduserIDKey,
HTTPClientIPKey: semconv.HTTPClientIPKey,
HTTPFlavorKey: semconv.HTTPFlavorKey,
HTTPMethodKey: semconv.HTTPMethodKey,
HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey,
HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey,
@@ -179,7 +141,7 @@ var hc = &httpConv{
HTTPStatusCodeKey: semconv.HTTPStatusCodeKey,
HTTPTargetKey: semconv.HTTPTargetKey,
HTTPURLKey: semconv.HTTPURLKey,
HTTPUserAgentKey: semconv.HTTPUserAgentKey,
UserAgentOriginalKey: semconv.UserAgentOriginalKey,
}
// ClientResponse returns attributes for an HTTP response received by a client
@@ -193,6 +155,10 @@ var hc = &httpConv{
//
// append(ClientResponse(resp), ClientRequest(resp.Request)...)
func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.status_code int
http.response_content_length int
*/
var n int
if resp.StatusCode > 0 {
n++
@@ -212,11 +178,31 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue {
}
// ClientRequest returns attributes for an HTTP request made by a client. The
// following attributes are always returned: "http.url", "http.flavor",
// "http.method", "net.peer.name". The following attributes are returned if the
// related values are defined in req: "net.peer.port", "http.user_agent",
// "http.request_content_length", "enduser.id".
// following attributes are always returned: "http.url", "http.method",
// "net.peer.name". The following attributes are returned if the related values
// are defined in req: "net.peer.port", "user_agent.original",
// "http.request_content_length", "user_agent.original".
func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
user_agent.original string
http.url string
net.peer.name string
net.peer.port int
http.request_content_length int
*/
/* The following semantic conventions are not returned:
http.status_code This requires the response. See ClientResponse.
http.response_content_length This requires the response. See ClientResponse.
net.sock.family This requires the socket used.
net.sock.peer.addr This requires the socket used.
net.sock.peer.name This requires the socket used.
net.sock.peer.port This requires the socket used.
http.resend_count This is something outside of a single request.
net.protocol.name The value is the Request is ignored, and the go client will always use "http".
net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0.
*/
n := 3 // URL, peer name, proto, and method.
var h string
if req.URL != nil {
@@ -234,14 +220,10 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue {
if req.ContentLength > 0 {
n++
}
userID, _, hasUserID := req.BasicAuth()
if hasUserID {
n++
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.method(req.Method))
attrs = append(attrs, c.flavor(req.Proto))
var u string
if req.URL != nil {
@@ -260,15 +242,43 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue {
}
if useragent != "" {
attrs = append(attrs, c.HTTPUserAgentKey.String(useragent))
attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
}
if l := req.ContentLength; l > 0 {
attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l))
}
if hasUserID {
attrs = append(attrs, c.EnduserIDKey.String(userID))
return attrs
}
// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The
// following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the related values
// are defined in req: "net.peer.port".
func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
net.peer.name string
net.peer.port int
*/
n := 2 // method, peer name.
var h string
if req.URL != nil {
h = req.URL.Host
}
peer, p := firstHostPort(h, req.Header.Get("Host"))
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
if port > 0 {
n++
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer))
if port > 0 {
attrs = append(attrs, c.NetConv.PeerPort(port))
}
return attrs
@@ -291,18 +301,35 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue {
// The req Host will be used to determine the server instead.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "http.flavor", "http.target", "net.host.name". The following attributes are
// returned if they related values are defined in req: "net.host.port",
// "net.sock.peer.addr", "net.sock.peer.port", "http.user_agent", "enduser.id",
// "http.client_ip".
// "http.target", "net.host.name". The following attributes are returned if they
// related values are defined in req: "net.host.port", "net.sock.peer.addr",
// "net.sock.peer.port", "user_agent.original", "http.client_ip",
// "net.protocol.name", "net.protocol.version".
func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue {
// TODO: This currently does not add the specification required
// `http.target` attribute. It has too high of a cardinality to safely be
// added. An alternate should be added, or this comment removed, when it is
// addressed by the specification. If it is ultimately decided to continue
// not including the attribute, the HTTPTargetKey field of the httpConv
// should be removed as well.
/* The following semantic conventions are returned if present:
http.method string
http.scheme string
net.host.name string
net.host.port int
net.sock.peer.addr string
net.sock.peer.port int
user_agent.original string
http.client_ip string
net.protocol.name string Note: not set if the value is "http".
net.protocol.version string
http.target string Note: doesn't include the query parameter.
*/
/* The following semantic conventions are not returned:
http.status_code This requires the response.
http.request_content_length This requires the len() of body, which can mutate it.
http.response_content_length This requires the response.
http.route This is not available.
net.sock.peer.name This would require a DNS lookup.
net.sock.host.addr The request doesn't have access to the underlying socket.
net.sock.host.port The request doesn't have access to the underlying socket.
*/
n := 4 // Method, scheme, proto, and host name.
var host string
var p int
@@ -330,19 +357,31 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K
if useragent != "" {
n++
}
userID, _, hasUserID := req.BasicAuth()
if hasUserID {
n++
}
clientIP := serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP != "" {
n++
}
var target string
if req.URL != nil {
target = req.URL.Path
if target != "" {
n++
}
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
n++
}
if protoVersion != "" {
n++
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.method(req.Method))
attrs = append(attrs, c.scheme(req.TLS != nil))
attrs = append(attrs, c.flavor(req.Proto))
attrs = append(attrs, c.NetConv.HostName(host))
if hostPort > 0 {
@@ -359,17 +398,24 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K
}
if useragent != "" {
attrs = append(attrs, c.HTTPUserAgentKey.String(useragent))
}
if hasUserID {
attrs = append(attrs, c.EnduserIDKey.String(userID))
attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
}
if clientIP != "" {
attrs = append(attrs, c.HTTPClientIPKey.String(clientIP))
}
if target != "" {
attrs = append(attrs, c.HTTPTargetKey.String(target))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName))
}
if protoVersion != "" {
attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion))
}
return attrs
}
@@ -391,17 +437,21 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K
// The req Host will be used to determine the server instead.
//
// The following attributes are always returned: "http.method", "http.scheme",
// "http.flavor", "net.host.name". The following attributes are
// returned if they related values are defined in req: "net.host.port".
// "net.host.name". The following attributes are returned if they related
// values are defined in req: "net.host.port".
func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue {
// TODO: This currently does not add the specification required
// `http.target` attribute. It has too high of a cardinality to safely be
// added. An alternate should be added, or this comment removed, when it is
// addressed by the specification. If it is ultimately decided to continue
// not including the attribute, the HTTPTargetKey field of the httpConv
// should be removed as well.
/* The following semantic conventions are returned if present:
http.scheme string
http.route string
http.method string
http.status_code int
net.host.name string
net.host.port int
net.protocol.name string Note: not set if the value is "http".
net.protocol.version string
*/
n := 4 // Method, scheme, proto, and host name.
n := 3 // Method, scheme, and host name.
var host string
var p int
if server == "" {
@@ -417,16 +467,29 @@ func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attr
if hostPort > 0 {
n++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
n++
}
if protoVersion != "" {
n++
}
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.methodMetric(req.Method))
attrs = append(attrs, c.scheme(req.TLS != nil))
attrs = append(attrs, c.flavor(req.Proto))
attrs = append(attrs, c.NetConv.HostName(host))
if hostPort > 0 {
attrs = append(attrs, c.NetConv.HostPort(hostPort))
}
if protoName != "" {
attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName))
}
if protoVersion != "" {
attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion))
}
return attrs
}
@@ -455,21 +518,6 @@ func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive
return c.HTTPSchemeHTTP
}
func (c *httpConv) flavor(proto string) attribute.KeyValue {
switch proto {
case "HTTP/1.0":
return c.HTTPFlavorKey.String("1.0")
case "HTTP/1.1":
return c.HTTPFlavorKey.String("1.1")
case "HTTP/2":
return c.HTTPFlavorKey.String("2.0")
case "HTTP/3":
return c.HTTPFlavorKey.String("3.0")
default:
return c.HTTPFlavorKey.String(proto)
}
}
func serverClientIP(xForwardedFor string) string {
if idx := strings.Index(xForwardedFor, ","); idx >= 0 {
xForwardedFor = xForwardedFor[:idx]
@@ -501,31 +549,6 @@ func firstHostPort(source ...string) (host string, port int) {
return
}
// RequestHeader returns the contents of h as OpenTelemetry attributes.
func (c *httpConv) RequestHeader(h http.Header) []attribute.KeyValue {
return c.header("http.request.header", h)
}
// ResponseHeader returns the contents of h as OpenTelemetry attributes.
func (c *httpConv) ResponseHeader(h http.Header) []attribute.KeyValue {
return c.header("http.response.header", h)
}
func (c *httpConv) header(prefix string, h http.Header) []attribute.KeyValue {
key := func(k string) attribute.Key {
k = strings.ToLower(k)
k = strings.ReplaceAll(k, "-", "_")
k = fmt.Sprintf("%s.%s", prefix, k)
return attribute.Key(k)
}
attrs := make([]attribute.KeyValue, 0, len(h))
for k, v := range h {
attrs = append(attrs, key(k).StringSlice(v))
}
return attrs
}
// ClientStatus returns a span status code and message for an HTTP status code
// value received by a client.
func (c *httpConv) ClientStatus(code int) (codes.Code, string) {

View File

@@ -2,17 +2,7 @@
// source: internal/shared/semconvutil/netconv.go.tmpl
// 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.
// SPDX-License-Identifier: Apache-2.0
package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
@@ -22,7 +12,7 @@ import (
"strings"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)
// NetTransport returns a trace attribute describing the transport protocol of the
@@ -32,24 +22,6 @@ func NetTransport(network string) attribute.KeyValue {
return nc.Transport(network)
}
// NetClient returns trace attributes for a client network connection to address.
// See net.Dial for information about acceptable address values, address should
// be the same as the one used to create conn. If conn is nil, only network
// peer attributes will be returned that describe address. Otherwise, the
// socket level information about conn will also be included.
func NetClient(address string, conn net.Conn) []attribute.KeyValue {
return nc.Client(address, conn)
}
// NetServer returns trace attributes for a network listener listening at address.
// See net.Listen for information about acceptable address values, address
// should be the same as the one used to create ln. If ln is nil, only network
// host attributes will be returned that describe address. Otherwise, the
// socket level information about ln will also be included.
func NetServer(address string, ln net.Listener) []attribute.KeyValue {
return nc.Server(address, ln)
}
// netConv are the network semantic convention attributes defined for a version
// of the OpenTelemetry specification.
type netConv struct {
@@ -57,6 +29,8 @@ type netConv struct {
NetHostPortKey attribute.Key
NetPeerNameKey attribute.Key
NetPeerPortKey attribute.Key
NetProtocolName attribute.Key
NetProtocolVersion attribute.Key
NetSockFamilyKey attribute.Key
NetSockPeerAddrKey attribute.Key
NetSockPeerPortKey attribute.Key
@@ -73,6 +47,8 @@ var nc = &netConv{
NetHostPortKey: semconv.NetHostPortKey,
NetPeerNameKey: semconv.NetPeerNameKey,
NetPeerPortKey: semconv.NetPeerPortKey,
NetProtocolName: semconv.NetProtocolNameKey,
NetProtocolVersion: semconv.NetProtocolVersionKey,
NetSockFamilyKey: semconv.NetSockFamilyKey,
NetSockPeerAddrKey: semconv.NetSockPeerAddrKey,
NetSockPeerPortKey: semconv.NetSockPeerPortKey,
@@ -116,57 +92,11 @@ func (c *netConv) Host(address string) []attribute.KeyValue {
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.HostName(h))
if p > 0 {
attrs = append(attrs, c.HostPort(int(p)))
attrs = append(attrs, c.HostPort(p))
}
return attrs
}
// Server returns attributes for a network listener listening at address. See
// net.Listen for information about acceptable address values, address should
// be the same as the one used to create ln. If ln is nil, only network host
// attributes will be returned that describe address. Otherwise, the socket
// level information about ln will also be included.
func (c *netConv) Server(address string, ln net.Listener) []attribute.KeyValue {
if ln == nil {
return c.Host(address)
}
lAddr := ln.Addr()
if lAddr == nil {
return c.Host(address)
}
hostName, hostPort := splitHostPort(address)
sockHostAddr, sockHostPort := splitHostPort(lAddr.String())
network := lAddr.Network()
sockFamily := family(network, sockHostAddr)
n := nonZeroStr(hostName, network, sockHostAddr, sockFamily)
n += positiveInt(hostPort, sockHostPort)
attr := make([]attribute.KeyValue, 0, n)
if hostName != "" {
attr = append(attr, c.HostName(hostName))
if hostPort > 0 {
// Only if net.host.name is set should net.host.port be.
attr = append(attr, c.HostPort(hostPort))
}
}
if network != "" {
attr = append(attr, c.Transport(network))
}
if sockFamily != "" {
attr = append(attr, c.NetSockFamilyKey.String(sockFamily))
}
if sockHostAddr != "" {
attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr))
if sockHostPort > 0 {
// Only if net.sock.host.addr is set should net.sock.host.port be.
attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort))
}
}
return attr
}
func (c *netConv) HostName(name string) attribute.KeyValue {
return c.NetHostNameKey.String(name)
}
@@ -175,85 +105,6 @@ func (c *netConv) HostPort(port int) attribute.KeyValue {
return c.NetHostPortKey.Int(port)
}
// Client returns attributes for a client network connection to address. See
// net.Dial for information about acceptable address values, address should be
// the same as the one used to create conn. If conn is nil, only network peer
// attributes will be returned that describe address. Otherwise, the socket
// level information about conn will also be included.
func (c *netConv) Client(address string, conn net.Conn) []attribute.KeyValue {
if conn == nil {
return c.Peer(address)
}
lAddr, rAddr := conn.LocalAddr(), conn.RemoteAddr()
var network string
switch {
case lAddr != nil:
network = lAddr.Network()
case rAddr != nil:
network = rAddr.Network()
default:
return c.Peer(address)
}
peerName, peerPort := splitHostPort(address)
var (
sockFamily string
sockPeerAddr string
sockPeerPort int
sockHostAddr string
sockHostPort int
)
if lAddr != nil {
sockHostAddr, sockHostPort = splitHostPort(lAddr.String())
}
if rAddr != nil {
sockPeerAddr, sockPeerPort = splitHostPort(rAddr.String())
}
switch {
case sockHostAddr != "":
sockFamily = family(network, sockHostAddr)
case sockPeerAddr != "":
sockFamily = family(network, sockPeerAddr)
}
n := nonZeroStr(peerName, network, sockPeerAddr, sockHostAddr, sockFamily)
n += positiveInt(peerPort, sockPeerPort, sockHostPort)
attr := make([]attribute.KeyValue, 0, n)
if peerName != "" {
attr = append(attr, c.PeerName(peerName))
if peerPort > 0 {
// Only if net.peer.name is set should net.peer.port be.
attr = append(attr, c.PeerPort(peerPort))
}
}
if network != "" {
attr = append(attr, c.Transport(network))
}
if sockFamily != "" {
attr = append(attr, c.NetSockFamilyKey.String(sockFamily))
}
if sockPeerAddr != "" {
attr = append(attr, c.NetSockPeerAddrKey.String(sockPeerAddr))
if sockPeerPort > 0 {
// Only if net.sock.peer.addr is set should net.sock.peer.port be.
attr = append(attr, c.NetSockPeerPortKey.Int(sockPeerPort))
}
}
if sockHostAddr != "" {
attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr))
if sockHostPort > 0 {
// Only if net.sock.host.addr is set should net.sock.host.port be.
attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort))
}
}
return attr
}
func family(network, address string) string {
switch network {
case "unix", "unixgram", "unixpacket":
@@ -269,26 +120,6 @@ func family(network, address string) string {
return ""
}
func nonZeroStr(strs ...string) int {
var n int
for _, str := range strs {
if str != "" {
n++
}
}
return n
}
func positiveInt(ints ...int) int {
var n int
for _, i := range ints {
if i > 0 {
n++
}
}
return n
}
// Peer returns attributes for a network peer address.
func (c *netConv) Peer(address string) []attribute.KeyValue {
h, p := splitHostPort(address)
@@ -307,7 +138,7 @@ func (c *netConv) Peer(address string) []attribute.KeyValue {
attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.PeerName(h))
if p > 0 {
attrs = append(attrs, c.PeerPort(int(p)))
attrs = append(attrs, c.PeerPort(p))
}
return attrs
}
@@ -366,3 +197,9 @@ func splitHostPort(hostport string) (host string, port int) {
}
return host, int(p)
}
func netProtocol(proto string) (name string, version string) {
name, version, _ = strings.Cut(proto, "/")
name = strings.ToLower(name)
return name, version
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -48,8 +37,12 @@ type labelerContextKeyType int
const lablelerContextKey labelerContextKeyType = 0
func injectLabeler(ctx context.Context, l *Labeler) context.Context {
return context.WithValue(ctx, lablelerContextKey, l)
// ContextWithLabeler returns a new context with the provided Labeler instance.
// Attributes added to the specified labeler will be injected into metrics
// emitted by the instrumentation. Only one labeller can be injected into the
// context. Injecting it multiple times will override the previous calls.
func ContextWithLabeler(parent context.Context, l *Labeler) context.Context {
return context.WithValue(parent, lablelerContextKey, l)
}
// LabelerFromContext retrieves a Labeler instance from the provided context if

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -19,31 +8,42 @@ import (
"io"
"net/http"
"net/http/httptrace"
"sync/atomic"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
"go.opentelemetry.io/otel/trace"
)
// Transport implements the http.RoundTripper interface and wraps
// outbound HTTP(S) requests with a span.
// outbound HTTP(S) requests with a span and enriches it with metrics.
type Transport struct {
rt http.RoundTripper
tracer trace.Tracer
meter metric.Meter
propagators propagation.TextMapPropagator
spanStartOptions []trace.SpanStartOption
filters []Filter
spanNameFormatter func(string, *http.Request) string
clientTrace func(context.Context) *httptrace.ClientTrace
requestBytesCounter metric.Int64Counter
responseBytesCounter metric.Int64Counter
latencyMeasure metric.Float64Histogram
}
var _ http.RoundTripper = &Transport{}
// NewTransport wraps the provided http.RoundTripper with one that
// starts a span and injects the span context into the outbound request headers.
// starts a span, injects the span context into the outbound request headers,
// and enriches it with metrics.
//
// If the provided http.RoundTripper is nil, http.DefaultTransport will be used
// as the base http.RoundTripper.
@@ -63,12 +63,14 @@ func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
c := newConfig(append(defaultOpts, opts...)...)
t.applyConfig(c)
t.createMeasures()
return &t
}
func (t *Transport) applyConfig(c *config) {
t.tracer = c.Tracer
t.meter = c.Meter
t.propagators = c.Propagators
t.spanStartOptions = c.SpanStartOptions
t.filters = c.Filters
@@ -76,6 +78,30 @@ func (t *Transport) applyConfig(c *config) {
t.clientTrace = c.ClientTrace
}
func (t *Transport) createMeasures() {
var err error
t.requestBytesCounter, err = t.meter.Int64Counter(
clientRequestSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP request messages."),
)
handleErr(err)
t.responseBytesCounter, err = t.meter.Int64Counter(
clientResponseSize,
metric.WithUnit("By"),
metric.WithDescription("Measures the size of HTTP response messages."),
)
handleErr(err)
t.latencyMeasure, err = t.meter.Float64Histogram(
clientDuration,
metric.WithUnit("ms"),
metric.WithDescription("Measures the duration of outbound HTTP requests."),
)
handleErr(err)
}
func defaultTransportFormatter(_ string, r *http.Request) string {
return "HTTP " + r.Method
}
@@ -84,6 +110,7 @@ func defaultTransportFormatter(_ string, r *http.Request) string {
// before handing the request to the configured base RoundTripper. The created span will
// end when the response body is closed or when a read from the body returns io.EOF.
func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
requestStartTime := time.Now()
for _, f := range t.filters {
if !f(r) {
// Simply pass through to the base RoundTripper if a filter rejects the request
@@ -109,7 +136,25 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx))
}
labeler, found := LabelerFromContext(ctx)
if !found {
ctx = ContextWithLabeler(ctx, labeler)
}
r = r.Clone(ctx) // According to RoundTripper spec, we shouldn't modify the origin request.
// use a body wrapper to determine the request size
var bw bodyWrapper
// if request body is nil or NoBody, we don't want to mutate the body as it
// will affect the identity of it in an unforeseeable way because we assert
// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
if r.Body != nil && r.Body != http.NoBody {
bw.ReadCloser = r.Body
// noop to prevent nil panic. not using this record fun yet.
bw.record = func(int64) {}
r.Body = &bw
}
span.SetAttributes(semconvutil.HTTPClientRequest(r)...)
t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))
@@ -121,9 +166,29 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
return res, err
}
// metrics
metricAttrs := append(labeler.Get(), semconvutil.HTTPClientRequestMetrics(r)...)
if res.StatusCode > 0 {
metricAttrs = append(metricAttrs, semconv.HTTPStatusCode(res.StatusCode))
}
o := metric.WithAttributeSet(attribute.NewSet(metricAttrs...))
addOpts := []metric.AddOption{o} // Allocate vararg slice once.
t.requestBytesCounter.Add(ctx, bw.read.Load(), addOpts...)
// For handling response bytes we leverage a callback when the client reads the http response
readRecordFunc := func(n int64) {
t.responseBytesCounter.Add(ctx, n, addOpts...)
}
// traces
span.SetAttributes(semconvutil.HTTPClientResponse(res)...)
span.SetStatus(semconvutil.HTTPClientStatus(res.StatusCode))
res.Body = newWrappedBody(span, res.Body)
res.Body = newWrappedBody(span, readRecordFunc, res.Body)
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
t.latencyMeasure.Record(ctx, elapsedTime, o)
return res, err
}
@@ -131,17 +196,17 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
// newWrappedBody returns a new and appropriately scoped *wrappedBody as an
// io.ReadCloser. If the passed body implements io.Writer, the returned value
// will implement io.ReadWriteCloser.
func newWrappedBody(span trace.Span, body io.ReadCloser) io.ReadCloser {
func newWrappedBody(span trace.Span, record func(n int64), body io.ReadCloser) io.ReadCloser {
// The successful protocol switch responses will have a body that
// implement an io.ReadWriteCloser. Ensure this interface type continues
// to be satisfied if that is the case.
if _, ok := body.(io.ReadWriteCloser); ok {
return &wrappedBody{span: span, body: body}
return &wrappedBody{span: span, record: record, body: body}
}
// Remove the implementation of the io.ReadWriteCloser and only implement
// the io.ReadCloser.
return struct{ io.ReadCloser }{&wrappedBody{span: span, body: body}}
return struct{ io.ReadCloser }{&wrappedBody{span: span, record: record, body: body}}
}
// wrappedBody is the response body type returned by the transport
@@ -153,8 +218,11 @@ func newWrappedBody(span trace.Span, body io.ReadCloser) io.ReadCloser {
// If the response body implements the io.Writer interface (i.e. for
// successful protocol switches), the wrapped body also will.
type wrappedBody struct {
span trace.Span
body io.ReadCloser
span trace.Span
recorded atomic.Bool
record func(n int64)
body io.ReadCloser
read atomic.Int64
}
var _ io.ReadWriteCloser = &wrappedBody{}
@@ -171,11 +239,14 @@ func (wb *wrappedBody) Write(p []byte) (int, error) {
func (wb *wrappedBody) Read(b []byte) (int, error) {
n, err := wb.body.Read(b)
// Record the number of bytes read
wb.read.Add(int64(n))
switch err {
case nil:
// nothing to do here but fall through to the return
case io.EOF:
wb.recordBytesRead()
wb.span.End()
default:
wb.span.RecordError(err)
@@ -184,7 +255,20 @@ func (wb *wrappedBody) Read(b []byte) (int, error) {
return n, err
}
// recordBytesRead is a function that ensures the number of bytes read is recorded once and only once.
func (wb *wrappedBody) recordBytesRead() {
// note: it is more performant (and equally correct) to use atomic.Bool over sync.Once here. In the event that
// two goroutines are racing to call this method, the number of bytes read will no longer increase. Using
// CompareAndSwap allows later goroutines to return quickly and not block waiting for the race winner to finish
// calling wb.record(wb.read.Load()).
if wb.recorded.CompareAndSwap(false, true) {
// Record the total number of bytes read
wb.record(wb.read.Load())
}
}
func (wb *wrappedBody) Close() error {
wb.recordBytesRead()
wb.span.End()
if wb.body != nil {
return wb.body.Close()

View File

@@ -1,22 +1,11 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
// Version is the current release version of the otelhttp instrumentation.
func Version() string {
return "0.44.0"
return "0.53.0"
// This string is updated by the pre_release.sh script during release
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -18,6 +7,7 @@ import (
"context"
"io"
"net/http"
"sync/atomic"
"go.opentelemetry.io/otel/propagation"
)
@@ -30,14 +20,14 @@ type bodyWrapper struct {
io.ReadCloser
record func(n int64) // must not be nil
read int64
read atomic.Int64
err error
}
func (w *bodyWrapper) Read(b []byte) (int, error) {
n, err := w.ReadCloser.Read(b)
n1 := int64(n)
w.read += n1
w.read.Add(n1)
w.err = err
w.record(n1)
return n, err
@@ -97,3 +87,13 @@ func (w *respWriterWrapper) WriteHeader(statusCode int) {
}
w.ResponseWriter.WriteHeader(statusCode)
}
func (w *respWriterWrapper) Flush() {
if !w.wroteHeader {
w.WriteHeader(http.StatusOK)
}
if f, ok := w.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}

View File

@@ -3,3 +3,7 @@ fo
te
collison
consequentially
ans
nam
valu
thirdparty

View File

@@ -5,6 +5,6 @@ check-filenames =
check-hidden =
ignore-words = .codespellignore
interactive = 1
skip = .git,go.mod,go.sum,semconv,venv,.tools
skip = .git,go.mod,go.sum,go.work,go.work.sum,semconv,venv,.tools
uri-ignore-words-list = *
write =

View File

@@ -14,13 +14,9 @@ go.work.sum
gen/
/example/dice/dice
/example/fib/fib
/example/fib/traces.txt
/example/jaeger/jaeger
/example/namedtracer/namedtracer
/example/otel-collector/otel-collector
/example/opencensus/opencensus
/example/passthrough/passthrough
/example/prometheus/prometheus
/example/view/view
/example/zipkin/zipkin

View File

@@ -1,3 +0,0 @@
[submodule "opentelemetry-proto"]
path = exporters/otlp/internal/opentelemetry-proto
url = https://github.com/open-telemetry/opentelemetry-proto

View File

@@ -11,6 +11,7 @@ linters:
enable:
- depguard
- errcheck
- errorlint
- godot
- gofumpt
- goimports
@@ -21,8 +22,11 @@ linters:
- misspell
- revive
- staticcheck
- tenv
- typecheck
- unconvert
- unused
- unparam
issues:
# Maximum issues count per one linter.
@@ -124,6 +128,8 @@ linters-settings:
- "**/example/**/*.go"
- "**/trace/*.go"
- "**/trace/**/*.go"
- "**/log/*.go"
- "**/log/**/*.go"
deny:
- pkg: "go.opentelemetry.io/otel/internal$"
desc: Do not use cross-module internal packages.

View File

@@ -8,9 +8,291 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
## [1.28.0/0.50.0/0.4.0] 2024-07-02
### Added
- The `IsEmpty` method is added to the `Instrument` type in `go.opentelemetry.io/otel/sdk/metric`.
This method is used to check if an `Instrument` instance is a zero-value. (#5431)
- Store and provide the emitted `context.Context` in `ScopeRecords` of `go.opentelemetry.io/otel/sdk/log/logtest`. (#5468)
- The `go.opentelemetry.io/otel/semconv/v1.26.0` package.
The package contains semantic conventions from the `v1.26.0` version of the OpenTelemetry Semantic Conventions. (#5476)
- The `AssertRecordEqual` method to `go.opentelemetry.io/otel/log/logtest` to allow comparison of two log records in tests. (#5499)
- The `WithHeaders` option to `go.opentelemetry.io/otel/exporters/zipkin` to allow configuring custom http headers while exporting spans. (#5530)
### Changed
- `Tracer.Start` in `go.opentelemetry.io/otel/trace/noop` no longer allocates a span for empty span context. (#5457)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/example/otel-collector`. (#5490)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/example/zipkin`. (#5490)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/exporters/zipkin`. (#5490)
- The exporter no longer exports the deprecated "otel.library.name" or "otel.library.version" attributes.
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/sdk/resource`. (#5490)
- Upgrade `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0` in `go.opentelemetry.io/otel/sdk/trace`. (#5490)
- `SimpleProcessor.OnEmit` in `go.opentelemetry.io/otel/sdk/log` no longer allocates a slice which makes it possible to have a zero-allocation log processing using `SimpleProcessor`. (#5493)
- Use non-generic functions in the `Start` method of `"go.opentelemetry.io/otel/sdk/trace".Trace` to reduce memory allocation. (#5497)
- `service.instance.id` is populated for a `Resource` created with `"go.opentelemetry.io/otel/sdk/resource".Default` with a default value when `OTEL_GO_X_RESOURCE` is set. (#5520)
- Improve performance of metric instruments in `go.opentelemetry.io/otel/sdk/metric` by removing unnecessary calls to `time.Now`. (#5545)
### Fixed
- Log a warning to the OpenTelemetry internal logger when a `Record` in `go.opentelemetry.io/otel/sdk/log` drops an attribute due to a limit being reached. (#5376)
- Identify the `Tracer` returned from the global `TracerProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426)
- Identify the `Meter` returned from the global `MeterProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426)
- Log a warning to the OpenTelemetry internal logger when a `Span` in `go.opentelemetry.io/otel/sdk/trace` drops an attribute, event, or link due to a limit being reached. (#5434)
- Document instrument name requirements in `go.opentelemetry.io/otel/metric`. (#5435)
- Prevent random number generation data-race for experimental rand exemplars in `go.opentelemetry.io/otel/sdk/metric`. (#5456)
- Fix counting number of dropped attributes of `Record` in `go.opentelemetry.io/otel/sdk/log`. (#5464)
- Fix panic in baggage creation when a member contains `0x80` char in key or value. (#5494)
- Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`. (#5508)
- Retry trace and span ID generation if it generated an invalid one in `go.opentelemetry.io/otel/sdk/trace`. (#5514)
- Fix stale timestamps reported by the last-value aggregation. (#5517)
- Indicate the `Exporter` in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp` must be created by the `New` method. (#5521)
- Improved performance in all `{Bool,Int64,Float64,String}SliceValue` functions of `go.opentelemetry.io/attributes` by reducing the number of allocations. (#5549)
## [1.27.0/0.49.0/0.3.0] 2024-05-21
### Added
- Add example for `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`. (#5242)
- Add `RecordFactory` in `go.opentelemetry.io/otel/sdk/log/logtest` to facilitate testing exporter and processor implementations. (#5258)
- Add `RecordFactory` in `go.opentelemetry.io/otel/log/logtest` to facilitate testing bridge implementations. (#5263)
- The count of dropped records from the `BatchProcessor` in `go.opentelemetry.io/otel/sdk/log` is logged. (#5276)
- Add metrics in the `otel-collector` example. (#5283)
- Add the synchronous gauge instrument to `go.opentelemetry.io/otel/metric`. (#5304)
- An `int64` or `float64` synchronous gauge instrument can now be created from a `Meter`.
- All implementations of the API (`go.opentelemetry.io/otel/metric/noop`, `go.opentelemetry.io/otel/sdk/metric`) are updated to support this instrument.
- Add logs to `go.opentelemetry.io/otel/example/dice`. (#5349)
### Changed
- The `Shutdown` method of `Exporter` in `go.opentelemetry.io/otel/exporters/stdout/stdouttrace` ignores the context cancellation and always returns `nil`. (#5189)
- The `ForceFlush` and `Shutdown` methods of the exporter returned by `New` in `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` ignore the context cancellation and always return `nil`. (#5189)
- Apply the value length limits to `Record` attributes in `go.opentelemetry.io/otel/sdk/log`. (#5230)
- De-duplicate map attributes added to a `Record` in `go.opentelemetry.io/otel/sdk/log`. (#5230)
- `go.opentelemetry.io/otel/exporters/stdout/stdoutlog` won't print timestamps when `WithoutTimestamps` option is set. (#5241)
- The `go.opentelemetry.io/otel/exporters/stdout/stdoutlog` exporter won't print `AttributeValueLengthLimit` and `AttributeCountLimit` fields now, instead it prints the `DroppedAttributes` field. (#5272)
- Improved performance in the `Stringer` implementation of `go.opentelemetry.io/otel/baggage.Member` by reducing the number of allocations. (#5286)
- Set the start time for last-value aggregates in `go.opentelemetry.io/otel/sdk/metric`. (#5305)
- The `Span` in `go.opentelemetry.io/otel/sdk/trace` will record links without span context if either non-empty `TraceState` or attributes are provided. (#5315)
- Upgrade all dependencies of `go.opentelemetry.io/otel/semconv/v1.24.0` to `go.opentelemetry.io/otel/semconv/v1.25.0`. (#5374)
### Fixed
- Comparison of unordered maps for `go.opentelemetry.io/otel/log.KeyValue` and `go.opentelemetry.io/otel/log.Value`. (#5306)
- Fix the empty output of `go.opentelemetry.io/otel/log.Value` in `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`. (#5311)
- Split the behavior of `Recorder` in `go.opentelemetry.io/otel/log/logtest` so it behaves as a `LoggerProvider` only. (#5365)
- Fix wrong package name of the error message when parsing endpoint URL in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#5371)
- Identify the `Logger` returned from the global `LoggerProvider` in `go.opentelemetry.io/otel/log/global` with its schema URL. (#5375)
## [1.26.0/0.48.0/0.2.0-alpha] 2024-04-24
### Added
- Add `Recorder` in `go.opentelemetry.io/otel/log/logtest` to facilitate testing the log bridge implementations. (#5134)
- Add span flags to OTLP spans and links exported by `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#5194)
- Make the initial alpha release of `go.opentelemetry.io/otel/sdk/log`.
This new module contains the Go implementation of the OpenTelemetry Logs SDK.
This module is unstable and breaking changes may be introduced.
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. (#5240)
- Make the initial alpha release of `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`.
This new module contains an OTLP exporter that transmits log telemetry using HTTP.
This module is unstable and breaking changes may be introduced.
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. (#5240)
- Make the initial alpha release of `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`.
This new module contains an exporter prints log records to STDOUT.
This module is unstable and breaking changes may be introduced.
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees. (#5240)
- The `go.opentelemetry.io/otel/semconv/v1.25.0` package.
The package contains semantic conventions from the `v1.25.0` version of the OpenTelemetry Semantic Conventions. (#5254)
### Changed
- Update `go.opentelemetry.io/proto/otlp` from v1.1.0 to v1.2.0. (#5177)
- Improve performance of baggage member character validation in `go.opentelemetry.io/otel/baggage`. (#5214)
- The `otel-collector` example now uses docker compose to bring up services instead of kubernetes. (#5244)
### Fixed
- Slice attribute values in `go.opentelemetry.io/otel/attribute` are now emitted as their JSON representation. (#5159)
## [1.25.0/0.47.0/0.0.8/0.1.0-alpha] 2024-04-05
### Added
- Add `WithProxy` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4906)
- Add `WithProxy` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlptracehttp`. (#4906)
- Add `AddLink` method to the `Span` interface in `go.opentelemetry.io/otel/trace`. (#5032)
- The `Enabled` method is added to the `Logger` interface in `go.opentelemetry.io/otel/log`.
This method is used to notify users if a log record will be emitted or not. (#5071)
- Add `SeverityUndefined` `const` to `go.opentelemetry.io/otel/log`.
This value represents an unset severity level. (#5072)
- Add `Empty` function in `go.opentelemetry.io/otel/log` to return a `KeyValue` for an empty value. (#5076)
- Add `go.opentelemetry.io/otel/log/global` to manage the global `LoggerProvider`.
This package is provided with the anticipation that all functionality will be migrate to `go.opentelemetry.io/otel` when `go.opentelemetry.io/otel/log` stabilizes.
At which point, users will be required to migrage their code, and this package will be deprecated then removed. (#5085)
- Add support for `Summary` metrics in the `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` exporters. (#5100)
- Add `otel.scope.name` and `otel.scope.version` tags to spans exported by `go.opentelemetry.io/otel/exporters/zipkin`. (#5108)
- Add support for `AddLink` to `go.opentelemetry.io/otel/bridge/opencensus`. (#5116)
- Add `String` method to `Value` and `KeyValue` in `go.opentelemetry.io/otel/log`. (#5117)
- Add Exemplar support to `go.opentelemetry.io/otel/exporters/prometheus`. (#5111)
- Add metric semantic conventions to `go.opentelemetry.io/otel/semconv/v1.24.0`. Future `semconv` packages will include metric semantic conventions as well. (#4528)
### Changed
- `SpanFromContext` and `SpanContextFromContext` in `go.opentelemetry.io/otel/trace` no longer make a heap allocation when the passed context has no span. (#5049)
- `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` now create a gRPC client in idle mode and with "dns" as the default resolver using [`grpc.NewClient`](https://pkg.go.dev/google.golang.org/grpc#NewClient). (#5151)
Because of that `WithDialOption` ignores [`grpc.WithBlock`](https://pkg.go.dev/google.golang.org/grpc#WithBlock), [`grpc.WithTimeout`](https://pkg.go.dev/google.golang.org/grpc#WithTimeout), and [`grpc.WithReturnConnectionError`](https://pkg.go.dev/google.golang.org/grpc#WithReturnConnectionError).
Notice that [`grpc.DialContext`](https://pkg.go.dev/google.golang.org/grpc#DialContext) which was used before is now deprecated.
### Fixed
- Clarify the documentation about equivalence guarantees for the `Set` and `Distinct` types in `go.opentelemetry.io/otel/attribute`. (#5027)
- Prevent default `ErrorHandler` self-delegation. (#5137)
- Update all dependencies to address [GO-2024-2687]. (#5139)
### Removed
- Drop support for [Go 1.20]. (#4967)
### Deprecated
- Deprecate `go.opentelemetry.io/otel/attribute.Sortable` type. (#4734)
- Deprecate `go.opentelemetry.io/otel/attribute.NewSetWithSortable` function. (#4734)
- Deprecate `go.opentelemetry.io/otel/attribute.NewSetWithSortableFiltered` function. (#4734)
## [1.24.0/0.46.0/0.0.1-alpha] 2024-02-23
This release is the last to support [Go 1.20].
The next release will require at least [Go 1.21].
### Added
- Support [Go 1.22]. (#4890)
- Add exemplar support to `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#4900)
- Add exemplar support to `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4900)
- The `go.opentelemetry.io/otel/log` module is added.
This module includes OpenTelemetry Go's implementation of the Logs Bridge API.
This module is in an alpha state, it is subject to breaking changes.
See our [versioning policy](./VERSIONING.md) for more info. (#4961)
- ARM64 platform to the compatibility testing suite. (#4994)
### Fixed
- Fix registration of multiple callbacks when using the global meter provider from `go.opentelemetry.io/otel`. (#4945)
- Fix negative buckets in output of exponential histograms. (#4956)
## [1.23.1] 2024-02-07
### Fixed
- Register all callbacks passed during observable instrument creation instead of just the last one multiple times in `go.opentelemetry.io/otel/sdk/metric`. (#4888)
## [1.23.0] 2024-02-06
This release contains the first stable, `v1`, release of the following modules:
- `go.opentelemetry.io/otel/bridge/opencensus`
- `go.opentelemetry.io/otel/bridge/opencensus/test`
- `go.opentelemetry.io/otel/example/opencensus`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`
- `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric`
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees.
### Added
- Add `WithEndpointURL` option to the `exporters/otlp/otlpmetric/otlpmetricgrpc`, `exporters/otlp/otlpmetric/otlpmetrichttp`, `exporters/otlp/otlptrace/otlptracegrpc` and `exporters/otlp/otlptrace/otlptracehttp` packages. (#4808)
- Experimental exemplar exporting is added to the metric SDK.
See [metric documentation](./sdk/metric/internal/x/README.md#exemplars) for more information about this feature and how to enable it. (#4871)
- `ErrSchemaURLConflict` is added to `go.opentelemetry.io/otel/sdk/resource`.
This error is returned when a merge of two `Resource`s with different (non-empty) schema URL is attempted. (#4876)
### Changed
- The `Merge` and `New` functions in `go.opentelemetry.io/otel/sdk/resource` now returns a partial result if there is a schema URL merge conflict.
Instead of returning `nil` when two `Resource`s with different (non-empty) schema URLs are merged the merged `Resource`, along with the new `ErrSchemaURLConflict` error, is returned.
It is up to the user to decide if they want to use the returned `Resource` or not.
It may have desired attributes overwritten or include stale semantic conventions. (#4876)
### Fixed
- Fix `ContainerID` resource detection on systemd when cgroup path has a colon. (#4449)
- Fix `go.opentelemetry.io/otel/sdk/metric` to cache instruments to avoid leaking memory when the same instrument is created multiple times. (#4820)
- Fix missing `Mix` and `Max` values for `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` by introducing `MarshalText` and `MarshalJSON` for the `Extrema` type in `go.opentelemetry.io/sdk/metric/metricdata`. (#4827)
## [1.23.0-rc.1] 2024-01-18
This is a release candidate for the v1.23.0 release.
That release is expected to include the `v1` release of the following modules:
- `go.opentelemetry.io/otel/bridge/opencensus`
- `go.opentelemetry.io/otel/bridge/opencensus/test`
- `go.opentelemetry.io/otel/example/opencensus`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`
- `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric`
See our [versioning policy](VERSIONING.md) for more information about these stability guarantees.
## [1.22.0/0.45.0] 2024-01-17
### Added
- The `go.opentelemetry.io/otel/semconv/v1.22.0` package.
The package contains semantic conventions from the `v1.22.0` version of the OpenTelemetry Semantic Conventions. (#4735)
- The `go.opentelemetry.io/otel/semconv/v1.23.0` package.
The package contains semantic conventions from the `v1.23.0` version of the OpenTelemetry Semantic Conventions. (#4746)
- The `go.opentelemetry.io/otel/semconv/v1.23.1` package.
The package contains semantic conventions from the `v1.23.1` version of the OpenTelemetry Semantic Conventions. (#4749)
- The `go.opentelemetry.io/otel/semconv/v1.24.0` package.
The package contains semantic conventions from the `v1.24.0` version of the OpenTelemetry Semantic Conventions. (#4770)
- Add `WithResourceAsConstantLabels` option to apply resource attributes for every metric emitted by the Prometheus exporter. (#4733)
- Experimental cardinality limiting is added to the metric SDK.
See [metric documentation](./sdk/metric/internal/x/README.md#cardinality-limit) for more information about this feature and how to enable it. (#4457)
- Add `NewMemberRaw` and `NewKeyValuePropertyRaw` in `go.opentelemetry.io/otel/baggage`. (#4804)
### Changed
- Upgrade all use of `go.opentelemetry.io/otel/semconv` to use `v1.24.0`. (#4754)
- Update transformations in `go.opentelemetry.io/otel/exporters/zipkin` to follow `v1.24.0` version of the OpenTelemetry specification. (#4754)
- Record synchronous measurements when the passed context is canceled instead of dropping in `go.opentelemetry.io/otel/sdk/metric`.
If you do not want to make a measurement when the context is cancelled, you need to handle it yourself (e.g `if ctx.Err() != nil`). (#4671)
- Improve `go.opentelemetry.io/otel/trace.TraceState`'s performance. (#4722)
- Improve `go.opentelemetry.io/otel/propagation.TraceContext`'s performance. (#4721)
- Improve `go.opentelemetry.io/otel/baggage` performance. (#4743)
- Improve performance of the `(*Set).Filter` method in `go.opentelemetry.io/otel/attribute` when the passed filter does not filter out any attributes from the set. (#4774)
- `Member.String` in `go.opentelemetry.io/otel/baggage` percent-encodes only when necessary. (#4775)
- Improve `go.opentelemetry.io/otel/trace.Span`'s performance when adding multiple attributes. (#4818)
- `Property.Value` in `go.opentelemetry.io/otel/baggage` now returns a raw string instead of a percent-encoded value. (#4804)
### Fixed
- Fix `Parse` in `go.opentelemetry.io/otel/baggage` to validate member value before percent-decoding. (#4755)
- Fix whitespace encoding of `Member.String` in `go.opentelemetry.io/otel/baggage`. (#4756)
- Fix observable not registered error when the asynchronous instrument has a drop aggregation in `go.opentelemetry.io/otel/sdk/metric`. (#4772)
- Fix baggage item key so that it is not canonicalized in `go.opentelemetry.io/otel/bridge/opentracing`. (#4776)
- Fix `go.opentelemetry.io/otel/bridge/opentracing` to properly handle baggage values that requires escaping during propagation. (#4804)
- Fix a bug where using multiple readers resulted in incorrect asynchronous counter values in `go.opentelemetry.io/otel/sdk/metric`. (#4742)
## [1.21.0/0.44.0] 2023-11-16
### Removed
- Remove the deprecated `go.opentelemetry.io/otel/bridge/opencensus.NewTracer`. (#4706)
- Remove the deprecated `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` module. (#4707)
- Remove the deprecated `go.opentelemetry.io/otel/example/view` module. (#4708)
- Remove the deprecated `go.opentelemetry.io/otel/example/fib` module. (#4723)
### Fixed
- Do not parse non-protobuf responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4719)
- Do not parse non-protobuf responses in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#4719)
## [1.20.0/0.43.0] 2023-11-10
This release brings a breaking change for custom trace API implementations. Some interfaces (`TracerProvider`, `Tracer`, `Span`) now embed the `go.opentelemetry.io/otel/trace/embedded` types. Implementors need to update their implementations based on what they want the default behavior to be. See the "API Implementations" section of the [trace API] package documentation for more information about how to accomplish this.
This release brings a breaking change for custom trace API implementations. Some interfaces (`TracerProvider`, `Tracer`, `Span`) now embed the `go.opentelemetry.io/otel/trace/embedded` types. Implementers need to update their implementations based on what they want the default behavior to be. See the "API Implementations" section of the [trace API] package documentation for more information about how to accomplish this.
### Added
@@ -42,15 +324,15 @@ This release brings a breaking change for custom trace API implementations. Some
- `go.opentelemetry.io/otel/bridge/opencensus.NewMetricProducer` returns a `*MetricProducer` struct instead of the metric.Producer interface. (#4583)
- The `TracerProvider` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.TracerProvider` type.
This extends the `TracerProvider` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be.
Implementers need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620)
- The `Tracer` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Tracer` type.
This extends the `Tracer` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be.
Implementers need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620)
- The `Span` in `go.opentelemetry.io/otel/trace` now embeds the `go.opentelemetry.io/otel/trace/embedded.Span` type.
This extends the `Span` interface and is is a breaking change for any existing implementation.
Implementors need to update their implementations based on what they want the default behavior of the interface to be.
Implementers need to update their implementations based on what they want the default behavior of the interface to be.
See the "API Implementations" section of the `go.opentelemetry.io/otel/trace` package documentation for more information about how to accomplish this. (#4620)
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` does no longer depend on `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#4660)
- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` does no longer depend on `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#4660)
@@ -686,7 +968,7 @@ The next release will require at least [Go 1.19].
- Exported `Status` codes in the `go.opentelemetry.io/otel/exporters/zipkin` exporter are now exported as all upper case values. (#3340)
- `Aggregation`s from `go.opentelemetry.io/otel/sdk/metric` with no data are not exported. (#3394, #3436)
- Re-enabled Attribute Filters in the Metric SDK. (#3396)
- Asynchronous callbacks are only called if they are registered with at least one instrument that does not use drop aggragation. (#3408)
- Asynchronous callbacks are only called if they are registered with at least one instrument that does not use drop aggregation. (#3408)
- Do not report empty partial-success responses in the `go.opentelemetry.io/otel/exporters/otlp` exporters. (#3438, #3432)
- Handle partial success responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporters. (#3162, #3440)
- Prevent duplicate Prometheus description, unit, and type. (#3469)
@@ -1731,7 +2013,7 @@ with major version 0.
- `NewExporter` from `exporters/otlp` now takes a `ProtocolDriver` as a parameter. (#1369)
- Many OTLP Exporter options became gRPC ProtocolDriver options. (#1369)
- Unify endpoint API that related to OTel exporter. (#1401)
- Optimize metric histogram aggregator to re-use its slice of buckets. (#1435)
- Optimize metric histogram aggregator to reuse its slice of buckets. (#1435)
- Metric aggregator Count() and histogram Bucket.Counts are consistently `uint64`. (1430)
- Histogram aggregator accepts functional options, uses default boundaries if none given. (#1434)
- `SamplingResult` now passed a `Tracestate` from the parent `SpanContext` (#1432)
@@ -2721,7 +3003,17 @@ It contains api and sdk for trace and meter.
- CircleCI build CI manifest files.
- CODEOWNERS file to track owners of this project.
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.20.0...HEAD
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.28.0...HEAD
[1.28.0/0.50.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.28.0
[1.27.0/0.49.0/0.3.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.27.0
[1.26.0/0.48.0/0.2.0-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.26.0
[1.25.0/0.47.0/0.0.8/0.1.0-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.25.0
[1.24.0/0.46.0/0.0.1-alpha]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.24.0
[1.23.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.1
[1.23.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0
[1.23.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.23.0-rc.1
[1.22.0/0.45.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.22.0
[1.21.0/0.44.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.21.0
[1.20.0/0.43.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.20.0
[1.19.0/0.42.0/0.0.7]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0
[1.19.0-rc.1/0.42.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0-rc.1
@@ -2794,6 +3086,8 @@ It contains api and sdk for trace and meter.
[0.1.1]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.1
[0.1.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.1.0
[Go 1.22]: https://go.dev/doc/go1.22
[Go 1.21]: https://go.dev/doc/go1.21
[Go 1.20]: https://go.dev/doc/go1.20
[Go 1.19]: https://go.dev/doc/go1.19
[Go 1.18]: https://go.dev/doc/go1.18
@@ -2801,3 +3095,5 @@ It contains api and sdk for trace and meter.
[metric API]:https://pkg.go.dev/go.opentelemetry.io/otel/metric
[metric SDK]:https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric
[trace API]:https://pkg.go.dev/go.opentelemetry.io/otel/trace
[GO-2024-2687]: https://pkg.go.dev/vuln/GO-2024-2687

View File

@@ -12,6 +12,6 @@
# https://help.github.com/en/articles/about-code-owners
#
* @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu
* @MrAlias @XSAM @dashpole @MadVikingGod @pellared @hanyuancheung @dmathieu
CODEOWNERS @MrAlias @MadVikingGod @pellared
CODEOWNERS @MrAlias @MadVikingGod @pellared @dashpole @XSAM @dmathieu

View File

@@ -90,6 +90,10 @@ git push <YOUR_FORK> <YOUR_BRANCH_NAME>
Open a pull request against the main `opentelemetry-go` repo. Be sure to add the pull
request ID to the entry you added to `CHANGELOG.md`.
Avoid rebasing and force-pushing to your branch to facilitate reviewing the pull request.
Rewriting Git history makes it difficult to keep track of iterations during code review.
All pull requests are squashed to a single commit upon merge to `main`.
### How to Receive Comments
* If the PR is not ready for review, please put `[WIP]` in the title,
@@ -197,6 +201,16 @@ You can install and run a "local Go Doc site" in the following way:
[`go.opentelemetry.io/otel/metric`](https://pkg.go.dev/go.opentelemetry.io/otel/metric)
is an example of a very well-documented package.
### README files
Each (non-internal, non-test, non-documentation) package must contain a
`README.md` file containing at least a title, and a `pkg.go.dev` badge.
The README should not be a repetition of Go doc comments.
You can verify the presence of all README files with the `make verify-readmes`
command.
## Style Guide
One of the primary goals of this project is that it is actually used by
@@ -556,6 +570,9 @@ functionality should be added, each one will need their own super-set
interfaces and will duplicate the pattern. For this reason, the simple targeted
interface that defines the specific functionality should be preferred.
See also:
[Keeping Your Modules Compatible: Working with interfaces](https://go.dev/blog/module-compatibility#working-with-interfaces).
### Testing
The tests should never leak goroutines.
@@ -587,27 +604,48 @@ this.
[^3]: https://github.com/open-telemetry/opentelemetry-go/issues/3548
### Ignoring context cancellation
OpenTelemetry API implementations need to ignore the cancellation of the context that are
passed when recording a value (e.g. starting a span, recording a measurement, emitting a log).
Recording methods should not return an error describing the cancellation state of the context
when they complete, nor should they abort any work.
This rule may not apply if the OpenTelemetry specification defines a timeout mechanism for
the method. In that case the context cancellation can be used for the timeout with the
restriction that this behavior is documented for the method. Otherwise, timeouts
are expected to be handled by the user calling the API, not the implementation.
Stoppage of the telemetry pipeline is handled by calling the appropriate `Shutdown` method
of a provider. It is assumed the context passed from a user is not used for this purpose.
Outside of the direct recording of telemetry from the API (e.g. exporting telemetry,
force flushing telemetry, shutting down a signal provider) the context cancellation
should be honored. This means all work done on behalf of the user provided context
should be canceled.
## Approvers and Maintainers
### Approvers
- [Evan Torrie](https://github.com/evantorrie), Verizon Media
- [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics
- [David Ashpole](https://github.com/dashpole), Google
- [Chester Cheung](https://github.com/hanyuancheung), Tencent
- [Damien Mathieu](https://github.com/dmathieu), Elastic
- [Anthony Mirabella](https://github.com/Aneurysm9), AWS
### Maintainers
- [Aaron Clawson](https://github.com/MadVikingGod), LightStep
- [Damien Mathieu](https://github.com/dmathieu), Elastic
- [David Ashpole](https://github.com/dashpole), Google
- [Robert Pająk](https://github.com/pellared), Splunk
- [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics
- [Tyler Yahn](https://github.com/MrAlias), Splunk
### Emeritus
- [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb
- [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep
- [Josh MacDonald](https://github.com/jmacd), LightStep
- [Anthony Mirabella](https://github.com/Aneurysm9), AWS
- [Evan Torrie](https://github.com/evantorrie), Yahoo
### Become an Approver or a Maintainer

View File

@@ -1,16 +1,5 @@
# 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.
# SPDX-License-Identifier: Apache-2.0
TOOLS_MOD_DIR := ./internal/tools
@@ -25,8 +14,8 @@ TIMEOUT = 60
.DEFAULT_GOAL := precommit
.PHONY: precommit ci
precommit: generate dependabot-generate license-check misspell go-mod-tidy golangci-lint-fix test-default
ci: generate dependabot-check license-check lint vanity-import-check build test-default check-clean-work-tree test-coverage
precommit: generate license-check misspell go-mod-tidy golangci-lint-fix verify-readmes verify-mods test-default
ci: generate license-check lint vanity-import-check verify-readmes verify-mods build test-default check-clean-work-tree test-coverage
# Tools
@@ -34,7 +23,7 @@ TOOLS = $(CURDIR)/.tools
$(TOOLS):
@mkdir -p $@
$(TOOLS)/%: | $(TOOLS)
$(TOOLS)/%: $(TOOLS_MOD_DIR)/go.mod | $(TOOLS)
cd $(TOOLS_MOD_DIR) && \
$(GO) build -o $@ $(PACKAGE)
@@ -50,9 +39,6 @@ $(TOOLS)/crosslink: PACKAGE=go.opentelemetry.io/build-tools/crosslink
SEMCONVKIT = $(TOOLS)/semconvkit
$(TOOLS)/semconvkit: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/semconvkit
DBOTCONF = $(TOOLS)/dbotconf
$(TOOLS)/dbotconf: PACKAGE=go.opentelemetry.io/build-tools/dbotconf
GOLANGCI_LINT = $(TOOLS)/golangci-lint
$(TOOLS)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint
@@ -81,7 +67,7 @@ GOVULNCHECK = $(TOOLS)/govulncheck
$(TOOLS)/govulncheck: PACKAGE=golang.org/x/vuln/cmd/govulncheck
.PHONY: tools
tools: $(CROSSLINK) $(DBOTCONF) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE)
tools: $(CROSSLINK) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT) $(GOTMPL) $(GORELEASE)
# Virtualized python tools via docker
@@ -110,7 +96,7 @@ $(PYTOOLS):
@$(DOCKERPY) bash -c "python3 -m venv $(VENVDIR) && $(PIP) install --upgrade pip"
# Install python packages into the virtual environment.
$(PYTOOLS)/%: | $(PYTOOLS)
$(PYTOOLS)/%: $(PYTOOLS)
@$(DOCKERPY) $(PIP) install -r requirements.txt
CODESPELL = $(PYTOOLS)/codespell
@@ -124,18 +110,18 @@ generate: go-generate vanity-import-fix
.PHONY: go-generate
go-generate: $(OTEL_GO_MOD_DIRS:%=go-generate/%)
go-generate/%: DIR=$*
go-generate/%: | $(STRINGER) $(GOTMPL)
go-generate/%: $(STRINGER) $(GOTMPL)
@echo "$(GO) generate $(DIR)/..." \
&& cd $(DIR) \
&& PATH="$(TOOLS):$${PATH}" $(GO) generate ./...
.PHONY: vanity-import-fix
vanity-import-fix: | $(PORTO)
vanity-import-fix: $(PORTO)
@$(PORTO) --include-internal -w .
# Generate go.work file for local development.
.PHONY: go-work
go-work: | $(CROSSLINK)
go-work: $(CROSSLINK)
$(CROSSLINK) work --root=$(shell pwd)
# Build
@@ -178,7 +164,7 @@ test/%:
COVERAGE_MODE = atomic
COVERAGE_PROFILE = coverage.out
.PHONY: test-coverage
test-coverage: | $(GOCOVMERGE)
test-coverage: $(GOCOVMERGE)
@set -e; \
printf "" > coverage.txt; \
for dir in $(ALL_COVERAGE_MOD_DIRS); do \
@@ -192,7 +178,7 @@ test-coverage: | $(GOCOVMERGE)
done; \
$(GOCOVMERGE) $$(find . -name coverage.out) > coverage.txt
# Adding a directory will include all benchmarks in that direcotry if a filter is not specified.
# Adding a directory will include all benchmarks in that directory if a filter is not specified.
BENCHMARK_TARGETS := sdk/trace
.PHONY: benchmark
benchmark: $(BENCHMARK_TARGETS:%=benchmark/%)
@@ -209,23 +195,23 @@ golangci-lint-fix: ARGS=--fix
golangci-lint-fix: golangci-lint
golangci-lint: $(OTEL_GO_MOD_DIRS:%=golangci-lint/%)
golangci-lint/%: DIR=$*
golangci-lint/%: | $(GOLANGCI_LINT)
golangci-lint/%: $(GOLANGCI_LINT)
@echo 'golangci-lint $(if $(ARGS),$(ARGS) ,)$(DIR)' \
&& cd $(DIR) \
&& $(GOLANGCI_LINT) run --allow-serial-runners $(ARGS)
.PHONY: crosslink
crosslink: | $(CROSSLINK)
crosslink: $(CROSSLINK)
@echo "Updating intra-repository dependencies in all go modules" \
&& $(CROSSLINK) --root=$(shell pwd) --prune
.PHONY: go-mod-tidy
go-mod-tidy: $(ALL_GO_MOD_DIRS:%=go-mod-tidy/%)
go-mod-tidy/%: DIR=$*
go-mod-tidy/%: | crosslink
go-mod-tidy/%: crosslink
@echo "$(GO) mod tidy in $(DIR)" \
&& cd $(DIR) \
&& $(GO) mod tidy -compat=1.20
&& $(GO) mod tidy -compat=1.21
.PHONY: lint-modules
lint-modules: go-mod-tidy
@@ -234,23 +220,23 @@ lint-modules: go-mod-tidy
lint: misspell lint-modules golangci-lint govulncheck
.PHONY: vanity-import-check
vanity-import-check: | $(PORTO)
vanity-import-check: $(PORTO)
@$(PORTO) --include-internal -l . || ( echo "(run: make vanity-import-fix)"; exit 1 )
.PHONY: misspell
misspell: | $(MISSPELL)
misspell: $(MISSPELL)
@$(MISSPELL) -w $(ALL_DOCS)
.PHONY: govulncheck
govulncheck: $(OTEL_GO_MOD_DIRS:%=govulncheck/%)
govulncheck/%: DIR=$*
govulncheck/%: | $(GOVULNCHECK)
govulncheck/%: $(GOVULNCHECK)
@echo "govulncheck ./... in $(DIR)" \
&& cd $(DIR) \
&& $(GOVULNCHECK) ./...
.PHONY: codespell
codespell: | $(CODESPELL)
codespell: $(CODESPELL)
@$(DOCKERPY) $(CODESPELL)
.PHONY: license-check
@@ -263,15 +249,6 @@ license-check:
exit 1; \
fi
DEPENDABOT_CONFIG = .github/dependabot.yml
.PHONY: dependabot-check
dependabot-check: | $(DBOTCONF)
@$(DBOTCONF) verify $(DEPENDABOT_CONFIG) || ( echo "(run: make dependabot-generate)"; exit 1 )
.PHONY: dependabot-generate
dependabot-generate: | $(DBOTCONF)
@$(DBOTCONF) generate > $(DEPENDABOT_CONFIG)
.PHONY: check-clean-work-tree
check-clean-work-tree:
@if ! git diff --quiet; then \
@@ -284,13 +261,11 @@ check-clean-work-tree:
SEMCONVPKG ?= "semconv/"
.PHONY: semconv-generate
semconv-generate: | $(SEMCONVGEN) $(SEMCONVKIT)
semconv-generate: $(SEMCONVGEN) $(SEMCONVKIT)
[ "$(TAG)" ] || ( echo "TAG unset: missing opentelemetry semantic-conventions tag"; exit 1 )
[ "$(OTEL_SEMCONV_REPO)" ] || ( echo "OTEL_SEMCONV_REPO unset: missing path to opentelemetry semantic-conventions repo"; exit 1 )
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=span -p conventionType=trace -f trace.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=attribute_group -p conventionType=trace -f attribute_group.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=event -p conventionType=event -f event.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=resource -p conventionType=resource -f resource.go -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
$(SEMCONVGEN) -i "$(OTEL_SEMCONV_REPO)/model/." --only=metric -f metric.go -t "$(SEMCONVPKG)/metric_template.j2" -s "$(TAG)"
$(SEMCONVKIT) -output "$(SEMCONVPKG)/$(TAG)" -tag "$(TAG)"
.PHONY: gorelease
@@ -302,17 +277,25 @@ gorelease/%:| $(GORELEASE)
&& $(GORELEASE) \
|| echo ""
.PHONY: verify-mods
verify-mods: $(MULTIMOD)
$(MULTIMOD) verify
.PHONY: prerelease
prerelease: | $(MULTIMOD)
prerelease: verify-mods
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
$(MULTIMOD) verify && $(MULTIMOD) prerelease -m ${MODSET}
$(MULTIMOD) prerelease -m ${MODSET}
COMMIT ?= "HEAD"
.PHONY: add-tags
add-tags: | $(MULTIMOD)
add-tags: verify-mods
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
$(MULTIMOD) verify && $(MULTIMOD) tag -m ${MODSET} -c ${COMMIT}
$(MULTIMOD) tag -m ${MODSET} -c ${COMMIT}
.PHONY: lint-markdown
lint-markdown:
docker run -v "$(CURDIR):$(WORKDIR)" docker://avtodev/markdown-lint:v1 -c $(WORKDIR)/.markdownlint.yaml $(WORKDIR)/**/*.md
lint-markdown:
docker run -v "$(CURDIR):$(WORKDIR)" avtodev/markdown-lint:v1 -c $(WORKDIR)/.markdownlint.yaml $(WORKDIR)/**/*.md
.PHONY: verify-readmes
verify-readmes:
./verify_readmes.sh

View File

@@ -11,14 +11,11 @@ It provides a set of APIs to directly measure performance and behavior of your s
## Project Status
| Signal | Status |
|---------|------------|
| Traces | Stable |
| Metrics | Stable |
| Logs | Design [1] |
- [1]: Currently the logs signal development is in a design phase ([#4696](https://github.com/open-telemetry/opentelemetry-go/issues/4696)).
No Logs Pull Requests are currently being accepted.
| Signal | Status |
|---------|--------------------|
| Traces | Stable |
| Metrics | Stable |
| Logs | Beta[^1] |
Progress and status specific to this repository is tracked in our
[project boards](https://github.com/open-telemetry/opentelemetry-go/projects)
@@ -28,6 +25,8 @@ and
Project versioning information and stability guarantees can be found in the
[versioning documentation](VERSIONING.md).
[^1]: https://github.com/orgs/open-telemetry/projects/43
### Compatibility
OpenTelemetry-Go ensures compatibility with the current supported versions of
@@ -50,23 +49,25 @@ Currently, this project supports the following environments.
| OS | Go Version | Architecture |
|---------|------------|--------------|
| Ubuntu | 1.22 | amd64 |
| Ubuntu | 1.21 | amd64 |
| Ubuntu | 1.20 | amd64 |
| Ubuntu | 1.22 | 386 |
| Ubuntu | 1.21 | 386 |
| Ubuntu | 1.20 | 386 |
| Linux | 1.22 | arm64 |
| Linux | 1.21 | arm64 |
| MacOS | 1.22 | amd64 |
| MacOS | 1.21 | amd64 |
| MacOS | 1.20 | amd64 |
| Windows | 1.22 | amd64 |
| Windows | 1.21 | amd64 |
| Windows | 1.20 | amd64 |
| Windows | 1.22 | 386 |
| Windows | 1.21 | 386 |
| Windows | 1.20 | 386 |
While this project should work for other systems, no compatibility guarantees
are made for those systems currently.
## Getting Started
You can find a getting started guide on [opentelemetry.io](https://opentelemetry.io/docs/go/getting-started/).
You can find a getting started guide on [opentelemetry.io](https://opentelemetry.io/docs/languages/go/getting-started/).
OpenTelemetry's goal is to provide a single set of APIs to capture distributed
traces and metrics from your application and send them to an observability
@@ -96,12 +97,12 @@ export pipeline to send that telemetry to an observability platform.
All officially supported exporters for the OpenTelemetry project are contained in the [exporters directory](./exporters).
| Exporter | Metrics | Traces |
|---------------------------------------|:-------:|:------:|
| [OTLP](./exporters/otlp/) | ✓ | ✓ |
| [Prometheus](./exporters/prometheus/) | ✓ | |
| [stdout](./exporters/stdout/) | ✓ | ✓ |
| [Zipkin](./exporters/zipkin/) | | ✓ |
| Exporter | Logs | Metrics | Traces |
|---------------------------------------|:----:|:-------:|:------:|
| [OTLP](./exporters/otlp/) | ✓ | ✓ | ✓ |
| [Prometheus](./exporters/prometheus/) | | ✓ | |
| [stdout](./exporters/stdout/) | ✓ | ✓ | ✓ |
| [Zipkin](./exporters/zipkin/) | | | ✓ |
## Contributing

View File

@@ -27,6 +27,12 @@ You can run `make gorelease` that runs [gorelease](https://pkg.go.dev/golang.org
You can check/report problems with `gorelease` [here](https://golang.org/issues/26420).
## Verify changes for contrib repository
If the changes in the main repository are going to affect the contrib repository, it is important to verify that the changes are compatible with the contrib repository.
Follow [the steps](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/RELEASING.md#verify-otel-changes) in the contrib repository to verify OTel changes.
## Pre-Release
First, decide which module sets will be released and update their versions
@@ -123,12 +129,12 @@ Once verified be sure to [make a release for the `contrib` repository](https://g
### Website Documentation
Update the [Go instrumentation documentation] in the OpenTelemetry website under [content/en/docs/instrumentation/go].
Update the [Go instrumentation documentation] in the OpenTelemetry website under [content/en/docs/languages/go].
Importantly, bump any package versions referenced to be the latest one you just released and ensure all code examples still compile and are accurate.
[OpenTelemetry Semantic Conventions]: https://github.com/open-telemetry/semantic-conventions
[Go instrumentation documentation]: https://opentelemetry.io/docs/instrumentation/go/
[content/en/docs/instrumentation/go]: https://github.com/open-telemetry/opentelemetry.io/tree/main/content/en/docs/instrumentation/go
[Go instrumentation documentation]: https://opentelemetry.io/docs/languages/go/
[content/en/docs/languages/go]: https://github.com/open-telemetry/opentelemetry.io/tree/main/content/en/docs/languages/go
### Demo Repository

3
vendor/go.opentelemetry.io/otel/attribute/README.md generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Attribute
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/attribute)](https://pkg.go.dev/go.opentelemetry.io/otel/attribute)

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
// Package attribute provides key and value attributes.
package attribute // import "go.opentelemetry.io/otel/attribute"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/otel/attribute"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/otel/attribute"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/otel/attribute"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/otel/attribute"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/otel/attribute"

View File

@@ -1,24 +1,14 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/otel/attribute"
import (
"cmp"
"encoding/json"
"reflect"
"slices"
"sort"
"sync"
)
type (
@@ -26,23 +16,33 @@ type (
// immutable set of attributes, with an internal cache for storing
// attribute encodings.
//
// This type supports the Equivalent method of comparison using values of
// type Distinct.
// This type will remain comparable for backwards compatibility. The
// equivalence of Sets across versions is not guaranteed to be stable.
// Prior versions may find two Sets to be equal or not when compared
// directly (i.e. ==), but subsequent versions may not. Users should use
// the Equals method to ensure stable equivalence checking.
//
// Users should also use the Distinct returned from Equivalent as a map key
// instead of a Set directly. In addition to that type providing guarantees
// on stable equivalence, it may also provide performance improvements.
Set struct {
equivalent Distinct
}
// Distinct wraps a variable-size array of KeyValue, constructed with keys
// in sorted order. This can be used as a map key or for equality checking
// between Sets.
// Distinct is a unique identifier of a Set.
//
// Distinct is designed to be ensures equivalence stability: comparisons
// will return the save value across versions. For this reason, Distinct
// should always be used as a map key instead of a Set.
Distinct struct {
iface interface{}
}
// Sortable implements sort.Interface, used for sorting KeyValue. This is
// an exported type to support a memory optimization. A pointer to one of
// these is needed for the call to sort.Stable(), which the caller may
// provide in order to avoid an allocation. See NewSetWithSortable().
// Sortable implements sort.Interface, used for sorting KeyValue.
//
// Deprecated: This type is no longer used. It was added as a performance
// optimization for Go < 1.21 that is no longer needed (Go < 1.21 is no
// longer supported by the module).
Sortable []KeyValue
)
@@ -56,12 +56,6 @@ var (
iface: [0]KeyValue{},
},
}
// sortables is a pool of Sortables used to create Sets with a user does
// not provide one.
sortables = sync.Pool{
New: func() interface{} { return new(Sortable) },
}
)
// EmptySet returns a reference to a Set with no elements.
@@ -187,13 +181,7 @@ func empty() Set {
// Except for empty sets, this method adds an additional allocation compared
// with calls that include a Sortable.
func NewSet(kvs ...KeyValue) Set {
// Check for empty set.
if len(kvs) == 0 {
return empty()
}
srt := sortables.Get().(*Sortable)
s, _ := NewSetWithSortableFiltered(kvs, srt, nil)
sortables.Put(srt)
s, _ := NewSetWithFiltered(kvs, nil)
return s
}
@@ -201,12 +189,10 @@ func NewSet(kvs ...KeyValue) Set {
// NewSetWithSortableFiltered for more details.
//
// This call includes a Sortable option as a memory optimization.
func NewSetWithSortable(kvs []KeyValue, tmp *Sortable) Set {
// Check for empty set.
if len(kvs) == 0 {
return empty()
}
s, _ := NewSetWithSortableFiltered(kvs, tmp, nil)
//
// Deprecated: Use [NewSet] instead.
func NewSetWithSortable(kvs []KeyValue, _ *Sortable) Set {
s, _ := NewSetWithFiltered(kvs, nil)
return s
}
@@ -220,10 +206,37 @@ func NewSetWithFiltered(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
if len(kvs) == 0 {
return empty(), nil
}
srt := sortables.Get().(*Sortable)
s, filtered := NewSetWithSortableFiltered(kvs, srt, filter)
sortables.Put(srt)
return s, filtered
// Stable sort so the following de-duplication can implement
// last-value-wins semantics.
slices.SortStableFunc(kvs, func(a, b KeyValue) int {
return cmp.Compare(a.Key, b.Key)
})
position := len(kvs) - 1
offset := position - 1
// The requirements stated above require that the stable
// result be placed in the end of the input slice, while
// overwritten values are swapped to the beginning.
//
// De-duplicate with last-value-wins semantics. Preserve
// duplicate values at the beginning of the input slice.
for ; offset >= 0; offset-- {
if kvs[offset].Key == kvs[position].Key {
continue
}
position--
kvs[offset], kvs[position] = kvs[position], kvs[offset]
}
kvs = kvs[position:]
if filter != nil {
if div := filteredToFront(kvs, filter); div != 0 {
return Set{equivalent: computeDistinct(kvs[div:])}, kvs[:div]
}
}
return Set{equivalent: computeDistinct(kvs)}, nil
}
// NewSetWithSortableFiltered returns a new Set.
@@ -249,82 +262,71 @@ func NewSetWithFiltered(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
//
// The second []KeyValue return value is a list of attributes that were
// excluded by the Filter (if non-nil).
func NewSetWithSortableFiltered(kvs []KeyValue, tmp *Sortable, filter Filter) (Set, []KeyValue) {
// Check for empty set.
if len(kvs) == 0 {
return empty(), nil
}
*tmp = kvs
// Stable sort so the following de-duplication can implement
// last-value-wins semantics.
sort.Stable(tmp)
*tmp = nil
position := len(kvs) - 1
offset := position - 1
// The requirements stated above require that the stable
// result be placed in the end of the input slice, while
// overwritten values are swapped to the beginning.
//
// De-duplicate with last-value-wins semantics. Preserve
// duplicate values at the beginning of the input slice.
for ; offset >= 0; offset-- {
if kvs[offset].Key == kvs[position].Key {
continue
}
position--
kvs[offset], kvs[position] = kvs[position], kvs[offset]
}
if filter != nil {
return filterSet(kvs[position:], filter)
}
return Set{
equivalent: computeDistinct(kvs[position:]),
}, nil
//
// Deprecated: Use [NewSetWithFiltered] instead.
func NewSetWithSortableFiltered(kvs []KeyValue, _ *Sortable, filter Filter) (Set, []KeyValue) {
return NewSetWithFiltered(kvs, filter)
}
// filterSet reorders kvs so that included keys are contiguous at the end of
// the slice, while excluded keys precede the included keys.
func filterSet(kvs []KeyValue, filter Filter) (Set, []KeyValue) {
var excluded []KeyValue
// Move attributes that do not match the filter so they're adjacent before
// calling computeDistinct().
distinctPosition := len(kvs)
// Swap indistinct keys forward and distinct keys toward the
// end of the slice.
offset := len(kvs) - 1
for ; offset >= 0; offset-- {
if filter(kvs[offset]) {
distinctPosition--
kvs[offset], kvs[distinctPosition] = kvs[distinctPosition], kvs[offset]
continue
// filteredToFront filters slice in-place using keep function. All KeyValues that need to
// be removed are moved to the front. All KeyValues that need to be kept are
// moved (in-order) to the back. The index for the first KeyValue to be kept is
// returned.
func filteredToFront(slice []KeyValue, keep Filter) int {
n := len(slice)
j := n
for i := n - 1; i >= 0; i-- {
if keep(slice[i]) {
j--
slice[i], slice[j] = slice[j], slice[i]
}
}
excluded = kvs[:distinctPosition]
return Set{
equivalent: computeDistinct(kvs[distinctPosition:]),
}, excluded
return j
}
// Filter returns a filtered copy of this Set. See the documentation for
// NewSetWithSortableFiltered for more details.
func (l *Set) Filter(re Filter) (Set, []KeyValue) {
if re == nil {
return Set{
equivalent: l.equivalent,
}, nil
return *l, nil
}
// Note: This could be refactored to avoid the temporary slice
// allocation, if it proves to be expensive.
return filterSet(l.ToSlice(), re)
// Iterate in reverse to the first attribute that will be filtered out.
n := l.Len()
first := n - 1
for ; first >= 0; first-- {
kv, _ := l.Get(first)
if !re(kv) {
break
}
}
// No attributes will be dropped, return the immutable Set l and nil.
if first < 0 {
return *l, nil
}
// Copy now that we know we need to return a modified set.
//
// Do not do this in-place on the underlying storage of *Set l. Sets are
// immutable and filtering should not change this.
slice := l.ToSlice()
// Don't re-iterate the slice if only slice[0] is filtered.
if first == 0 {
// It is safe to assume len(slice) >= 1 given we found at least one
// attribute above that needs to be filtered out.
return Set{equivalent: computeDistinct(slice[1:])}, slice[:1]
}
// Move the filtered slice[first] to the front (preserving order).
kv := slice[first]
copy(slice[1:first+1], slice[:first])
slice[0] = kv
// Do not re-evaluate re(slice[first+1:]).
div := filteredToFront(slice[1:first+1], re) + 1
return Set{equivalent: computeDistinct(slice[div:])}, slice[:div]
}
// computeDistinct returns a Distinct using either the fixed- or
@@ -404,7 +406,7 @@ func (l *Set) MarshalJSON() ([]byte, error) {
return json.Marshal(l.equivalent.iface)
}
// MarshalLog is the marshaling function used by the logging system to represent this exporter.
// MarshalLog is the marshaling function used by the logging system to represent this Set.
func (l Set) MarshalLog() interface{} {
kvs := make(map[string]string)
for _, kv := range l.ToSlice() {

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package attribute // import "go.opentelemetry.io/otel/attribute"
@@ -242,15 +231,27 @@ func (v Value) Emit() string {
case BOOL:
return strconv.FormatBool(v.AsBool())
case INT64SLICE:
return fmt.Sprint(v.asInt64Slice())
j, err := json.Marshal(v.asInt64Slice())
if err != nil {
return fmt.Sprintf("invalid: %v", v.asInt64Slice())
}
return string(j)
case INT64:
return strconv.FormatInt(v.AsInt64(), 10)
case FLOAT64SLICE:
return fmt.Sprint(v.asFloat64Slice())
j, err := json.Marshal(v.asFloat64Slice())
if err != nil {
return fmt.Sprintf("invalid: %v", v.asFloat64Slice())
}
return string(j)
case FLOAT64:
return fmt.Sprint(v.AsFloat64())
case STRINGSLICE:
return fmt.Sprint(v.asStringSlice())
j, err := json.Marshal(v.asStringSlice())
if err != nil {
return fmt.Sprintf("invalid: %v", v.asStringSlice())
}
return string(j)
case STRING:
return v.stringly
default:

3
vendor/go.opentelemetry.io/otel/baggage/README.md generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Baggage
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/baggage)](https://pkg.go.dev/go.opentelemetry.io/otel/baggage)

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package baggage // import "go.opentelemetry.io/otel/baggage"
@@ -18,8 +7,8 @@ import (
"errors"
"fmt"
"net/url"
"regexp"
"strings"
"unicode/utf8"
"go.opentelemetry.io/otel/internal/baggage"
)
@@ -32,16 +21,6 @@ const (
listDelimiter = ","
keyValueDelimiter = "="
propertyDelimiter = ";"
keyDef = `([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+)`
valueDef = `([\x21\x23-\x2b\x2d-\x3a\x3c-\x5B\x5D-\x7e]*)`
keyValueDef = `\s*` + keyDef + `\s*` + keyValueDelimiter + `\s*` + valueDef + `\s*`
)
var (
keyRe = regexp.MustCompile(`^` + keyDef + `$`)
valueRe = regexp.MustCompile(`^` + valueDef + `$`)
propertyRe = regexp.MustCompile(`^(?:\s*` + keyDef + `\s*|` + keyValueDef + `)$`)
)
var (
@@ -67,7 +46,7 @@ type Property struct {
//
// If key is invalid, an error will be returned.
func NewKeyProperty(key string) (Property, error) {
if !keyRe.MatchString(key) {
if !validateKey(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
@@ -77,14 +56,29 @@ func NewKeyProperty(key string) (Property, error) {
// NewKeyValueProperty returns a new Property for key with value.
//
// If key or value are invalid, an error will be returned.
// The passed key must be compliant with W3C Baggage specification.
// The passed value must be percent-encoded as defined in W3C Baggage specification.
//
// Notice: Consider using [NewKeyValuePropertyRaw] instead
// that does not require percent-encoding of the value.
func NewKeyValueProperty(key, value string) (Property, error) {
if !keyRe.MatchString(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
if !valueRe.MatchString(value) {
if !validateValue(value) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
decodedValue, err := url.PathUnescape(value)
if err != nil {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
return NewKeyValuePropertyRaw(key, decodedValue)
}
// NewKeyValuePropertyRaw returns a new Property for key with value.
//
// The passed key must be compliant with W3C Baggage specification.
func NewKeyValuePropertyRaw(key, value string) (Property, error) {
if !validateKey(key) {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
p := Property{
key: key,
@@ -106,20 +100,11 @@ func parseProperty(property string) (Property, error) {
return newInvalidProperty(), nil
}
match := propertyRe.FindStringSubmatch(property)
if len(match) != 4 {
p, ok := parsePropertyInternal(property)
if !ok {
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property)
}
var p Property
if match[1] != "" {
p.key = match[1]
} else {
p.key = match[2]
p.value = match[3]
p.hasValue = true
}
return p, nil
}
@@ -130,12 +115,9 @@ func (p Property) validate() error {
return fmt.Errorf("invalid property: %w", err)
}
if !keyRe.MatchString(p.key) {
if !validateKey(p.key) {
return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
}
if p.hasValue && !valueRe.MatchString(p.value) {
return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
}
if !p.hasValue && p.value != "" {
return errFunc(errors.New("inconsistent value"))
}
@@ -154,11 +136,11 @@ func (p Property) Value() (string, bool) {
return p.value, p.hasValue
}
// String encodes Property into a string compliant with the W3C Baggage
// String encodes Property into a header string compliant with the W3C Baggage
// specification.
func (p Property) String() string {
if p.hasValue {
return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, p.value)
return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, valueEscape(p.value))
}
return p.key
}
@@ -218,7 +200,7 @@ func (p properties) validate() error {
return nil
}
// String encodes properties into a string compliant with the W3C Baggage
// String encodes properties into a header string compliant with the W3C Baggage
// specification.
func (p properties) String() string {
props := make([]string, len(p))
@@ -240,11 +222,28 @@ type Member struct {
hasData bool
}
// NewMember returns a new Member from the passed arguments. The key will be
// used directly while the value will be url decoded after validation. An error
// is returned if the created Member would be invalid according to the W3C
// Baggage specification.
// NewMember returns a new Member from the passed arguments.
//
// The passed key must be compliant with W3C Baggage specification.
// The passed value must be percent-encoded as defined in W3C Baggage specification.
//
// Notice: Consider using [NewMemberRaw] instead
// that does not require percent-encoding of the value.
func NewMember(key, value string, props ...Property) (Member, error) {
if !validateValue(value) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
decodedValue, err := url.PathUnescape(value)
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
return NewMemberRaw(key, decodedValue, props...)
}
// NewMemberRaw returns a new Member from the passed arguments.
//
// The passed key must be compliant with W3C Baggage specification.
func NewMemberRaw(key, value string, props ...Property) (Member, error) {
m := Member{
key: key,
value: value,
@@ -254,11 +253,6 @@ func NewMember(key, value string, props ...Property) (Member, error) {
if err := m.validate(); err != nil {
return newInvalidMember(), err
}
decodedValue, err := url.PathUnescape(value)
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
}
m.value = decodedValue
return m, nil
}
@@ -274,11 +268,7 @@ func parseMember(member string) (Member, error) {
return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
}
var (
key, value string
props properties
)
var props properties
keyValue, properties, found := strings.Cut(member, propertyDelimiter)
if found {
// Parse the member properties.
@@ -299,36 +289,34 @@ func parseMember(member string) (Member, error) {
}
// "Leading and trailing whitespaces are allowed but MUST be trimmed
// when converting the header into a data structure."
key = strings.TrimSpace(k)
var err error
value, err = url.PathUnescape(strings.TrimSpace(v))
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %q", err, value)
}
if !keyRe.MatchString(key) {
key := strings.TrimSpace(k)
if !validateKey(key) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
}
if !valueRe.MatchString(value) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
val := strings.TrimSpace(v)
if !validateValue(val) {
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, v)
}
// Decode a percent-encoded value.
value, err := url.PathUnescape(val)
if err != nil {
return newInvalidMember(), fmt.Errorf("%w: %w", errInvalidValue, err)
}
return Member{key: key, value: value, properties: props, hasData: true}, nil
}
// validate ensures m conforms to the W3C Baggage specification.
// A key is just an ASCII string, but a value must be URL encoded UTF-8,
// returning an error otherwise.
// A key must be an ASCII string, returning an error otherwise.
func (m Member) validate() error {
if !m.hasData {
return fmt.Errorf("%w: %q", errInvalidMember, m)
}
if !keyRe.MatchString(m.key) {
if !validateKey(m.key) {
return fmt.Errorf("%w: %q", errInvalidKey, m.key)
}
if !valueRe.MatchString(m.value) {
return fmt.Errorf("%w: %q", errInvalidValue, m.value)
}
return m.properties.validate()
}
@@ -341,13 +329,15 @@ func (m Member) Value() string { return m.value }
// Properties returns a copy of the Member properties.
func (m Member) Properties() []Property { return m.properties.Copy() }
// String encodes Member into a string compliant with the W3C Baggage
// String encodes Member into a header string compliant with the W3C Baggage
// specification.
func (m Member) String() string {
// A key is just an ASCII string, but a value is URL encoded UTF-8.
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, url.QueryEscape(m.value))
// A key is just an ASCII string. A value is restricted to be
// US-ASCII characters excluding CTLs, whitespace,
// DQUOTE, comma, semicolon, and backslash.
s := m.key + keyValueDelimiter + valueEscape(m.value)
if len(m.properties) > 0 {
s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
s += propertyDelimiter + m.properties.String()
}
return s
}
@@ -536,9 +526,8 @@ func (b Baggage) Len() int {
return len(b.list)
}
// String encodes Baggage into a string compliant with the W3C Baggage
// specification. The returned string will be invalid if the Baggage contains
// any invalid list-members.
// String encodes Baggage into a header string compliant with the W3C Baggage
// specification.
func (b Baggage) String() string {
members := make([]string, 0, len(b.list))
for k, v := range b.list {
@@ -550,3 +539,372 @@ func (b Baggage) String() string {
}
return strings.Join(members, listDelimiter)
}
// parsePropertyInternal attempts to decode a Property from the passed string.
// It follows the spec at https://www.w3.org/TR/baggage/#definition.
func parsePropertyInternal(s string) (p Property, ok bool) {
// For the entire function we will use " key = value " as an example.
// Attempting to parse the key.
// First skip spaces at the beginning "< >key = value " (they could be empty).
index := skipSpace(s, 0)
// Parse the key: " <key> = value ".
keyStart := index
keyEnd := index
for _, c := range s[keyStart:] {
if !validateKeyChar(c) {
break
}
keyEnd++
}
// If we couldn't find any valid key character,
// it means the key is either empty or invalid.
if keyStart == keyEnd {
return
}
// Skip spaces after the key: " key< >= value ".
index = skipSpace(s, keyEnd)
if index == len(s) {
// A key can have no value, like: " key ".
ok = true
p.key = s[keyStart:keyEnd]
return
}
// If we have not reached the end and we can't find the '=' delimiter,
// it means the property is invalid.
if s[index] != keyValueDelimiter[0] {
return
}
// Attempting to parse the value.
// Match: " key =< >value ".
index = skipSpace(s, index+1)
// Match the value string: " key = <value> ".
// A valid property can be: " key =".
// Therefore, we don't have to check if the value is empty.
valueStart := index
valueEnd := index
for _, c := range s[valueStart:] {
if !validateValueChar(c) {
break
}
valueEnd++
}
// Skip all trailing whitespaces: " key = value< >".
index = skipSpace(s, valueEnd)
// If after looking for the value and skipping whitespaces
// we have not reached the end, it means the property is
// invalid, something like: " key = value value1".
if index != len(s) {
return
}
// Decode a percent-encoded value.
value, err := url.PathUnescape(s[valueStart:valueEnd])
if err != nil {
return
}
ok = true
p.key = s[keyStart:keyEnd]
p.hasValue = true
p.value = value
return
}
func skipSpace(s string, offset int) int {
i := offset
for ; i < len(s); i++ {
c := s[i]
if c != ' ' && c != '\t' {
break
}
}
return i
}
var safeKeyCharset = [utf8.RuneSelf]bool{
// 0x23 to 0x27
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
// 0x30 to 0x39
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
// 0x41 to 0x5a
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'V': true,
'W': true,
'X': true,
'Y': true,
'Z': true,
// 0x5e to 0x7a
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
// remainder
'!': true,
'*': true,
'+': true,
'-': true,
'.': true,
'|': true,
'~': true,
}
func validateKey(s string) bool {
if len(s) == 0 {
return false
}
for _, c := range s {
if !validateKeyChar(c) {
return false
}
}
return true
}
func validateKeyChar(c int32) bool {
return c >= 0 && c < int32(utf8.RuneSelf) && safeKeyCharset[c]
}
func validateValue(s string) bool {
for _, c := range s {
if !validateValueChar(c) {
return false
}
}
return true
}
var safeValueCharset = [utf8.RuneSelf]bool{
'!': true, // 0x21
// 0x23 to 0x2b
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'(': true,
')': true,
'*': true,
'+': true,
// 0x2d to 0x3a
'-': true,
'.': true,
'/': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
':': true,
// 0x3c to 0x5b
'<': true, // 0x3C
'=': true, // 0x3D
'>': true, // 0x3E
'?': true, // 0x3F
'@': true, // 0x40
'A': true, // 0x41
'B': true, // 0x42
'C': true, // 0x43
'D': true, // 0x44
'E': true, // 0x45
'F': true, // 0x46
'G': true, // 0x47
'H': true, // 0x48
'I': true, // 0x49
'J': true, // 0x4A
'K': true, // 0x4B
'L': true, // 0x4C
'M': true, // 0x4D
'N': true, // 0x4E
'O': true, // 0x4F
'P': true, // 0x50
'Q': true, // 0x51
'R': true, // 0x52
'S': true, // 0x53
'T': true, // 0x54
'U': true, // 0x55
'V': true, // 0x56
'W': true, // 0x57
'X': true, // 0x58
'Y': true, // 0x59
'Z': true, // 0x5A
'[': true, // 0x5B
// 0x5d to 0x7e
']': true, // 0x5D
'^': true, // 0x5E
'_': true, // 0x5F
'`': true, // 0x60
'a': true, // 0x61
'b': true, // 0x62
'c': true, // 0x63
'd': true, // 0x64
'e': true, // 0x65
'f': true, // 0x66
'g': true, // 0x67
'h': true, // 0x68
'i': true, // 0x69
'j': true, // 0x6A
'k': true, // 0x6B
'l': true, // 0x6C
'm': true, // 0x6D
'n': true, // 0x6E
'o': true, // 0x6F
'p': true, // 0x70
'q': true, // 0x71
'r': true, // 0x72
's': true, // 0x73
't': true, // 0x74
'u': true, // 0x75
'v': true, // 0x76
'w': true, // 0x77
'x': true, // 0x78
'y': true, // 0x79
'z': true, // 0x7A
'{': true, // 0x7B
'|': true, // 0x7C
'}': true, // 0x7D
'~': true, // 0x7E
}
func validateValueChar(c int32) bool {
return c >= 0 && c < int32(utf8.RuneSelf) && safeValueCharset[c]
}
// valueEscape escapes the string so it can be safely placed inside a baggage value,
// replacing special characters with %XX sequences as needed.
//
// The implementation is based on:
// https://github.com/golang/go/blob/f6509cf5cdbb5787061b784973782933c47f1782/src/net/url/url.go#L285.
func valueEscape(s string) string {
hexCount := 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c) {
hexCount++
}
}
if hexCount == 0 {
return s
}
var buf [64]byte
var t []byte
required := len(s) + 2*hexCount
if required <= len(buf) {
t = buf[:required]
} else {
t = make([]byte, required)
}
j := 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(s[i]) {
const upperhex = "0123456789ABCDEF"
t[j] = '%'
t[j+1] = upperhex[c>>4]
t[j+2] = upperhex[c&15]
j += 3
} else {
t[j] = c
j++
}
}
return string(t)
}
// shouldEscape returns true if the specified byte should be escaped when
// appearing in a baggage value string.
func shouldEscape(c byte) bool {
if c == '%' {
// The percent character must be encoded so that percent-encoding can work.
return true
}
return !validateValueChar(int32(c))
}

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package baggage // import "go.opentelemetry.io/otel/baggage"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
/*
Package baggage provides functionality for storing and retrieving

3
vendor/go.opentelemetry.io/otel/codes/README.md generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Codes
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/codes)](https://pkg.go.dev/go.opentelemetry.io/otel/codes)

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package codes // import "go.opentelemetry.io/otel/codes"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
/*
Package codes defines the canonical error codes used by OpenTelemetry.

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
/*
Package otel provides global access to the OpenTelemetry API. The subpackages of
@@ -22,7 +11,7 @@ transmitted anywhere. An implementation of the OpenTelemetry SDK, like the
default SDK implementation (go.opentelemetry.io/otel/sdk), and associated
exporters are used to process and transport this data.
To read the getting started guide, see https://opentelemetry.io/docs/go/getting-started/.
To read the getting started guide, see https://opentelemetry.io/docs/languages/go/getting-started/.
To read more about tracing, see go.opentelemetry.io/otel/trace.

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otel // import "go.opentelemetry.io/otel"

View File

@@ -0,0 +1,3 @@
# OTLP Trace Exporter
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/exporters/otlp/otlptrace)](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlptrace)

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
/*
Package otlptrace contains abstractions for OTLP span exporters.

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
@@ -104,7 +93,7 @@ func NewUnstarted(client Client) *Exporter {
}
}
// MarshalLog is the marshaling function used by the logging system to represent this exporter.
// MarshalLog is the marshaling function used by the logging system to represent this Exporter.
func (e *Exporter) MarshalLog() interface{} {
return struct {
Type string

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"

View File

@@ -1,16 +1,5 @@
// 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.
// SPDX-License-Identifier: Apache-2.0
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
@@ -121,6 +110,7 @@ func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
if psid := sd.Parent().SpanID(); psid.IsValid() {
s.ParentSpanId = psid[:]
}
s.Flags = buildSpanFlags(sd.Parent())
return s
}
@@ -157,16 +147,28 @@ func links(links []tracesdk.Link) []*tracepb.Span_Link {
tid := otLink.SpanContext.TraceID()
sid := otLink.SpanContext.SpanID()
flags := buildSpanFlags(otLink.SpanContext)
sl = append(sl, &tracepb.Span_Link{
TraceId: tid[:],
SpanId: sid[:],
Attributes: KeyValues(otLink.Attributes),
DroppedAttributesCount: uint32(otLink.DroppedAttributeCount),
Flags: flags,
})
}
return sl
}
func buildSpanFlags(sc trace.SpanContext) uint32 {
flags := tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK
if sc.IsRemote() {
flags |= tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK
}
return uint32(flags)
}
// spanEvents transforms span Events to an OTLP span events.
func spanEvents(es []tracesdk.Event) []*tracepb.Span_Event {
if len(es) == 0 {

View File

@@ -0,0 +1,3 @@
# OTLP Trace gRPC Exporter
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc)](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc)

Some files were not shown because too many files have changed in this diff Show More