Compare commits

..

3 Commits

Author SHA1 Message Date
Michael 'Flimmy' Flemming
a1537fd338 set auth for git push 2026-01-13 14:13:39 +01:00
Michael 'Flimmy' Flemming
1b4b99d729 add actual pr commands 2026-01-13 11:35:23 +01:00
Michael 'Flimmy' Flemming
f825e8356e add pipeline to autogenerate a docs pr for env changes 2026-01-12 16:29:23 +01:00
18 changed files with 1467 additions and 796 deletions

View File

@@ -576,7 +576,7 @@ def main(ctx):
),
)
pipelines = test_pipelines + build_release_pipelines + notifyMatrix(ctx)
pipelines = test_pipelines + build_release_pipelines + gen_docs_pr(ctx) + notifyMatrix(ctx)
pipelineSanityChecks(pipelines)
return savePipelineNumber(ctx) + pipelines
@@ -2213,6 +2213,50 @@ def makeGoGenerate(module):
},
]
def gen_docs_pr(ctx):
return [{
"name": "gen-docs-pr",
"skip_clone": True,
"workspace": {
"base": "/woodpecker",
"path": "docs_gen_pr",
},
"steps": [
{
"name": "make-docs-pr",
"image": "quay.io/opencloudeu/golang-ci",
"environment": {
"GH_TOKEN": {
"from_secret": "github_token",
},
},
"commands": [
'git config --global user.email "devops@opencloud.eu"',
'git config --global user.name "openclouders"',
"gh repo clone opencloud-eu/markdown-docs-generator /woodpecker/docs_gen_pr",
"make git-clone",
"make all",
'cd tmpdocs && git remote set-url origin $(git remote get-url origin | sed -e "s:https\\://:https\\://openclouders\\:${GH_TOKEN}@:") ; cd -',
"make prepare-docs-pullrequest",
'cd tmpdocs && git remote set-url origin $(git remote get-url origin | sed -e "s:https\\://[^@]*@:https\\://:") ; cd -',
"make create-docs-pullrequest",
]
},
],
"when": [
{
"event": "push",
"branch": "main",
"path": "services/*/pkg/config/**/*.go",
},
{
"event": "push",
"branch": "add_docs_pr_gen_pipeline",
"path": ".woodpecker.star",
},
],
}]
def notifyMatrix(ctx):
result = [{
"name": "chat-notifications",

View File

@@ -1,51 +1,5 @@
# Changelog
## [5.0.0](https://github.com/opencloud-eu/opencloud/releases/tag/v5.0.0) - 2026-01-13
### ❤️ Thanks to all contributors! ❤️
@ScharfViktor, @butonic, @dragonchaser, @fschade, @micbar, @rhafer, @saw-jan
### 💥 Breaking changes
- merge ocdav into frontend [[#1958](https://github.com/opencloud-eu/opencloud/pull/1958)]
### ✅ Tests
- ci: fix unwanted workflow skip in the cron pipelines [[#2117](https://github.com/opencloud-eu/opencloud/pull/2117)]
- [POC] ci: skip previously passed workflows on pipeline restart [[#2099](https://github.com/opencloud-eu/opencloud/pull/2099)]
- [tests-only] test: wait post-processing to finish for MKCOL requests [[#2092](https://github.com/opencloud-eu/opencloud/pull/2092)]
- [tests-only] test: fix API tests [[#2087](https://github.com/opencloud-eu/opencloud/pull/2087)]
- [full-ci] use graph api in the enforcePasswordPublicLink.feature [[#2050](https://github.com/opencloud-eu/opencloud/pull/2050)]
- [full-ci][tests-only] test: check last email content with retries as emails can be delayed [[#2038](https://github.com/opencloud-eu/opencloud/pull/2038)]
- skip collaborativePosix tests in CI [[#2039](https://github.com/opencloud-eu/opencloud/pull/2039)]
### 🐛 Bug Fixes
- fix: Show username in unprivileged search results [[#2104](https://github.com/opencloud-eu/opencloud/pull/2104)]
- fix(thumbnailer): missing font panic [[#2097](https://github.com/opencloud-eu/opencloud/pull/2097)]
- Remove sub-service binary entrypoints and fix antivirus only server cmd [[#2043](https://github.com/opencloud-eu/opencloud/pull/2043)]
- fix(thumbnailer): respect image boundaries and text wrappings [[#2062](https://github.com/opencloud-eu/opencloud/pull/2062)]
- fix: cobra viper flags and env [[#2047](https://github.com/opencloud-eu/opencloud/pull/2047)]
- fix service name in suture logs [[#2052](https://github.com/opencloud-eu/opencloud/pull/2052)]
### 📈 Enhancement
- benchmark client enhancements [[#1856](https://github.com/opencloud-eu/opencloud/pull/1856)]
- allow http2 connections to proxy [[#2040](https://github.com/opencloud-eu/opencloud/pull/2040)]
- migrate from urfave/cli to spf13/cobra [[#1954](https://github.com/opencloud-eu/opencloud/pull/1954)]
### 📦️ Dependencies
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.63.0 to 0.64.0 [[#2109](https://github.com/opencloud-eu/opencloud/pull/2109)]
- build(deps): bump github.com/kovidgoyal/imaging from 1.8.17 to 1.8.18 [[#2107](https://github.com/opencloud-eu/opencloud/pull/2107)]
- build(deps): bump google.golang.org/grpc from 1.77.0 to 1.78.0 [[#2106](https://github.com/opencloud-eu/opencloud/pull/2106)]
- build(deps): bump go.opentelemetry.io/otel/sdk from 1.38.0 to 1.39.0 [[#2069](https://github.com/opencloud-eu/opencloud/pull/2069)]
- build(deps): bump github.com/opensearch-project/opensearch-go/v4 from 4.5.0 to 4.6.0 [[#2068](https://github.com/opencloud-eu/opencloud/pull/2068)]
- build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch from 0.39.0 to 0.40.0 [[#1967](https://github.com/opencloud-eu/opencloud/pull/1967)]
- build(deps): bump golang.org/x/net from 0.47.0 to 0.48.0 [[#2061](https://github.com/opencloud-eu/opencloud/pull/2061)]
- build(deps): bump github.com/open-policy-agent/opa from 1.10.1 to 1.11.0 [[#1930](https://github.com/opencloud-eu/opencloud/pull/1930)]
## [4.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v4.1.0) - 2025-12-15
### ❤️ Thanks to all contributors! ❤️

2
go.mod
View File

@@ -95,7 +95,7 @@ require (
go-micro.dev/v4 v4.11.0
go.etcd.io/bbolt v1.4.3
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
go.opentelemetry.io/contrib/zpages v0.63.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0

4
go.sum
View File

@@ -1311,8 +1311,8 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/contrib/zpages v0.63.0 h1:TppOKuZGbqXMgsfjqq3i09N5Vbo1JLtLImUqiTPGnX4=
go.opentelemetry.io/contrib/zpages v0.63.0/go.mod h1:5F8uugz75ay/MMhRRhxAXY33FuaI8dl7jTxefrIy5qk=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=

View File

@@ -0,0 +1,28 @@
// Copyright 2018-2021 CERN
//
// 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.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package ocdav
import (
// initialize reva registries by importing the relevant loader packages
// see cmd/revad/runtime/loader.go for other loaders if a service is not found
_ "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/credential/loader"
_ "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/token/loader"
_ "github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth/tokenwriter/loader"
_ "github.com/opencloud-eu/reva/v2/pkg/token/manager/loader"
)

View File

@@ -0,0 +1,376 @@
// Copyright 2018-2021 CERN
//
// 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.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package ocdav
import (
"context"
"crypto/tls"
"time"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav"
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/config"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/storage/favorite"
"github.com/rs/zerolog"
"go-micro.dev/v4/broker"
"go.opentelemetry.io/otel/trace"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
TLSConfig *tls.Config
Broker broker.Broker
Address string
Logger zerolog.Logger
Context context.Context
// Metrics *metrics.Metrics
// Flags []cli.Flag
Name string
JWTSecret string
FavoriteManager favorite.Manager
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
TracesExporter string
TraceProvider trace.TracerProvider
MetricsEnabled bool
MetricsNamespace string
MetricsSubsystem string
// ocdav.* is internal so we need to set config options individually
config config.Config
lockSystem ocdav.LockSystem
AllowCredentials bool
AllowedOrigins []string
AllowedHeaders []string
AllowedMethods []string
AllowDepthInfinity bool
RegisterTTL time.Duration
RegisterInterval time.Duration
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// TLSConfig provides a function to set the TLSConfig option.
func TLSConfig(config *tls.Config) Option {
return func(o *Options) {
o.TLSConfig = config
}
}
// Broker provides a function to set the Broker option.
func Broker(b broker.Broker) Option {
return func(o *Options) {
o.Broker = b
}
}
// Address provides a function to set the address option.
func Address(val string) Option {
return func(o *Options) {
o.Address = val
}
}
func AllowDepthInfinity(val bool) Option {
return func(o *Options) {
o.AllowDepthInfinity = val
}
}
// JWTSecret provides a function to set the jwt secret option.
func JWTSecret(s string) Option {
return func(o *Options) {
o.JWTSecret = s
}
}
// MachineAuthAPIKey provides a function to set the machine auth api key option.
func MachineAuthAPIKey(s string) Option {
return func(o *Options) {
o.config.MachineAuthAPIKey = s
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Logger provides a function to set the logger option.
func Logger(val zerolog.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Name provides a function to set the Name option.
func Name(val string) Option {
return func(o *Options) {
o.Name = val
}
}
// Prefix provides a function to set the prefix config option.
func Prefix(val string) Option {
return func(o *Options) {
o.config.Prefix = val
}
}
// FilesNamespace provides a function to set the FilesNamespace config option.
func FilesNamespace(val string) Option {
return func(o *Options) {
o.config.FilesNamespace = val
}
}
// WebdavNamespace provides a function to set the WebdavNamespace config option.
func WebdavNamespace(val string) Option {
return func(o *Options) {
o.config.WebdavNamespace = val
}
}
// SharesNamespace provides a function to set the SharesNamespace config option.
func SharesNamespace(val string) Option {
return func(o *Options) {
o.config.SharesNamespace = val
}
}
// OCMNamespace provides a function to set the OCMNamespace config option.
func OCMNamespace(val string) Option {
return func(o *Options) {
o.config.OCMNamespace = val
}
}
// GatewaySvc provides a function to set the GatewaySvc config option.
func GatewaySvc(val string) Option {
return func(o *Options) {
o.config.GatewaySvc = val
}
}
// Timeout provides a function to set the Timeout config option.
func Timeout(val int64) Option {
return func(o *Options) {
o.config.Timeout = val
}
}
// Insecure provides a function to set the Insecure config option.
func Insecure(val bool) Option {
return func(o *Options) {
o.config.Insecure = val
}
}
// PublicURL provides a function to set the PublicURL config option.
func PublicURL(val string) Option {
return func(o *Options) {
o.config.PublicURL = val
}
}
// FavoriteManager provides a function to set the FavoriteManager option.
func FavoriteManager(val favorite.Manager) Option {
return func(o *Options) {
o.FavoriteManager = val
}
}
// GatewaySelector provides a function to set the GatewaySelector option.
func GatewaySelector(val pool.Selectable[gateway.GatewayAPIClient]) Option {
return func(o *Options) {
o.GatewaySelector = val
}
}
// LockSystem provides a function to set the LockSystem option.
func LockSystem(val ocdav.LockSystem) Option {
return func(o *Options) {
o.lockSystem = val
}
}
// WithTracesExporter option
func WithTracesExporter(exporter string) Option {
return func(o *Options) {
o.TracesExporter = exporter
}
}
// WithTracingExporter option
// Deprecated: unused
func WithTracingExporter(exporter string) Option {
return func(o *Options) {}
}
// WithTraceProvider option
func WithTraceProvider(provider trace.TracerProvider) Option {
return func(o *Options) {
o.TraceProvider = provider
}
}
// Version provides a function to set the Version config option.
func Version(val string) Option {
return func(o *Options) {
o.config.Version = val
}
}
// VersionString provides a function to set the VersionString config option.
func VersionString(val string) Option {
return func(o *Options) {
o.config.VersionString = val
}
}
// Edition provides a function to set the Edition config option.
func Edition(val string) Option {
return func(o *Options) {
o.config.Edition = val
}
}
// Product provides a function to set the Product config option.
func Product(val string) Option {
return func(o *Options) {
o.config.Product = val
}
}
// ProductName provides a function to set the ProductName config option.
func ProductName(val string) Option {
return func(o *Options) {
o.config.ProductName = val
}
}
// ProductVersion provides a function to set the ProductVersion config option.
func ProductVersion(val string) Option {
return func(o *Options) {
o.config.ProductVersion = val
}
}
// MetricsEnabled provides a function to set the MetricsEnabled config option.
func MetricsEnabled(val bool) Option {
return func(o *Options) {
o.MetricsEnabled = val
}
}
// MetricsNamespace provides a function to set the MetricsNamespace config option.
func MetricsNamespace(val string) Option {
return func(o *Options) {
o.MetricsNamespace = val
}
}
// MetricsSubsystem provides a function to set the MetricsSubsystem config option.
func MetricsSubsystem(val string) Option {
return func(o *Options) {
o.MetricsSubsystem = val
}
}
// AllowCredentials provides a function to set the AllowCredentials option.
func AllowCredentials(val bool) Option {
return func(o *Options) {
o.AllowCredentials = val
}
}
// AllowedOrigins provides a function to set the AllowedOrigins option.
func AllowedOrigins(val []string) Option {
return func(o *Options) {
o.AllowedOrigins = val
}
}
// AllowedMethods provides a function to set the AllowedMethods option.
func AllowedMethods(val []string) Option {
return func(o *Options) {
o.AllowedMethods = val
}
}
// AllowedHeaders provides a function to set the AllowedHeaders option.
func AllowedHeaders(val []string) Option {
return func(o *Options) {
o.AllowedHeaders = val
}
}
// ItemNameInvalidChars provides a function to set forbidden characters in file or folder names
func ItemNameInvalidChars(chars []string) Option {
return func(o *Options) {
o.config.NameValidation.InvalidChars = chars
}
}
// ItemNameMaxLength provides a function to set the maximum length of a file or folder name
func ItemNameMaxLength(i int) Option {
return func(o *Options) {
o.config.NameValidation.MaxLength = i
}
}
// RegisterTTL provides a function to set the RegisterTTL option.
func RegisterTTL(ttl time.Duration) Option {
return func(o *Options) {
o.RegisterTTL = ttl
}
}
// RegisterInterval provides a function to set the RegisterInterval option.
func RegisterInterval(interval time.Duration) Option {
return func(o *Options) {
o.RegisterInterval = interval
}
}
// URLSigningSharedSecret provides a function to set the URLSigningSharedSecret config option.
func URLSigningSharedSecret(secret string) Option {
return func(o *Options) {
o.config.URLSigningSharedSecret = secret
}
}

View File

@@ -0,0 +1,229 @@
// Copyright 2018-2021 CERN
//
// 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.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package ocdav
import (
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
httpServer "github.com/go-micro/plugins/v4/server/http"
"github.com/opencloud-eu/opencloud/pkg/registry"
"github.com/opencloud-eu/reva/v2/internal/http/interceptors/appctx"
"github.com/opencloud-eu/reva/v2/internal/http/interceptors/auth"
cors2 "github.com/opencloud-eu/reva/v2/internal/http/interceptors/cors"
revaLogMiddleware "github.com/opencloud-eu/reva/v2/internal/http/interceptors/log"
"github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/rhttp/global"
"github.com/opencloud-eu/reva/v2/pkg/storage/favorite/memory"
rtrace "github.com/opencloud-eu/reva/v2/pkg/trace"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"go-micro.dev/v4"
"go-micro.dev/v4/server"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
func init() {
// register method with chi before any routing is set up
chi.RegisterMethod(ocdav.MethodPropfind)
chi.RegisterMethod(ocdav.MethodProppatch)
chi.RegisterMethod(ocdav.MethodLock)
chi.RegisterMethod(ocdav.MethodUnlock)
chi.RegisterMethod(ocdav.MethodCopy)
chi.RegisterMethod(ocdav.MethodMove)
chi.RegisterMethod(ocdav.MethodMkcol)
chi.RegisterMethod(ocdav.MethodReport)
}
const (
// ServerName to use when announcing the service to the registry
ServerName = "ocdav"
)
// Service initializes the ocdav service and underlying http server.
func Service(opts ...Option) (micro.Service, error) {
sopts := newOptions(opts...)
// set defaults
if err := setDefaults(&sopts); err != nil {
return nil, err
}
sopts.Logger = sopts.Logger.With().Str("name", sopts.Name).Logger()
srv := httpServer.NewServer(
server.Broker(sopts.Broker),
server.TLSConfig(sopts.TLSConfig),
server.Name(sopts.Name),
server.Address(sopts.Address), // Address defaults to ":0" and will pick any free port
server.Version(sopts.config.VersionString),
server.RegisterTTL(sopts.RegisterTTL),
server.RegisterInterval(sopts.RegisterInterval),
)
revaService, err := ocdav.NewWith(&sopts.config, sopts.FavoriteManager, sopts.lockSystem, &sopts.Logger, sopts.GatewaySelector)
if err != nil {
return nil, err
}
r := chi.NewRouter()
tp := sopts.TraceProvider
if tp == nil {
tp = rtrace.NewTracerProvider(sopts.Name, sopts.TracesExporter)
}
if err := useMiddlewares(r, &sopts, revaService, tp); err != nil {
return nil, err
}
r.Handle("/*", revaService.Handler())
_ = chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
sopts.Logger.Debug().Str("service", "ocdav").Str("method", method).Str("route", route).Int("middlewares", len(middlewares)).Msg("serving endpoint")
return nil
})
hd := srv.NewHandler(r)
if err := srv.Handle(hd); err != nil {
return nil, err
}
service := micro.NewService(
micro.Server(srv),
micro.Registry(registry.GetRegistry()),
)
// finally, return the service so it can be Run() by the caller himself
return service, nil
}
func setDefaults(sopts *Options) error {
// set defaults
if sopts.Name == "" {
sopts.Name = ServerName
}
if sopts.lockSystem == nil {
selector, err := pool.GatewaySelector(sopts.config.GatewaySvc)
if err != nil {
return errors.Wrap(err, "error getting gateway selector")
}
sopts.lockSystem = ocdav.NewCS3LS(selector)
}
if sopts.FavoriteManager == nil {
sopts.FavoriteManager, _ = memory.New(map[string]interface{}{})
}
if !strings.HasPrefix(sopts.config.Prefix, "/") {
sopts.config.Prefix = "/" + sopts.config.Prefix
}
if sopts.config.VersionString == "" {
sopts.config.VersionString = "0.0.0"
}
sopts.config.AllowPropfindDepthInfinitiy = sopts.AllowDepthInfinity
return nil
}
func useMiddlewares(r *chi.Mux, sopts *Options, svc global.Service, tp trace.TracerProvider) error {
// auth
for _, v := range svc.Unprotected() {
sopts.Logger.Info().Str("url", v).Msg("unprotected URL")
}
authMiddle, err := auth.New(map[string]interface{}{
"gatewaysvc": sopts.config.GatewaySvc,
"token_managers": map[string]interface{}{
"jwt": map[string]interface{}{
"secret": sopts.JWTSecret,
},
},
}, svc.Unprotected(), tp)
if err != nil {
return err
}
// log
lm := revaLogMiddleware.New()
cors, _, err := cors2.New(map[string]interface{}{
"allow_credentials": sopts.AllowCredentials,
"allowed_methods": sopts.AllowedMethods,
"allowed_headers": sopts.AllowedHeaders,
"allowed_origins": sopts.AllowedOrigins,
})
if err != nil {
return err
}
// tracing
tm := traceHandler(tp, "ocdav")
// metrics
pm := func(h http.Handler) http.Handler { return h }
if sopts.MetricsEnabled {
namespace := sopts.MetricsNamespace
if namespace == "" {
namespace = "reva"
}
subsystem := sopts.MetricsSubsystem
if subsystem == "" {
subsystem = "ocdav"
}
counter := promauto.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "http_requests_total",
Help: "The total number of processed " + subsystem + " HTTP requests for " + namespace,
})
pm = func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
counter.Inc()
})
}
}
// ctx
cm := appctx.New(sopts.Logger, tp)
// request-id
rm := middleware.RequestID
// actually register
r.Use(pm, tm, lm, authMiddle, rm, cm, cors)
return nil
}
func traceHandler(tp trace.TracerProvider, name string) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := rtrace.Propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
t := tp.Tracer("reva")
ctx, span := t.Start(ctx, name)
defer span.End()
rtrace.Propagator.Inject(ctx, propagation.HeaderCarrier(r.Header))
h.ServeHTTP(w, r.WithContext(ctx))
})
}
}

View File

@@ -14,15 +14,9 @@ import (
// 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.
//
// Deprecated: [DefaultClient] will be removed in a future release.
// Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport
var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)}
// Get is a convenient replacement for http.Get that adds a span around the request.
//
// Deprecated: [Get] will be removed in a future release.
// Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport
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 {
@@ -32,9 +26,6 @@ func Get(ctx context.Context, targetURL string) (resp *http.Response, err error)
}
// Head is a convenient replacement for http.Head that adds a span around the request.
//
// Deprecated: [Head] will be removed in a future release.
// Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport
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 {
@@ -44,9 +35,6 @@ func Head(ctx context.Context, targetURL string) (resp *http.Response, err error
}
// Post is a convenient replacement for http.Post that adds a span around the request.
//
// Deprecated: [Post] will be removed in a future release.
// Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport
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 {
@@ -57,9 +45,6 @@ func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (r
}
// PostForm is a convenient replacement for http.PostForm that adds a span around the request.
//
// Deprecated: [PostForm] will be removed in a future release.
// Create your own [http.Client] based on the [Transport] example: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp#example-NewTransport
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()))
}

View File

@@ -26,6 +26,7 @@ type config struct {
Meter metric.Meter
Propagators propagation.TextMapPropagator
SpanStartOptions []trace.SpanStartOption
PublicEndpoint bool
PublicEndpointFn func(*http.Request) bool
ReadEvent bool
WriteEvent bool
@@ -95,19 +96,17 @@ func WithMeterProvider(provider metric.MeterProvider) Option {
// 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.
//
// Deprecated: Use [WithPublicEndpointFn] instead.
// To migrate, replace WithPublicEndpoint() with:
//
// WithPublicEndpointFn(func(*http.Request) bool { return true })
func WithPublicEndpoint() Option {
return WithPublicEndpointFn(func(*http.Request) bool { return true })
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
@@ -144,13 +143,11 @@ func WithFilter(f Filter) Option {
})
}
// Event represents message event types for [WithMessageEvents].
type Event int
type event int
// Different types of events that can be recorded, see WithMessageEvents.
const (
unspecifiedEvents Event = iota
ReadEvents
ReadEvents event = iota
WriteEvents
)
@@ -163,7 +160,7 @@ const (
// using the ReadBytesKey
// - WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
// using the WriteBytesKey
func WithMessageEvents(events ...Event) Option {
func WithMessageEvents(events ...event) Option {
return optionFunc(func(c *config) {
for _, e := range events {
switch e {

View File

@@ -2,5 +2,6 @@
// 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.
// 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"

View File

@@ -29,6 +29,7 @@ type middleware struct {
writeEvent bool
filters []Filter
spanNameFormatter func(string, *http.Request) string
publicEndpoint bool
publicEndpointFn func(*http.Request) bool
metricAttributesFn func(*http.Request) []attribute.KeyValue
@@ -76,6 +77,7 @@ func (h *middleware) configure(c *config) {
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)
@@ -100,7 +102,7 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
}
opts = append(opts, h.spanStartOptions...)
if h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx)) {
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() {
@@ -222,9 +224,6 @@ func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.Ke
// WithRouteTag annotates spans and metrics with the provided route name
// with HTTP route attribute.
//
// Deprecated: spans are automatically annotated with the route attribute.
// To annotate metrics, use the [WithMetricAttributesFn] option.
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) {

View File

@@ -1,305 +0,0 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/client.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 (
"context"
"fmt"
"net/http"
"reflect"
"slices"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/httpconv"
)
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
}
func (n 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, ""
}
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
func (n HTTPClient) 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, semconv.URLFull(u))
attrs = append(attrs, semconv.ServerAddress(requestHost))
if eligiblePort > 0 {
attrs = append(attrs, semconv.ServerPort(eligiblePort))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
}
return attrs
}
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
func (n HTTPClient) 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, semconv.HTTPResponseStatusCode(resp.StatusCode))
}
if isErrorStatusCode(resp.StatusCode) {
errorType := strconv.Itoa(resp.StatusCode)
attrs = append(attrs, semconv.ErrorTypeKey.String(errorType))
}
return attrs
}
func (n HTTPClient) 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 semconv.ErrorTypeOther
}
return semconv.ErrorTypeKey.String(value)
}
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconv.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
}
func (n HTTPClient) 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,
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
semconv.ServerAddress(requestHost),
n.scheme(req),
)
if port > 0 {
attributes = append(attributes, semconv.ServerPort(port))
}
if protoName != "" {
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
}
return attributes
}
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 (n HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts {
opts := map[string]MetricOpts{}
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
opts["new"] = MetricOpts{
measurement: set,
addOptions: set,
}
return opts
}
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) {
n.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption())
n.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption())
}
// TraceAttributes returns attributes for httptrace.
func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return []attribute.KeyValue{
semconv.ServerAddress(host),
}
}
func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue {
if req.URL != nil && req.URL.Scheme != "" {
return semconv.URLScheme(req.URL.Scheme)
}
if req.TLS != nil {
return semconv.URLScheme("https")
}
return semconv.URLScheme("http")
}
func isErrorStatusCode(code int) bool {
return code >= 400 || code < 100
}

View 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)
}

View File

@@ -6,10 +6,10 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/
// 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/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_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

View 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
}

View File

@@ -1,403 +0,0 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/server.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 (
"context"
"fmt"
"net/http"
"slices"
"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"
"go.opentelemetry.io/otel/semconv/v1.37.0/httpconv"
)
type RequestTraceAttrsOpts struct {
// If set, this is used as value for the "http.client_ip" attribute.
HTTPClientIP string
}
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
}
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
}
// 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 (n 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, ""
}
// 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 HTTPServer) 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,
semconv.ServerAddress(host),
method,
scheme,
)
if hostPort > 0 {
attrs = append(attrs, semconv.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, semconv.NetworkPeerAddress(peer))
if peerPort > 0 {
attrs = append(attrs, semconv.NetworkPeerPort(peerPort))
}
}
if useragent != "" {
attrs = append(attrs, semconv.UserAgentOriginal(useragent))
}
if clientIP != "" {
attrs = append(attrs, semconv.ClientAddress(clientIP))
}
if req.URL != nil && req.URL.Path != "" {
attrs = append(attrs, semconv.URLPath(req.URL.Path))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
}
if route != "" {
attrs = append(attrs, n.Route(route))
}
return attrs
}
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
attr := semconv.NetworkTransportPipe
switch network {
case "tcp", "tcp4", "tcp6":
attr = semconv.NetworkTransportTCP
case "udp", "udp4", "udp6":
attr = semconv.NetworkTransportUDP
case "unix", "unixgram", "unixpacket":
attr = semconv.NetworkTransportUnix
}
return []attribute.KeyValue{attr}
}
type ServerMetricData struct {
ServerName string
ResponseSize int64
MetricData
MetricAttributes
}
type MetricAttributes struct {
Req *http.Request
StatusCode int
Route string
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 (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
*recordOpts = append(*recordOpts, o)
n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o)
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconv.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
}
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
if https {
return semconv.URLScheme("https")
}
return semconv.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 HTTPServer) 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,
semconv.HTTPRequestBodySize(int(resp.ReadBytes)),
)
}
if resp.WriteBytes > 0 {
attributes = append(attributes,
semconv.HTTPResponseBodySize(int(resp.WriteBytes)),
)
}
if resp.StatusCode > 0 {
attributes = append(attributes,
semconv.HTTPResponseStatusCode(resp.StatusCode),
)
}
return attributes
}
// Route returns the attribute for the route.
func (n HTTPServer) Route(route string) attribute.KeyValue {
return semconv.HTTPRoute(route)
}
func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, 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++
}
if route != "" {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
n.scheme(req.TLS != nil),
semconv.ServerAddress(host))
if hostPort > 0 {
attributes = append(attributes, semconv.ServerPort(hostPort))
}
if protoName != "" {
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
}
if route != "" {
attributes = append(attributes, semconv.HTTPRoute(route))
}
return attributes
}

View File

@@ -5,6 +5,6 @@ package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http
// Version is the current release version of the otelhttp instrumentation.
func Version() string {
return "0.64.0"
return "0.63.0"
// This string is updated by the pre_release.sh script during release
}

5
vendor/modules.txt vendored
View File

@@ -1556,6 +1556,7 @@ github.com/opencloud-eu/reva/v2/pkg/metrics/driver/loader
github.com/opencloud-eu/reva/v2/pkg/metrics/driver/registry
github.com/opencloud-eu/reva/v2/pkg/metrics/driver/xcloud
github.com/opencloud-eu/reva/v2/pkg/metrics/reader
github.com/opencloud-eu/reva/v2/pkg/micro/ocdav
github.com/opencloud-eu/reva/v2/pkg/mime
github.com/opencloud-eu/reva/v2/pkg/ocm/client
github.com/opencloud-eu/reva/v2/pkg/ocm/invite
@@ -2328,8 +2329,8 @@ go.opentelemetry.io/auto/sdk/internal/telemetry
## explicit; go 1.24.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal
# go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
## explicit; go 1.24.0
# go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
## explicit; go 1.23.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv