Phase 2: Implement Execution Environment Abstraction (v0.3.0)
This commit implements Phase 2 of the CHORUS Task Execution Engine development plan, providing a comprehensive execution environment abstraction layer with Docker container sandboxing support. ## New Features ### Core Sandbox Interface - Comprehensive ExecutionSandbox interface with isolated task execution - Support for command execution, file I/O, environment management - Resource usage monitoring and sandbox lifecycle management - Standardized error handling with SandboxError types and categories ### Docker Container Sandbox Implementation - Full Docker API integration with secure container creation - Transparent repository mounting with configurable read/write access - Advanced security policies with capability dropping and privilege controls - Comprehensive resource limits (CPU, memory, disk, processes, file handles) - Support for tmpfs mounts, masked paths, and read-only bind mounts - Container lifecycle management with proper cleanup and health monitoring ### Security & Resource Management - Configurable security policies with SELinux, AppArmor, and Seccomp support - Fine-grained capability management with secure defaults - Network isolation options with configurable DNS and proxy settings - Resource monitoring with real-time CPU, memory, and network usage tracking - Comprehensive ulimits configuration for process and file handle limits ### Repository Integration - Seamless repository mounting from local paths to container workspaces - Git configuration support with user credentials and global settings - File inclusion/exclusion patterns for selective repository access - Configurable permissions and ownership for mounted repositories ### Testing Infrastructure - Comprehensive test suite with 60+ test cases covering all functionality - Docker integration tests with Alpine Linux containers (skipped in short mode) - Mock sandbox implementation for unit testing without Docker dependencies - Security policy validation tests with read-only filesystem enforcement - Resource usage monitoring and cleanup verification tests ## Technical Details ### Dependencies Added - github.com/docker/docker v28.4.0+incompatible - Docker API client - github.com/docker/go-connections v0.6.0 - Docker connection utilities - github.com/docker/go-units v0.5.0 - Docker units and formatting - Associated Docker API dependencies for complete container management ### Architecture - Interface-driven design enabling multiple sandbox implementations - Comprehensive configuration structures for all sandbox aspects - Resource usage tracking with detailed metrics collection - Error handling with retryable error classification - Proper cleanup and resource management throughout sandbox lifecycle ### Compatibility - Maintains backward compatibility with existing CHORUS architecture - Designed for future integration with Phase 3 Core Task Execution Engine - Extensible design supporting additional sandbox implementations (VM, process) This Phase 2 implementation provides the foundation for secure, isolated task execution that will be integrated with the AI model providers from Phase 1 in the upcoming Phase 3 development. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
231
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE
generated
vendored
Normal file
231
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
50
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/client.go
generated
vendored
Normal file
50
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/client.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultClient is the default Client and is used by Get, Head, Post and PostForm.
|
||||
// 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)}
|
||||
|
||||
// Get is a convenient replacement for http.Get that adds a span around the request.
|
||||
func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// Head is a convenient replacement for http.Head that adds a span around the request.
|
||||
func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodHead, targetURL, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// Post is a convenient replacement for http.Post that adds a span around the request.
|
||||
func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// PostForm is a convenient replacement for http.PostForm that adds a span around the request.
|
||||
func PostForm(ctx context.Context, targetURL string, data url.Values) (resp *http.Response, err error) {
|
||||
return Post(ctx, targetURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
}
|
||||
27
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go
generated
vendored
Normal file
27
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/common.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Attribute keys that can be added to a span.
|
||||
const (
|
||||
ReadBytesKey = attribute.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read
|
||||
ReadErrorKey = attribute.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded)
|
||||
WroteBytesKey = attribute.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written
|
||||
WriteErrorKey = attribute.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded)
|
||||
)
|
||||
|
||||
// Filter is a predicate used to determine whether a given http.request should
|
||||
// be traced. A Filter must return true if the request should be traced.
|
||||
type Filter func(*http.Request) bool
|
||||
|
||||
func newTracer(tp trace.TracerProvider) trace.Tracer {
|
||||
return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version()))
|
||||
}
|
||||
210
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/config.go
generated
vendored
Normal file
210
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/config.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type config struct {
|
||||
ServerName string
|
||||
Tracer trace.Tracer
|
||||
Meter metric.Meter
|
||||
Propagators propagation.TextMapPropagator
|
||||
SpanStartOptions []trace.SpanStartOption
|
||||
PublicEndpoint bool
|
||||
PublicEndpointFn func(*http.Request) bool
|
||||
ReadEvent bool
|
||||
WriteEvent bool
|
||||
Filters []Filter
|
||||
SpanNameFormatter func(string, *http.Request) string
|
||||
ClientTrace func(context.Context) *httptrace.ClientTrace
|
||||
|
||||
TracerProvider trace.TracerProvider
|
||||
MeterProvider metric.MeterProvider
|
||||
MetricAttributesFn func(*http.Request) []attribute.KeyValue
|
||||
}
|
||||
|
||||
// Option interface used for setting optional config properties.
|
||||
type Option interface {
|
||||
apply(*config)
|
||||
}
|
||||
|
||||
type optionFunc func(*config)
|
||||
|
||||
func (o optionFunc) apply(c *config) {
|
||||
o(c)
|
||||
}
|
||||
|
||||
// newConfig creates a new config struct and applies opts to it.
|
||||
func newConfig(opts ...Option) *config {
|
||||
c := &config{
|
||||
Propagators: otel.GetTextMapPropagator(),
|
||||
MeterProvider: otel.GetMeterProvider(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.apply(c)
|
||||
}
|
||||
|
||||
// Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context.
|
||||
if c.TracerProvider != nil {
|
||||
c.Tracer = newTracer(c.TracerProvider)
|
||||
}
|
||||
|
||||
c.Meter = c.MeterProvider.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version()),
|
||||
)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
|
||||
// If none is specified, the global provider is used.
|
||||
func WithTracerProvider(provider trace.TracerProvider) Option {
|
||||
return optionFunc(func(cfg *config) {
|
||||
if provider != nil {
|
||||
cfg.TracerProvider = provider
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithMeterProvider specifies a meter provider to use for creating a meter.
|
||||
// If none is specified, the global provider is used.
|
||||
func WithMeterProvider(provider metric.MeterProvider) Option {
|
||||
return optionFunc(func(cfg *config) {
|
||||
if provider != nil {
|
||||
cfg.MeterProvider = provider
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithPublicEndpoint configures the Handler to link the span with an incoming
|
||||
// span context. If this option is not provided, then the association is a child
|
||||
// association instead of a link.
|
||||
func WithPublicEndpoint() Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.PublicEndpoint = true
|
||||
})
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn.
|
||||
func WithPublicEndpointFn(fn func(*http.Request) bool) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.PublicEndpointFn = fn
|
||||
})
|
||||
}
|
||||
|
||||
// WithPropagators configures specific propagators. If this
|
||||
// option isn't specified, then the global TextMapPropagator is used.
|
||||
func WithPropagators(ps propagation.TextMapPropagator) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
if ps != nil {
|
||||
c.Propagators = ps
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpanOptions configures an additional set of
|
||||
// trace.SpanOptions, which are applied to each new span.
|
||||
func WithSpanOptions(opts ...trace.SpanStartOption) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
// WithFilter adds a filter to the list of filters used by the handler.
|
||||
// If any filter indicates to exclude a request then the request will not be
|
||||
// traced. All filters must allow a request to be traced for a Span to be created.
|
||||
// If no filters are provided then all requests are traced.
|
||||
// Filters will be invoked for each processed request, it is advised to make them
|
||||
// simple and fast.
|
||||
func WithFilter(f Filter) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.Filters = append(c.Filters, f)
|
||||
})
|
||||
}
|
||||
|
||||
type event int
|
||||
|
||||
// Different types of events that can be recorded, see WithMessageEvents.
|
||||
const (
|
||||
ReadEvents event = iota
|
||||
WriteEvents
|
||||
)
|
||||
|
||||
// WithMessageEvents configures the Handler to record the specified events
|
||||
// (span.AddEvent) on spans. By default only summary attributes are added at the
|
||||
// end of the request.
|
||||
//
|
||||
// Valid events are:
|
||||
// - ReadEvents: Record the number of bytes read after every http.Request.Body.Read
|
||||
// using the ReadBytesKey
|
||||
// - WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
|
||||
// using the WriteBytesKey
|
||||
func WithMessageEvents(events ...event) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
for _, e := range events {
|
||||
switch e {
|
||||
case ReadEvents:
|
||||
c.ReadEvent = true
|
||||
case WriteEvents:
|
||||
c.WriteEvent = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WithSpanNameFormatter takes a function that will be called on every
|
||||
// request and the returned string will become the Span Name.
|
||||
//
|
||||
// When using [http.ServeMux] (or any middleware that sets the Pattern of [http.Request]),
|
||||
// the span name formatter will run twice. Once when the span is created, and
|
||||
// second time after the middleware, so the pattern can be used.
|
||||
func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.SpanNameFormatter = f
|
||||
})
|
||||
}
|
||||
|
||||
// WithClientTrace takes a function that returns client trace instance that will be
|
||||
// applied to the requests sent through the otelhttp Transport.
|
||||
func WithClientTrace(f func(context.Context) *httptrace.ClientTrace) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.ClientTrace = f
|
||||
})
|
||||
}
|
||||
|
||||
// WithServerName returns an Option that sets the name of the (virtual) server
|
||||
// handling requests.
|
||||
func WithServerName(server string) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.ServerName = server
|
||||
})
|
||||
}
|
||||
|
||||
// WithMetricAttributesFn returns an Option to set a function that maps an HTTP request to a slice of attribute.KeyValue.
|
||||
// These attributes will be included in metrics for every request.
|
||||
func WithMetricAttributesFn(metricAttributesFn func(r *http.Request) []attribute.KeyValue) Option {
|
||||
return optionFunc(func(c *config) {
|
||||
c.MetricAttributesFn = metricAttributesFn
|
||||
})
|
||||
}
|
||||
7
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/doc.go
generated
vendored
Normal file
7
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/doc.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// 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
|
||||
// routes WithRouteTag.
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
238
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go
generated
vendored
Normal file
238
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/felixge/httpsnoop"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
)
|
||||
|
||||
// middleware is an http middleware which wraps the next handler in a span.
|
||||
type middleware struct {
|
||||
operation string
|
||||
server string
|
||||
|
||||
tracer trace.Tracer
|
||||
propagators propagation.TextMapPropagator
|
||||
spanStartOptions []trace.SpanStartOption
|
||||
readEvent bool
|
||||
writeEvent bool
|
||||
filters []Filter
|
||||
spanNameFormatter func(string, *http.Request) string
|
||||
publicEndpoint bool
|
||||
publicEndpointFn func(*http.Request) bool
|
||||
metricAttributesFn func(*http.Request) []attribute.KeyValue
|
||||
|
||||
semconv semconv.HTTPServer
|
||||
}
|
||||
|
||||
func defaultHandlerFormatter(operation string, _ *http.Request) string {
|
||||
return operation
|
||||
}
|
||||
|
||||
// NewHandler wraps the passed handler in a span named after the operation and
|
||||
// enriches it with metrics.
|
||||
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
|
||||
return NewMiddleware(operation, opts...)(handler)
|
||||
}
|
||||
|
||||
// NewMiddleware returns a tracing and metrics instrumentation middleware.
|
||||
// The handler returned by the middleware wraps a handler
|
||||
// in a span named after the operation and enriches it with metrics.
|
||||
func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler {
|
||||
h := middleware{
|
||||
operation: operation,
|
||||
}
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||
WithSpanNameFormatter(defaultHandlerFormatter),
|
||||
}
|
||||
|
||||
c := newConfig(append(defaultOpts, opts...)...)
|
||||
h.configure(c)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveHTTP(w, r, next)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *middleware) configure(c *config) {
|
||||
h.tracer = c.Tracer
|
||||
h.propagators = c.Propagators
|
||||
h.spanStartOptions = c.SpanStartOptions
|
||||
h.readEvent = c.ReadEvent
|
||||
h.writeEvent = c.WriteEvent
|
||||
h.filters = c.Filters
|
||||
h.spanNameFormatter = c.SpanNameFormatter
|
||||
h.publicEndpoint = c.PublicEndpoint
|
||||
h.publicEndpointFn = c.PublicEndpointFn
|
||||
h.server = c.ServerName
|
||||
h.semconv = semconv.NewHTTPServer(c.Meter)
|
||||
h.metricAttributesFn = c.MetricAttributesFn
|
||||
}
|
||||
|
||||
// serveHTTP sets up tracing and calls the given next http.Handler with the span
|
||||
// context injected into the request context.
|
||||
func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) {
|
||||
requestStartTime := time.Now()
|
||||
for _, f := range h.filters {
|
||||
if !f(r) {
|
||||
// Simply pass through to the handler if a filter rejects the request
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
|
||||
opts := []trace.SpanStartOption{
|
||||
trace.WithAttributes(h.semconv.RequestTraceAttrs(h.server, r, semconv.RequestTraceAttrsOpts{})...),
|
||||
}
|
||||
|
||||
opts = append(opts, h.spanStartOptions...)
|
||||
if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) {
|
||||
opts = append(opts, trace.WithNewRoot())
|
||||
// Linking incoming span context if any for public endpoint.
|
||||
if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
|
||||
opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s}))
|
||||
}
|
||||
}
|
||||
|
||||
tracer := h.tracer
|
||||
|
||||
if tracer == nil {
|
||||
if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
|
||||
tracer = newTracer(span.TracerProvider())
|
||||
} else {
|
||||
tracer = newTracer(otel.GetTracerProvider())
|
||||
}
|
||||
}
|
||||
|
||||
if startTime := StartTimeFromContext(ctx); !startTime.IsZero() {
|
||||
opts = append(opts, trace.WithTimestamp(startTime))
|
||||
requestStartTime = startTime
|
||||
}
|
||||
|
||||
ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
|
||||
defer span.End()
|
||||
|
||||
readRecordFunc := func(int64) {}
|
||||
if h.readEvent {
|
||||
readRecordFunc = func(n int64) {
|
||||
span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n)))
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
bw := request.NewBodyWrapper(r.Body, readRecordFunc)
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
r.Body = bw
|
||||
}
|
||||
|
||||
writeRecordFunc := func(int64) {}
|
||||
if h.writeEvent {
|
||||
writeRecordFunc = func(n int64) {
|
||||
span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n)))
|
||||
}
|
||||
}
|
||||
|
||||
rww := request.NewRespWriterWrapper(w, writeRecordFunc)
|
||||
|
||||
// Wrap w to use our ResponseWriter methods while also exposing
|
||||
// other interfaces that w may implement (http.CloseNotifier,
|
||||
// http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom).
|
||||
|
||||
w = httpsnoop.Wrap(w, httpsnoop.Hooks{
|
||||
Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc {
|
||||
return rww.Header
|
||||
},
|
||||
Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
|
||||
return rww.Write
|
||||
},
|
||||
WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
|
||||
return rww.WriteHeader
|
||||
},
|
||||
Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc {
|
||||
return rww.Flush
|
||||
},
|
||||
})
|
||||
|
||||
labeler, found := LabelerFromContext(ctx)
|
||||
if !found {
|
||||
ctx = ContextWithLabeler(ctx, labeler)
|
||||
}
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
if r.Pattern != "" {
|
||||
span.SetName(h.spanNameFormatter(h.operation, r))
|
||||
}
|
||||
|
||||
statusCode := rww.StatusCode()
|
||||
bytesWritten := rww.BytesWritten()
|
||||
span.SetStatus(h.semconv.Status(statusCode))
|
||||
span.SetAttributes(h.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{
|
||||
StatusCode: statusCode,
|
||||
ReadBytes: bw.BytesRead(),
|
||||
ReadError: bw.Error(),
|
||||
WriteBytes: bytesWritten,
|
||||
WriteError: rww.Error(),
|
||||
})...)
|
||||
|
||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
|
||||
|
||||
metricAttributes := semconv.MetricAttributes{
|
||||
Req: r,
|
||||
StatusCode: statusCode,
|
||||
AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...),
|
||||
}
|
||||
|
||||
h.semconv.RecordMetrics(ctx, semconv.ServerMetricData{
|
||||
ServerName: h.server,
|
||||
ResponseSize: bytesWritten,
|
||||
MetricAttributes: metricAttributes,
|
||||
MetricData: semconv.MetricData{
|
||||
RequestSize: bw.BytesRead(),
|
||||
ElapsedTime: elapsedTime,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue {
|
||||
var attributeForRequest []attribute.KeyValue
|
||||
if h.metricAttributesFn != nil {
|
||||
attributeForRequest = h.metricAttributesFn(r)
|
||||
}
|
||||
return attributeForRequest
|
||||
}
|
||||
|
||||
// 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(nil).Route(route)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
span := trace.SpanFromContext(r.Context())
|
||||
span.SetAttributes(attr)
|
||||
|
||||
labeler, _ := LabelerFromContext(r.Context())
|
||||
labeler.Add(attr)
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
80
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request/body_wrapper.go
generated
vendored
Normal file
80
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request/body_wrapper.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/request/body_wrapper.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package request provides types and functionality to handle HTTP request
|
||||
// handling.
|
||||
package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _ io.ReadCloser = &BodyWrapper{}
|
||||
|
||||
// BodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number
|
||||
// of bytes read and the last error.
|
||||
type BodyWrapper struct {
|
||||
io.ReadCloser
|
||||
OnRead func(n int64) // must not be nil
|
||||
|
||||
mu sync.Mutex
|
||||
read int64
|
||||
err error
|
||||
}
|
||||
|
||||
// NewBodyWrapper creates a new BodyWrapper.
|
||||
//
|
||||
// The onRead attribute is a callback that will be called every time the data
|
||||
// is read, with the number of bytes being read.
|
||||
func NewBodyWrapper(body io.ReadCloser, onRead func(int64)) *BodyWrapper {
|
||||
return &BodyWrapper{
|
||||
ReadCloser: body,
|
||||
OnRead: onRead,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads the data from the io.ReadCloser, and stores the number of bytes
|
||||
// read and the error.
|
||||
func (w *BodyWrapper) Read(b []byte) (int, error) {
|
||||
n, err := w.ReadCloser.Read(b)
|
||||
n1 := int64(n)
|
||||
|
||||
w.updateReadData(n1, err)
|
||||
w.OnRead(n1)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (w *BodyWrapper) updateReadData(n int64, err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
w.read += n
|
||||
if err != nil {
|
||||
w.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the io.ReadCloser.
|
||||
func (w *BodyWrapper) Close() error {
|
||||
return w.ReadCloser.Close()
|
||||
}
|
||||
|
||||
// BytesRead returns the number of bytes read up to this point.
|
||||
func (w *BodyWrapper) BytesRead() int64 {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
return w.read
|
||||
}
|
||||
|
||||
// Error returns the last error.
|
||||
func (w *BodyWrapper) Error() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
return w.err
|
||||
}
|
||||
10
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request/gen.go
generated
vendored
Normal file
10
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request/gen.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
|
||||
// Generate request package:
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/request/body_wrapper.go.tmpl "--data={}" --out=body_wrapper.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/request/body_wrapper_test.go.tmpl "--data={}" --out=body_wrapper_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/request/resp_writer_wrapper.go.tmpl "--data={}" --out=resp_writer_wrapper.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/request/resp_writer_wrapper_test.go.tmpl "--data={}" --out=resp_writer_wrapper_test.go
|
||||
122
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request/resp_writer_wrapper.go
generated
vendored
Normal file
122
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request/resp_writer_wrapper.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/request/resp_writer_wrapper.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _ http.ResponseWriter = &RespWriterWrapper{}
|
||||
|
||||
// RespWriterWrapper wraps a http.ResponseWriter in order to track the number of
|
||||
// bytes written, the last error, and to catch the first written statusCode.
|
||||
// TODO: The wrapped http.ResponseWriter doesn't implement any of the optional
|
||||
// types (http.Hijacker, http.Pusher, http.CloseNotifier, etc)
|
||||
// that may be useful when using it in real life situations.
|
||||
type RespWriterWrapper struct {
|
||||
http.ResponseWriter
|
||||
OnWrite func(n int64) // must not be nil
|
||||
|
||||
mu sync.RWMutex
|
||||
written int64
|
||||
statusCode int
|
||||
err error
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
// NewRespWriterWrapper creates a new RespWriterWrapper.
|
||||
//
|
||||
// The onWrite attribute is a callback that will be called every time the data
|
||||
// is written, with the number of bytes that were written.
|
||||
func NewRespWriterWrapper(w http.ResponseWriter, onWrite func(int64)) *RespWriterWrapper {
|
||||
return &RespWriterWrapper{
|
||||
ResponseWriter: w,
|
||||
OnWrite: onWrite,
|
||||
statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes the bytes array into the [ResponseWriter], and tracks the
|
||||
// number of bytes written and last error.
|
||||
func (w *RespWriterWrapper) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if !w.wroteHeader {
|
||||
w.writeHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
n, err := w.ResponseWriter.Write(p)
|
||||
n1 := int64(n)
|
||||
w.OnWrite(n1)
|
||||
w.written += n1
|
||||
w.err = err
|
||||
return n, err
|
||||
}
|
||||
|
||||
// WriteHeader persists initial statusCode for span attribution.
|
||||
// All calls to WriteHeader will be propagated to the underlying ResponseWriter
|
||||
// and will persist the statusCode from the first call.
|
||||
// Blocking consecutive calls to WriteHeader alters expected behavior and will
|
||||
// remove warning logs from net/http where developers will notice incorrect handler implementations.
|
||||
func (w *RespWriterWrapper) WriteHeader(statusCode int) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
w.writeHeader(statusCode)
|
||||
}
|
||||
|
||||
// writeHeader persists the status code for span attribution, and propagates
|
||||
// the call to the underlying ResponseWriter.
|
||||
// It does not acquire a lock, and therefore assumes that is being handled by a
|
||||
// parent method.
|
||||
func (w *RespWriterWrapper) writeHeader(statusCode int) {
|
||||
if !w.wroteHeader {
|
||||
w.wroteHeader = true
|
||||
w.statusCode = statusCode
|
||||
}
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
// Flush implements [http.Flusher].
|
||||
func (w *RespWriterWrapper) Flush() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if !w.wroteHeader {
|
||||
w.writeHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// BytesWritten returns the number of bytes written.
|
||||
func (w *RespWriterWrapper) BytesWritten() int64 {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
|
||||
return w.written
|
||||
}
|
||||
|
||||
// StatusCode returns the HTTP status code that was sent.
|
||||
func (w *RespWriterWrapper) StatusCode() int {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
|
||||
return w.statusCode
|
||||
}
|
||||
|
||||
// Error returns the last error.
|
||||
func (w *RespWriterWrapper) Error() error {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
|
||||
return w.err
|
||||
}
|
||||
248
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/env.go
generated
vendored
Normal file
248
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/env.go
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/env.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/semconv/v1.37.0/httpconv"
|
||||
)
|
||||
|
||||
// OTelSemConvStabilityOptIn is an environment variable.
|
||||
// That can be set to "http/dup" to keep getting the old HTTP semantic conventions.
|
||||
const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN"
|
||||
|
||||
type ResponseTelemetry struct {
|
||||
StatusCode int
|
||||
ReadBytes int64
|
||||
ReadError error
|
||||
WriteBytes int64
|
||||
WriteError error
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
requestBodySizeHistogram httpconv.ServerRequestBodySize
|
||||
responseBodySizeHistogram httpconv.ServerResponseBodySize
|
||||
requestDurationHistogram httpconv.ServerRequestDuration
|
||||
}
|
||||
|
||||
// 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, opts RequestTraceAttrsOpts) []attribute.KeyValue {
|
||||
return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)
|
||||
}
|
||||
|
||||
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
|
||||
return []attribute.KeyValue{
|
||||
CurrentHTTPServer{}.NetworkTransportAttr(network),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return CurrentHTTPServer{}.ResponseTraceAttrs(resp)
|
||||
}
|
||||
|
||||
// Route returns the attribute for the route.
|
||||
func (s HTTPServer) Route(route string) attribute.KeyValue {
|
||||
return CurrentHTTPServer{}.Route(route)
|
||||
}
|
||||
|
||||
// Status 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 (s HTTPServer) Status(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, ""
|
||||
}
|
||||
|
||||
type ServerMetricData struct {
|
||||
ServerName string
|
||||
ResponseSize int64
|
||||
|
||||
MetricData
|
||||
MetricAttributes
|
||||
}
|
||||
|
||||
type MetricAttributes struct {
|
||||
Req *http.Request
|
||||
StatusCode int
|
||||
AdditionalAttributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
type MetricData struct {
|
||||
RequestSize int64
|
||||
|
||||
// The request duration, in milliseconds
|
||||
ElapsedTime float64
|
||||
}
|
||||
|
||||
var (
|
||||
metricAddOptionPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &[]metric.AddOption{}
|
||||
},
|
||||
}
|
||||
|
||||
metricRecordOptionPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &[]metric.RecordOption{}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
||||
attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
|
||||
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
||||
*recordOpts = append(*recordOpts, o)
|
||||
s.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
|
||||
s.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
|
||||
s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o)
|
||||
*recordOpts = (*recordOpts)[:0]
|
||||
metricRecordOptionPool.Put(recordOpts)
|
||||
}
|
||||
|
||||
// hasOptIn returns true if the comma-separated version string contains the
|
||||
// exact optIn value.
|
||||
func hasOptIn(version, optIn string) bool {
|
||||
for _, v := range strings.Split(version, ",") {
|
||||
if strings.TrimSpace(v) == optIn {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewHTTPServer(meter metric.Meter) HTTPServer {
|
||||
server := HTTPServer{}
|
||||
|
||||
var err error
|
||||
server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter)
|
||||
handleErr(err)
|
||||
|
||||
server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter)
|
||||
handleErr(err)
|
||||
|
||||
server.requestDurationHistogram, err = httpconv.NewServerRequestDuration(
|
||||
meter,
|
||||
metric.WithExplicitBucketBoundaries(
|
||||
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
|
||||
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
|
||||
),
|
||||
)
|
||||
handleErr(err)
|
||||
return server
|
||||
}
|
||||
|
||||
type HTTPClient struct {
|
||||
requestBodySize httpconv.ClientRequestBodySize
|
||||
requestDuration httpconv.ClientRequestDuration
|
||||
}
|
||||
|
||||
func NewHTTPClient(meter metric.Meter) HTTPClient {
|
||||
client := HTTPClient{}
|
||||
|
||||
var err error
|
||||
client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter)
|
||||
handleErr(err)
|
||||
|
||||
client.requestDuration, err = httpconv.NewClientRequestDuration(
|
||||
meter,
|
||||
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
|
||||
)
|
||||
handleErr(err)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// RequestTraceAttrs returns attributes for an HTTP request made by a client.
|
||||
func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
|
||||
return CurrentHTTPClient{}.RequestTraceAttrs(req)
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
|
||||
func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
|
||||
return CurrentHTTPClient{}.ResponseTraceAttrs(resp)
|
||||
}
|
||||
|
||||
func (c HTTPClient) Status(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 400 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
func (c HTTPClient) ErrorType(err error) attribute.KeyValue {
|
||||
return CurrentHTTPClient{}.ErrorType(err)
|
||||
}
|
||||
|
||||
type MetricOpts struct {
|
||||
measurement metric.MeasurementOption
|
||||
addOptions metric.AddOption
|
||||
}
|
||||
|
||||
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
|
||||
return o.measurement
|
||||
}
|
||||
|
||||
func (o MetricOpts) AddOptions() metric.AddOption {
|
||||
return o.addOptions
|
||||
}
|
||||
|
||||
func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts {
|
||||
opts := map[string]MetricOpts{}
|
||||
|
||||
attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
||||
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||
opts["new"] = MetricOpts{
|
||||
measurement: set,
|
||||
addOptions: set,
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) {
|
||||
s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption())
|
||||
s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption())
|
||||
}
|
||||
|
||||
func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
|
||||
return CurrentHTTPClient{}.TraceAttributes(host)
|
||||
}
|
||||
15
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/gen.go
generated
vendored
Normal file
15
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/gen.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
// Generate semconv package:
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=bench_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=common_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconvtest_test.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util.go
|
||||
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util_test.go
|
||||
517
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go
generated
vendored
Normal file
517
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go
generated
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/httpconv.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package semconv provides OpenTelemetry semantic convention types and
|
||||
// functionality.
|
||||
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0"
|
||||
)
|
||||
|
||||
type RequestTraceAttrsOpts struct {
|
||||
// If set, this is used as value for the "http.client_ip" attribute.
|
||||
HTTPClientIP string
|
||||
}
|
||||
|
||||
type CurrentHTTPServer 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 (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []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)
|
||||
|
||||
peer, peerPort := SplitHostPort(req.RemoteAddr)
|
||||
if 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++
|
||||
}
|
||||
|
||||
// For client IP, use, in order:
|
||||
// 1. The value passed in the options
|
||||
// 2. The value in the X-Forwarded-For header
|
||||
// 3. The peer address
|
||||
clientIP := opts.HTTPClientIP
|
||||
if clientIP == "" {
|
||||
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
|
||||
if clientIP == "" {
|
||||
clientIP = peer
|
||||
}
|
||||
}
|
||||
if clientIP != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
if req.URL != nil && req.URL.Path != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" && protoName != "http" {
|
||||
count++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
count++
|
||||
}
|
||||
|
||||
route := httpRoute(req.Pattern)
|
||||
if route != "" {
|
||||
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 != "" {
|
||||
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))
|
||||
}
|
||||
|
||||
if route != "" {
|
||||
attrs = append(attrs, n.Route(route))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
return semconvNew.NetworkTransportTCP
|
||||
case "udp", "udp4", "udp6":
|
||||
return semconvNew.NetworkTransportUDP
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
return semconvNew.NetworkTransportUnix
|
||||
default:
|
||||
return semconvNew.NetworkTransportPipe
|
||||
}
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) 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 CurrentHTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
|
||||
if https {
|
||||
return semconvNew.URLScheme("https")
|
||||
}
|
||||
return semconvNew.URLScheme("http")
|
||||
}
|
||||
|
||||
// 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 (n CurrentHTTPServer) 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 CurrentHTTPServer) Route(route string) attribute.KeyValue {
|
||||
return semconvNew.HTTPRoute(route)
|
||||
}
|
||||
|
||||
func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
num := len(additionalAttributes) + 3
|
||||
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 {
|
||||
num++
|
||||
}
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
num++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
num++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, num)
|
||||
attributes = append(attributes,
|
||||
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
|
||||
n.scheme(req.TLS != nil),
|
||||
semconvNew.ServerAddress(host))
|
||||
|
||||
if hostPort > 0 {
|
||||
attributes = append(attributes, semconvNew.ServerPort(hostPort))
|
||||
}
|
||||
if protoName != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
type CurrentHTTPClient struct{}
|
||||
|
||||
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
|
||||
func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
|
||||
/*
|
||||
below attributes are returned:
|
||||
- http.request.method
|
||||
- http.request.method.original
|
||||
- url.full
|
||||
- server.address
|
||||
- server.port
|
||||
- network.protocol.name
|
||||
- network.protocol.version
|
||||
*/
|
||||
numOfAttributes := 3 // URL, server address, proto, and method.
|
||||
|
||||
var urlHost string
|
||||
if req.URL != nil {
|
||||
urlHost = req.URL.Host
|
||||
}
|
||||
var requestHost string
|
||||
var requestPort int
|
||||
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
|
||||
requestHost, requestPort = SplitHostPort(hostport)
|
||||
if requestHost != "" || requestPort > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
|
||||
if eligiblePort > 0 {
|
||||
numOfAttributes++
|
||||
}
|
||||
useragent := req.UserAgent()
|
||||
if useragent != "" {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" && protoName != "http" {
|
||||
numOfAttributes++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
method, originalMethod := n.method(req.Method)
|
||||
if originalMethod != (attribute.KeyValue{}) {
|
||||
numOfAttributes++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
|
||||
|
||||
attrs = append(attrs, method)
|
||||
if originalMethod != (attribute.KeyValue{}) {
|
||||
attrs = append(attrs, originalMethod)
|
||||
}
|
||||
|
||||
var u string
|
||||
if req.URL != nil {
|
||||
// Remove any username/password info that may be in the URL.
|
||||
userinfo := req.URL.User
|
||||
req.URL.User = nil
|
||||
u = req.URL.String()
|
||||
// Restore any username/password info that was removed.
|
||||
req.URL.User = userinfo
|
||||
}
|
||||
attrs = append(attrs, semconvNew.URLFull(u))
|
||||
|
||||
attrs = append(attrs, semconvNew.ServerAddress(requestHost))
|
||||
if eligiblePort > 0 {
|
||||
attrs = append(attrs, semconvNew.ServerPort(eligiblePort))
|
||||
}
|
||||
|
||||
if protoName != "" && protoName != "http" {
|
||||
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
|
||||
func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
|
||||
/*
|
||||
below attributes are returned:
|
||||
- http.response.status_code
|
||||
- error.type
|
||||
*/
|
||||
var count int
|
||||
if resp.StatusCode > 0 {
|
||||
count++
|
||||
}
|
||||
|
||||
if isErrorStatusCode(resp.StatusCode) {
|
||||
count++
|
||||
}
|
||||
|
||||
attrs := make([]attribute.KeyValue, 0, count)
|
||||
if resp.StatusCode > 0 {
|
||||
attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode))
|
||||
}
|
||||
|
||||
if isErrorStatusCode(resp.StatusCode) {
|
||||
errorType := strconv.Itoa(resp.StatusCode)
|
||||
attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue {
|
||||
t := reflect.TypeOf(err)
|
||||
var value string
|
||||
if t.PkgPath() == "" && t.Name() == "" {
|
||||
// Likely a builtin type.
|
||||
value = t.String()
|
||||
} else {
|
||||
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
return semconvNew.ErrorTypeOther
|
||||
}
|
||||
|
||||
return semconvNew.ErrorTypeKey.String(value)
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) 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 CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||
num := len(additionalAttributes) + 2
|
||||
var h string
|
||||
if req.URL != nil {
|
||||
h = req.URL.Host
|
||||
}
|
||||
var requestHost string
|
||||
var requestPort int
|
||||
for _, hostport := range []string{h, req.Header.Get("Host")} {
|
||||
requestHost, requestPort = SplitHostPort(hostport)
|
||||
if requestHost != "" || requestPort > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
|
||||
if port > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
protoName, protoVersion := netProtocol(req.Proto)
|
||||
if protoName != "" {
|
||||
num++
|
||||
}
|
||||
if protoVersion != "" {
|
||||
num++
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
num++
|
||||
}
|
||||
|
||||
attributes := slices.Grow(additionalAttributes, num)
|
||||
attributes = append(attributes,
|
||||
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
|
||||
semconvNew.ServerAddress(requestHost),
|
||||
n.scheme(req),
|
||||
)
|
||||
|
||||
if port > 0 {
|
||||
attributes = append(attributes, semconvNew.ServerPort(port))
|
||||
}
|
||||
if protoName != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
|
||||
}
|
||||
if protoVersion != "" {
|
||||
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
|
||||
}
|
||||
|
||||
if statusCode > 0 {
|
||||
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
|
||||
// TraceAttributes returns attributes for httptrace.
|
||||
func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue {
|
||||
return []attribute.KeyValue{
|
||||
semconvNew.ServerAddress(host),
|
||||
}
|
||||
}
|
||||
|
||||
func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue {
|
||||
if req.URL != nil && req.URL.Scheme != "" {
|
||||
return semconvNew.URLScheme(req.URL.Scheme)
|
||||
}
|
||||
if req.TLS != nil {
|
||||
return semconvNew.URLScheme("https")
|
||||
}
|
||||
return semconvNew.URLScheme("http")
|
||||
}
|
||||
|
||||
func isErrorStatusCode(code int) bool {
|
||||
return code >= 400 || code < 100
|
||||
}
|
||||
127
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/util.go
generated
vendored
Normal file
127
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/util.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/semconv/util.go.tmpl
|
||||
|
||||
// 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"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconvNew "go.opentelemetry.io/otel/semconv/v1.37.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.LastIndexByte(hostport, ']')
|
||||
if addrEnd < 0 {
|
||||
// Invalid hostport.
|
||||
return
|
||||
}
|
||||
if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 {
|
||||
host = hostport[1:addrEnd]
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if i := strings.LastIndexByte(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) //nolint:gosec // Byte size checked 16 above.
|
||||
}
|
||||
|
||||
func requiredHTTPPort(https bool, port int) int { //nolint:revive // ignore linter
|
||||
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.IndexByte(xForwardedFor, ','); idx >= 0 {
|
||||
xForwardedFor = xForwardedFor[:idx]
|
||||
}
|
||||
return xForwardedFor
|
||||
}
|
||||
|
||||
func httpRoute(pattern string) string {
|
||||
if idx := strings.IndexByte(pattern, '/'); idx >= 0 {
|
||||
return pattern[idx:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func netProtocol(proto string) (name string, version string) {
|
||||
name, version, _ = strings.Cut(proto, "/")
|
||||
switch name {
|
||||
case "HTTP":
|
||||
name = "http"
|
||||
case "QUIC":
|
||||
name = "quic"
|
||||
case "SPDY":
|
||||
name = "spdy"
|
||||
default:
|
||||
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,
|
||||
}
|
||||
|
||||
func handleErr(err error) {
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
}
|
||||
|
||||
func standardizeHTTPMethod(method string) string {
|
||||
method = strings.ToUpper(method)
|
||||
switch method {
|
||||
case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace:
|
||||
default:
|
||||
method = "_OTHER"
|
||||
}
|
||||
return method
|
||||
}
|
||||
58
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/labeler.go
generated
vendored
Normal file
58
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/labeler.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
// Labeler is used to allow instrumented HTTP handlers to add custom attributes to
|
||||
// the metrics recorded by the net/http instrumentation.
|
||||
type Labeler struct {
|
||||
mu sync.Mutex
|
||||
attributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
// Add attributes to a Labeler.
|
||||
func (l *Labeler) Add(ls ...attribute.KeyValue) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.attributes = append(l.attributes, ls...)
|
||||
}
|
||||
|
||||
// Get returns a copy of the attributes added to the Labeler.
|
||||
func (l *Labeler) Get() []attribute.KeyValue {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
ret := make([]attribute.KeyValue, len(l.attributes))
|
||||
copy(ret, l.attributes)
|
||||
return ret
|
||||
}
|
||||
|
||||
type labelerContextKeyType int
|
||||
|
||||
const labelerContextKey labelerContextKeyType = 0
|
||||
|
||||
// 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, labelerContextKey, l)
|
||||
}
|
||||
|
||||
// LabelerFromContext retrieves a Labeler instance from the provided context if
|
||||
// one is available. If no Labeler was found in the provided context a new, empty
|
||||
// Labeler is returned and the second return value is false. In this case it is
|
||||
// safe to use the Labeler but any attributes added to it will not be used.
|
||||
func LabelerFromContext(ctx context.Context) (*Labeler, bool) {
|
||||
l, ok := ctx.Value(labelerContextKey).(*Labeler)
|
||||
if !ok {
|
||||
l = &Labeler{}
|
||||
}
|
||||
return l, ok
|
||||
}
|
||||
29
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/start_time_context.go
generated
vendored
Normal file
29
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/start_time_context.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type startTimeContextKeyType int
|
||||
|
||||
const startTimeContextKey startTimeContextKeyType = 0
|
||||
|
||||
// ContextWithStartTime returns a new context with the provided start time. The
|
||||
// start time will be used for metrics and traces emitted by the
|
||||
// instrumentation. Only one labeller can be injected into the context.
|
||||
// Injecting it multiple times will override the previous calls.
|
||||
func ContextWithStartTime(parent context.Context, start time.Time) context.Context {
|
||||
return context.WithValue(parent, startTimeContextKey, start)
|
||||
}
|
||||
|
||||
// StartTimeFromContext retrieves a time.Time from the provided context if one
|
||||
// is available. If no start time was found in the provided context, a new,
|
||||
// zero start time is returned and the second return value is false.
|
||||
func StartTimeFromContext(ctx context.Context) time.Time {
|
||||
t, _ := ctx.Value(startTimeContextKey).(time.Time)
|
||||
return t
|
||||
}
|
||||
275
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go
generated
vendored
Normal file
275
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/transport.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
||||
)
|
||||
|
||||
// Transport implements the http.RoundTripper interface and wraps
|
||||
// outbound HTTP(S) requests with a span and enriches it with metrics.
|
||||
type Transport struct {
|
||||
rt http.RoundTripper
|
||||
|
||||
tracer trace.Tracer
|
||||
propagators propagation.TextMapPropagator
|
||||
spanStartOptions []trace.SpanStartOption
|
||||
filters []Filter
|
||||
spanNameFormatter func(string, *http.Request) string
|
||||
clientTrace func(context.Context) *httptrace.ClientTrace
|
||||
metricAttributesFn func(*http.Request) []attribute.KeyValue
|
||||
|
||||
semconv semconv.HTTPClient
|
||||
}
|
||||
|
||||
var _ http.RoundTripper = &Transport{}
|
||||
|
||||
// NewTransport wraps the provided http.RoundTripper with one that
|
||||
// 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.
|
||||
func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
|
||||
if base == nil {
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
|
||||
t := Transport{
|
||||
rt: base,
|
||||
}
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)),
|
||||
WithSpanNameFormatter(defaultTransportFormatter),
|
||||
}
|
||||
|
||||
c := newConfig(append(defaultOpts, opts...)...)
|
||||
t.applyConfig(c)
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *Transport) applyConfig(c *config) {
|
||||
t.tracer = c.Tracer
|
||||
t.propagators = c.Propagators
|
||||
t.spanStartOptions = c.SpanStartOptions
|
||||
t.filters = c.Filters
|
||||
t.spanNameFormatter = c.SpanNameFormatter
|
||||
t.clientTrace = c.ClientTrace
|
||||
t.semconv = semconv.NewHTTPClient(c.Meter)
|
||||
t.metricAttributesFn = c.MetricAttributesFn
|
||||
}
|
||||
|
||||
func defaultTransportFormatter(_ string, r *http.Request) string {
|
||||
return "HTTP " + r.Method
|
||||
}
|
||||
|
||||
// RoundTrip creates a Span and propagates its context via the provided request's headers
|
||||
// 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
|
||||
return t.rt.RoundTrip(r)
|
||||
}
|
||||
}
|
||||
|
||||
tracer := t.tracer
|
||||
|
||||
if tracer == nil {
|
||||
if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
|
||||
tracer = newTracer(span.TracerProvider())
|
||||
} else {
|
||||
tracer = newTracer(otel.GetTracerProvider())
|
||||
}
|
||||
}
|
||||
|
||||
opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options
|
||||
|
||||
ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
|
||||
|
||||
if t.clientTrace != nil {
|
||||
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.
|
||||
|
||||
// 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.
|
||||
bw := request.NewBodyWrapper(r.Body, func(int64) {})
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
r.Body = bw
|
||||
}
|
||||
|
||||
span.SetAttributes(t.semconv.RequestTraceAttrs(r)...)
|
||||
t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))
|
||||
|
||||
res, err := t.rt.RoundTrip(r)
|
||||
|
||||
// Defer metrics recording function to record the metrics on error or no error.
|
||||
defer func() {
|
||||
metricAttributes := semconv.MetricAttributes{
|
||||
Req: r,
|
||||
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
metricAttributes.StatusCode = res.StatusCode
|
||||
}
|
||||
|
||||
metricOpts := t.semconv.MetricOptions(metricAttributes)
|
||||
|
||||
metricData := semconv.MetricData{
|
||||
RequestSize: bw.BytesRead(),
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
readRecordFunc := func(int64) {}
|
||||
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)
|
||||
|
||||
metricData.ElapsedTime = elapsedTime
|
||||
|
||||
t.semconv.RecordMetrics(ctx, metricData, metricOpts)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
// set error type attribute if the error is part of the predefined
|
||||
// error types.
|
||||
// otherwise, record it as an exception
|
||||
if errType := t.semconv.ErrorType(err); errType.Valid() {
|
||||
span.SetAttributes(errType)
|
||||
} else {
|
||||
span.RecordError(err)
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
span.End()
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// traces
|
||||
span.SetAttributes(t.semconv.ResponseTraceAttrs(res)...)
|
||||
span.SetStatus(t.semconv.Status(res.StatusCode))
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (t *Transport) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue {
|
||||
var attributeForRequest []attribute.KeyValue
|
||||
if t.metricAttributesFn != nil {
|
||||
attributeForRequest = t.metricAttributesFn(r)
|
||||
}
|
||||
return attributeForRequest
|
||||
}
|
||||
|
||||
// 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, 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, record: record, body: body}
|
||||
}
|
||||
|
||||
// Remove the implementation of the io.ReadWriteCloser and only implement
|
||||
// the io.ReadCloser.
|
||||
return struct{ io.ReadCloser }{&wrappedBody{span: span, record: record, body: body}}
|
||||
}
|
||||
|
||||
// wrappedBody is the response body type returned by the transport
|
||||
// instrumentation to complete a span. Errors encountered when using the
|
||||
// response body are recorded in span tracking the response.
|
||||
//
|
||||
// The span tracking the response is ended when this body is closed.
|
||||
//
|
||||
// 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
|
||||
recorded atomic.Bool
|
||||
record func(n int64)
|
||||
body io.ReadCloser
|
||||
read atomic.Int64
|
||||
}
|
||||
|
||||
var _ io.ReadWriteCloser = &wrappedBody{}
|
||||
|
||||
func (wb *wrappedBody) Write(p []byte) (int, error) {
|
||||
// This will not panic given the guard in newWrappedBody.
|
||||
n, err := wb.body.(io.Writer).Write(p)
|
||||
if err != nil {
|
||||
wb.span.RecordError(err)
|
||||
wb.span.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
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)
|
||||
wb.span.SetStatus(codes.Error, err.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()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
10
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go
generated
vendored
Normal file
10
vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/version.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// 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.63.0"
|
||||
// This string is updated by the pre_release.sh script during release
|
||||
}
|
||||
Reference in New Issue
Block a user