From 3870c4033022903d1fc663e49639af7469e4308a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:32:47 -0800 Subject: [PATCH 01/12] fix(deps): update googleapis to e6fa225 (#6413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [google.golang.org/genproto/googleapis/api](https://redirect.github.com/googleapis/go-genproto) | require | digest | `a4fef06` -> `e6fa225` | | [google.golang.org/genproto/googleapis/api](https://redirect.github.com/googleapis/go-genproto) | indirect | digest | `a4fef06` -> `e6fa225` | | [google.golang.org/genproto/googleapis/rpc](https://redirect.github.com/googleapis/go-genproto) | indirect | digest | `a4fef06` -> `e6fa225` | --- ### Configuration ๐Ÿ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). ๐Ÿšฆ **Automerge**: Disabled by config. Please merge this manually once you are satisfied. โ™ป **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. ๐Ÿ”• **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/open-telemetry/opentelemetry-go-contrib). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- config/go.mod | 4 ++-- config/go.sum | 8 ++++---- examples/otel-collector/go.mod | 4 ++-- examples/otel-collector/go.sum | 8 ++++---- exporters/autoexport/go.mod | 4 ++-- exporters/autoexport/go.sum | 8 ++++---- .../aws/aws-lambda-go/otellambda/xrayconfig/go.mod | 4 ++-- .../aws/aws-lambda-go/otellambda/xrayconfig/go.sum | 8 ++++---- .../google.golang.org/grpc/otelgrpc/example/go.mod | 2 +- .../google.golang.org/grpc/otelgrpc/example/go.sum | 4 ++-- instrumentation/google.golang.org/grpc/otelgrpc/go.mod | 2 +- instrumentation/google.golang.org/grpc/otelgrpc/go.sum | 4 ++-- .../google.golang.org/grpc/otelgrpc/test/go.mod | 2 +- .../google.golang.org/grpc/otelgrpc/test/go.sum | 4 ++-- propagators/opencensus/examples/go.mod | 2 +- propagators/opencensus/examples/go.sum | 4 ++-- samplers/jaegerremote/example/go.mod | 2 +- samplers/jaegerremote/example/go.sum | 4 ++-- samplers/jaegerremote/go.mod | 2 +- samplers/jaegerremote/go.sum | 4 ++-- 20 files changed, 42 insertions(+), 42 deletions(-) diff --git a/config/go.mod b/config/go.mod index 55a354476f7..1412a25e94c 100644 --- a/config/go.mod +++ b/config/go.mod @@ -45,8 +45,8 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/grpc v1.68.1 // indirect google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/config/go.sum b/config/go.sum index f95cac28469..7dd0251afb9 100644 --- a/config/go.sum +++ b/config/go.sum @@ -89,10 +89,10 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/examples/otel-collector/go.mod b/examples/otel-collector/go.mod index ae801632c7f..988e31163bc 100644 --- a/examples/otel-collector/go.mod +++ b/examples/otel-collector/go.mod @@ -24,7 +24,7 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/examples/otel-collector/go.sum b/examples/otel-collector/go.sum index 49c253a074c..8cb2d14af93 100644 --- a/examples/otel-collector/go.sum +++ b/examples/otel-collector/go.sum @@ -45,10 +45,10 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/exporters/autoexport/go.mod b/exporters/autoexport/go.mod index e2c7117c910..0e4ac8497be 100644 --- a/exporters/autoexport/go.mod +++ b/exporters/autoexport/go.mod @@ -47,8 +47,8 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/grpc v1.68.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/exporters/autoexport/go.sum b/exporters/autoexport/go.sum index f95cac28469..7dd0251afb9 100644 --- a/exporters/autoexport/go.sum +++ b/exporters/autoexport/go.sum @@ -89,10 +89,10 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.mod b/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.mod index d0b469055e3..7d5cad63a0a 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.mod +++ b/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.mod @@ -35,8 +35,8 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.sum b/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.sum index 98c424a74c1..8d26b97eeca 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.sum +++ b/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig/go.sum @@ -49,10 +49,10 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/example/go.mod b/instrumentation/google.golang.org/grpc/otelgrpc/example/go.mod index 2caf15df32f..18ff0f97c6f 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/example/go.mod +++ b/instrumentation/google.golang.org/grpc/otelgrpc/example/go.mod @@ -22,6 +22,6 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/example/go.sum b/instrumentation/google.golang.org/grpc/otelgrpc/example/go.sum index cd3a75a9236..3387a4fc9dd 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/example/go.sum +++ b/instrumentation/google.golang.org/grpc/otelgrpc/example/go.sum @@ -31,8 +31,8 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/go.mod b/instrumentation/google.golang.org/grpc/otelgrpc/go.mod index 27b8ad4856a..2ab99160ab7 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/go.mod +++ b/instrumentation/google.golang.org/grpc/otelgrpc/go.mod @@ -19,6 +19,6 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/go.sum b/instrumentation/google.golang.org/grpc/otelgrpc/go.sum index 9e026240408..43d32f47d6a 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/go.sum +++ b/instrumentation/google.golang.org/grpc/otelgrpc/go.sum @@ -25,8 +25,8 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/test/go.mod b/instrumentation/google.golang.org/grpc/otelgrpc/test/go.mod index 70468ed32f9..cce74de9f49 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/test/go.mod +++ b/instrumentation/google.golang.org/grpc/otelgrpc/test/go.mod @@ -24,7 +24,7 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/test/go.sum b/instrumentation/google.golang.org/grpc/otelgrpc/test/go.sum index 45d1ef1c13b..d4da438db81 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/test/go.sum +++ b/instrumentation/google.golang.org/grpc/otelgrpc/test/go.sum @@ -38,8 +38,8 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/propagators/opencensus/examples/go.mod b/propagators/opencensus/examples/go.mod index fe4dc3229b8..9069acda8c8 100644 --- a/propagators/opencensus/examples/go.mod +++ b/propagators/opencensus/examples/go.mod @@ -25,7 +25,7 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/propagators/opencensus/examples/go.sum b/propagators/opencensus/examples/go.sum index 02318dc7331..c20ddf15547 100644 --- a/propagators/opencensus/examples/go.sum +++ b/propagators/opencensus/examples/go.sum @@ -108,8 +108,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/samplers/jaegerremote/example/go.mod b/samplers/jaegerremote/example/go.mod index 5873dd34ad8..8bffe53246c 100644 --- a/samplers/jaegerremote/example/go.mod +++ b/samplers/jaegerremote/example/go.mod @@ -18,7 +18,7 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/sys v0.28.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/protobuf v1.35.2 // indirect ) diff --git a/samplers/jaegerremote/example/go.sum b/samplers/jaegerremote/example/go.sum index dc4d5f9bce5..eec8b318455 100644 --- a/samplers/jaegerremote/example/go.sum +++ b/samplers/jaegerremote/example/go.sum @@ -56,8 +56,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/samplers/jaegerremote/go.mod b/samplers/jaegerremote/go.mod index e536ec6faa6..7376b90a7b5 100644 --- a/samplers/jaegerremote/go.mod +++ b/samplers/jaegerremote/go.mod @@ -8,7 +8,7 @@ require ( github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel/sdk v1.32.0 go.opentelemetry.io/otel/trace v1.32.0 - google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 ) require ( diff --git a/samplers/jaegerremote/go.sum b/samplers/jaegerremote/go.sum index eef04af9c80..8a3d3f46d22 100644 --- a/samplers/jaegerremote/go.sum +++ b/samplers/jaegerremote/go.sum @@ -54,8 +54,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY= -google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 812fa9a9b5cab0b4628b1af46a78b5ca03b76a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Tue, 10 Dec 2024 11:22:29 +0100 Subject: [PATCH 02/12] [chore] Fix versions.yaml (#6414) Address https://github.com/open-telemetry/opentelemetry-go-contrib/pull/6386/files#r1866059073 Order modules in `experimental-bridge` --- versions.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/versions.yaml b/versions.yaml index c62e6e0fa37..c27222655e6 100644 --- a/versions.yaml +++ b/versions.yaml @@ -71,10 +71,10 @@ module-sets: experimental-bridge: version: v0.7.0 modules: - - go.opentelemetry.io/contrib/bridges/otelslog + - go.opentelemetry.io/contrib/bridges/otellogr - go.opentelemetry.io/contrib/bridges/otellogrus + - go.opentelemetry.io/contrib/bridges/otelslog - go.opentelemetry.io/contrib/bridges/otelzap - - go.opentelemetry.io/contrib/bridges/otellogr experimental-processors: version: v0.5.0 modules: @@ -85,7 +85,6 @@ module-sets: modules: - go.opentelemetry.io/contrib/detectors/azure/azurevm excluded-modules: - - go.opentelemetry.io/contrib/bridges/otellogr - go.opentelemetry.io/contrib/examples/dice - go.opentelemetry.io/contrib/examples/namedtracer - go.opentelemetry.io/contrib/examples/opencensus From cee0910a9664967b8f56f4a1a520fb5b49c0e0db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:36:19 +0100 Subject: [PATCH 03/12] chore(deps): update k8s.io/utils digest to 24370be (#6416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [k8s.io/utils](https://redirect.github.com/kubernetes/utils) | indirect | digest | `6fe5fd8` -> `24370be` | --- ### Configuration ๐Ÿ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). ๐Ÿšฆ **Automerge**: Disabled by config. Please merge this manually once you are satisfied. โ™ป **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. ๐Ÿ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/open-telemetry/opentelemetry-go-contrib). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- detectors/aws/eks/go.mod | 2 +- detectors/aws/eks/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/detectors/aws/eks/go.mod b/detectors/aws/eks/go.mod index 176f84b381f..9c623c35a79 100644 --- a/detectors/aws/eks/go.mod +++ b/detectors/aws/eks/go.mod @@ -48,7 +48,7 @@ require ( k8s.io/api v0.31.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f // indirect - k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect sigs.k8s.io/json v0.0.0-20241009153224-e386a8af8d30 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/detectors/aws/eks/go.sum b/detectors/aws/eks/go.sum index 74b3a5c2246..6b2d40a1d42 100644 --- a/detectors/aws/eks/go.sum +++ b/detectors/aws/eks/go.sum @@ -141,8 +141,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f h1:nLHvOvs1CZ+FAEwR4EqLeRLfbtWQNlIu5g393Hq/1UM= k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f/go.mod h1:iZjdMQzunI7O/sUrf/5WRX1gvaAIam32lKx9+paoLbU= -k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= -k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20241009153224-e386a8af8d30 h1:ObU1vgTtAle8WwCKgcDkPjLJYwlazQpIjzSA0asMhy4= sigs.k8s.io/json v0.0.0-20241009153224-e386a8af8d30/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.3 h1:sCP7Vv3xx/CWIuTPVN38lUPx0uw0lcLfzaiDa8Ja01A= From 3af31bd845f7328cacd1f5d2b76a6913687df73d Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:23:29 -0800 Subject: [PATCH 04/12] config: separate schema version support into separate directories (#6412) This makes the migration from one version to another explicit in any downstream dependencies. This will make dealing with breaking changes much easier in the Collector as it will allow us to have end users toggle a feature gate to enable support for new schema versions. Doing this today is not possible as we cannot import both a v0.2.0 and a v0.3.0 compatible config package. With the change in this PR, this will be possible. v0.2.0 directory was a copy based on 355fbbdb83be04e56837c8f4d583dae8bcc137bb --------- Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 1 + Makefile | 3 +- config/doc.go | 9 +- config/v0.2.0/config.go | 152 ++++ config/v0.2.0/config_test.go | 383 ++++++++ config/v0.2.0/generated_config.go | 780 ++++++++++++++++ config/v0.2.0/log.go | 155 ++++ config/v0.2.0/log_test.go | 412 +++++++++ config/v0.2.0/metric.go | 496 ++++++++++ config/v0.2.0/metric_test.go | 1111 +++++++++++++++++++++++ config/v0.2.0/resource.go | 63 ++ config/v0.2.0/resource_test.go | 116 +++ config/v0.2.0/trace.go | 197 ++++ config/v0.2.0/trace_test.go | 535 +++++++++++ config/{ => v0.3.0}/config.go | 2 +- config/{ => v0.3.0}/config_json.go | 2 +- config/{ => v0.3.0}/config_test.go | 4 +- config/{ => v0.3.0}/config_yaml.go | 2 +- config/{ => v0.3.0}/generated_config.go | 0 config/{ => v0.3.0}/log.go | 2 +- config/{ => v0.3.0}/log_test.go | 2 +- config/{ => v0.3.0}/metric.go | 2 +- config/{ => v0.3.0}/metric_test.go | 2 +- config/{ => v0.3.0}/resource.go | 2 +- config/{ => v0.3.0}/resource_test.go | 2 +- config/{ => v0.3.0}/trace.go | 2 +- config/{ => v0.3.0}/trace_test.go | 0 27 files changed, 4421 insertions(+), 16 deletions(-) create mode 100644 config/v0.2.0/config.go create mode 100644 config/v0.2.0/config_test.go create mode 100644 config/v0.2.0/generated_config.go create mode 100644 config/v0.2.0/log.go create mode 100644 config/v0.2.0/log_test.go create mode 100644 config/v0.2.0/metric.go create mode 100644 config/v0.2.0/metric_test.go create mode 100644 config/v0.2.0/resource.go create mode 100644 config/v0.2.0/resource_test.go create mode 100644 config/v0.2.0/trace.go create mode 100644 config/v0.2.0/trace_test.go rename config/{ => v0.3.0}/config.go (98%) rename config/{ => v0.3.0}/config_json.go (99%) rename config/{ => v0.3.0}/config_test.go (99%) rename config/{ => v0.3.0}/config_yaml.go (93%) rename config/{ => v0.3.0}/generated_config.go (100%) rename config/{ => v0.3.0}/log.go (98%) rename config/{ => v0.3.0}/log_test.go (99%) rename config/{ => v0.3.0}/metric.go (99%) rename config/{ => v0.3.0}/metric_test.go (99%) rename config/{ => v0.3.0}/resource.go (95%) rename config/{ => v0.3.0}/resource_test.go (97%) rename config/{ => v0.3.0}/trace.go (98%) rename config/{ => v0.3.0}/trace_test.go (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e61f0af8f6b..552756be8d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Change the span name to be `GET /path` so it complies with the OTel HTTP semantic conventions in `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho`. (#6365) - Record errors instead of setting the `gin.errors` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6346) +- The `go.opentelemetry.io/contrib/config` now supports multiple schemas in subdirectories (ie. `go.opentelemetry.io/contrib/config/v0.3.0`) for easier migration. (#6412) ### Fixed diff --git a/Makefile b/Makefile index 8b9f9ead06d..73d2e165b44 100644 --- a/Makefile +++ b/Makefile @@ -325,11 +325,12 @@ OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION=v0.3.0 genjsonschema-cleanup: rm -Rf ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} -GENERATED_CONFIG=./config/generated_config.go +GENERATED_CONFIG=./config/${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION}/generated_config.go # Generate structs for configuration from opentelemetry-configuration schema genjsonschema: genjsonschema-cleanup $(GOJSONSCHEMA) mkdir -p ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} + mkdir -p ./config/${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION} curl -sSL https://api.github.com/repos/open-telemetry/opentelemetry-configuration/tarball/${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION} | tar xz --strip 1 -C ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} $(GOJSONSCHEMA) \ --capitalization ID \ diff --git a/config/doc.go b/config/doc.go index 293b43abb25..ffda72986b6 100644 --- a/config/doc.go +++ b/config/doc.go @@ -1,7 +1,10 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -// Package config can be used to parse a configuration file -// that follows the JSON Schema defined by the OpenTelemetry -// Configuration schema. +// Package config can be used to parse a configuration file that follows +// the JSON Schema defined by the OpenTelemetry Configuration schema. Different +// versions of the schema are supported by the code in the directory that +// matches the version number of the schema. For example, the import +// go.opentelemetry.io/contrib/config/v0.3.0 includes code that supports the +// v0.3.0 release of the configuration schema. package config // import "go.opentelemetry.io/contrib/config" diff --git a/config/v0.2.0/config.go b/config/v0.2.0/config.go new file mode 100644 index 00000000000..de1bd8a9b0c --- /dev/null +++ b/config/v0.2.0/config.go @@ -0,0 +1,152 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/contrib/config/v0.2.0" + +import ( + "context" + "errors" + + "gopkg.in/yaml.v3" + + "go.opentelemetry.io/otel/log" + nooplog "go.opentelemetry.io/otel/log/noop" + "go.opentelemetry.io/otel/metric" + noopmetric "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/trace" + nooptrace "go.opentelemetry.io/otel/trace/noop" +) + +const ( + protocolProtobufHTTP = "http/protobuf" + protocolProtobufGRPC = "grpc/protobuf" + + compressionGzip = "gzip" + compressionNone = "none" +) + +type configOptions struct { + ctx context.Context + opentelemetryConfig OpenTelemetryConfiguration +} + +type shutdownFunc func(context.Context) error + +func noopShutdown(context.Context) error { + return nil +} + +// SDK is a struct that contains all the providers +// configured via the configuration model. +type SDK struct { + meterProvider metric.MeterProvider + tracerProvider trace.TracerProvider + loggerProvider log.LoggerProvider + shutdown shutdownFunc +} + +// TracerProvider returns a configured trace.TracerProvider. +func (s *SDK) TracerProvider() trace.TracerProvider { + return s.tracerProvider +} + +// MeterProvider returns a configured metric.MeterProvider. +func (s *SDK) MeterProvider() metric.MeterProvider { + return s.meterProvider +} + +// LoggerProvider returns a configured log.LoggerProvider. +func (s *SDK) LoggerProvider() log.LoggerProvider { + return s.loggerProvider +} + +// Shutdown calls shutdown on all configured providers. +func (s *SDK) Shutdown(ctx context.Context) error { + return s.shutdown(ctx) +} + +var noopSDK = SDK{ + loggerProvider: nooplog.LoggerProvider{}, + meterProvider: noopmetric.MeterProvider{}, + tracerProvider: nooptrace.TracerProvider{}, + shutdown: func(ctx context.Context) error { return nil }, +} + +// NewSDK creates SDK providers based on the configuration model. +func NewSDK(opts ...ConfigurationOption) (SDK, error) { + o := configOptions{} + for _, opt := range opts { + o = opt.apply(o) + } + if o.opentelemetryConfig.Disabled != nil && *o.opentelemetryConfig.Disabled { + return noopSDK, nil + } + + r, err := newResource(o.opentelemetryConfig.Resource) + if err != nil { + return noopSDK, err + } + + mp, mpShutdown, err := meterProvider(o, r) + if err != nil { + return noopSDK, err + } + + tp, tpShutdown, err := tracerProvider(o, r) + if err != nil { + return noopSDK, err + } + + lp, lpShutdown, err := loggerProvider(o, r) + if err != nil { + return noopSDK, err + } + + return SDK{ + meterProvider: mp, + tracerProvider: tp, + loggerProvider: lp, + shutdown: func(ctx context.Context) error { + return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) + }, + }, nil +} + +// ConfigurationOption configures options for providers. +type ConfigurationOption interface { + apply(configOptions) configOptions +} + +type configurationOptionFunc func(configOptions) configOptions + +func (fn configurationOptionFunc) apply(cfg configOptions) configOptions { + return fn(cfg) +} + +// WithContext sets the context.Context for the SDK. +func WithContext(ctx context.Context) ConfigurationOption { + return configurationOptionFunc(func(c configOptions) configOptions { + c.ctx = ctx + return c + }) +} + +// WithOpenTelemetryConfiguration sets the OpenTelemetryConfiguration used +// to produce the SDK. +func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) ConfigurationOption { + return configurationOptionFunc(func(c configOptions) configOptions { + c.opentelemetryConfig = cfg + return c + }) +} + +// ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration. +func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { + var cfg OpenTelemetryConfiguration + err := yaml.Unmarshal(file, &cfg) + if err != nil { + return nil, err + } + + return &cfg, nil +} diff --git a/config/v0.2.0/config_test.go b/config/v0.2.0/config_test.go new file mode 100644 index 00000000000..653c6e978fd --- /dev/null +++ b/config/v0.2.0/config_test.go @@ -0,0 +1,383 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "context" + "encoding/json" + "errors" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + lognoop "go.opentelemetry.io/otel/log/noop" + metricnoop "go.opentelemetry.io/otel/metric/noop" + sdklog "go.opentelemetry.io/otel/sdk/log" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + tracenoop "go.opentelemetry.io/otel/trace/noop" +) + +func TestNewSDK(t *testing.T) { + tests := []struct { + name string + cfg []ConfigurationOption + wantTracerProvider any + wantMeterProvider any + wantLoggerProvider any + wantErr error + wantShutdownErr error + }{ + { + name: "no-configuration", + wantTracerProvider: tracenoop.NewTracerProvider(), + wantMeterProvider: metricnoop.NewMeterProvider(), + wantLoggerProvider: lognoop.NewLoggerProvider(), + }, + { + name: "with-configuration", + cfg: []ConfigurationOption{ + WithContext(context.Background()), + WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ + TracerProvider: &TracerProvider{}, + MeterProvider: &MeterProvider{}, + LoggerProvider: &LoggerProvider{}, + }), + }, + wantTracerProvider: &sdktrace.TracerProvider{}, + wantMeterProvider: &sdkmetric.MeterProvider{}, + wantLoggerProvider: &sdklog.LoggerProvider{}, + }, + { + name: "with-sdk-disabled", + cfg: []ConfigurationOption{ + WithContext(context.Background()), + WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ + Disabled: ptr(true), + TracerProvider: &TracerProvider{}, + MeterProvider: &MeterProvider{}, + LoggerProvider: &LoggerProvider{}, + }), + }, + wantTracerProvider: tracenoop.NewTracerProvider(), + wantMeterProvider: metricnoop.NewMeterProvider(), + wantLoggerProvider: lognoop.NewLoggerProvider(), + }, + } + for _, tt := range tests { + sdk, err := NewSDK(tt.cfg...) + require.Equal(t, tt.wantErr, err) + assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) + assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) + assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) + require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(context.Background())) + } +} + +var v02OpenTelemetryConfig = OpenTelemetryConfiguration{ + Disabled: ptr(false), + FileFormat: "0.2", + AttributeLimits: &AttributeLimits{ + AttributeCountLimit: ptr(128), + AttributeValueLengthLimit: ptr(4096), + }, + LoggerProvider: &LoggerProvider{ + Limits: &LogRecordLimits{ + AttributeCountLimit: ptr(128), + AttributeValueLengthLimit: ptr(4096), + }, + Processors: []LogRecordProcessor{ + { + Batch: &BatchLogRecordProcessor{ + ExportTimeout: ptr(30000), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Certificate: ptr("/app/cert.pem"), + ClientCertificate: ptr("/app/cert.pem"), + ClientKey: ptr("/app/cert.pem"), + Compression: ptr("gzip"), + Endpoint: "http://localhost:4318", + Headers: Headers{ + "api-key": "1234", + }, + Insecure: ptr(false), + Protocol: "http/protobuf", + Timeout: ptr(10000), + }, + }, + MaxExportBatchSize: ptr(512), + MaxQueueSize: ptr(2048), + ScheduleDelay: ptr(5000), + }, + }, + { + Simple: &SimpleLogRecordProcessor{ + Exporter: LogRecordExporter{ + Console: Console{}, + }, + }, + }, + }, + }, + MeterProvider: &MeterProvider{ + Readers: []MetricReader{ + { + Pull: &PullMetricReader{ + Exporter: MetricExporter{ + Prometheus: &Prometheus{ + Host: ptr("localhost"), + Port: ptr(9464), + WithResourceConstantLabels: &IncludeExclude{ + Excluded: []string{"service.attr1"}, + Included: []string{"service*"}, + }, + WithoutScopeInfo: ptr(false), + WithoutTypeSuffix: ptr(false), + WithoutUnits: ptr(false), + }, + }, + }, + }, + { + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Certificate: ptr("/app/cert.pem"), + ClientCertificate: ptr("/app/cert.pem"), + ClientKey: ptr("/app/cert.pem"), + Compression: ptr("gzip"), + DefaultHistogramAggregation: ptr(OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram), + Endpoint: "http://localhost:4318", + Headers: Headers{ + "api-key": "1234", + }, + Insecure: ptr(false), + Protocol: "http/protobuf", + TemporalityPreference: ptr("delta"), + Timeout: ptr(10000), + }, + }, + Interval: ptr(5000), + Timeout: ptr(30000), + }, + }, + { + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + Console: Console{}, + }, + }, + }, + }, + Views: []View{ + { + Selector: &ViewSelector{ + InstrumentName: ptr("my-instrument"), + InstrumentType: ptr(ViewSelectorInstrumentTypeHistogram), + MeterName: ptr("my-meter"), + MeterSchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), + MeterVersion: ptr("1.0.0"), + Unit: ptr("ms"), + }, + Stream: &ViewStream{ + Aggregation: &ViewStreamAggregation{ + ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ + Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, + RecordMinMax: ptr(true), + }, + }, + AttributeKeys: []string{"key1", "key2"}, + Description: ptr("new_description"), + Name: ptr("new_instrument_name"), + }, + }, + }, + }, + Propagator: &Propagator{ + Composite: []string{"tracecontext", "baggage", "b3", "b3multi", "jaeger", "xray", "ottrace"}, + }, + Resource: &Resource{ + Attributes: Attributes{ + "service.name": "unknown_service", + }, + Detectors: &Detectors{ + Attributes: &DetectorsAttributes{ + Excluded: []string{"process.command_args"}, + Included: []string{"process.*"}, + }, + }, + SchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), + }, + TracerProvider: &TracerProvider{ + Limits: &SpanLimits{ + AttributeCountLimit: ptr(128), + AttributeValueLengthLimit: ptr(4096), + EventCountLimit: ptr(128), + EventAttributeCountLimit: ptr(128), + LinkCountLimit: ptr(128), + LinkAttributeCountLimit: ptr(128), + }, + Processors: []SpanProcessor{ + { + Batch: &BatchSpanProcessor{ + ExportTimeout: ptr(30000), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Certificate: ptr("/app/cert.pem"), + ClientCertificate: ptr("/app/cert.pem"), + ClientKey: ptr("/app/cert.pem"), + Compression: ptr("gzip"), + Endpoint: "http://localhost:4318", + Headers: Headers{ + "api-key": "1234", + }, + Insecure: ptr(false), + Protocol: "http/protobuf", + Timeout: ptr(10000), + }, + }, + MaxExportBatchSize: ptr(512), + MaxQueueSize: ptr(2048), + ScheduleDelay: ptr(5000), + }, + }, + { + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + Zipkin: &Zipkin{ + Endpoint: "http://localhost:9411/api/v2/spans", + Timeout: ptr(10000), + }, + }, + }, + }, + { + Simple: &SimpleSpanProcessor{ + Exporter: SpanExporter{ + Console: Console{}, + }, + }, + }, + }, + Sampler: &Sampler{ + ParentBased: &SamplerParentBased{ + LocalParentNotSampled: &Sampler{ + AlwaysOff: SamplerAlwaysOff{}, + }, + LocalParentSampled: &Sampler{ + AlwaysOn: SamplerAlwaysOn{}, + }, + RemoteParentNotSampled: &Sampler{ + AlwaysOff: SamplerAlwaysOff{}, + }, + RemoteParentSampled: &Sampler{ + AlwaysOn: SamplerAlwaysOn{}, + }, + Root: &Sampler{ + TraceIDRatioBased: &SamplerTraceIDRatioBased{ + Ratio: ptr(0.0001), + }, + }, + }, + }, + }, +} + +func TestParseYAML(t *testing.T) { + tests := []struct { + name string + input string + wantErr error + wantType interface{} + }{ + { + name: "valid YAML config", + input: `valid_empty.yaml`, + wantErr: nil, + wantType: &OpenTelemetryConfiguration{ + Disabled: ptr(false), + FileFormat: "0.1", + }, + }, + { + name: "invalid config", + input: "invalid_bool.yaml", + wantErr: errors.New(`yaml: unmarshal errors: + line 2: cannot unmarshal !!str ` + "`notabool`" + ` into bool`), + }, + { + name: "valid v0.2 config", + input: "v0.2.yaml", + wantType: &v02OpenTelemetryConfig, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) + require.NoError(t, err) + + got, err := ParseYAML(b) + if tt.wantErr != nil { + require.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + require.NoError(t, err) + assert.Equal(t, tt.wantType, got) + } + }) + } +} + +func TestSerializeJSON(t *testing.T) { + tests := []struct { + name string + input string + wantErr error + wantType interface{} + }{ + { + name: "valid JSON config", + input: `valid_empty.json`, + wantErr: nil, + wantType: OpenTelemetryConfiguration{ + Disabled: ptr(false), + FileFormat: "0.1", + }, + }, + { + name: "invalid config", + input: "invalid_bool.json", + wantErr: errors.New(`json: cannot unmarshal string into Go struct field Plain.disabled of type bool`), + }, + { + name: "valid v0.2 config", + input: "v0.2.json", + wantType: v02OpenTelemetryConfig, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) + require.NoError(t, err) + + var got OpenTelemetryConfiguration + err = json.Unmarshal(b, &got) + + if tt.wantErr != nil { + require.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + require.NoError(t, err) + assert.Equal(t, tt.wantType, got) + } + }) + } +} + +func ptr[T any](v T) *T { + return &v +} diff --git a/config/v0.2.0/generated_config.go b/config/v0.2.0/generated_config.go new file mode 100644 index 00000000000..2315641db64 --- /dev/null +++ b/config/v0.2.0/generated_config.go @@ -0,0 +1,780 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package config + +import "encoding/json" +import "fmt" +import "reflect" + +type AttributeLimits struct { + // AttributeCountLimit corresponds to the JSON schema field + // "attribute_count_limit". + AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` + + // AttributeValueLengthLimit corresponds to the JSON schema field + // "attribute_value_length_limit". + AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` + + AdditionalProperties interface{} +} + +type Attributes map[string]interface{} + +type BatchLogRecordProcessor struct { + // ExportTimeout corresponds to the JSON schema field "export_timeout". + ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` + + // Exporter corresponds to the JSON schema field "exporter". + Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` + + // MaxExportBatchSize corresponds to the JSON schema field + // "max_export_batch_size". + MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` + + // MaxQueueSize corresponds to the JSON schema field "max_queue_size". + MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` + + // ScheduleDelay corresponds to the JSON schema field "schedule_delay". + ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *BatchLogRecordProcessor) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["exporter"]; raw != nil && !ok { + return fmt.Errorf("field exporter in BatchLogRecordProcessor: required") + } + type Plain BatchLogRecordProcessor + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = BatchLogRecordProcessor(plain) + return nil +} + +type BatchSpanProcessor struct { + // ExportTimeout corresponds to the JSON schema field "export_timeout". + ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` + + // Exporter corresponds to the JSON schema field "exporter". + Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` + + // MaxExportBatchSize corresponds to the JSON schema field + // "max_export_batch_size". + MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` + + // MaxQueueSize corresponds to the JSON schema field "max_queue_size". + MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` + + // ScheduleDelay corresponds to the JSON schema field "schedule_delay". + ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *BatchSpanProcessor) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["exporter"]; raw != nil && !ok { + return fmt.Errorf("field exporter in BatchSpanProcessor: required") + } + type Plain BatchSpanProcessor + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = BatchSpanProcessor(plain) + return nil +} + +type Common map[string]interface{} + +type Console map[string]interface{} + +type Detectors struct { + // Attributes corresponds to the JSON schema field "attributes". + Attributes *DetectorsAttributes `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` +} + +type DetectorsAttributes struct { + // Excluded corresponds to the JSON schema field "excluded". + Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` + + // Included corresponds to the JSON schema field "included". + Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` +} + +type Headers map[string]string + +type IncludeExclude struct { + // Excluded corresponds to the JSON schema field "excluded". + Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` + + // Included corresponds to the JSON schema field "included". + Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` +} + +type LogRecordExporter struct { + // Console corresponds to the JSON schema field "console". + Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` + + // OTLP corresponds to the JSON schema field "otlp". + OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` + + AdditionalProperties interface{} +} + +type LogRecordLimits struct { + // AttributeCountLimit corresponds to the JSON schema field + // "attribute_count_limit". + AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` + + // AttributeValueLengthLimit corresponds to the JSON schema field + // "attribute_value_length_limit". + AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` +} + +type LogRecordProcessor struct { + // Batch corresponds to the JSON schema field "batch". + Batch *BatchLogRecordProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` + + // Simple corresponds to the JSON schema field "simple". + Simple *SimpleLogRecordProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` + + AdditionalProperties interface{} +} + +type LoggerProvider struct { + // Limits corresponds to the JSON schema field "limits". + Limits *LogRecordLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` + + // Processors corresponds to the JSON schema field "processors". + Processors []LogRecordProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` +} + +type MeterProvider struct { + // Readers corresponds to the JSON schema field "readers". + Readers []MetricReader `json:"readers,omitempty" yaml:"readers,omitempty" mapstructure:"readers,omitempty"` + + // Views corresponds to the JSON schema field "views". + Views []View `json:"views,omitempty" yaml:"views,omitempty" mapstructure:"views,omitempty"` +} + +type MetricExporter struct { + // Console corresponds to the JSON schema field "console". + Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` + + // OTLP corresponds to the JSON schema field "otlp". + OTLP *OTLPMetric `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` + + // Prometheus corresponds to the JSON schema field "prometheus". + Prometheus *Prometheus `json:"prometheus,omitempty" yaml:"prometheus,omitempty" mapstructure:"prometheus,omitempty"` + + AdditionalProperties interface{} +} + +type MetricReader struct { + // Periodic corresponds to the JSON schema field "periodic". + Periodic *PeriodicMetricReader `json:"periodic,omitempty" yaml:"periodic,omitempty" mapstructure:"periodic,omitempty"` + + // Pull corresponds to the JSON schema field "pull". + Pull *PullMetricReader `json:"pull,omitempty" yaml:"pull,omitempty" mapstructure:"pull,omitempty"` +} + +type OTLP struct { + // Certificate corresponds to the JSON schema field "certificate". + Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` + + // ClientCertificate corresponds to the JSON schema field "client_certificate". + ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` + + // ClientKey corresponds to the JSON schema field "client_key". + ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` + + // Compression corresponds to the JSON schema field "compression". + Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` + + // Endpoint corresponds to the JSON schema field "endpoint". + Endpoint string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` + + // Headers corresponds to the JSON schema field "headers". + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` + + // Insecure corresponds to the JSON schema field "insecure". + Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` + + // Protocol corresponds to the JSON schema field "protocol". + Protocol string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` + + // Timeout corresponds to the JSON schema field "timeout". + Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` +} + +type OTLPMetric struct { + // Certificate corresponds to the JSON schema field "certificate". + Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` + + // ClientCertificate corresponds to the JSON schema field "client_certificate". + ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` + + // ClientKey corresponds to the JSON schema field "client_key". + ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` + + // Compression corresponds to the JSON schema field "compression". + Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` + + // DefaultHistogramAggregation corresponds to the JSON schema field + // "default_histogram_aggregation". + DefaultHistogramAggregation *OTLPMetricDefaultHistogramAggregation `json:"default_histogram_aggregation,omitempty" yaml:"default_histogram_aggregation,omitempty" mapstructure:"default_histogram_aggregation,omitempty"` + + // Endpoint corresponds to the JSON schema field "endpoint". + Endpoint string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` + + // Headers corresponds to the JSON schema field "headers". + Headers Headers `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` + + // Insecure corresponds to the JSON schema field "insecure". + Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` + + // Protocol corresponds to the JSON schema field "protocol". + Protocol string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` + + // TemporalityPreference corresponds to the JSON schema field + // "temporality_preference". + TemporalityPreference *string `json:"temporality_preference,omitempty" yaml:"temporality_preference,omitempty" mapstructure:"temporality_preference,omitempty"` + + // Timeout corresponds to the JSON schema field "timeout". + Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` +} + +type OTLPMetricDefaultHistogramAggregation string + +const OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram OTLPMetricDefaultHistogramAggregation = "base2_exponential_bucket_histogram" +const OTLPMetricDefaultHistogramAggregationExplicitBucketHistogram OTLPMetricDefaultHistogramAggregation = "explicit_bucket_histogram" + +var enumValues_OTLPMetricDefaultHistogramAggregation = []interface{}{ + "explicit_bucket_histogram", + "base2_exponential_bucket_histogram", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OTLPMetricDefaultHistogramAggregation) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_OTLPMetricDefaultHistogramAggregation { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_OTLPMetricDefaultHistogramAggregation, v) + } + *j = OTLPMetricDefaultHistogramAggregation(v) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OTLPMetric) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["endpoint"]; raw != nil && !ok { + return fmt.Errorf("field endpoint in OTLPMetric: required") + } + if _, ok := raw["protocol"]; raw != nil && !ok { + return fmt.Errorf("field protocol in OTLPMetric: required") + } + type Plain OTLPMetric + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = OTLPMetric(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OTLP) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["endpoint"]; raw != nil && !ok { + return fmt.Errorf("field endpoint in OTLP: required") + } + if _, ok := raw["protocol"]; raw != nil && !ok { + return fmt.Errorf("field protocol in OTLP: required") + } + type Plain OTLP + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = OTLP(plain) + return nil +} + +type OpenTelemetryConfiguration struct { + // AttributeLimits corresponds to the JSON schema field "attribute_limits". + AttributeLimits *AttributeLimits `json:"attribute_limits,omitempty" yaml:"attribute_limits,omitempty" mapstructure:"attribute_limits,omitempty"` + + // Disabled corresponds to the JSON schema field "disabled". + Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` + + // FileFormat corresponds to the JSON schema field "file_format". + FileFormat string `json:"file_format" yaml:"file_format" mapstructure:"file_format"` + + // LoggerProvider corresponds to the JSON schema field "logger_provider". + LoggerProvider *LoggerProvider `json:"logger_provider,omitempty" yaml:"logger_provider,omitempty" mapstructure:"logger_provider,omitempty"` + + // MeterProvider corresponds to the JSON schema field "meter_provider". + MeterProvider *MeterProvider `json:"meter_provider,omitempty" yaml:"meter_provider,omitempty" mapstructure:"meter_provider,omitempty"` + + // Propagator corresponds to the JSON schema field "propagator". + Propagator *Propagator `json:"propagator,omitempty" yaml:"propagator,omitempty" mapstructure:"propagator,omitempty"` + + // Resource corresponds to the JSON schema field "resource". + Resource *Resource `json:"resource,omitempty" yaml:"resource,omitempty" mapstructure:"resource,omitempty"` + + // TracerProvider corresponds to the JSON schema field "tracer_provider". + TracerProvider *TracerProvider `json:"tracer_provider,omitempty" yaml:"tracer_provider,omitempty" mapstructure:"tracer_provider,omitempty"` + + AdditionalProperties interface{} +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OpenTelemetryConfiguration) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["file_format"]; raw != nil && !ok { + return fmt.Errorf("field file_format in OpenTelemetryConfiguration: required") + } + type Plain OpenTelemetryConfiguration + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = OpenTelemetryConfiguration(plain) + return nil +} + +type PeriodicMetricReader struct { + // Exporter corresponds to the JSON schema field "exporter". + Exporter MetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` + + // Interval corresponds to the JSON schema field "interval". + Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` + + // Timeout corresponds to the JSON schema field "timeout". + Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *PeriodicMetricReader) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["exporter"]; raw != nil && !ok { + return fmt.Errorf("field exporter in PeriodicMetricReader: required") + } + type Plain PeriodicMetricReader + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = PeriodicMetricReader(plain) + return nil +} + +type Prometheus struct { + // Host corresponds to the JSON schema field "host". + Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` + + // Port corresponds to the JSON schema field "port". + Port *int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"port,omitempty"` + + // WithResourceConstantLabels corresponds to the JSON schema field + // "with_resource_constant_labels". + WithResourceConstantLabels *IncludeExclude `json:"with_resource_constant_labels,omitempty" yaml:"with_resource_constant_labels,omitempty" mapstructure:"with_resource_constant_labels,omitempty"` + + // WithoutScopeInfo corresponds to the JSON schema field "without_scope_info". + WithoutScopeInfo *bool `json:"without_scope_info,omitempty" yaml:"without_scope_info,omitempty" mapstructure:"without_scope_info,omitempty"` + + // WithoutTypeSuffix corresponds to the JSON schema field "without_type_suffix". + WithoutTypeSuffix *bool `json:"without_type_suffix,omitempty" yaml:"without_type_suffix,omitempty" mapstructure:"without_type_suffix,omitempty"` + + // WithoutUnits corresponds to the JSON schema field "without_units". + WithoutUnits *bool `json:"without_units,omitempty" yaml:"without_units,omitempty" mapstructure:"without_units,omitempty"` +} + +type Propagator struct { + // Composite corresponds to the JSON schema field "composite". + Composite []string `json:"composite,omitempty" yaml:"composite,omitempty" mapstructure:"composite,omitempty"` + + AdditionalProperties interface{} +} + +type PullMetricReader struct { + // Exporter corresponds to the JSON schema field "exporter". + Exporter MetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *PullMetricReader) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["exporter"]; raw != nil && !ok { + return fmt.Errorf("field exporter in PullMetricReader: required") + } + type Plain PullMetricReader + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = PullMetricReader(plain) + return nil +} + +type Resource struct { + // Attributes corresponds to the JSON schema field "attributes". + Attributes Attributes `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` + + // Detectors corresponds to the JSON schema field "detectors". + Detectors *Detectors `json:"detectors,omitempty" yaml:"detectors,omitempty" mapstructure:"detectors,omitempty"` + + // SchemaUrl corresponds to the JSON schema field "schema_url". + SchemaUrl *string `json:"schema_url,omitempty" yaml:"schema_url,omitempty" mapstructure:"schema_url,omitempty"` +} + +type Sampler struct { + // AlwaysOff corresponds to the JSON schema field "always_off". + AlwaysOff SamplerAlwaysOff `json:"always_off,omitempty" yaml:"always_off,omitempty" mapstructure:"always_off,omitempty"` + + // AlwaysOn corresponds to the JSON schema field "always_on". + AlwaysOn SamplerAlwaysOn `json:"always_on,omitempty" yaml:"always_on,omitempty" mapstructure:"always_on,omitempty"` + + // JaegerRemote corresponds to the JSON schema field "jaeger_remote". + JaegerRemote *SamplerJaegerRemote `json:"jaeger_remote,omitempty" yaml:"jaeger_remote,omitempty" mapstructure:"jaeger_remote,omitempty"` + + // ParentBased corresponds to the JSON schema field "parent_based". + ParentBased *SamplerParentBased `json:"parent_based,omitempty" yaml:"parent_based,omitempty" mapstructure:"parent_based,omitempty"` + + // TraceIDRatioBased corresponds to the JSON schema field "trace_id_ratio_based". + TraceIDRatioBased *SamplerTraceIDRatioBased `json:"trace_id_ratio_based,omitempty" yaml:"trace_id_ratio_based,omitempty" mapstructure:"trace_id_ratio_based,omitempty"` + + AdditionalProperties interface{} +} + +type SamplerAlwaysOff map[string]interface{} + +type SamplerAlwaysOn map[string]interface{} + +type SamplerJaegerRemote struct { + // Endpoint corresponds to the JSON schema field "endpoint". + Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` + + // InitialSampler corresponds to the JSON schema field "initial_sampler". + InitialSampler *Sampler `json:"initial_sampler,omitempty" yaml:"initial_sampler,omitempty" mapstructure:"initial_sampler,omitempty"` + + // Interval corresponds to the JSON schema field "interval". + Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` +} + +type SamplerParentBased struct { + // LocalParentNotSampled corresponds to the JSON schema field + // "local_parent_not_sampled". + LocalParentNotSampled *Sampler `json:"local_parent_not_sampled,omitempty" yaml:"local_parent_not_sampled,omitempty" mapstructure:"local_parent_not_sampled,omitempty"` + + // LocalParentSampled corresponds to the JSON schema field "local_parent_sampled". + LocalParentSampled *Sampler `json:"local_parent_sampled,omitempty" yaml:"local_parent_sampled,omitempty" mapstructure:"local_parent_sampled,omitempty"` + + // RemoteParentNotSampled corresponds to the JSON schema field + // "remote_parent_not_sampled". + RemoteParentNotSampled *Sampler `json:"remote_parent_not_sampled,omitempty" yaml:"remote_parent_not_sampled,omitempty" mapstructure:"remote_parent_not_sampled,omitempty"` + + // RemoteParentSampled corresponds to the JSON schema field + // "remote_parent_sampled". + RemoteParentSampled *Sampler `json:"remote_parent_sampled,omitempty" yaml:"remote_parent_sampled,omitempty" mapstructure:"remote_parent_sampled,omitempty"` + + // Root corresponds to the JSON schema field "root". + Root *Sampler `json:"root,omitempty" yaml:"root,omitempty" mapstructure:"root,omitempty"` +} + +type SamplerTraceIDRatioBased struct { + // Ratio corresponds to the JSON schema field "ratio". + Ratio *float64 `json:"ratio,omitempty" yaml:"ratio,omitempty" mapstructure:"ratio,omitempty"` +} + +type SimpleLogRecordProcessor struct { + // Exporter corresponds to the JSON schema field "exporter". + Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SimpleLogRecordProcessor) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["exporter"]; raw != nil && !ok { + return fmt.Errorf("field exporter in SimpleLogRecordProcessor: required") + } + type Plain SimpleLogRecordProcessor + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SimpleLogRecordProcessor(plain) + return nil +} + +type SimpleSpanProcessor struct { + // Exporter corresponds to the JSON schema field "exporter". + Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SimpleSpanProcessor) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["exporter"]; raw != nil && !ok { + return fmt.Errorf("field exporter in SimpleSpanProcessor: required") + } + type Plain SimpleSpanProcessor + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SimpleSpanProcessor(plain) + return nil +} + +type SpanExporter struct { + // Console corresponds to the JSON schema field "console". + Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` + + // OTLP corresponds to the JSON schema field "otlp". + OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` + + // Zipkin corresponds to the JSON schema field "zipkin". + Zipkin *Zipkin `json:"zipkin,omitempty" yaml:"zipkin,omitempty" mapstructure:"zipkin,omitempty"` + + AdditionalProperties interface{} +} + +type SpanLimits struct { + // AttributeCountLimit corresponds to the JSON schema field + // "attribute_count_limit". + AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` + + // AttributeValueLengthLimit corresponds to the JSON schema field + // "attribute_value_length_limit". + AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` + + // EventAttributeCountLimit corresponds to the JSON schema field + // "event_attribute_count_limit". + EventAttributeCountLimit *int `json:"event_attribute_count_limit,omitempty" yaml:"event_attribute_count_limit,omitempty" mapstructure:"event_attribute_count_limit,omitempty"` + + // EventCountLimit corresponds to the JSON schema field "event_count_limit". + EventCountLimit *int `json:"event_count_limit,omitempty" yaml:"event_count_limit,omitempty" mapstructure:"event_count_limit,omitempty"` + + // LinkAttributeCountLimit corresponds to the JSON schema field + // "link_attribute_count_limit". + LinkAttributeCountLimit *int `json:"link_attribute_count_limit,omitempty" yaml:"link_attribute_count_limit,omitempty" mapstructure:"link_attribute_count_limit,omitempty"` + + // LinkCountLimit corresponds to the JSON schema field "link_count_limit". + LinkCountLimit *int `json:"link_count_limit,omitempty" yaml:"link_count_limit,omitempty" mapstructure:"link_count_limit,omitempty"` +} + +type SpanProcessor struct { + // Batch corresponds to the JSON schema field "batch". + Batch *BatchSpanProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` + + // Simple corresponds to the JSON schema field "simple". + Simple *SimpleSpanProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` + + AdditionalProperties interface{} +} + +type TracerProvider struct { + // Limits corresponds to the JSON schema field "limits". + Limits *SpanLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` + + // Processors corresponds to the JSON schema field "processors". + Processors []SpanProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` + + // Sampler corresponds to the JSON schema field "sampler". + Sampler *Sampler `json:"sampler,omitempty" yaml:"sampler,omitempty" mapstructure:"sampler,omitempty"` +} + +type View struct { + // Selector corresponds to the JSON schema field "selector". + Selector *ViewSelector `json:"selector,omitempty" yaml:"selector,omitempty" mapstructure:"selector,omitempty"` + + // Stream corresponds to the JSON schema field "stream". + Stream *ViewStream `json:"stream,omitempty" yaml:"stream,omitempty" mapstructure:"stream,omitempty"` +} + +type ViewSelector struct { + // InstrumentName corresponds to the JSON schema field "instrument_name". + InstrumentName *string `json:"instrument_name,omitempty" yaml:"instrument_name,omitempty" mapstructure:"instrument_name,omitempty"` + + // InstrumentType corresponds to the JSON schema field "instrument_type". + InstrumentType *ViewSelectorInstrumentType `json:"instrument_type,omitempty" yaml:"instrument_type,omitempty" mapstructure:"instrument_type,omitempty"` + + // MeterName corresponds to the JSON schema field "meter_name". + MeterName *string `json:"meter_name,omitempty" yaml:"meter_name,omitempty" mapstructure:"meter_name,omitempty"` + + // MeterSchemaUrl corresponds to the JSON schema field "meter_schema_url". + MeterSchemaUrl *string `json:"meter_schema_url,omitempty" yaml:"meter_schema_url,omitempty" mapstructure:"meter_schema_url,omitempty"` + + // MeterVersion corresponds to the JSON schema field "meter_version". + MeterVersion *string `json:"meter_version,omitempty" yaml:"meter_version,omitempty" mapstructure:"meter_version,omitempty"` + + // Unit corresponds to the JSON schema field "unit". + Unit *string `json:"unit,omitempty" yaml:"unit,omitempty" mapstructure:"unit,omitempty"` +} + +type ViewSelectorInstrumentType string + +const ViewSelectorInstrumentTypeCounter ViewSelectorInstrumentType = "counter" +const ViewSelectorInstrumentTypeHistogram ViewSelectorInstrumentType = "histogram" +const ViewSelectorInstrumentTypeObservableCounter ViewSelectorInstrumentType = "observable_counter" +const ViewSelectorInstrumentTypeObservableGauge ViewSelectorInstrumentType = "observable_gauge" +const ViewSelectorInstrumentTypeObservableUpDownCounter ViewSelectorInstrumentType = "observable_up_down_counter" +const ViewSelectorInstrumentTypeUpDownCounter ViewSelectorInstrumentType = "up_down_counter" + +var enumValues_ViewSelectorInstrumentType = []interface{}{ + "counter", + "histogram", + "observable_counter", + "observable_gauge", + "observable_up_down_counter", + "up_down_counter", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ViewSelectorInstrumentType) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_ViewSelectorInstrumentType { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ViewSelectorInstrumentType, v) + } + *j = ViewSelectorInstrumentType(v) + return nil +} + +type ViewStream struct { + // Aggregation corresponds to the JSON schema field "aggregation". + Aggregation *ViewStreamAggregation `json:"aggregation,omitempty" yaml:"aggregation,omitempty" mapstructure:"aggregation,omitempty"` + + // AttributeKeys corresponds to the JSON schema field "attribute_keys". + AttributeKeys []string `json:"attribute_keys,omitempty" yaml:"attribute_keys,omitempty" mapstructure:"attribute_keys,omitempty"` + + // Description corresponds to the JSON schema field "description". + Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` + + // Name corresponds to the JSON schema field "name". + Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` +} + +type ViewStreamAggregation struct { + // Base2ExponentialBucketHistogram corresponds to the JSON schema field + // "base2_exponential_bucket_histogram". + Base2ExponentialBucketHistogram *ViewStreamAggregationBase2ExponentialBucketHistogram `json:"base2_exponential_bucket_histogram,omitempty" yaml:"base2_exponential_bucket_histogram,omitempty" mapstructure:"base2_exponential_bucket_histogram,omitempty"` + + // Default corresponds to the JSON schema field "default". + Default ViewStreamAggregationDefault `json:"default,omitempty" yaml:"default,omitempty" mapstructure:"default,omitempty"` + + // Drop corresponds to the JSON schema field "drop". + Drop ViewStreamAggregationDrop `json:"drop,omitempty" yaml:"drop,omitempty" mapstructure:"drop,omitempty"` + + // ExplicitBucketHistogram corresponds to the JSON schema field + // "explicit_bucket_histogram". + ExplicitBucketHistogram *ViewStreamAggregationExplicitBucketHistogram `json:"explicit_bucket_histogram,omitempty" yaml:"explicit_bucket_histogram,omitempty" mapstructure:"explicit_bucket_histogram,omitempty"` + + // LastValue corresponds to the JSON schema field "last_value". + LastValue ViewStreamAggregationLastValue `json:"last_value,omitempty" yaml:"last_value,omitempty" mapstructure:"last_value,omitempty"` + + // Sum corresponds to the JSON schema field "sum". + Sum ViewStreamAggregationSum `json:"sum,omitempty" yaml:"sum,omitempty" mapstructure:"sum,omitempty"` +} + +type ViewStreamAggregationBase2ExponentialBucketHistogram struct { + // MaxScale corresponds to the JSON schema field "max_scale". + MaxScale *int `json:"max_scale,omitempty" yaml:"max_scale,omitempty" mapstructure:"max_scale,omitempty"` + + // MaxSize corresponds to the JSON schema field "max_size". + MaxSize *int `json:"max_size,omitempty" yaml:"max_size,omitempty" mapstructure:"max_size,omitempty"` + + // RecordMinMax corresponds to the JSON schema field "record_min_max". + RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` +} + +type ViewStreamAggregationDefault map[string]interface{} + +type ViewStreamAggregationDrop map[string]interface{} + +type ViewStreamAggregationExplicitBucketHistogram struct { + // Boundaries corresponds to the JSON schema field "boundaries". + Boundaries []float64 `json:"boundaries,omitempty" yaml:"boundaries,omitempty" mapstructure:"boundaries,omitempty"` + + // RecordMinMax corresponds to the JSON schema field "record_min_max". + RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` +} + +type ViewStreamAggregationLastValue map[string]interface{} + +type ViewStreamAggregationSum map[string]interface{} + +type Zipkin struct { + // Endpoint corresponds to the JSON schema field "endpoint". + Endpoint string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` + + // Timeout corresponds to the JSON schema field "timeout". + Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Zipkin) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["endpoint"]; raw != nil && !ok { + return fmt.Errorf("field endpoint in Zipkin: required") + } + type Plain Zipkin + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Zipkin(plain) + return nil +} diff --git a/config/v0.2.0/log.go b/config/v0.2.0/log.go new file mode 100644 index 00000000000..bd302cfa75a --- /dev/null +++ b/config/v0.2.0/log.go @@ -0,0 +1,155 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/contrib/config/v0.2.0" + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/noop" + sdklog "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/resource" +) + +func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvider, shutdownFunc, error) { + if cfg.opentelemetryConfig.LoggerProvider == nil { + return noop.NewLoggerProvider(), noopShutdown, nil + } + opts := []sdklog.LoggerProviderOption{ + sdklog.WithResource(res), + } + var errs []error + for _, processor := range cfg.opentelemetryConfig.LoggerProvider.Processors { + sp, err := logProcessor(cfg.ctx, processor) + if err == nil { + opts = append(opts, sdklog.WithProcessor(sp)) + } else { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return noop.NewLoggerProvider(), noopShutdown, errors.Join(errs...) + } + + lp := sdklog.NewLoggerProvider(opts...) + return lp, lp.Shutdown, nil +} + +func logProcessor(ctx context.Context, processor LogRecordProcessor) (sdklog.Processor, error) { + if processor.Batch != nil && processor.Simple != nil { + return nil, errors.New("must not specify multiple log processor type") + } + if processor.Batch != nil { + exp, err := logExporter(ctx, processor.Batch.Exporter) + if err != nil { + return nil, err + } + return batchLogProcessor(processor.Batch, exp) + } + if processor.Simple != nil { + exp, err := logExporter(ctx, processor.Simple.Exporter) + if err != nil { + return nil, err + } + return sdklog.NewSimpleProcessor(exp), nil + } + return nil, errors.New("unsupported log processor type, must be one of simple or batch") +} + +func logExporter(ctx context.Context, exporter LogRecordExporter) (sdklog.Exporter, error) { + if exporter.Console != nil && exporter.OTLP != nil { + return nil, errors.New("must not specify multiple exporters") + } + + if exporter.Console != nil { + return stdoutlog.New( + stdoutlog.WithPrettyPrint(), + ) + } + + if exporter.OTLP != nil { + switch exporter.OTLP.Protocol { + case protocolProtobufHTTP: + return otlpHTTPLogExporter(ctx, exporter.OTLP) + default: + return nil, fmt.Errorf("unsupported protocol %q", exporter.OTLP.Protocol) + } + } + return nil, errors.New("no valid log exporter") +} + +func batchLogProcessor(blp *BatchLogRecordProcessor, exp sdklog.Exporter) (*sdklog.BatchProcessor, error) { + var opts []sdklog.BatchProcessorOption + if blp.ExportTimeout != nil { + if *blp.ExportTimeout < 0 { + return nil, fmt.Errorf("invalid export timeout %d", *blp.ExportTimeout) + } + opts = append(opts, sdklog.WithExportTimeout(time.Millisecond*time.Duration(*blp.ExportTimeout))) + } + if blp.MaxExportBatchSize != nil { + if *blp.MaxExportBatchSize < 0 { + return nil, fmt.Errorf("invalid batch size %d", *blp.MaxExportBatchSize) + } + opts = append(opts, sdklog.WithExportMaxBatchSize(*blp.MaxExportBatchSize)) + } + if blp.MaxQueueSize != nil { + if *blp.MaxQueueSize < 0 { + return nil, fmt.Errorf("invalid queue size %d", *blp.MaxQueueSize) + } + opts = append(opts, sdklog.WithMaxQueueSize(*blp.MaxQueueSize)) + } + + if blp.ScheduleDelay != nil { + if *blp.ScheduleDelay < 0 { + return nil, fmt.Errorf("invalid schedule delay %d", *blp.ScheduleDelay) + } + opts = append(opts, sdklog.WithExportInterval(time.Millisecond*time.Duration(*blp.ScheduleDelay))) + } + + return sdklog.NewBatchProcessor(exp, opts...), nil +} + +func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter, error) { + var opts []otlploghttp.Option + + if len(otlpConfig.Endpoint) > 0 { + u, err := url.ParseRequestURI(otlpConfig.Endpoint) + if err != nil { + return nil, err + } + opts = append(opts, otlploghttp.WithEndpoint(u.Host)) + + if u.Scheme == "http" { + opts = append(opts, otlploghttp.WithInsecure()) + } + if len(u.Path) > 0 { + opts = append(opts, otlploghttp.WithURLPath(u.Path)) + } + } + if otlpConfig.Compression != nil { + switch *otlpConfig.Compression { + case compressionGzip: + opts = append(opts, otlploghttp.WithCompression(otlploghttp.GzipCompression)) + case compressionNone: + opts = append(opts, otlploghttp.WithCompression(otlploghttp.NoCompression)) + default: + return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) + } + } + if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { + opts = append(opts, otlploghttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) + } + if len(otlpConfig.Headers) > 0 { + opts = append(opts, otlploghttp.WithHeaders(otlpConfig.Headers)) + } + + return otlploghttp.New(ctx, opts...) +} diff --git a/config/v0.2.0/log_test.go b/config/v0.2.0/log_test.go new file mode 100644 index 00000000000..24effa7d283 --- /dev/null +++ b/config/v0.2.0/log_test.go @@ -0,0 +1,412 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "context" + "errors" + "net/url" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/noop" + sdklog "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/resource" +) + +func TestLoggerProvider(t *testing.T) { + tests := []struct { + name string + cfg configOptions + wantProvider log.LoggerProvider + wantErr error + }{ + { + name: "no-logger-provider-configured", + wantProvider: noop.NewLoggerProvider(), + }, + { + name: "error-in-config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + LoggerProvider: &LoggerProvider{ + Processors: []LogRecordProcessor{ + { + Simple: &SimpleLogRecordProcessor{}, + Batch: &BatchLogRecordProcessor{}, + }, + }, + }, + }, + }, + wantProvider: noop.NewLoggerProvider(), + wantErr: errors.Join(errors.New("must not specify multiple log processor type")), + }, + } + for _, tt := range tests { + mp, shutdown, err := loggerProvider(tt.cfg, resource.Default()) + require.Equal(t, tt.wantProvider, mp) + assert.Equal(t, tt.wantErr, err) + require.NoError(t, shutdown(context.Background())) + } +} + +func TestLogProcessor(t *testing.T) { + ctx := context.Background() + + otlpHTTPExporter, err := otlploghttp.New(ctx) + require.NoError(t, err) + + consoleExporter, err := stdoutlog.New( + stdoutlog.WithPrettyPrint(), + ) + require.NoError(t, err) + + testCases := []struct { + name string + processor LogRecordProcessor + args any + wantErr error + wantProcessor sdklog.Processor + }{ + { + name: "no processor", + wantErr: errors.New("unsupported log processor type, must be one of simple or batch"), + }, + { + name: "multiple processor types", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + Exporter: LogRecordExporter{}, + }, + Simple: &SimpleLogRecordProcessor{}, + }, + wantErr: errors.New("must not specify multiple log processor type"), + }, + { + name: "batch processor invalid batch size otlphttp exporter", + + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(-1), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + }, + }, + }, + }, + wantErr: errors.New("invalid batch size -1"), + }, + { + name: "batch processor invalid export timeout otlphttp exporter", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + ExportTimeout: ptr(-2), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + }, + }, + }, + }, + wantErr: errors.New("invalid export timeout -2"), + }, + { + name: "batch processor invalid queue size otlphttp exporter", + + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxQueueSize: ptr(-3), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + }, + }, + }, + }, + wantErr: errors.New("invalid queue size -3"), + }, + { + name: "batch processor invalid schedule delay console exporter", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + ScheduleDelay: ptr(-4), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + }, + }, + }, + }, + wantErr: errors.New("invalid schedule delay -4"), + }, + { + name: "batch processor invalid exporter", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + Exporter: LogRecordExporter{}, + }, + }, + wantErr: errors.New("no valid log exporter"), + }, + { + name: "batch/console", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + Console: map[string]any{}, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(consoleExporter), + }, + { + name: "batch/otlp-http-exporter", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "http://localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-exporter-with-path", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "http://localhost:4318/path/123", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-exporter-no-endpoint", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-exporter-no-scheme", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-invalid-protocol", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "invalid", + Endpoint: "https://10.0.0.0:443", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: errors.New("unsupported protocol \"invalid\""), + }, + { + name: "batch/otlp-http-invalid-endpoint", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: " ", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + }, + { + name: "batch/otlp-http-none-compression", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-invalid-compression", + processor: LogRecordProcessor{ + Batch: &BatchLogRecordProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("invalid"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: errors.New("unsupported compression \"invalid\""), + }, + { + name: "simple/no-exporter", + processor: LogRecordProcessor{ + Simple: &SimpleLogRecordProcessor{ + Exporter: LogRecordExporter{}, + }, + }, + wantErr: errors.New("no valid log exporter"), + }, + { + name: "simple/console", + processor: LogRecordProcessor{ + Simple: &SimpleLogRecordProcessor{ + Exporter: LogRecordExporter{ + Console: map[string]any{}, + }, + }, + }, + wantProcessor: sdklog.NewSimpleProcessor(consoleExporter), + }, + { + name: "simple/otlp-exporter", + processor: LogRecordProcessor{ + Simple: &SimpleLogRecordProcessor{ + Exporter: LogRecordExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdklog.NewSimpleProcessor(otlpHTTPExporter), + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, err := logProcessor(context.Background(), tt.processor) + require.Equal(t, tt.wantErr, err) + if tt.wantProcessor == nil { + require.Nil(t, got) + } else { + require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) + wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName("exporter").Elem().Type() + gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() + require.Equal(t, wantExporterType.String(), gotExporterType.String()) + } + }) + } +} diff --git a/config/v0.2.0/metric.go b/config/v0.2.0/metric.go new file mode 100644 index 00000000000..458da85f909 --- /dev/null +++ b/config/v0.2.0/metric.go @@ -0,0 +1,496 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/contrib/config/v0.2.0" + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "net" + "net/http" + "net/url" + "os" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + otelprom "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/sdk/instrumentation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/resource" +) + +var zeroScope instrumentation.Scope + +const instrumentKindUndefined = sdkmetric.InstrumentKind(0) + +func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvider, shutdownFunc, error) { + if cfg.opentelemetryConfig.MeterProvider == nil { + return noop.NewMeterProvider(), noopShutdown, nil + } + opts := []sdkmetric.Option{ + sdkmetric.WithResource(res), + } + + var errs []error + for _, reader := range cfg.opentelemetryConfig.MeterProvider.Readers { + r, err := metricReader(cfg.ctx, reader) + if err == nil { + opts = append(opts, sdkmetric.WithReader(r)) + } else { + errs = append(errs, err) + } + } + for _, vw := range cfg.opentelemetryConfig.MeterProvider.Views { + v, err := view(vw) + if err == nil { + opts = append(opts, sdkmetric.WithView(v)) + } else { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return noop.NewMeterProvider(), noopShutdown, errors.Join(errs...) + } + + mp := sdkmetric.NewMeterProvider(opts...) + return mp, mp.Shutdown, nil +} + +func metricReader(ctx context.Context, r MetricReader) (sdkmetric.Reader, error) { + if r.Periodic != nil && r.Pull != nil { + return nil, errors.New("must not specify multiple metric reader type") + } + + if r.Periodic != nil { + var opts []sdkmetric.PeriodicReaderOption + if r.Periodic.Interval != nil { + opts = append(opts, sdkmetric.WithInterval(time.Duration(*r.Periodic.Interval)*time.Millisecond)) + } + + if r.Periodic.Timeout != nil { + opts = append(opts, sdkmetric.WithTimeout(time.Duration(*r.Periodic.Timeout)*time.Millisecond)) + } + return periodicExporter(ctx, r.Periodic.Exporter, opts...) + } + + if r.Pull != nil { + return pullReader(ctx, r.Pull.Exporter) + } + return nil, errors.New("no valid metric reader") +} + +func pullReader(ctx context.Context, exporter MetricExporter) (sdkmetric.Reader, error) { + if exporter.Prometheus != nil { + return prometheusReader(ctx, exporter.Prometheus) + } + return nil, errors.New("no valid metric exporter") +} + +func periodicExporter(ctx context.Context, exporter MetricExporter, opts ...sdkmetric.PeriodicReaderOption) (sdkmetric.Reader, error) { + if exporter.Console != nil && exporter.OTLP != nil { + return nil, errors.New("must not specify multiple exporters") + } + if exporter.Console != nil { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + + exp, err := stdoutmetric.New( + stdoutmetric.WithEncoder(enc), + ) + if err != nil { + return nil, err + } + return sdkmetric.NewPeriodicReader(exp, opts...), nil + } + if exporter.OTLP != nil { + var err error + var exp sdkmetric.Exporter + switch exporter.OTLP.Protocol { + case protocolProtobufHTTP: + exp, err = otlpHTTPMetricExporter(ctx, exporter.OTLP) + case protocolProtobufGRPC: + exp, err = otlpGRPCMetricExporter(ctx, exporter.OTLP) + default: + return nil, fmt.Errorf("unsupported protocol %q", exporter.OTLP.Protocol) + } + if err != nil { + return nil, err + } + return sdkmetric.NewPeriodicReader(exp, opts...), nil + } + return nil, errors.New("no valid metric exporter") +} + +func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { + opts := []otlpmetrichttp.Option{} + + if len(otlpConfig.Endpoint) > 0 { + u, err := url.ParseRequestURI(otlpConfig.Endpoint) + if err != nil { + return nil, err + } + opts = append(opts, otlpmetrichttp.WithEndpoint(u.Host)) + + if u.Scheme == "http" { + opts = append(opts, otlpmetrichttp.WithInsecure()) + } + if len(u.Path) > 0 { + opts = append(opts, otlpmetrichttp.WithURLPath(u.Path)) + } + } + if otlpConfig.Compression != nil { + switch *otlpConfig.Compression { + case compressionGzip: + opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression)) + case compressionNone: + opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression)) + default: + return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) + } + } + if otlpConfig.Timeout != nil { + opts = append(opts, otlpmetrichttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) + } + if len(otlpConfig.Headers) > 0 { + opts = append(opts, otlpmetrichttp.WithHeaders(otlpConfig.Headers)) + } + if otlpConfig.TemporalityPreference != nil { + switch *otlpConfig.TemporalityPreference { + case "delta": + opts = append(opts, otlpmetrichttp.WithTemporalitySelector(deltaTemporality)) + case "cumulative": + opts = append(opts, otlpmetrichttp.WithTemporalitySelector(cumulativeTemporality)) + case "lowmemory": + opts = append(opts, otlpmetrichttp.WithTemporalitySelector(lowMemory)) + default: + return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) + } + } + + return otlpmetrichttp.New(ctx, opts...) +} + +func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { + var opts []otlpmetricgrpc.Option + + if len(otlpConfig.Endpoint) > 0 { + u, err := url.ParseRequestURI(otlpConfig.Endpoint) + if err != nil { + return nil, err + } + // ParseRequestURI leaves the Host field empty when no + // scheme is specified (i.e. localhost:4317). This check is + // here to support the case where a user may not specify a + // scheme. The code does its best effort here by using + // otlpConfig.Endpoint as-is in that case + if u.Host != "" { + opts = append(opts, otlpmetricgrpc.WithEndpoint(u.Host)) + } else { + opts = append(opts, otlpmetricgrpc.WithEndpoint(otlpConfig.Endpoint)) + } + if u.Scheme == "http" { + opts = append(opts, otlpmetricgrpc.WithInsecure()) + } + } + + if otlpConfig.Compression != nil { + switch *otlpConfig.Compression { + case compressionGzip: + opts = append(opts, otlpmetricgrpc.WithCompressor(*otlpConfig.Compression)) + case compressionNone: + // none requires no options + default: + return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) + } + } + if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { + opts = append(opts, otlpmetricgrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) + } + if len(otlpConfig.Headers) > 0 { + opts = append(opts, otlpmetricgrpc.WithHeaders(otlpConfig.Headers)) + } + if otlpConfig.TemporalityPreference != nil { + switch *otlpConfig.TemporalityPreference { + case "delta": + opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(deltaTemporality)) + case "cumulative": + opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(cumulativeTemporality)) + case "lowmemory": + opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(lowMemory)) + default: + return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) + } + } + + return otlpmetricgrpc.New(ctx, opts...) +} + +func cumulativeTemporality(sdkmetric.InstrumentKind) metricdata.Temporality { + return metricdata.CumulativeTemporality +} + +func deltaTemporality(ik sdkmetric.InstrumentKind) metricdata.Temporality { + switch ik { + case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram, sdkmetric.InstrumentKindObservableCounter: + return metricdata.DeltaTemporality + default: + return metricdata.CumulativeTemporality + } +} + +func lowMemory(ik sdkmetric.InstrumentKind) metricdata.Temporality { + switch ik { + case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram: + return metricdata.DeltaTemporality + default: + return metricdata.CumulativeTemporality + } +} + +func prometheusReader(ctx context.Context, prometheusConfig *Prometheus) (sdkmetric.Reader, error) { + var opts []otelprom.Option + if prometheusConfig.Host == nil { + return nil, errors.New("host must be specified") + } + if prometheusConfig.Port == nil { + return nil, errors.New("port must be specified") + } + if prometheusConfig.WithoutScopeInfo != nil && *prometheusConfig.WithoutScopeInfo { + opts = append(opts, otelprom.WithoutScopeInfo()) + } + if prometheusConfig.WithoutTypeSuffix != nil && *prometheusConfig.WithoutTypeSuffix { + opts = append(opts, otelprom.WithoutCounterSuffixes()) + } + if prometheusConfig.WithoutUnits != nil && *prometheusConfig.WithoutUnits { + opts = append(opts, otelprom.WithoutUnits()) + } + if prometheusConfig.WithResourceConstantLabels != nil { + if prometheusConfig.WithResourceConstantLabels.Included != nil { + var keys []attribute.Key + for _, val := range prometheusConfig.WithResourceConstantLabels.Included { + keys = append(keys, attribute.Key(val)) + } + otelprom.WithResourceAsConstantLabels(attribute.NewAllowKeysFilter(keys...)) + } + if prometheusConfig.WithResourceConstantLabels.Excluded != nil { + var keys []attribute.Key + for _, val := range prometheusConfig.WithResourceConstantLabels.Included { + keys = append(keys, attribute.Key(val)) + } + otelprom.WithResourceAsConstantLabels(attribute.NewDenyKeysFilter(keys...)) + } + } + + reg := prometheus.NewRegistry() + opts = append(opts, otelprom.WithRegisterer(reg)) + + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) + server := http.Server{ + // Timeouts are necessary to make a server resilient to attacks, but ListenAndServe doesn't set any. + // We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + Handler: mux, + } + addr := fmt.Sprintf("%s:%d", *prometheusConfig.Host, *prometheusConfig.Port) + + reader, err := otelprom.New(opts...) + if err != nil { + return nil, fmt.Errorf("error creating otel prometheus exporter: %w", err) + } + lis, err := net.Listen("tcp", addr) + if err != nil { + return nil, errors.Join( + fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err), + reader.Shutdown(ctx), + ) + } + + go func() { + if err := server.Serve(lis); err != nil && errors.Is(err, http.ErrServerClosed) { + otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err)) + } + }() + + return readerWithServer{reader, &server}, nil +} + +type readerWithServer struct { + sdkmetric.Reader + server *http.Server +} + +func (rws readerWithServer) Shutdown(ctx context.Context) error { + return errors.Join( + rws.Reader.Shutdown(ctx), + rws.server.Shutdown(ctx), + ) +} + +func view(v View) (sdkmetric.View, error) { + if v.Selector == nil { + return nil, errors.New("view: no selector provided") + } + + inst, err := instrument(*v.Selector) + if err != nil { + return nil, err + } + + return sdkmetric.NewView(inst, stream(v.Stream)), nil +} + +func instrument(vs ViewSelector) (sdkmetric.Instrument, error) { + kind, err := instrumentKind(vs.InstrumentType) + if err != nil { + return sdkmetric.Instrument{}, fmt.Errorf("view_selector: %w", err) + } + inst := sdkmetric.Instrument{ + Name: strOrEmpty(vs.InstrumentName), + Unit: strOrEmpty(vs.Unit), + Kind: kind, + Scope: instrumentation.Scope{ + Name: strOrEmpty(vs.MeterName), + Version: strOrEmpty(vs.MeterVersion), + SchemaURL: strOrEmpty(vs.MeterSchemaUrl), + }, + } + + if instrumentIsEmpty(inst) { + return sdkmetric.Instrument{}, errors.New("view_selector: empty selector not supporter") + } + return inst, nil +} + +func stream(vs *ViewStream) sdkmetric.Stream { + if vs == nil { + return sdkmetric.Stream{} + } + + return sdkmetric.Stream{ + Name: strOrEmpty(vs.Name), + Description: strOrEmpty(vs.Description), + Aggregation: aggregation(vs.Aggregation), + AttributeFilter: attributeFilter(vs.AttributeKeys), + } +} + +func attributeFilter(attributeKeys []string) attribute.Filter { + var attrKeys []attribute.Key + for _, attrStr := range attributeKeys { + attrKeys = append(attrKeys, attribute.Key(attrStr)) + } + return attribute.NewAllowKeysFilter(attrKeys...) +} + +func aggregation(aggr *ViewStreamAggregation) sdkmetric.Aggregation { + if aggr == nil { + return nil + } + + if aggr.Base2ExponentialBucketHistogram != nil { + return sdkmetric.AggregationBase2ExponentialHistogram{ + MaxSize: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxSize), + MaxScale: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxScale), + // Need to negate because config has the positive action RecordMinMax. + NoMinMax: !boolOrFalse(aggr.Base2ExponentialBucketHistogram.RecordMinMax), + } + } + if aggr.Default != nil { + // TODO: Understand what to set here. + return nil + } + if aggr.Drop != nil { + return sdkmetric.AggregationDrop{} + } + if aggr.ExplicitBucketHistogram != nil { + return sdkmetric.AggregationExplicitBucketHistogram{ + Boundaries: aggr.ExplicitBucketHistogram.Boundaries, + // Need to negate because config has the positive action RecordMinMax. + NoMinMax: !boolOrFalse(aggr.ExplicitBucketHistogram.RecordMinMax), + } + } + if aggr.LastValue != nil { + return sdkmetric.AggregationLastValue{} + } + if aggr.Sum != nil { + return sdkmetric.AggregationSum{} + } + return nil +} + +func instrumentKind(vsit *ViewSelectorInstrumentType) (sdkmetric.InstrumentKind, error) { + if vsit == nil { + // Equivalent to instrumentKindUndefined. + return instrumentKindUndefined, nil + } + + switch *vsit { + case ViewSelectorInstrumentTypeCounter: + return sdkmetric.InstrumentKindCounter, nil + case ViewSelectorInstrumentTypeUpDownCounter: + return sdkmetric.InstrumentKindUpDownCounter, nil + case ViewSelectorInstrumentTypeHistogram: + return sdkmetric.InstrumentKindHistogram, nil + case ViewSelectorInstrumentTypeObservableCounter: + return sdkmetric.InstrumentKindObservableCounter, nil + case ViewSelectorInstrumentTypeObservableUpDownCounter: + return sdkmetric.InstrumentKindObservableUpDownCounter, nil + case ViewSelectorInstrumentTypeObservableGauge: + return sdkmetric.InstrumentKindObservableGauge, nil + } + + return instrumentKindUndefined, errors.New("instrument_type: invalid value") +} + +func instrumentIsEmpty(i sdkmetric.Instrument) bool { + return i.Name == "" && + i.Description == "" && + i.Kind == instrumentKindUndefined && + i.Unit == "" && + i.Scope == zeroScope +} + +func boolOrFalse(pBool *bool) bool { + if pBool == nil { + return false + } + return *pBool +} + +func int32OrZero(pInt *int) int32 { + if pInt == nil { + return 0 + } + i := *pInt + if i > math.MaxInt32 { + return math.MaxInt32 + } + if i < math.MinInt32 { + return math.MinInt32 + } + return int32(i) // nolint: gosec // Overflow and underflow checked above. +} + +func strOrEmpty(pStr *string) string { + if pStr == nil { + return "" + } + return *pStr +} diff --git a/config/v0.2.0/metric_test.go b/config/v0.2.0/metric_test.go new file mode 100644 index 00000000000..79e706a30cc --- /dev/null +++ b/config/v0.2.0/metric_test.go @@ -0,0 +1,1111 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "context" + "errors" + "net/url" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + otelprom "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/sdk/instrumentation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" +) + +func TestMeterProvider(t *testing.T) { + tests := []struct { + name string + cfg configOptions + wantProvider metric.MeterProvider + wantErr error + }{ + { + name: "no-meter-provider-configured", + wantProvider: noop.NewMeterProvider(), + }, + { + name: "error-in-config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + MeterProvider: &MeterProvider{ + Readers: []MetricReader{ + { + Periodic: &PeriodicMetricReader{}, + Pull: &PullMetricReader{}, + }, + }, + }, + }, + }, + wantProvider: noop.NewMeterProvider(), + wantErr: errors.Join(errors.New("must not specify multiple metric reader type")), + }, + { + name: "multiple-errors-in-config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + MeterProvider: &MeterProvider{ + Readers: []MetricReader{ + { + Periodic: &PeriodicMetricReader{}, + Pull: &PullMetricReader{}, + }, + { + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + Console: Console{}, + OTLP: &OTLPMetric{}, + }, + }, + }, + }, + }, + }, + }, + wantProvider: noop.NewMeterProvider(), + wantErr: errors.Join(errors.New("must not specify multiple metric reader type"), errors.New("must not specify multiple exporters")), + }, + } + for _, tt := range tests { + mp, shutdown, err := meterProvider(tt.cfg, resource.Default()) + require.Equal(t, tt.wantProvider, mp) + assert.Equal(t, tt.wantErr, err) + require.NoError(t, shutdown(context.Background())) + } +} + +func TestReader(t *testing.T) { + consoleExporter, err := stdoutmetric.New( + stdoutmetric.WithPrettyPrint(), + ) + require.NoError(t, err) + ctx := context.Background() + otlpGRPCExporter, err := otlpmetricgrpc.New(ctx) + require.NoError(t, err) + otlpHTTPExporter, err := otlpmetrichttp.New(ctx) + require.NoError(t, err) + promExporter, err := otelprom.New() + require.NoError(t, err) + testCases := []struct { + name string + reader MetricReader + args any + wantErr error + wantReader sdkmetric.Reader + }{ + { + name: "no reader", + wantErr: errors.New("no valid metric reader"), + }, + { + name: "pull/no-exporter", + reader: MetricReader{ + Pull: &PullMetricReader{}, + }, + wantErr: errors.New("no valid metric exporter"), + }, + { + name: "pull/prometheus-no-host", + reader: MetricReader{ + Pull: &PullMetricReader{ + Exporter: MetricExporter{ + Prometheus: &Prometheus{}, + }, + }, + }, + wantErr: errors.New("host must be specified"), + }, + { + name: "pull/prometheus-no-port", + reader: MetricReader{ + Pull: &PullMetricReader{ + Exporter: MetricExporter{ + Prometheus: &Prometheus{ + Host: ptr("localhost"), + }, + }, + }, + }, + wantErr: errors.New("port must be specified"), + }, + { + name: "pull/prometheus", + reader: MetricReader{ + Pull: &PullMetricReader{ + Exporter: MetricExporter{ + Prometheus: &Prometheus{ + Host: ptr("localhost"), + Port: ptr(0), + WithoutScopeInfo: ptr(true), + WithoutUnits: ptr(true), + WithoutTypeSuffix: ptr(true), + WithResourceConstantLabels: &IncludeExclude{ + Included: []string{"include"}, + Excluded: []string{"exclude"}, + }, + }, + }, + }, + }, + wantReader: readerWithServer{promExporter, nil}, + }, + { + name: "periodic/otlp-exporter-invalid-protocol", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/invalid", + }, + }, + }, + }, + wantErr: errors.New("unsupported protocol \"http/invalid\""), + }, + { + name: "periodic/otlp-grpc-exporter", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "http://localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-exporter-with-path", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "http://localhost:4318/path/123", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-exporter-no-endpoint", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-exporter-no-scheme", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-invalid-endpoint", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: " ", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + }, + { + name: "periodic/otlp-grpc-none-compression", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-delta-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("delta"), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-cumulative-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("cumulative"), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-lowmemory-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("lowmemory"), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), + }, + { + name: "periodic/otlp-grpc-invalid-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("invalid"), + }, + }, + }, + }, + wantErr: errors.New("unsupported temporality preference \"invalid\""), + }, + { + name: "periodic/otlp-grpc-invalid-compression", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("invalid"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: errors.New("unsupported compression \"invalid\""), + }, + { + name: "periodic/otlp-http-exporter", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "http://localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-exporter-with-path", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "http://localhost:4318/path/123", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-exporter-no-endpoint", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-exporter-no-scheme", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-invalid-endpoint", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: " ", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + }, + { + name: "periodic/otlp-http-none-compression", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-cumulative-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("cumulative"), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-lowmemory-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("lowmemory"), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-delta-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("delta"), + }, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), + }, + { + name: "periodic/otlp-http-invalid-temporality", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + TemporalityPreference: ptr("invalid"), + }, + }, + }, + }, + wantErr: errors.New("unsupported temporality preference \"invalid\""), + }, + { + name: "periodic/otlp-http-invalid-compression", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + OTLP: &OTLPMetric{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("invalid"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: errors.New("unsupported compression \"invalid\""), + }, + { + name: "periodic/no-exporter", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{}, + }, + }, + wantErr: errors.New("no valid metric exporter"), + }, + { + name: "periodic/console-exporter", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Exporter: MetricExporter{ + Console: Console{}, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader(consoleExporter), + }, + { + name: "periodic/console-exporter-with-extra-options", + reader: MetricReader{ + Periodic: &PeriodicMetricReader{ + Interval: ptr(30_000), + Timeout: ptr(5_000), + Exporter: MetricExporter{ + Console: Console{}, + }, + }, + }, + wantReader: sdkmetric.NewPeriodicReader( + consoleExporter, + sdkmetric.WithInterval(30_000*time.Millisecond), + sdkmetric.WithTimeout(5_000*time.Millisecond), + ), + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, err := metricReader(context.Background(), tt.reader) + require.Equal(t, tt.wantErr, err) + if tt.wantReader == nil { + require.Nil(t, got) + } else { + require.Equal(t, reflect.TypeOf(tt.wantReader), reflect.TypeOf(got)) + var fieldName string + switch reflect.TypeOf(tt.wantReader).String() { + case "*metric.PeriodicReader": + fieldName = "exporter" + case "config.readerWithServer": + fieldName = "Reader" + default: + fieldName = "e" + } + wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantReader)).FieldByName(fieldName).Elem().Type() + gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() + require.Equal(t, wantExporterType.String(), gotExporterType.String()) + require.NoError(t, got.Shutdown(context.Background())) + } + }) + } +} + +func TestView(t *testing.T) { + testCases := []struct { + name string + view View + args any + wantErr string + matchInstrument *sdkmetric.Instrument + wantStream sdkmetric.Stream + wantResult bool + }{ + { + name: "no selector", + wantErr: "view: no selector provided", + }, + { + name: "selector/invalid_type", + view: View{ + Selector: &ViewSelector{ + InstrumentType: (*ViewSelectorInstrumentType)(ptr("invalid_type")), + }, + }, + wantErr: "view_selector: instrument_type: invalid value", + }, + { + name: "selector/invalid_type", + view: View{ + Selector: &ViewSelector{}, + }, + wantErr: "view_selector: empty selector not supporter", + }, + { + name: "all selectors match", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), + Unit: ptr("test_unit"), + MeterName: ptr("test_meter_name"), + MeterVersion: ptr("test_meter_version"), + MeterSchemaUrl: ptr("test_schema_url"), + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "test_name", + Unit: "test_unit", + Kind: sdkmetric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "test_meter_name", + Version: "test_meter_version", + SchemaURL: "test_schema_url", + }, + }, + wantStream: sdkmetric.Stream{Name: "test_name", Unit: "test_unit"}, + wantResult: true, + }, + { + name: "all selectors no match name", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), + Unit: ptr("test_unit"), + MeterName: ptr("test_meter_name"), + MeterVersion: ptr("test_meter_version"), + MeterSchemaUrl: ptr("test_schema_url"), + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "not_match", + Unit: "test_unit", + Kind: sdkmetric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "test_meter_name", + Version: "test_meter_version", + SchemaURL: "test_schema_url", + }, + }, + wantStream: sdkmetric.Stream{}, + wantResult: false, + }, + { + name: "all selectors no match unit", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), + Unit: ptr("test_unit"), + MeterName: ptr("test_meter_name"), + MeterVersion: ptr("test_meter_version"), + MeterSchemaUrl: ptr("test_schema_url"), + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "test_name", + Unit: "not_match", + Kind: sdkmetric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "test_meter_name", + Version: "test_meter_version", + SchemaURL: "test_schema_url", + }, + }, + wantStream: sdkmetric.Stream{}, + wantResult: false, + }, + { + name: "all selectors no match kind", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + InstrumentType: (*ViewSelectorInstrumentType)(ptr("histogram")), + Unit: ptr("test_unit"), + MeterName: ptr("test_meter_name"), + MeterVersion: ptr("test_meter_version"), + MeterSchemaUrl: ptr("test_schema_url"), + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "test_name", + Unit: "test_unit", + Kind: sdkmetric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "test_meter_name", + Version: "test_meter_version", + SchemaURL: "test_schema_url", + }, + }, + wantStream: sdkmetric.Stream{}, + wantResult: false, + }, + { + name: "all selectors no match meter name", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), + Unit: ptr("test_unit"), + MeterName: ptr("test_meter_name"), + MeterVersion: ptr("test_meter_version"), + MeterSchemaUrl: ptr("test_schema_url"), + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "test_name", + Unit: "test_unit", + Kind: sdkmetric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "not_match", + Version: "test_meter_version", + SchemaURL: "test_schema_url", + }, + }, + wantStream: sdkmetric.Stream{}, + wantResult: false, + }, + { + name: "all selectors no match meter version", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), + Unit: ptr("test_unit"), + MeterName: ptr("test_meter_name"), + MeterVersion: ptr("test_meter_version"), + MeterSchemaUrl: ptr("test_schema_url"), + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "test_name", + Unit: "test_unit", + Kind: sdkmetric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "test_meter_name", + Version: "not_match", + SchemaURL: "test_schema_url", + }, + }, + wantStream: sdkmetric.Stream{}, + wantResult: false, + }, + { + name: "all selectors no match meter schema url", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), + Unit: ptr("test_unit"), + MeterName: ptr("test_meter_name"), + MeterVersion: ptr("test_meter_version"), + MeterSchemaUrl: ptr("test_schema_url"), + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "test_name", + Unit: "test_unit", + Kind: sdkmetric.InstrumentKindCounter, + Scope: instrumentation.Scope{ + Name: "test_meter_name", + Version: "test_meter_version", + SchemaURL: "not_match", + }, + }, + wantStream: sdkmetric.Stream{}, + wantResult: false, + }, + { + name: "with stream", + view: View{ + Selector: &ViewSelector{ + InstrumentName: ptr("test_name"), + Unit: ptr("test_unit"), + }, + Stream: &ViewStream{ + Name: ptr("new_name"), + Description: ptr("new_description"), + AttributeKeys: []string{"foo", "bar"}, + Aggregation: &ViewStreamAggregation{Sum: make(ViewStreamAggregationSum)}, + }, + }, + matchInstrument: &sdkmetric.Instrument{ + Name: "test_name", + Description: "test_description", + Unit: "test_unit", + }, + wantStream: sdkmetric.Stream{ + Name: "new_name", + Description: "new_description", + Unit: "test_unit", + Aggregation: sdkmetric.AggregationSum{}, + }, + wantResult: true, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, err := view(tt.view) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + require.Nil(t, got) + } else { + require.NoError(t, err) + gotStream, gotResult := got(*tt.matchInstrument) + // Remove filter, since it cannot be compared + gotStream.AttributeFilter = nil + require.Equal(t, tt.wantStream, gotStream) + require.Equal(t, tt.wantResult, gotResult) + } + }) + } +} + +func TestInstrumentType(t *testing.T) { + testCases := []struct { + name string + instType *ViewSelectorInstrumentType + wantErr error + wantKind sdkmetric.InstrumentKind + }{ + { + name: "nil", + wantKind: sdkmetric.InstrumentKind(0), + }, + { + name: "counter", + instType: (*ViewSelectorInstrumentType)(ptr("counter")), + wantKind: sdkmetric.InstrumentKindCounter, + }, + { + name: "up_down_counter", + instType: (*ViewSelectorInstrumentType)(ptr("up_down_counter")), + wantKind: sdkmetric.InstrumentKindUpDownCounter, + }, + { + name: "histogram", + instType: (*ViewSelectorInstrumentType)(ptr("histogram")), + wantKind: sdkmetric.InstrumentKindHistogram, + }, + { + name: "observable_counter", + instType: (*ViewSelectorInstrumentType)(ptr("observable_counter")), + wantKind: sdkmetric.InstrumentKindObservableCounter, + }, + { + name: "observable_up_down_counter", + instType: (*ViewSelectorInstrumentType)(ptr("observable_up_down_counter")), + wantKind: sdkmetric.InstrumentKindObservableUpDownCounter, + }, + { + name: "observable_gauge", + instType: (*ViewSelectorInstrumentType)(ptr("observable_gauge")), + wantKind: sdkmetric.InstrumentKindObservableGauge, + }, + { + name: "invalid", + instType: (*ViewSelectorInstrumentType)(ptr("invalid")), + wantErr: errors.New("instrument_type: invalid value"), + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, err := instrumentKind(tt.instType) + if tt.wantErr != nil { + require.Equal(t, tt.wantErr, err) + require.Zero(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tt.wantKind, got) + } + }) + } +} + +func TestAggregation(t *testing.T) { + testCases := []struct { + name string + aggregation *ViewStreamAggregation + wantAggregation sdkmetric.Aggregation + }{ + { + name: "nil", + wantAggregation: nil, + }, + { + name: "empty", + aggregation: &ViewStreamAggregation{}, + wantAggregation: nil, + }, + { + name: "Base2ExponentialBucketHistogram empty", + aggregation: &ViewStreamAggregation{ + Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{}, + }, + wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ + MaxSize: 0, + MaxScale: 0, + NoMinMax: true, + }, + }, + { + name: "Base2ExponentialBucketHistogram", + aggregation: &ViewStreamAggregation{ + Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{ + MaxSize: ptr(2), + MaxScale: ptr(3), + RecordMinMax: ptr(true), + }, + }, + wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ + MaxSize: 2, + MaxScale: 3, + NoMinMax: false, + }, + }, + { + name: "Default", + aggregation: &ViewStreamAggregation{ + Default: make(ViewStreamAggregationDefault), + }, + wantAggregation: nil, + }, + { + name: "Drop", + aggregation: &ViewStreamAggregation{ + Drop: make(ViewStreamAggregationDrop), + }, + wantAggregation: sdkmetric.AggregationDrop{}, + }, + { + name: "ExplicitBucketHistogram empty", + aggregation: &ViewStreamAggregation{ + ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{}, + }, + wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ + Boundaries: nil, + NoMinMax: true, + }, + }, + { + name: "ExplicitBucketHistogram", + aggregation: &ViewStreamAggregation{ + ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ + Boundaries: []float64{1, 2, 3}, + RecordMinMax: ptr(true), + }, + }, + wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ + Boundaries: []float64{1, 2, 3}, + NoMinMax: false, + }, + }, + { + name: "LastValue", + aggregation: &ViewStreamAggregation{ + LastValue: make(ViewStreamAggregationLastValue), + }, + wantAggregation: sdkmetric.AggregationLastValue{}, + }, + { + name: "Sum", + aggregation: &ViewStreamAggregation{ + Sum: make(ViewStreamAggregationSum), + }, + wantAggregation: sdkmetric.AggregationSum{}, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got := aggregation(tt.aggregation) + require.Equal(t, tt.wantAggregation, got) + }) + } +} + +func TestAttributeFilter(t *testing.T) { + testCases := []struct { + name string + attributeKeys []string + wantPass []string + wantFail []string + }{ + { + name: "empty", + attributeKeys: []string{}, + wantPass: nil, + wantFail: []string{"foo", "bar"}, + }, + { + name: "filter", + attributeKeys: []string{"foo"}, + wantPass: []string{"foo"}, + wantFail: []string{"bar"}, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got := attributeFilter(tt.attributeKeys) + for _, pass := range tt.wantPass { + require.True(t, got(attribute.KeyValue{Key: attribute.Key(pass), Value: attribute.StringValue("")})) + } + for _, fail := range tt.wantFail { + require.False(t, got(attribute.KeyValue{Key: attribute.Key(fail), Value: attribute.StringValue("")})) + } + }) + } +} diff --git a/config/v0.2.0/resource.go b/config/v0.2.0/resource.go new file mode 100644 index 00000000000..7c24e109f72 --- /dev/null +++ b/config/v0.2.0/resource.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/contrib/config/v0.2.0" + +import ( + "fmt" + "strconv" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource" +) + +func keyVal(k string, v any) attribute.KeyValue { + switch val := v.(type) { + case bool: + return attribute.Bool(k, val) + case int64: + return attribute.Int64(k, val) + case uint64: + return attribute.String(k, strconv.FormatUint(val, 10)) + case float64: + return attribute.Float64(k, val) + case int8: + return attribute.Int64(k, int64(val)) + case uint8: + return attribute.Int64(k, int64(val)) + case int16: + return attribute.Int64(k, int64(val)) + case uint16: + return attribute.Int64(k, int64(val)) + case int32: + return attribute.Int64(k, int64(val)) + case uint32: + return attribute.Int64(k, int64(val)) + case float32: + return attribute.Float64(k, float64(val)) + case int: + return attribute.Int(k, val) + case uint: + return attribute.String(k, strconv.FormatUint(uint64(val), 10)) + case string: + return attribute.String(k, val) + default: + return attribute.String(k, fmt.Sprint(v)) + } +} + +func newResource(res *Resource) (*resource.Resource, error) { + if res == nil || res.Attributes == nil { + return resource.Default(), nil + } + var attrs []attribute.KeyValue + + for k, v := range res.Attributes { + attrs = append(attrs, keyVal(k, v)) + } + + return resource.Merge(resource.Default(), + resource.NewWithAttributes(*res.SchemaUrl, + attrs..., + )) +} diff --git a/config/v0.2.0/resource_test.go b/config/v0.2.0/resource_test.go new file mode 100644 index 00000000000..12c15a843e5 --- /dev/null +++ b/config/v0.2.0/resource_test.go @@ -0,0 +1,116 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" +) + +type mockType struct{} + +func TestNewResource(t *testing.T) { + res, err := resource.Merge(resource.Default(), + resource.NewWithAttributes(semconv.SchemaURL, + semconv.ServiceName("service-a"), + )) + other := mockType{} + require.NoError(t, err) + resWithAttrs, err := resource.Merge(resource.Default(), + resource.NewWithAttributes(semconv.SchemaURL, + semconv.ServiceName("service-a"), + attribute.Bool("attr-bool", true), + attribute.String("attr-uint64", fmt.Sprintf("%d", 164)), + attribute.Int64("attr-int64", int64(-164)), + attribute.Float64("attr-float64", float64(64.0)), + attribute.Int64("attr-int8", int64(-18)), + attribute.Int64("attr-uint8", int64(18)), + attribute.Int64("attr-int16", int64(-116)), + attribute.Int64("attr-uint16", int64(116)), + attribute.Int64("attr-int32", int64(-132)), + attribute.Int64("attr-uint32", int64(132)), + attribute.Float64("attr-float32", float64(32.0)), + attribute.Int64("attr-int", int64(-1)), + attribute.String("attr-uint", fmt.Sprintf("%d", 1)), + attribute.String("attr-string", "string-val"), + attribute.String("attr-default", fmt.Sprintf("%v", other)), + )) + require.NoError(t, err) + tests := []struct { + name string + config *Resource + wantResource *resource.Resource + wantErr error + }{ + { + name: "no-resource-configuration", + wantResource: resource.Default(), + }, + { + name: "resource-no-attributes", + config: &Resource{}, + wantResource: resource.Default(), + }, + { + name: "resource-with-attributes-invalid-schema", + config: &Resource{ + SchemaUrl: ptr("https://opentelemetry.io/invalid-schema"), + Attributes: Attributes{ + "service.name": "service-a", + }, + }, + wantResource: resource.NewSchemaless(res.Attributes()...), + wantErr: resource.ErrSchemaURLConflict, + }, + { + name: "resource-with-attributes-and-schema", + config: &Resource{ + Attributes: Attributes{ + "service.name": "service-a", + }, + SchemaUrl: ptr(semconv.SchemaURL), + }, + wantResource: res, + }, + { + name: "resource-with-additional-attributes-and-schema", + config: &Resource{ + Attributes: Attributes{ + "service.name": "service-a", + "attr-bool": true, + "attr-int64": int64(-164), + "attr-uint64": uint64(164), + "attr-float64": float64(64.0), + "attr-int8": int8(-18), + "attr-uint8": uint8(18), + "attr-int16": int16(-116), + "attr-uint16": uint16(116), + "attr-int32": int32(-132), + "attr-uint32": uint32(132), + "attr-float32": float32(32.0), + "attr-int": int(-1), + "attr-uint": uint(1), + "attr-string": "string-val", + "attr-default": other, + }, + SchemaUrl: ptr(semconv.SchemaURL), + }, + wantResource: resWithAttrs, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newResource(tt.config) + assert.ErrorIs(t, err, tt.wantErr) + assert.Equal(t, tt.wantResource, got) + }) + } +} diff --git a/config/v0.2.0/trace.go b/config/v0.2.0/trace.go new file mode 100644 index 00000000000..c639c174328 --- /dev/null +++ b/config/v0.2.0/trace.go @@ -0,0 +1,197 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/contrib/config/v0.2.0" + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) { + if cfg.opentelemetryConfig.TracerProvider == nil { + return noop.NewTracerProvider(), noopShutdown, nil + } + opts := []sdktrace.TracerProviderOption{ + sdktrace.WithResource(res), + } + var errs []error + for _, processor := range cfg.opentelemetryConfig.TracerProvider.Processors { + sp, err := spanProcessor(cfg.ctx, processor) + if err == nil { + opts = append(opts, sdktrace.WithSpanProcessor(sp)) + } else { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return noop.NewTracerProvider(), noopShutdown, errors.Join(errs...) + } + tp := sdktrace.NewTracerProvider(opts...) + return tp, tp.Shutdown, nil +} + +func spanExporter(ctx context.Context, exporter SpanExporter) (sdktrace.SpanExporter, error) { + if exporter.Console != nil && exporter.OTLP != nil { + return nil, errors.New("must not specify multiple exporters") + } + + if exporter.Console != nil { + return stdouttrace.New( + stdouttrace.WithPrettyPrint(), + ) + } + if exporter.OTLP != nil { + switch exporter.OTLP.Protocol { + case protocolProtobufHTTP: + return otlpHTTPSpanExporter(ctx, exporter.OTLP) + case protocolProtobufGRPC: + return otlpGRPCSpanExporter(ctx, exporter.OTLP) + default: + return nil, fmt.Errorf("unsupported protocol %q", exporter.OTLP.Protocol) + } + } + return nil, errors.New("no valid span exporter") +} + +func spanProcessor(ctx context.Context, processor SpanProcessor) (sdktrace.SpanProcessor, error) { + if processor.Batch != nil && processor.Simple != nil { + return nil, errors.New("must not specify multiple span processor type") + } + if processor.Batch != nil { + exp, err := spanExporter(ctx, processor.Batch.Exporter) + if err != nil { + return nil, err + } + return batchSpanProcessor(processor.Batch, exp) + } + if processor.Simple != nil { + exp, err := spanExporter(ctx, processor.Simple.Exporter) + if err != nil { + return nil, err + } + return sdktrace.NewSimpleSpanProcessor(exp), nil + } + return nil, errors.New("unsupported span processor type, must be one of simple or batch") +} + +func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { + var opts []otlptracegrpc.Option + + if len(otlpConfig.Endpoint) > 0 { + u, err := url.ParseRequestURI(otlpConfig.Endpoint) + if err != nil { + return nil, err + } + // ParseRequestURI leaves the Host field empty when no + // scheme is specified (i.e. localhost:4317). This check is + // here to support the case where a user may not specify a + // scheme. The code does its best effort here by using + // otlpConfig.Endpoint as-is in that case. + if u.Host != "" { + opts = append(opts, otlptracegrpc.WithEndpoint(u.Host)) + } else { + opts = append(opts, otlptracegrpc.WithEndpoint(otlpConfig.Endpoint)) + } + + if u.Scheme == "http" { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + } + + if otlpConfig.Compression != nil { + switch *otlpConfig.Compression { + case compressionGzip: + opts = append(opts, otlptracegrpc.WithCompressor(*otlpConfig.Compression)) + case compressionNone: + // none requires no options + default: + return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) + } + } + if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { + opts = append(opts, otlptracegrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) + } + if len(otlpConfig.Headers) > 0 { + opts = append(opts, otlptracegrpc.WithHeaders(otlpConfig.Headers)) + } + + return otlptracegrpc.New(ctx, opts...) +} + +func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { + var opts []otlptracehttp.Option + + if len(otlpConfig.Endpoint) > 0 { + u, err := url.ParseRequestURI(otlpConfig.Endpoint) + if err != nil { + return nil, err + } + opts = append(opts, otlptracehttp.WithEndpoint(u.Host)) + + if u.Scheme == "http" { + opts = append(opts, otlptracehttp.WithInsecure()) + } + if len(u.Path) > 0 { + opts = append(opts, otlptracehttp.WithURLPath(u.Path)) + } + } + if otlpConfig.Compression != nil { + switch *otlpConfig.Compression { + case compressionGzip: + opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.GzipCompression)) + case compressionNone: + opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.NoCompression)) + default: + return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) + } + } + if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { + opts = append(opts, otlptracehttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) + } + if len(otlpConfig.Headers) > 0 { + opts = append(opts, otlptracehttp.WithHeaders(otlpConfig.Headers)) + } + + return otlptracehttp.New(ctx, opts...) +} + +func batchSpanProcessor(bsp *BatchSpanProcessor, exp sdktrace.SpanExporter) (sdktrace.SpanProcessor, error) { + var opts []sdktrace.BatchSpanProcessorOption + if bsp.ExportTimeout != nil { + if *bsp.ExportTimeout < 0 { + return nil, fmt.Errorf("invalid export timeout %d", *bsp.ExportTimeout) + } + opts = append(opts, sdktrace.WithExportTimeout(time.Millisecond*time.Duration(*bsp.ExportTimeout))) + } + if bsp.MaxExportBatchSize != nil { + if *bsp.MaxExportBatchSize < 0 { + return nil, fmt.Errorf("invalid batch size %d", *bsp.MaxExportBatchSize) + } + opts = append(opts, sdktrace.WithMaxExportBatchSize(*bsp.MaxExportBatchSize)) + } + if bsp.MaxQueueSize != nil { + if *bsp.MaxQueueSize < 0 { + return nil, fmt.Errorf("invalid queue size %d", *bsp.MaxQueueSize) + } + opts = append(opts, sdktrace.WithMaxQueueSize(*bsp.MaxQueueSize)) + } + if bsp.ScheduleDelay != nil { + if *bsp.ScheduleDelay < 0 { + return nil, fmt.Errorf("invalid schedule delay %d", *bsp.ScheduleDelay) + } + opts = append(opts, sdktrace.WithBatchTimeout(time.Millisecond*time.Duration(*bsp.ScheduleDelay))) + } + return sdktrace.NewBatchSpanProcessor(exp, opts...), nil +} diff --git a/config/v0.2.0/trace_test.go b/config/v0.2.0/trace_test.go new file mode 100644 index 00000000000..4f4a197770e --- /dev/null +++ b/config/v0.2.0/trace_test.go @@ -0,0 +1,535 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "context" + "errors" + "net/url" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +func TestTracerPovider(t *testing.T) { + tests := []struct { + name string + cfg configOptions + wantProvider trace.TracerProvider + wantErr error + }{ + { + name: "no-tracer-provider-configured", + wantProvider: noop.NewTracerProvider(), + }, + { + name: "error-in-config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + TracerProvider: &TracerProvider{ + Processors: []SpanProcessor{ + { + Batch: &BatchSpanProcessor{}, + Simple: &SimpleSpanProcessor{}, + }, + }, + }, + }, + }, + wantProvider: noop.NewTracerProvider(), + wantErr: errors.Join(errors.New("must not specify multiple span processor type")), + }, + { + name: "multiple-errors-in-config", + cfg: configOptions{ + opentelemetryConfig: OpenTelemetryConfiguration{ + TracerProvider: &TracerProvider{ + Processors: []SpanProcessor{ + { + Batch: &BatchSpanProcessor{}, + Simple: &SimpleSpanProcessor{}, + }, + { + Simple: &SimpleSpanProcessor{ + Exporter: SpanExporter{ + Console: Console{}, + OTLP: &OTLP{}, + }, + }, + }, + }, + }, + }, + }, + wantProvider: noop.NewTracerProvider(), + wantErr: errors.Join(errors.New("must not specify multiple span processor type"), errors.New("must not specify multiple exporters")), + }, + } + for _, tt := range tests { + tp, shutdown, err := tracerProvider(tt.cfg, resource.Default()) + require.Equal(t, tt.wantProvider, tp) + assert.Equal(t, tt.wantErr, err) + require.NoError(t, shutdown(context.Background())) + } +} + +func TestSpanProcessor(t *testing.T) { + consoleExporter, err := stdouttrace.New( + stdouttrace.WithPrettyPrint(), + ) + require.NoError(t, err) + ctx := context.Background() + otlpGRPCExporter, err := otlptracegrpc.New(ctx) + require.NoError(t, err) + otlpHTTPExporter, err := otlptracehttp.New(ctx) + require.NoError(t, err) + testCases := []struct { + name string + processor SpanProcessor + args any + wantErr error + wantProcessor sdktrace.SpanProcessor + }{ + { + name: "no processor", + wantErr: errors.New("unsupported span processor type, must be one of simple or batch"), + }, + { + name: "multiple processor types", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{}, + }, + Simple: &SimpleSpanProcessor{}, + }, + wantErr: errors.New("must not specify multiple span processor type"), + }, + { + name: "batch processor invalid exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{}, + }, + }, + wantErr: errors.New("no valid span exporter"), + }, + { + name: "batch processor invalid batch size console exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(-1), + Exporter: SpanExporter{ + Console: Console{}, + }, + }, + }, + wantErr: errors.New("invalid batch size -1"), + }, + { + name: "batch processor invalid export timeout console exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + ExportTimeout: ptr(-2), + Exporter: SpanExporter{ + Console: Console{}, + }, + }, + }, + wantErr: errors.New("invalid export timeout -2"), + }, + { + name: "batch processor invalid queue size console exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxQueueSize: ptr(-3), + Exporter: SpanExporter{ + Console: Console{}, + }, + }, + }, + wantErr: errors.New("invalid queue size -3"), + }, + { + name: "batch processor invalid schedule delay console exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + ScheduleDelay: ptr(-4), + Exporter: SpanExporter{ + Console: Console{}, + }, + }, + }, + wantErr: errors.New("invalid schedule delay -4"), + }, + { + name: "batch processor with multiple exporters", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + Exporter: SpanExporter{ + Console: Console{}, + OTLP: &OTLP{}, + }, + }, + }, + wantErr: errors.New("must not specify multiple exporters"), + }, + { + name: "batch processor console exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + Console: Console{}, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(consoleExporter), + }, + { + name: "batch/otlp-exporter-invalid-protocol", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/invalid", + }, + }, + }, + }, + wantErr: errors.New("unsupported protocol \"http/invalid\""), + }, + { + name: "batch/otlp-grpc-exporter-no-endpoint", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "grpc/protobuf", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), + }, + { + name: "batch/otlp-grpc-exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "grpc/protobuf", + Endpoint: "http://localhost:4317", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), + }, + { + name: "batch/otlp-grpc-exporter-no-scheme", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4317", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), + }, + { + name: "batch/otlp-grpc-invalid-endpoint", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "grpc/protobuf", + Endpoint: " ", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + }, + { + name: "batch/otlp-grpc-invalid-compression", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "grpc/protobuf", + Endpoint: "localhost:4317", + Compression: ptr("invalid"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: errors.New("unsupported compression \"invalid\""), + }, + { + name: "batch/otlp-http-exporter", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "http://localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-exporter-with-path", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "http://localhost:4318/path/123", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-exporter-no-endpoint", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-exporter-no-scheme", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-invalid-endpoint", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: " ", + Compression: ptr("gzip"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, + }, + { + name: "batch/otlp-http-none-compression", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("none"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), + }, + { + name: "batch/otlp-http-invalid-compression", + processor: SpanProcessor{ + Batch: &BatchSpanProcessor{ + MaxExportBatchSize: ptr(0), + ExportTimeout: ptr(0), + MaxQueueSize: ptr(0), + ScheduleDelay: ptr(0), + Exporter: SpanExporter{ + OTLP: &OTLP{ + Protocol: "http/protobuf", + Endpoint: "localhost:4318", + Compression: ptr("invalid"), + Timeout: ptr(1000), + Headers: map[string]string{ + "test": "test1", + }, + }, + }, + }, + }, + wantErr: errors.New("unsupported compression \"invalid\""), + }, + { + name: "simple/no-exporter", + processor: SpanProcessor{ + Simple: &SimpleSpanProcessor{ + Exporter: SpanExporter{}, + }, + }, + wantErr: errors.New("no valid span exporter"), + }, + { + name: "simple/console-exporter", + processor: SpanProcessor{ + Simple: &SimpleSpanProcessor{ + Exporter: SpanExporter{ + Console: Console{}, + }, + }, + }, + wantProcessor: sdktrace.NewSimpleSpanProcessor(consoleExporter), + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, err := spanProcessor(context.Background(), tt.processor) + require.Equal(t, tt.wantErr, err) + if tt.wantProcessor == nil { + require.Nil(t, got) + } else { + require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) + var fieldName string + switch reflect.TypeOf(tt.wantProcessor).String() { + case "*trace.simpleSpanProcessor": + fieldName = "exporter" + default: + fieldName = "e" + } + wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName(fieldName).Elem().Type() + gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() + require.Equal(t, wantExporterType.String(), gotExporterType.String()) + } + }) + } +} diff --git a/config/config.go b/config/v0.3.0/config.go similarity index 98% rename from config/config.go rename to config/v0.3.0/config.go index 80d72c9364d..44030d4f612 100644 --- a/config/config.go +++ b/config/v0.3.0/config.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" import ( "context" diff --git a/config/config_json.go b/config/v0.3.0/config_json.go similarity index 99% rename from config/config_json.go rename to config/v0.3.0/config_json.go index a42e1d28601..f350067280d 100644 --- a/config/config_json.go +++ b/config/v0.3.0/config_json.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" import ( "encoding/json" diff --git a/config/config_test.go b/config/v0.3.0/config_test.go similarity index 99% rename from config/config_test.go rename to config/v0.3.0/config_test.go index cdc3ddcd45b..0f7822bd8bb 100644 --- a/config/config_test.go +++ b/config/v0.3.0/config_test.go @@ -424,7 +424,7 @@ func TestParseYAML(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b, err := os.ReadFile(filepath.Join("testdata", tt.input)) + b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) require.NoError(t, err) got, err := ParseYAML(b) @@ -473,7 +473,7 @@ func TestSerializeJSON(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b, err := os.ReadFile(filepath.Join("testdata", tt.input)) + b, err := os.ReadFile(filepath.Join("..", "testdata", tt.input)) require.NoError(t, err) var got OpenTelemetryConfiguration diff --git a/config/config_yaml.go b/config/v0.3.0/config_yaml.go similarity index 93% rename from config/config_yaml.go rename to config/v0.3.0/config_yaml.go index 88234178880..94af68e6f98 100644 --- a/config/config_yaml.go +++ b/config/v0.3.0/config_yaml.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" import ( "fmt" diff --git a/config/generated_config.go b/config/v0.3.0/generated_config.go similarity index 100% rename from config/generated_config.go rename to config/v0.3.0/generated_config.go diff --git a/config/log.go b/config/v0.3.0/log.go similarity index 98% rename from config/log.go rename to config/v0.3.0/log.go index 81a769238cd..40ad54dbfd1 100644 --- a/config/log.go +++ b/config/v0.3.0/log.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" import ( "context" diff --git a/config/log_test.go b/config/v0.3.0/log_test.go similarity index 99% rename from config/log_test.go rename to config/v0.3.0/log_test.go index 3e1efe11b3a..f04d521eb50 100644 --- a/config/log_test.go +++ b/config/v0.3.0/log_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config import ( "context" diff --git a/config/metric.go b/config/v0.3.0/metric.go similarity index 99% rename from config/metric.go rename to config/v0.3.0/metric.go index de5629a7edb..c551a5b91fe 100644 --- a/config/metric.go +++ b/config/v0.3.0/metric.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" import ( "context" diff --git a/config/metric_test.go b/config/v0.3.0/metric_test.go similarity index 99% rename from config/metric_test.go rename to config/v0.3.0/metric_test.go index 07defc008bb..b6c6d095fa8 100644 --- a/config/metric_test.go +++ b/config/v0.3.0/metric_test.go @@ -150,7 +150,7 @@ func TestReader(t *testing.T) { Exporter: PullMetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), - Port: ptr(8888), + Port: ptr(0), WithoutScopeInfo: ptr(true), WithoutUnits: ptr(true), WithoutTypeSuffix: ptr(true), diff --git a/config/resource.go b/config/v0.3.0/resource.go similarity index 95% rename from config/resource.go rename to config/v0.3.0/resource.go index d2b5ed3ac9a..4983374aa6a 100644 --- a/config/resource.go +++ b/config/v0.3.0/resource.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" import ( "fmt" diff --git a/config/resource_test.go b/config/v0.3.0/resource_test.go similarity index 97% rename from config/resource_test.go rename to config/v0.3.0/resource_test.go index 5a9f756f2b8..3a80e7c280a 100644 --- a/config/resource_test.go +++ b/config/v0.3.0/resource_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config import ( "fmt" diff --git a/config/trace.go b/config/v0.3.0/trace.go similarity index 98% rename from config/trace.go rename to config/v0.3.0/trace.go index 1b08b2c56ae..f1e6552cde3 100644 --- a/config/trace.go +++ b/config/v0.3.0/trace.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package config // import "go.opentelemetry.io/contrib/config" +package config // import "go.opentelemetry.io/contrib/config/v0.3.0" import ( "context" diff --git a/config/trace_test.go b/config/v0.3.0/trace_test.go similarity index 100% rename from config/trace_test.go rename to config/v0.3.0/trace_test.go From a761314bf5ad8fa17921aa1e59e46c1689ebfc29 Mon Sep 17 00:00:00 2001 From: Ivan Santos <301291+pragmaticivan@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:49:34 -0600 Subject: [PATCH 05/12] feat(otelaws): add sns attribute instrumentation (#6388) **Notes:** * Only the `semconv/v1.27.0` was available in the latest published version of the otel module. * The current project setup doesn't take into consideration all the various hooks (`m.initializeMiddlewareBefore, m.initializeMiddlewareAfter, m.finalizeMiddlewareAfter, m.deserializeMiddleware`). All attributes are currently being set on `initializeMiddlewareAfter`. * Ref: https://opentelemetry.io/docs/specs/semconv/attributes-registry/messaging/ * Added changelog Co-authored-by: Tyler Yahn --- CHANGELOG.md | 1 + .../aws-lambda-go/otellambda/example/go.mod | 1 + .../aws-lambda-go/otellambda/example/go.sum | 2 + .../aws/aws-sdk-go-v2/otelaws/attributes.go | 2 + .../aws/aws-sdk-go-v2/otelaws/example/go.mod | 1 + .../aws/aws-sdk-go-v2/otelaws/example/go.sum | 2 + .../aws/aws-sdk-go-v2/otelaws/go.mod | 1 + .../aws/aws-sdk-go-v2/otelaws/go.sum | 2 + .../aws-sdk-go-v2/otelaws/snsattributes.go | 47 ++++++++++++++ .../otelaws/snsattributes_test.go | 62 +++++++++++++++++++ .../aws/aws-sdk-go-v2/otelaws/test/go.mod | 1 + .../aws/aws-sdk-go-v2/otelaws/test/go.sum | 2 + 12 files changed, 124 insertions(+) create mode 100644 instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes.go create mode 100644 instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 552756be8d7..7a27860223f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Added support exporting logs via OTLP over gRPC in `go.opentelemetry.io/contrib/config`. (#6340) - The `go.opentelemetry.io/contrib/bridges/otellogr` module. This module provides an OpenTelemetry logging bridge for `github.com/go-logr/logr`. (#6386) +- Added SNS instrumentation in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6388) ### Changed diff --git a/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.mod b/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.mod index 203dba246a5..6d48e334cdb 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.mod +++ b/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.mod @@ -37,6 +37,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect diff --git a/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.sum b/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.sum index 4e7028506f5..3af883e9cb0 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.sum +++ b/instrumentation/github.com/aws/aws-lambda-go/otellambda/example/go.sum @@ -32,6 +32,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlm github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 h1:N3o8mXK6/MP24BtD9sb51omEO9J9cgPM3Ughc293dZc= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7/go.mod h1:AAHZydTB8/V2zn3WNwjLXBK1RAcSEpDNmFfrmjvrJQg= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 h1:mFLfxLZB/TVQwNJAYox4WaxpIu+dFVIcExrmRmRCOhw= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2/go.mod h1:GnvfTdlvcpD+or3oslHPOn4Mu6KaCwlCp+0p0oqWnrM= github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/attributes.go b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/attributes.go index 5a00c7402ce..daf8c5f7cfe 100644 --- a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/attributes.go +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/attributes.go @@ -8,6 +8,7 @@ import ( v2Middleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/sns" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" @@ -25,6 +26,7 @@ const ( var servicemap = map[string]AttributeSetter{ dynamodb.ServiceID: DynamoDBAttributeSetter, sqs.ServiceID: SQSAttributeSetter, + sns.ServiceID: SNSAttributeSetter, } // SystemAttr return the AWS RPC system attribute. diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.mod b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.mod index b970583c894..897895c4032 100644 --- a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.mod +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.mod @@ -29,6 +29,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.sum b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.sum index 796dbd1cbaf..13243c095ca 100644 --- a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.sum +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example/go.sum @@ -30,6 +30,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlm github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 h1:N3o8mXK6/MP24BtD9sb51omEO9J9cgPM3Ughc293dZc= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7/go.mod h1:AAHZydTB8/V2zn3WNwjLXBK1RAcSEpDNmFfrmjvrJQg= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 h1:mFLfxLZB/TVQwNJAYox4WaxpIu+dFVIcExrmRmRCOhw= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2/go.mod h1:GnvfTdlvcpD+or3oslHPOn4Mu6KaCwlCp+0p0oqWnrM= github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.mod b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.mod index 9f0ded5d80f..fe1e86193bf 100644 --- a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.mod +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.mod @@ -5,6 +5,7 @@ go 1.22 require ( github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0 + github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 github.com/aws/smithy-go v1.22.1 github.com/stretchr/testify v1.10.0 diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.sum b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.sum index 254ddb945cf..856710f531a 100644 --- a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.sum +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/go.sum @@ -10,6 +10,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhv github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 h1:nbmKXZzXPJn41CcD4HsHsGWqvKjLKz9kWu6XxvLmf1s= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6/go.mod h1:SJhcisfKfAawsdNQoZMBEjg+vyN2lH6rO6fP+T94z5Y= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 h1:N3o8mXK6/MP24BtD9sb51omEO9J9cgPM3Ughc293dZc= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7/go.mod h1:AAHZydTB8/V2zn3WNwjLXBK1RAcSEpDNmFfrmjvrJQg= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 h1:mFLfxLZB/TVQwNJAYox4WaxpIu+dFVIcExrmRmRCOhw= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2/go.mod h1:GnvfTdlvcpD+or3oslHPOn4Mu6KaCwlCp+0p0oqWnrM= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes.go b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes.go new file mode 100644 index 00000000000..03c0405804f --- /dev/null +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" + +import ( + "context" + "strings" + + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/aws/smithy-go/middleware" + + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" +) + +// SNSAttributeSetter sets SNS specific attributes depending on the SNS operation is being performed. +func SNSAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue { + snsAttributes := []attribute.KeyValue{semconv.MessagingSystemKey.String("aws_sns")} + + switch v := in.Parameters.(type) { + case *sns.PublishBatchInput: + snsAttributes = append(snsAttributes, + semconv.MessagingDestinationName(extractDestinationName(v.TopicArn, nil)), + semconv.MessagingOperationTypePublish, + semconv.MessagingOperationName("publish_batch_input"), + semconv.MessagingBatchMessageCount(len(v.PublishBatchRequestEntries)), + ) + case *sns.PublishInput: + snsAttributes = append(snsAttributes, + semconv.MessagingDestinationName(extractDestinationName(v.TopicArn, v.TargetArn)), + semconv.MessagingOperationTypePublish, + semconv.MessagingOperationName("publish_input"), + ) + } + + return snsAttributes +} + +func extractDestinationName(topicArn, targetArn *string) string { + if topicArn != nil && *topicArn != "" { + return (*topicArn)[strings.LastIndex(*topicArn, ":")+1:] + } else if targetArn != nil && *targetArn != "" { + return (*targetArn)[strings.LastIndex(*targetArn, ":")+1:] + } + return "" +} diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes_test.go b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes_test.go new file mode 100644 index 00000000000..fcf319b8361 --- /dev/null +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/snsattributes_test.go @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelaws + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/aws/aws-sdk-go-v2/service/sns/types" + "github.com/aws/smithy-go/middleware" + "github.com/stretchr/testify/assert" + + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" +) + +func TestPublishInput(t *testing.T) { + input := middleware.InitializeInput{ + Parameters: &sns.PublishInput{ + TopicArn: aws.String("arn:aws:sns:us-east-2:444455556666:my-topic"), + }, + } + + attributes := SNSAttributeSetter(context.Background(), input) + + assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns")) + assert.Contains(t, attributes, semconv.MessagingDestinationName("my-topic")) + assert.Contains(t, attributes, semconv.MessagingOperationName("publish_input")) + assert.Contains(t, attributes, semconv.MessagingOperationTypePublish) +} + +func TestPublishInputWithNoDestination(t *testing.T) { + input := middleware.InitializeInput{ + Parameters: &sns.PublishInput{}, + } + + attributes := SNSAttributeSetter(context.Background(), input) + + assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns")) + assert.Contains(t, attributes, semconv.MessagingDestinationName("")) + assert.Contains(t, attributes, semconv.MessagingOperationName("publish_input")) + assert.Contains(t, attributes, semconv.MessagingOperationTypePublish) +} + +func TestPublishBatchInput(t *testing.T) { + input := middleware.InitializeInput{ + Parameters: &sns.PublishBatchInput{ + TopicArn: aws.String("arn:aws:sns:us-east-2:444455556666:my-topic-batch"), + PublishBatchRequestEntries: []types.PublishBatchRequestEntry{}, + }, + } + + attributes := SNSAttributeSetter(context.Background(), input) + + assert.Contains(t, attributes, semconv.MessagingSystemKey.String("aws_sns")) + assert.Contains(t, attributes, semconv.MessagingDestinationName("my-topic-batch")) + assert.Contains(t, attributes, semconv.MessagingOperationName("publish_batch_input")) + assert.Contains(t, attributes, semconv.MessagingOperationTypePublish) + assert.Contains(t, attributes, semconv.MessagingBatchMessageCount(0)) +} diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.mod b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.mod index a738c4af35f..fcc3e51936e 100644 --- a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.mod +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.mod @@ -19,6 +19,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.sum b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.sum index c30e301c36c..33cb0af59c3 100644 --- a/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.sum +++ b/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test/go.sum @@ -12,6 +12,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 h1:nbmK github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6/go.mod h1:SJhcisfKfAawsdNQoZMBEjg+vyN2lH6rO6fP+T94z5Y= github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3 h1:pDBrvz7CMK381q5U+nPqtSQZZid5z1XH8lsI6kHNcSY= github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3/go.mod h1:rDMeB13C/RS0/zw68RQD4LLiWChf5tZBKjEQmjtHa/c= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7 h1:N3o8mXK6/MP24BtD9sb51omEO9J9cgPM3Ughc293dZc= +github.com/aws/aws-sdk-go-v2/service/sns v1.33.7/go.mod h1:AAHZydTB8/V2zn3WNwjLXBK1RAcSEpDNmFfrmjvrJQg= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2 h1:mFLfxLZB/TVQwNJAYox4WaxpIu+dFVIcExrmRmRCOhw= github.com/aws/aws-sdk-go-v2/service/sqs v1.37.2/go.mod h1:GnvfTdlvcpD+or3oslHPOn4Mu6KaCwlCp+0p0oqWnrM= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= From 637e1e5c5bc4723d0d90c83218c4ed43fabc1d5f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:01:36 -0800 Subject: [PATCH 06/12] fix(deps): update golang.org/x/exp digest to 1443442 (#6417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | golang.org/x/exp | require | digest | `43b7b7c` -> `1443442` | --- ### Configuration ๐Ÿ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). ๐Ÿšฆ **Automerge**: Disabled by config. Please merge this manually once you are satisfied. โ™ป **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. ๐Ÿ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/open-telemetry/opentelemetry-go-contrib). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 47bf9b57a9d..6c49caa9f8e 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -14,7 +14,7 @@ require ( go.opentelemetry.io/build-tools/crosslink v0.15.0 go.opentelemetry.io/build-tools/gotmpl v0.15.0 go.opentelemetry.io/build-tools/multimod v0.15.0 - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d + golang.org/x/exp v0.0.0-20241210172134-14434422244c golang.org/x/tools v0.28.0 golang.org/x/vuln v1.1.3 ) diff --git a/tools/go.sum b/tools/go.sum index 366a96fb03c..1cfcf7fdbbe 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -518,8 +518,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241210172134-14434422244c h1:G0f8LmhCW7rzpybldgSjhhKDCwW7mYO0Qr6HZDb0HJA= +golang.org/x/exp v0.0.0-20241210172134-14434422244c/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= From 5139bd78a1ffaeaecc3ebb275ee151f253f168e0 Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Tue, 10 Dec 2024 20:16:40 +0100 Subject: [PATCH 07/12] Switch the otelhttp semconv tests to use metricdatatest (#6400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This switches the otelhttp semconv tests to use the [metricdatatest](https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest) package rather than a custom struct with a partial metrics implementation. This is meant to make it easier to start implementing the client and server metrics. --------- Co-authored-by: Robert Pajฤ…k Co-authored-by: Tyler Yahn --- .../otelhttp/internal/semconv/common_test.go | 155 --------- .../net/http/otelhttp/internal/semconv/env.go | 28 +- .../otelhttp/internal/semconv/env_test.go | 42 --- .../otelhttp/internal/semconv/httpconv.go | 34 +- .../internal/semconv/httpconv_test.go | 257 +++++---------- .../internal/semconv/test/common_test.go | 68 ++++ .../otelhttp/internal/semconv/test/go.mod | 25 ++ .../otelhttp/internal/semconv/test/go.sum | 31 ++ .../internal/semconv/test/httpconv_test.go | 192 +++++++++++ .../internal/semconv/test/v1.20.0_test.go | 308 ++++++++++++++++++ .../http/otelhttp/internal/semconv/util.go | 4 +- .../otelhttp/internal/semconv/util_test.go | 2 +- .../http/otelhttp/internal/semconv/v1.20.0.go | 32 +- .../otelhttp/internal/semconv/v1.20.0_test.go | 188 ----------- 14 files changed, 758 insertions(+), 608 deletions(-) delete mode 100644 instrumentation/net/http/otelhttp/internal/semconv/common_test.go create mode 100644 instrumentation/net/http/otelhttp/internal/semconv/test/common_test.go create mode 100644 instrumentation/net/http/otelhttp/internal/semconv/test/go.mod create mode 100644 instrumentation/net/http/otelhttp/internal/semconv/test/go.sum create mode 100644 instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go create mode 100644 instrumentation/net/http/otelhttp/internal/semconv/test/v1.20.0_test.go diff --git a/instrumentation/net/http/otelhttp/internal/semconv/common_test.go b/instrumentation/net/http/otelhttp/internal/semconv/common_test.go deleted file mode 100644 index c6b373b6c37..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconv/common_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -type testServerReq struct { - hostname string - serverPort int - peerAddr string - peerPort int - clientIP string -} - -func testTraceRequest(t *testing.T, serv HTTPServer, want func(testServerReq) []attribute.KeyValue) { - t.Helper() - - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - close(got) - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) - - const user = "alice" - req.SetBasicAuth(user, "pswrd") - - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) - - srvReq := testServerReq{ - hostname: srvURL.Hostname(), - serverPort: int(srvPort), - peerAddr: peer, - peerPort: peerPort, - clientIP: clientIP, - } - - assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req)) -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClient{}.Status(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/env.go b/instrumentation/net/http/otelhttp/internal/semconv/env.go index fec528536a5..bbbfeddfc21 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/env.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/env.go @@ -50,9 +50,9 @@ type HTTPServer struct { // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { if s.duplicate { - return append(oldHTTPServer{}.RequestTraceAttrs(server, req), newHTTPServer{}.RequestTraceAttrs(server, req)...) + return append(OldHTTPServer{}.RequestTraceAttrs(server, req), CurrentHTTPServer{}.RequestTraceAttrs(server, req)...) } - return oldHTTPServer{}.RequestTraceAttrs(server, req) + return OldHTTPServer{}.RequestTraceAttrs(server, req) } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. @@ -60,14 +60,14 @@ func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request) []attrib // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { if s.duplicate { - return append(oldHTTPServer{}.ResponseTraceAttrs(resp), newHTTPServer{}.ResponseTraceAttrs(resp)...) + return append(OldHTTPServer{}.ResponseTraceAttrs(resp), CurrentHTTPServer{}.ResponseTraceAttrs(resp)...) } - return oldHTTPServer{}.ResponseTraceAttrs(resp) + return OldHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. func (s HTTPServer) Route(route string) attribute.KeyValue { - return oldHTTPServer{}.Route(route) + return OldHTTPServer{}.Route(route) } // Status returns a span status code and message for an HTTP status code @@ -108,7 +108,7 @@ func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { return } - attributes := oldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) addOpts := []metric.AddOption{o} s.requestBytesCounter.Add(ctx, md.RequestSize, addOpts...) @@ -124,7 +124,7 @@ func NewHTTPServer(meter metric.Meter) HTTPServer { server := HTTPServer{ duplicate: duplicate, } - server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = oldHTTPServer{}.createMeasures(meter) + server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) return server } @@ -142,25 +142,25 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { client := HTTPClient{ duplicate: env == "http/dup", } - client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = oldHTTPClient{}.createMeasures(meter) + client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter) return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { if c.duplicate { - return append(oldHTTPClient{}.RequestTraceAttrs(req), newHTTPClient{}.RequestTraceAttrs(req)...) + return append(OldHTTPClient{}.RequestTraceAttrs(req), CurrentHTTPClient{}.RequestTraceAttrs(req)...) } - return oldHTTPClient{}.RequestTraceAttrs(req) + return OldHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { if c.duplicate { - return append(oldHTTPClient{}.ResponseTraceAttrs(resp), newHTTPClient{}.ResponseTraceAttrs(resp)...) + return append(OldHTTPClient{}.ResponseTraceAttrs(resp), CurrentHTTPClient{}.ResponseTraceAttrs(resp)...) } - return oldHTTPClient{}.ResponseTraceAttrs(resp) + return OldHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -175,7 +175,7 @@ func (c HTTPClient) Status(code int) (codes.Code, string) { func (c HTTPClient) ErrorType(err error) attribute.KeyValue { if c.duplicate { - return newHTTPClient{}.ErrorType(err) + return CurrentHTTPClient{}.ErrorType(err) } return attribute.KeyValue{} @@ -195,7 +195,7 @@ func (o MetricOpts) AddOptions() metric.AddOption { } func (c HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts { - attributes := oldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) + attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) // TODO: Duplicate Metrics set := metric.WithAttributeSet(attribute.NewSet(attributes...)) return MetricOpts{ diff --git a/instrumentation/net/http/otelhttp/internal/semconv/env_test.go b/instrumentation/net/http/otelhttp/internal/semconv/env_test.go index 3a02a777373..3dd2c96509c 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/env_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/env_test.go @@ -10,9 +10,6 @@ import ( "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/noop" ) @@ -93,42 +90,3 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { }) } } - -type testInst struct { - embedded.Int64Counter - embedded.Float64Histogram - - intValue int64 - floatValue float64 - attributes []attribute.KeyValue -} - -func (t *testInst) Add(ctx context.Context, incr int64, options ...metric.AddOption) { - t.intValue = incr - cfg := metric.NewAddConfig(options) - attr := cfg.Attributes() - t.attributes = attr.ToSlice() -} - -func (t *testInst) Record(ctx context.Context, value float64, options ...metric.RecordOption) { - t.floatValue = value - cfg := metric.NewRecordConfig(options) - attr := cfg.Attributes() - t.attributes = attr.ToSlice() -} - -func NewTestHTTPServer() HTTPServer { - return HTTPServer{ - requestBytesCounter: &testInst{}, - responseBytesCounter: &testInst{}, - serverLatencyMeasure: &testInst{}, - } -} - -func NewTestHTTPClient() HTTPClient { - return HTTPClient{ - requestBytesCounter: &testInst{}, - responseBytesCounter: &testInst{}, - latencyMeasure: &testInst{}, - } -} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go b/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go index 745b8c67bc4..dc9ec7bc39e 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go @@ -14,7 +14,7 @@ import ( semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0" ) -type newHTTPServer struct{} +type CurrentHTTPServer struct{} // TraceRequest returns trace attributes for an HTTP request received by a // server. @@ -32,18 +32,18 @@ type newHTTPServer struct{} // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. -func (n newHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { +func (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { - host, p = splitHostPort(req.Host) + host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. - host, p = splitHostPort(server) + host, p = SplitHostPort(server) if p < 0 { - _, p = splitHostPort(req.Host) + _, p = SplitHostPort(req.Host) } } @@ -59,7 +59,7 @@ func (n newHTTPServer) RequestTraceAttrs(server string, req *http.Request) []att scheme := n.scheme(req.TLS != nil) - if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" { + if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ @@ -104,7 +104,7 @@ func (n newHTTPServer) RequestTraceAttrs(server string, req *http.Request) []att attrs = append(attrs, methodOriginal) } - if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" { + 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)) @@ -135,7 +135,7 @@ func (n newHTTPServer) RequestTraceAttrs(server string, req *http.Request) []att return attrs } -func (n newHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { +func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} } @@ -150,7 +150,7 @@ func (n newHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyV return semconvNew.HTTPRequestMethodGet, orig } -func (n newHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive +func (n CurrentHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return semconvNew.URLScheme("https") } @@ -160,7 +160,7 @@ func (n newHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive // TraceResponse returns trace attributes for telemetry from an HTTP response. // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (n newHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { +func (n CurrentHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { @@ -195,14 +195,14 @@ func (n newHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.Ke } // Route returns the attribute for the route. -func (n newHTTPServer) Route(route string) attribute.KeyValue { +func (n CurrentHTTPServer) Route(route string) attribute.KeyValue { return semconvNew.HTTPRoute(route) } -type newHTTPClient struct{} +type CurrentHTTPClient struct{} // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. -func (n newHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { +func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method @@ -222,7 +222,7 @@ func (n newHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { - requestHost, requestPort = splitHostPort(hostport) + requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } @@ -284,7 +284,7 @@ func (n newHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. -func (n newHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { +func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code @@ -311,7 +311,7 @@ func (n newHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyVa return attrs } -func (n newHTTPClient) ErrorType(err error) attribute.KeyValue { +func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { @@ -328,7 +328,7 @@ func (n newHTTPClient) ErrorType(err error) attribute.KeyValue { return semconvNew.ErrorTypeKey.String(value) } -func (n newHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { +func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go index 6a3f6c09a4f..47b4db548ce 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go @@ -4,97 +4,16 @@ package semconv import ( - "errors" - "fmt" "net/http" - "net/http/httptest" - "strings" + "strconv" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) -func TestNewTraceRequest(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") - serv := NewHTTPServer(nil) - want := func(req testServerReq) []attribute.KeyValue { - return []attribute.KeyValue{ - attribute.String("http.request.method", "GET"), - attribute.String("url.scheme", "http"), - attribute.String("server.address", req.hostname), - attribute.Int("server.port", req.serverPort), - attribute.String("network.peer.address", req.peerAddr), - attribute.Int("network.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("client.address", req.clientIP), - attribute.String("network.protocol.version", "1.1"), - attribute.String("url.path", "/"), - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - } - } - testTraceRequest(t, serv, want) -} - -func TestNewTraceResponse(t *testing.T) { - testCases := []struct { - name string - resp ResponseTelemetry - want []attribute.KeyValue - }{ - { - name: "empty", - resp: ResponseTelemetry{}, - want: nil, - }, - { - name: "no errors", - resp: ResponseTelemetry{ - StatusCode: 200, - ReadBytes: 701, - WriteBytes: 802, - }, - want: []attribute.KeyValue{ - attribute.Int("http.request.body.size", 701), - attribute.Int("http.response.body.size", 802), - attribute.Int("http.response.status_code", 200), - }, - }, - { - name: "with errors", - resp: ResponseTelemetry{ - StatusCode: 200, - ReadBytes: 701, - ReadError: fmt.Errorf("read error"), - WriteBytes: 802, - WriteError: fmt.Errorf("write error"), - }, - want: []attribute.KeyValue{ - attribute.Int("http.request.body.size", 701), - attribute.Int("http.response.body.size", 802), - attribute.Int("http.response.status_code", 200), - }, - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - got := newHTTPServer{}.ResponseTraceAttrs(tt.resp) - assert.ElementsMatch(t, tt.want, got) - }) - } -} - func TestNewMethod(t *testing.T) { testCases := []struct { method string @@ -123,104 +42,96 @@ func TestNewMethod(t *testing.T) { for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { - got, gotOrig := newHTTPServer{}.method(tt.method) + got, gotOrig := CurrentHTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } -func TestNewTraceRequest_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") - body := strings.NewReader("Hello, world!") - url := "https://example.com:8888/foo/bar?stuff=morestuff" - req := httptest.NewRequest("pOST", url, body) - req.Header.Set("User-Agent", "go-test-agent") - - want := []attribute.KeyValue{ - attribute.String("http.request.method", "POST"), - attribute.String("http.request.method_original", "pOST"), - attribute.String("http.method", "pOST"), - attribute.String("url.full", url), - attribute.String("http.url", url), - attribute.String("server.address", "example.com"), - attribute.Int("server.port", 8888), - attribute.String("network.protocol.version", "1.1"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.String("user_agent.original", "go-test-agent"), - attribute.Int("http.request_content_length", 13), - } - client := NewHTTPClient(nil) - assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) -} - -func TestNewTraceResponse_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") - testcases := []struct { - resp http.Response - want []attribute.KeyValue - }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, - } - - for _, tt := range testcases { - client := NewHTTPClient(nil) - assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) - } -} - -func TestClientRequest(t *testing.T) { - body := strings.NewReader("Hello, world!") - url := "https://example.com:8888/foo/bar?stuff=morestuff" - req := httptest.NewRequest("pOST", url, body) - req.Header.Set("User-Agent", "go-test-agent") - - want := []attribute.KeyValue{ - attribute.String("http.request.method", "POST"), - attribute.String("http.request.method_original", "pOST"), - attribute.String("url.full", url), - attribute.String("server.address", "example.com"), - attribute.Int("server.port", 8888), - attribute.String("network.protocol.version", "1.1"), - } - got := newHTTPClient{}.RequestTraceAttrs(req) - assert.ElementsMatch(t, want, got) -} - -func TestClientResponse(t *testing.T) { - testcases := []struct { - resp http.Response - want []attribute.KeyValue +func TestHTTPClientStatus(t *testing.T) { + tests := []struct { + code int + stat codes.Code + msg bool }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, + {0, codes.Error, true}, + {http.StatusContinue, codes.Unset, false}, + {http.StatusSwitchingProtocols, codes.Unset, false}, + {http.StatusProcessing, codes.Unset, false}, + {http.StatusEarlyHints, codes.Unset, false}, + {http.StatusOK, codes.Unset, false}, + {http.StatusCreated, codes.Unset, false}, + {http.StatusAccepted, codes.Unset, false}, + {http.StatusNonAuthoritativeInfo, codes.Unset, false}, + {http.StatusNoContent, codes.Unset, false}, + {http.StatusResetContent, codes.Unset, false}, + {http.StatusPartialContent, codes.Unset, false}, + {http.StatusMultiStatus, codes.Unset, false}, + {http.StatusAlreadyReported, codes.Unset, false}, + {http.StatusIMUsed, codes.Unset, false}, + {http.StatusMultipleChoices, codes.Unset, false}, + {http.StatusMovedPermanently, codes.Unset, false}, + {http.StatusFound, codes.Unset, false}, + {http.StatusSeeOther, codes.Unset, false}, + {http.StatusNotModified, codes.Unset, false}, + {http.StatusUseProxy, codes.Unset, false}, + {306, codes.Unset, false}, + {http.StatusTemporaryRedirect, codes.Unset, false}, + {http.StatusPermanentRedirect, codes.Unset, false}, + {http.StatusBadRequest, codes.Error, false}, + {http.StatusUnauthorized, codes.Error, false}, + {http.StatusPaymentRequired, codes.Error, false}, + {http.StatusForbidden, codes.Error, false}, + {http.StatusNotFound, codes.Error, false}, + {http.StatusMethodNotAllowed, codes.Error, false}, + {http.StatusNotAcceptable, codes.Error, false}, + {http.StatusProxyAuthRequired, codes.Error, false}, + {http.StatusRequestTimeout, codes.Error, false}, + {http.StatusConflict, codes.Error, false}, + {http.StatusGone, codes.Error, false}, + {http.StatusLengthRequired, codes.Error, false}, + {http.StatusPreconditionFailed, codes.Error, false}, + {http.StatusRequestEntityTooLarge, codes.Error, false}, + {http.StatusRequestURITooLong, codes.Error, false}, + {http.StatusUnsupportedMediaType, codes.Error, false}, + {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, + {http.StatusExpectationFailed, codes.Error, false}, + {http.StatusTeapot, codes.Error, false}, + {http.StatusMisdirectedRequest, codes.Error, false}, + {http.StatusUnprocessableEntity, codes.Error, false}, + {http.StatusLocked, codes.Error, false}, + {http.StatusFailedDependency, codes.Error, false}, + {http.StatusTooEarly, codes.Error, false}, + {http.StatusUpgradeRequired, codes.Error, false}, + {http.StatusPreconditionRequired, codes.Error, false}, + {http.StatusTooManyRequests, codes.Error, false}, + {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, + {http.StatusUnavailableForLegalReasons, codes.Error, false}, + {499, codes.Error, false}, + {http.StatusInternalServerError, codes.Error, false}, + {http.StatusNotImplemented, codes.Error, false}, + {http.StatusBadGateway, codes.Error, false}, + {http.StatusServiceUnavailable, codes.Error, false}, + {http.StatusGatewayTimeout, codes.Error, false}, + {http.StatusHTTPVersionNotSupported, codes.Error, false}, + {http.StatusVariantAlsoNegotiates, codes.Error, false}, + {http.StatusInsufficientStorage, codes.Error, false}, + {http.StatusLoopDetected, codes.Error, false}, + {http.StatusNotExtended, codes.Error, false}, + {http.StatusNetworkAuthenticationRequired, codes.Error, false}, + {600, codes.Error, true}, } - for _, tt := range testcases { - got := newHTTPClient{}.ResponseTraceAttrs(&tt.resp) - assert.ElementsMatch(t, tt.want, got) - } -} - -func TestRequestErrorType(t *testing.T) { - testcases := []struct { - err error - want attribute.KeyValue - }{ - {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, - {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv.customError")}, - } - - for _, tt := range testcases { - got := newHTTPClient{}.ErrorType(tt.err) - assert.Equal(t, tt.want, got) + for _, test := range tests { + t.Run(strconv.Itoa(test.code), func(t *testing.T) { + c, msg := HTTPClient{}.Status(test.code) + assert.Equal(t, test.stat, c) + if test.msg && msg == "" { + t.Errorf("expected non-empty message for %d", test.code) + } else if !test.msg && msg != "" { + t.Errorf("expected empty message for %d, got: %s", test.code, msg) + } + }) } } - -type customError struct{} - -func (customError) Error() string { - return "custom error" -} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/common_test.go b/instrumentation/net/http/otelhttp/internal/semconv/test/common_test.go new file mode 100644 index 00000000000..5711c6c40a3 --- /dev/null +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/common_test.go @@ -0,0 +1,68 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package test + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" + "go.opentelemetry.io/otel/attribute" +) + +type testServerReq struct { + hostname string + serverPort int + peerAddr string + peerPort int + clientIP string +} + +func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { + t.Helper() + + got := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + got <- r + close(got) + w.WriteHeader(http.StatusOK) + } + + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() + + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) + + resp, err := srv.Client().Get(srv.URL) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + + req := <-got + peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) + + const user = "alice" + req.SetBasicAuth(user, "pswrd") + + const clientIP = "127.0.0.5" + req.Header.Add("X-Forwarded-For", clientIP) + + srvReq := testServerReq{ + hostname: srvURL.Hostname(), + serverPort: int(srvPort), + peerAddr: peer, + peerPort: peerPort, + clientIP: clientIP, + } + + assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req)) +} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod b/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod new file mode 100644 index 00000000000..39fcd365602 --- /dev/null +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod @@ -0,0 +1,25 @@ +module go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/test + +go 1.22 + +require ( + github.com/stretchr/testify v1.10.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.0.0-00010101000000-000000000000 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 + go.opentelemetry.io/otel/sdk/metric v1.32.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect + golang.org/x/sys v0.27.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../../ diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/go.sum b/instrumentation/net/http/otelhttp/internal/semconv/test/go.sum new file mode 100644 index 00000000000..9362e90df3f --- /dev/null +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/go.sum @@ -0,0 +1,31 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go new file mode 100644 index 00000000000..da37f9765b3 --- /dev/null +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go @@ -0,0 +1,192 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package test + +import ( + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" + "go.opentelemetry.io/otel/attribute" +) + +func TestNewTraceRequest(t *testing.T) { + t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") + serv := semconv.NewHTTPServer(nil) + want := func(req testServerReq) []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.String("http.request.method", "GET"), + attribute.String("url.scheme", "http"), + attribute.String("server.address", req.hostname), + attribute.Int("server.port", req.serverPort), + attribute.String("network.peer.address", req.peerAddr), + attribute.Int("network.peer.port", req.peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("client.address", req.clientIP), + attribute.String("network.protocol.version", "1.1"), + attribute.String("url.path", "/"), + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", req.hostname), + attribute.Int("net.host.port", req.serverPort), + attribute.String("net.sock.peer.addr", req.peerAddr), + attribute.Int("net.sock.peer.port", req.peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", req.clientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + } + } + testTraceRequest(t, serv, want) +} + +func TestNewTraceResponse(t *testing.T) { + testCases := []struct { + name string + resp semconv.ResponseTelemetry + want []attribute.KeyValue + }{ + { + name: "empty", + resp: semconv.ResponseTelemetry{}, + want: nil, + }, + { + name: "no errors", + resp: semconv.ResponseTelemetry{ + StatusCode: 200, + ReadBytes: 701, + WriteBytes: 802, + }, + want: []attribute.KeyValue{ + attribute.Int("http.request.body.size", 701), + attribute.Int("http.response.body.size", 802), + attribute.Int("http.response.status_code", 200), + }, + }, + { + name: "with errors", + resp: semconv.ResponseTelemetry{ + StatusCode: 200, + ReadBytes: 701, + ReadError: fmt.Errorf("read error"), + WriteBytes: 802, + WriteError: fmt.Errorf("write error"), + }, + want: []attribute.KeyValue{ + attribute.Int("http.request.body.size", 701), + attribute.Int("http.response.body.size", 802), + attribute.Int("http.response.status_code", 200), + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got := semconv.CurrentHTTPServer{}.ResponseTraceAttrs(tt.resp) + assert.ElementsMatch(t, tt.want, got) + }) + } +} + +func TestNewTraceRequest_Client(t *testing.T) { + t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") + body := strings.NewReader("Hello, world!") + url := "https://example.com:8888/foo/bar?stuff=morestuff" + req := httptest.NewRequest("pOST", url, body) + req.Header.Set("User-Agent", "go-test-agent") + + want := []attribute.KeyValue{ + attribute.String("http.request.method", "POST"), + attribute.String("http.request.method_original", "pOST"), + attribute.String("http.method", "pOST"), + attribute.String("url.full", url), + attribute.String("http.url", url), + attribute.String("server.address", "example.com"), + attribute.Int("server.port", 8888), + attribute.String("network.protocol.version", "1.1"), + attribute.String("net.peer.name", "example.com"), + attribute.Int("net.peer.port", 8888), + attribute.String("user_agent.original", "go-test-agent"), + attribute.Int("http.request_content_length", 13), + } + client := semconv.NewHTTPClient(nil) + assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) +} + +func TestNewTraceResponse_Client(t *testing.T) { + t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") + testcases := []struct { + resp http.Response + want []attribute.KeyValue + }{ + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, + } + + for _, tt := range testcases { + client := semconv.NewHTTPClient(nil) + assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) + } +} + +func TestClientRequest(t *testing.T) { + body := strings.NewReader("Hello, world!") + url := "https://example.com:8888/foo/bar?stuff=morestuff" + req := httptest.NewRequest("pOST", url, body) + req.Header.Set("User-Agent", "go-test-agent") + + want := []attribute.KeyValue{ + attribute.String("http.request.method", "POST"), + attribute.String("http.request.method_original", "pOST"), + attribute.String("url.full", url), + attribute.String("server.address", "example.com"), + attribute.Int("server.port", 8888), + attribute.String("network.protocol.version", "1.1"), + } + got := semconv.CurrentHTTPClient{}.RequestTraceAttrs(req) + assert.ElementsMatch(t, want, got) +} + +func TestClientResponse(t *testing.T) { + testcases := []struct { + resp http.Response + want []attribute.KeyValue + }{ + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, + } + + for _, tt := range testcases { + got := semconv.CurrentHTTPClient{}.ResponseTraceAttrs(&tt.resp) + assert.ElementsMatch(t, tt.want, got) + } +} + +func TestRequestErrorType(t *testing.T) { + testcases := []struct { + err error + want attribute.KeyValue + }{ + {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, + {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv/test.customError")}, + } + + for _, tt := range testcases { + got := semconv.CurrentHTTPClient{}.ErrorType(tt.err) + assert.Equal(t, tt.want, got) + } +} + +type customError struct{} + +func (customError) Error() string { + return "custom error" +} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/v1.20.0_test.go b/instrumentation/net/http/otelhttp/internal/semconv/test/v1.20.0_test.go new file mode 100644 index 00000000000..e46d7b2781f --- /dev/null +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/v1.20.0_test.go @@ -0,0 +1,308 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package test + +import ( + "context" + "fmt" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/instrumentation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" +) + +func TestV120TraceRequest(t *testing.T) { + // Anything but "http" or "http/dup" works. + t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "old") + serv := semconv.NewHTTPServer(nil) + want := func(req testServerReq) []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", req.hostname), + attribute.Int("net.host.port", req.serverPort), + attribute.String("net.sock.peer.addr", req.peerAddr), + attribute.Int("net.sock.peer.port", req.peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", req.clientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + } + } + testTraceRequest(t, serv, want) +} + +func TestV120TraceResponse(t *testing.T) { + testCases := []struct { + name string + resp semconv.ResponseTelemetry + want []attribute.KeyValue + }{ + { + name: "empty", + resp: semconv.ResponseTelemetry{}, + want: nil, + }, + { + name: "no errors", + resp: semconv.ResponseTelemetry{ + StatusCode: 200, + ReadBytes: 701, + WriteBytes: 802, + }, + want: []attribute.KeyValue{ + attribute.Int("http.request_content_length", 701), + attribute.Int("http.response_content_length", 802), + attribute.Int("http.status_code", 200), + }, + }, + { + name: "with errors", + resp: semconv.ResponseTelemetry{ + StatusCode: 200, + ReadBytes: 701, + ReadError: fmt.Errorf("read error"), + WriteBytes: 802, + WriteError: fmt.Errorf("write error"), + }, + want: []attribute.KeyValue{ + attribute.Int("http.request_content_length", 701), + attribute.String("http.read_error", "read error"), + attribute.Int("http.response_content_length", 802), + attribute.String("http.write_error", "write error"), + attribute.Int("http.status_code", 200), + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got := semconv.OldHTTPServer{}.ResponseTraceAttrs(tt.resp) + assert.ElementsMatch(t, tt.want, got) + }) + } +} + +func TestV120RecordMetrics(t *testing.T) { + reader := sdkmetric.NewManualReader() + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) + + server := semconv.NewHTTPServer(mp.Meter("test")) + req, err := http.NewRequest("POST", "http://example.com", nil) + assert.NoError(t, err) + + server.RecordMetrics(context.Background(), semconv.ServerMetricData{ + ServerName: "stuff", + ResponseSize: 200, + MetricAttributes: semconv.MetricAttributes{ + Req: req, + StatusCode: 301, + AdditionalAttributes: []attribute.KeyValue{ + attribute.String("key", "value"), + }, + }, + MetricData: semconv.MetricData{ + RequestSize: 100, + ElapsedTime: 300, + }, + }) + + rm := metricdata.ResourceMetrics{} + require.NoError(t, reader.Collect(context.Background(), &rm)) + require.Len(t, rm.ScopeMetrics, 1) + require.Len(t, rm.ScopeMetrics[0].Metrics, 3) + + attrs := attribute.NewSet( + attribute.String("http.scheme", "http"), + attribute.String("http.method", "POST"), + attribute.Int64("http.status_code", 301), + attribute.String("key", "value"), + attribute.String("net.host.name", "stuff"), + attribute.String("net.protocol.name", "http"), + attribute.String("net.protocol.version", "1.1"), + ) + + expectedScopeMetric := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "test", + }, + Metrics: []metricdata.Metrics{ + { + Name: "http.server.request.size", + Description: "Measures the size of HTTP request messages.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attrs, + }, + }, + }, + }, + { + Name: "http.server.response.size", + Description: "Measures the size of HTTP response messages.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attrs, + }, + }, + }, + }, + { + Name: "http.server.duration", + Description: "Measures the duration of inbound HTTP requests.", + Unit: "ms", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: attrs, + }, + }, + }, + }, + }, + } + + metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) +} + +func TestV120ClientRequest(t *testing.T) { + body := strings.NewReader("Hello, world!") + url := "https://example.com:8888/foo/bar?stuff=morestuff" + req, err := http.NewRequest("POST", url, body) + assert.NoError(t, err) + req.Header.Set("User-Agent", "go-test-agent") + + want := []attribute.KeyValue{ + attribute.String("http.method", "POST"), + attribute.String("http.url", url), + attribute.String("net.peer.name", "example.com"), + attribute.Int("net.peer.port", 8888), + attribute.Int("http.request_content_length", body.Len()), + attribute.String("user_agent.original", "go-test-agent"), + } + got := semconv.OldHTTPClient{}.RequestTraceAttrs(req) + assert.ElementsMatch(t, want, got) +} + +func TestV120ClientResponse(t *testing.T) { + resp := http.Response{ + StatusCode: 200, + ContentLength: 123, + } + + want := []attribute.KeyValue{ + attribute.Int("http.response_content_length", 123), + attribute.Int("http.status_code", 200), + } + + got := semconv.OldHTTPClient{}.ResponseTraceAttrs(&resp) + assert.ElementsMatch(t, want, got) +} + +func TestV120ClientMetrics(t *testing.T) { + reader := sdkmetric.NewManualReader() + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) + + client := semconv.NewHTTPClient(mp.Meter("test")) + req, err := http.NewRequest("POST", "http://example.com", nil) + assert.NoError(t, err) + + opts := client.MetricOptions(semconv.MetricAttributes{ + Req: req, + StatusCode: 301, + AdditionalAttributes: []attribute.KeyValue{ + attribute.String("key", "value"), + }, + }) + + ctx := context.Background() + + client.RecordResponseSize(ctx, 200, opts.AddOptions()) + + client.RecordMetrics(ctx, semconv.MetricData{ + RequestSize: 100, + ElapsedTime: 300, + }, opts) + + rm := metricdata.ResourceMetrics{} + require.NoError(t, reader.Collect(context.Background(), &rm)) + require.Len(t, rm.ScopeMetrics, 1) + require.Len(t, rm.ScopeMetrics[0].Metrics, 3) + + attrs := attribute.NewSet( + attribute.String("http.method", "POST"), + attribute.Int64("http.status_code", 301), + attribute.String("key", "value"), + attribute.String("net.peer.name", "example.com"), + ) + + expectedScopeMetric := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "test", + }, + Metrics: []metricdata.Metrics{ + { + Name: "http.client.request.size", + Description: "Measures the size of HTTP request messages.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attrs, + }, + }, + }, + }, + { + Name: "http.client.response.size", + Description: "Measures the size of HTTP response messages.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attrs, + }, + }, + }, + }, + { + Name: "http.client.duration", + Description: "Measures the duration of outbound HTTP requests.", + Unit: "ms", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: attrs, + }, + }, + }, + }, + }, + } + + metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) +} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/util.go b/instrumentation/net/http/otelhttp/internal/semconv/util.go index e6e14924f57..93e8d0f94c1 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/util.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/util.go @@ -14,14 +14,14 @@ import ( semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0" ) -// splitHostPort splits a network address hostport of the form "host", +// 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) { +func SplitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { diff --git a/instrumentation/net/http/otelhttp/internal/semconv/util_test.go b/instrumentation/net/http/otelhttp/internal/semconv/util_test.go index c5310f90d80..b0fe5439883 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/util_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/util_test.go @@ -34,7 +34,7 @@ func TestSplitHostPort(t *testing.T) { } for _, test := range tests { - h, p := splitHostPort(test.hostport) + h, p := SplitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go index 5367732ec5d..c042249dd72 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go @@ -17,7 +17,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) -type oldHTTPServer struct{} +type OldHTTPServer struct{} // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. @@ -35,14 +35,14 @@ type oldHTTPServer struct{} // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. -func (o oldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { +func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { return semconvutil.HTTPServerRequest(server, req) } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (o oldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { +func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { attributes := []attribute.KeyValue{} if resp.ReadBytes > 0 { @@ -67,7 +67,7 @@ func (o oldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.Ke } // Route returns the attribute for the route. -func (o oldHTTPServer) Route(route string) attribute.KeyValue { +func (o OldHTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } @@ -84,7 +84,7 @@ const ( serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds ) -func (h oldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { +func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { if meter == nil { return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} } @@ -113,17 +113,17 @@ func (h oldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, return requestBytesCounter, responseBytesCounter, serverLatencyMeasure } -func (o oldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { n := len(additionalAttributes) + 3 var host string var p int if server == "" { - host, p = splitHostPort(req.Host) + host, p = SplitHostPort(req.Host) } else { // Prioritize the primary server name. - host, p = splitHostPort(server) + host, p = SplitHostPort(server) if p < 0 { - _, p = splitHostPort(req.Host) + _, p = SplitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) @@ -164,24 +164,24 @@ func (o oldHTTPServer) MetricAttributes(server string, req *http.Request, status return attributes } -func (o oldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive +func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return semconv.HTTPSchemeHTTPS } return semconv.HTTPSchemeHTTP } -type oldHTTPClient struct{} +type OldHTTPClient struct{} -func (o oldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { +func (o OldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { return semconvutil.HTTPClientRequest(req) } -func (o oldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { +func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { return semconvutil.HTTPClientResponse(resp) } -func (o oldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.status_code int @@ -197,7 +197,7 @@ func (o oldHTTPClient) MetricAttributes(req *http.Request, statusCode int, addit var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { - requestHost, requestPort = splitHostPort(hostport) + requestHost, requestPort = SplitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } @@ -235,7 +235,7 @@ const ( clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds ) -func (o oldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { +func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { if meter == nil { return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go index eef382a5047..a3731492729 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go @@ -4,10 +4,6 @@ package semconv import ( - "context" - "fmt" - "net/http" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -15,190 +11,6 @@ import ( "go.opentelemetry.io/otel/attribute" ) -func TestV120TraceRequest(t *testing.T) { - // Anything but "http" or "http/dup" works. - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "old") - serv := NewHTTPServer(nil) - want := func(req testServerReq) []attribute.KeyValue { - return []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - } - } - testTraceRequest(t, serv, want) -} - -func TestV120TraceResponse(t *testing.T) { - testCases := []struct { - name string - resp ResponseTelemetry - want []attribute.KeyValue - }{ - { - name: "empty", - resp: ResponseTelemetry{}, - want: nil, - }, - { - name: "no errors", - resp: ResponseTelemetry{ - StatusCode: 200, - ReadBytes: 701, - WriteBytes: 802, - }, - want: []attribute.KeyValue{ - attribute.Int("http.request_content_length", 701), - attribute.Int("http.response_content_length", 802), - attribute.Int("http.status_code", 200), - }, - }, - { - name: "with errors", - resp: ResponseTelemetry{ - StatusCode: 200, - ReadBytes: 701, - ReadError: fmt.Errorf("read error"), - WriteBytes: 802, - WriteError: fmt.Errorf("write error"), - }, - want: []attribute.KeyValue{ - attribute.Int("http.request_content_length", 701), - attribute.String("http.read_error", "read error"), - attribute.Int("http.response_content_length", 802), - attribute.String("http.write_error", "write error"), - attribute.Int("http.status_code", 200), - }, - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - got := oldHTTPServer{}.ResponseTraceAttrs(tt.resp) - assert.ElementsMatch(t, tt.want, got) - }) - } -} - -func TestV120RecordMetrics(t *testing.T) { - server := NewTestHTTPServer() - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - server.RecordMetrics(context.Background(), ServerMetricData{ - ServerName: "stuff", - ResponseSize: 200, - MetricAttributes: MetricAttributes{ - Req: req, - StatusCode: 301, - AdditionalAttributes: []attribute.KeyValue{ - attribute.String("key", "value"), - }, - }, - MetricData: MetricData{ - RequestSize: 100, - ElapsedTime: 300, - }, - }) - - assert.Equal(t, int64(100), server.requestBytesCounter.(*testInst).intValue) - assert.Equal(t, int64(200), server.responseBytesCounter.(*testInst).intValue) - assert.Equal(t, float64(300), server.serverLatencyMeasure.(*testInst).floatValue) - - want := []attribute.KeyValue{ - attribute.String("http.scheme", "http"), - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("key", "value"), - attribute.String("net.host.name", "stuff"), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - } - - assert.ElementsMatch(t, want, server.requestBytesCounter.(*testInst).attributes) - assert.ElementsMatch(t, want, server.responseBytesCounter.(*testInst).attributes) - assert.ElementsMatch(t, want, server.serverLatencyMeasure.(*testInst).attributes) -} - -func TestV120ClientRequest(t *testing.T) { - body := strings.NewReader("Hello, world!") - url := "https://example.com:8888/foo/bar?stuff=morestuff" - req, err := http.NewRequest("POST", url, body) - assert.NoError(t, err) - req.Header.Set("User-Agent", "go-test-agent") - - want := []attribute.KeyValue{ - attribute.String("http.method", "POST"), - attribute.String("http.url", url), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.Int("http.request_content_length", body.Len()), - attribute.String("user_agent.original", "go-test-agent"), - } - got := oldHTTPClient{}.RequestTraceAttrs(req) - assert.ElementsMatch(t, want, got) -} - -func TestV120ClientResponse(t *testing.T) { - resp := http.Response{ - StatusCode: 200, - ContentLength: 123, - } - - want := []attribute.KeyValue{ - attribute.Int("http.response_content_length", 123), - attribute.Int("http.status_code", 200), - } - - got := oldHTTPClient{}.ResponseTraceAttrs(&resp) - assert.ElementsMatch(t, want, got) -} - -func TestV120ClientMetrics(t *testing.T) { - client := NewTestHTTPClient() - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - opts := client.MetricOptions(MetricAttributes{ - Req: req, - StatusCode: 301, - AdditionalAttributes: []attribute.KeyValue{ - attribute.String("key", "value"), - }, - }) - - ctx := context.Background() - - client.RecordResponseSize(ctx, 200, opts.AddOptions()) - - client.RecordMetrics(ctx, MetricData{ - RequestSize: 100, - ElapsedTime: 300, - }, opts) - - assert.Equal(t, int64(100), client.requestBytesCounter.(*testInst).intValue) - assert.Equal(t, int64(200), client.responseBytesCounter.(*testInst).intValue) - assert.Equal(t, float64(300), client.latencyMeasure.(*testInst).floatValue) - - want := []attribute.KeyValue{ - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("key", "value"), - attribute.String("net.peer.name", "example.com"), - } - - assert.ElementsMatch(t, want, client.requestBytesCounter.(*testInst).attributes) - assert.ElementsMatch(t, want, client.responseBytesCounter.(*testInst).attributes) - assert.ElementsMatch(t, want, client.latencyMeasure.(*testInst).attributes) -} - func TestStandardizeHTTPMethodMetric(t *testing.T) { testCases := []struct { method string From 6991f75185e6e1703428da76dd8ce044e06fdebe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:19:14 -0800 Subject: [PATCH 08/12] chore(deps): update module golang.org/x/sys to v0.28.0 (#6420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | golang.org/x/sys | `v0.27.0` -> `v0.28.0` | [![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fsys/v0.28.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/go/golang.org%2fx%2fsys/v0.28.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/go/golang.org%2fx%2fsys/v0.27.0/v0.28.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fsys/v0.27.0/v0.28.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Configuration ๐Ÿ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). ๐Ÿšฆ **Automerge**: Disabled by config. Please merge this manually once you are satisfied. โ™ป **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. ๐Ÿ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/open-telemetry/opentelemetry-go-contrib). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../net/http/otelhttp/internal/semconv/test/go.mod | 2 +- .../net/http/otelhttp/internal/semconv/test/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod b/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod index 39fcd365602..99c176e1117 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod @@ -18,7 +18,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect - golang.org/x/sys v0.27.0 // indirect + golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/go.sum b/instrumentation/net/http/otelhttp/internal/semconv/test/go.sum index 9362e90df3f..e7d28f79666 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/test/go.sum +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/go.sum @@ -23,8 +23,8 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 8e192109f08b0df6bbc8e2bf9fc4b1efb6e8df6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:22:39 -0800 Subject: [PATCH 09/12] fix(deps): update golang.org/x/exp digest to 1829a12 (#6421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | golang.org/x/exp | require | digest | `1443442` -> `1829a12` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Configuration ๐Ÿ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). ๐Ÿšฆ **Automerge**: Disabled by config. Please merge this manually once you are satisfied. โ™ป **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. ๐Ÿ”• **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/open-telemetry/opentelemetry-go-contrib). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 6c49caa9f8e..226aad7aeda 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -14,7 +14,7 @@ require ( go.opentelemetry.io/build-tools/crosslink v0.15.0 go.opentelemetry.io/build-tools/gotmpl v0.15.0 go.opentelemetry.io/build-tools/multimod v0.15.0 - golang.org/x/exp v0.0.0-20241210172134-14434422244c + golang.org/x/exp v0.0.0-20241210194714-1829a127f884 golang.org/x/tools v0.28.0 golang.org/x/vuln v1.1.3 ) diff --git a/tools/go.sum b/tools/go.sum index 1cfcf7fdbbe..54180d60d4f 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -518,8 +518,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20241210172134-14434422244c h1:G0f8LmhCW7rzpybldgSjhhKDCwW7mYO0Qr6HZDb0HJA= -golang.org/x/exp v0.0.0-20241210172134-14434422244c/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= From 0abc9317be66e97187110ba82fa174ce9e9bfdba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Wed, 11 Dec 2024 08:09:58 +0100 Subject: [PATCH 10/12] otelslog: Split code attributes (#6415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow semantic conventions more strictly. See the following thread: https://github.com/open-telemetry/semantic-conventions/pull/1624#discussion_r1870985463 Follows https://github.com/open-telemetry/opentelemetry-go-contrib/pull/6253 There is no noticeable performance overhead: ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/contrib/bridges/otelslog cpu: Intel(R) Core(TM) i9-10885H CPU @ 2.40GHz โ”‚ old.txt โ”‚ new.txt โ”‚ โ”‚ sec/op โ”‚ sec/op vs base โ”‚ Handler/(WithSource).Handle-16 260.4n ยฑ 21% 234.0n ยฑ 5% -10.14% (p=0.035 n=10) โ”‚ old.txt โ”‚ new.txt โ”‚ โ”‚ B/op โ”‚ B/op vs base โ”‚ Handler/(WithSource).Handle-16 248.0 ยฑ 0% 248.0 ยฑ 0% ~ (p=1.000 n=10) ยน ยน all samples are equal โ”‚ old.txt โ”‚ new.txt โ”‚ โ”‚ allocs/op โ”‚ allocs/op vs base โ”‚ Handler/(WithSource).Handle-16 2.000 ยฑ 0% 2.000 ยฑ 0% ~ (p=1.000 n=10) ยน ยน all samples are equal ``` --- CHANGELOG.md | 1 + bridges/otelslog/handler.go | 17 ++++++++++- bridges/otelslog/handler_test.go | 52 +++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a27860223f..4354cc4115d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Fixed the value for configuring the OTLP exporter to use `grpc` instead of `grpc/protobuf` in `go.opentelemetry.io/contrib/config`. (#6338) - Allow marshaling types in `go.opentelemetry.io/contrib/config`. (#6347) - Removed the redundant handling of panic from the `HTML` function in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6373) +- The `code.function` attribute emitted by `go.opentelemetry.io/contrib/bridges/otelslog` now stores just the function name instead the package path-qualified function name. The `code.namespace` attribute now stores the package path. (#6415) diff --git a/bridges/otelslog/handler.go b/bridges/otelslog/handler.go index 6d40533a0b5..26e9994715f 100644 --- a/bridges/otelslog/handler.go +++ b/bridges/otelslog/handler.go @@ -50,6 +50,7 @@ import ( "log/slog" "runtime" "slices" + "strings" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" @@ -192,9 +193,11 @@ func (h *Handler) convertRecord(r slog.Record) log.Record { if h.source { fs := runtime.CallersFrames([]uintptr{r.PC}) f, _ := fs.Next() + funcName, namespace := splitFuncName(f.Function) record.AddAttributes( log.String(string(semconv.CodeFilepathKey), f.File), - log.String(string(semconv.CodeFunctionKey), f.Function), + log.String(string(semconv.CodeFunctionKey), funcName), + log.String(string(semconv.CodeNamespaceKey), namespace), log.Int(string(semconv.CodeLineNumberKey), f.Line), ) } @@ -476,3 +479,15 @@ func convert(v slog.Value) log.Value { return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", v.Kind(), v.Any())) } } + +// splitFuncName splits package path-qualified function name into +// function name and package full name (namespace). E.g. it splits +// "github.com/my/repo/pkg.foo" into +// "foo" and "github.com/my/repo/pkg". +func splitFuncName(f string) (string, string) { + i := strings.LastIndexByte(f, '.') + if i < 0 { + return "", "" + } + return f[i+1:], f[:i] +} diff --git a/bridges/otelslog/handler_test.go b/bridges/otelslog/handler_test.go index 9ca30603cdb..41c3679f206 100644 --- a/bridges/otelslog/handler_test.go +++ b/bridges/otelslog/handler_test.go @@ -230,7 +230,7 @@ func (h *wrapper) Handle(ctx context.Context, r slog.Record) error { func TestSLogHandler(t *testing.T) { // Capture the PC of this line pc, file, line, _ := runtime.Caller(0) - funcName := runtime.FuncForPC(pc).Name() + funcName, namespace := splitFuncName(runtime.FuncForPC(pc).Name()) cases := []testCase{ { @@ -414,6 +414,7 @@ func TestSLogHandler(t *testing.T) { checks: [][]check{{ hasAttr(string(semconv.CodeFilepathKey), file), hasAttr(string(semconv.CodeFunctionKey), funcName), + hasAttr(string(semconv.CodeNamespaceKey), namespace), hasAttr(string(semconv.CodeLineNumberKey), int64(line)), }}, options: []Option{WithSource(true)}, @@ -510,6 +511,42 @@ func TestHandlerEnabled(t *testing.T) { assert.True(t, h.Enabled(ctx, slog.LevelDebug), "context not passed") } +func TestSplitFuncName(t *testing.T) { + testCases := []struct { + fullFuncName string + wantFuncName string + wantNamespace string + }{ + { + fullFuncName: "github.com/my/repo/pkg.foo", + wantFuncName: "foo", + wantNamespace: "github.com/my/repo/pkg", + }, + { + fullFuncName: "net/http.Get", + wantFuncName: "Get", + wantNamespace: "net/http", + }, + { + fullFuncName: "invalid", + wantFuncName: "", + wantNamespace: "", + }, + { + fullFuncName: ".", + wantFuncName: "", + wantNamespace: "", + }, + } + for _, tc := range testCases { + t.Run(tc.fullFuncName, func(t *testing.T) { + gotFuncName, gotNamespace := splitFuncName(tc.fullFuncName) + assert.Equal(t, tc.wantFuncName, gotFuncName) + assert.Equal(t, tc.wantNamespace, gotNamespace) + }) + } +} + func BenchmarkHandler(b *testing.B) { var ( h slog.Handler @@ -639,5 +676,18 @@ func BenchmarkHandler(b *testing.B) { }) }) + b.Run("(WithSource).Handle", func(b *testing.B) { + handlers := make([]*Handler, b.N) + for i := range handlers { + handlers[i] = NewHandler("", WithSource(true)) + } + + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + err = handlers[n].Handle(ctx, record) + } + }) + _, _ = h, err } From 6ab504eb9c6d35245c16d5514d32f4ec12e8cc41 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 08:21:34 +0100 Subject: [PATCH 11/12] fix(deps): update kubernetes packages to v0.31.4 (#6422) --- detectors/aws/eks/go.mod | 6 +++--- detectors/aws/eks/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/detectors/aws/eks/go.mod b/detectors/aws/eks/go.mod index 9c623c35a79..cd7984ef1cb 100644 --- a/detectors/aws/eks/go.mod +++ b/detectors/aws/eks/go.mod @@ -6,8 +6,8 @@ require ( github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.32.0 go.opentelemetry.io/otel/sdk v1.32.0 - k8s.io/apimachinery v0.31.3 - k8s.io/client-go v0.31.3 + k8s.io/apimachinery v0.31.4 + k8s.io/client-go v0.31.4 ) require ( @@ -45,7 +45,7 @@ require ( google.golang.org/protobuf v1.35.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.31.3 // indirect + k8s.io/api v0.31.4 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f // indirect k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect diff --git a/detectors/aws/eks/go.sum b/detectors/aws/eks/go.sum index 6b2d40a1d42..385215c3d2c 100644 --- a/detectors/aws/eks/go.sum +++ b/detectors/aws/eks/go.sum @@ -131,12 +131,12 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= -k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= -k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= -k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= -k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= +k8s.io/api v0.31.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM= +k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw= +k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM= +k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ= +k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f h1:nLHvOvs1CZ+FAEwR4EqLeRLfbtWQNlIu5g393Hq/1UM= From eb2064ce56b61515a0c4cd66eb6853be89ecb949 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:49:17 +0100 Subject: [PATCH 12/12] fix(deps): update module github.com/labstack/echo/v4 to v4.13.1 (#6424) --- .../github.com/labstack/echo/otelecho/example/go.mod | 2 +- .../github.com/labstack/echo/otelecho/example/go.sum | 4 ++-- instrumentation/github.com/labstack/echo/otelecho/go.mod | 2 +- instrumentation/github.com/labstack/echo/otelecho/go.sum | 4 ++-- instrumentation/github.com/labstack/echo/otelecho/test/go.mod | 2 +- instrumentation/github.com/labstack/echo/otelecho/test/go.sum | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/instrumentation/github.com/labstack/echo/otelecho/example/go.mod b/instrumentation/github.com/labstack/echo/otelecho/example/go.mod index f027340f262..4d43ed09914 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/example/go.mod +++ b/instrumentation/github.com/labstack/echo/otelecho/example/go.mod @@ -8,7 +8,7 @@ replace ( ) require ( - github.com/labstack/echo/v4 v4.13.0 + github.com/labstack/echo/v4 v4.13.1 go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.57.0 go.opentelemetry.io/otel v1.32.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 diff --git a/instrumentation/github.com/labstack/echo/otelecho/example/go.sum b/instrumentation/github.com/labstack/echo/otelecho/example/go.sum index e50a9be3917..86bafa827f0 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/example/go.sum +++ b/instrumentation/github.com/labstack/echo/otelecho/example/go.sum @@ -9,8 +9,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/labstack/echo/v4 v4.13.0 h1:8DjSi4H/k+RqoOmwXkxW14A2H1pdPdS95+qmdJ4q1Tg= -github.com/labstack/echo/v4 v4.13.0/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= +github.com/labstack/echo/v4 v4.13.1 h1:q3+CpQlYhJwpRr9+08pX0IdlabTIpnIhrg2AKPSKhFE= +github.com/labstack/echo/v4 v4.13.1/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= diff --git a/instrumentation/github.com/labstack/echo/otelecho/go.mod b/instrumentation/github.com/labstack/echo/otelecho/go.mod index d4211af10d0..fb735eac293 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/go.mod +++ b/instrumentation/github.com/labstack/echo/otelecho/go.mod @@ -5,7 +5,7 @@ go 1.22 replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../propagators/b3 require ( - github.com/labstack/echo/v4 v4.13.0 + github.com/labstack/echo/v4 v4.13.1 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/contrib/propagators/b3 v1.32.0 go.opentelemetry.io/otel v1.32.0 diff --git a/instrumentation/github.com/labstack/echo/otelecho/go.sum b/instrumentation/github.com/labstack/echo/otelecho/go.sum index 3f17cb61fcf..c87b5906ffa 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/go.sum +++ b/instrumentation/github.com/labstack/echo/otelecho/go.sum @@ -7,8 +7,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/labstack/echo/v4 v4.13.0 h1:8DjSi4H/k+RqoOmwXkxW14A2H1pdPdS95+qmdJ4q1Tg= -github.com/labstack/echo/v4 v4.13.0/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= +github.com/labstack/echo/v4 v4.13.1 h1:q3+CpQlYhJwpRr9+08pX0IdlabTIpnIhrg2AKPSKhFE= +github.com/labstack/echo/v4 v4.13.1/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= diff --git a/instrumentation/github.com/labstack/echo/otelecho/test/go.mod b/instrumentation/github.com/labstack/echo/otelecho/test/go.mod index ea2bfb4ac16..4f96623101f 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/test/go.mod +++ b/instrumentation/github.com/labstack/echo/otelecho/test/go.mod @@ -4,7 +4,7 @@ module go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otel go 1.22 require ( - github.com/labstack/echo/v4 v4.13.0 + github.com/labstack/echo/v4 v4.13.1 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.57.0 go.opentelemetry.io/otel v1.32.0 diff --git a/instrumentation/github.com/labstack/echo/otelecho/test/go.sum b/instrumentation/github.com/labstack/echo/otelecho/test/go.sum index 0bc39292138..f74a6b818a9 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/test/go.sum +++ b/instrumentation/github.com/labstack/echo/otelecho/test/go.sum @@ -9,8 +9,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/labstack/echo/v4 v4.13.0 h1:8DjSi4H/k+RqoOmwXkxW14A2H1pdPdS95+qmdJ4q1Tg= -github.com/labstack/echo/v4 v4.13.0/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= +github.com/labstack/echo/v4 v4.13.1 h1:q3+CpQlYhJwpRr9+08pX0IdlabTIpnIhrg2AKPSKhFE= +github.com/labstack/echo/v4 v4.13.1/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=