[WCF] NetFx client bytecode instrumentation (#2541)

This commit is contained in:
Mateusz Łach 2023-05-18 09:34:26 +02:00 committed by GitHub
parent 22139a12a4
commit 38f438e5f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 209 additions and 51 deletions

View File

@ -48,3 +48,4 @@ GRPCNETCLIENT
ENTITYFRAMEWORKCORE
appcmd
inetsrv
WCFCLIENT

View File

@ -139,6 +139,7 @@ due to lack of stable semantic convention.
| `SQLCLIENT` | [Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient) and [System.Data.SqlClient](https://www.nuget.org/packages/System.Data.SqlClient) | * \[3\] | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `STACKEXCHANGEREDIS` | [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis) **Not supported on .NET Framework** | ≥2.0.405 < 3.0.0 | source & bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `WCF` | [System.ServiceModel](https://www.nuget.org/packages/System.ServiceModel) **No support for server side on .NET**. For configuration see [WCF Instrumentation Configuration](wcf-config.md) | * \[4\] | source | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `WCFCLIENT` | [System.ServiceModel](https://www.nuget.org/packages/System.ServiceModel) **Supported on .NET Framework**. | ≥4.0.0.0 < 5.0.0.0 | source & bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
\[1\]: Only integrated pipeline mode is supported.

View File

@ -4,44 +4,6 @@
## WCF Client Configuration (.NET Framework)
Add the `IClientMessageInspector` instrumentation via a behavior extension on
the clients you want to instrument:
```xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="telemetryExtension" type="OpenTelemetry.Instrumentation.Wcf.TelemetryEndpointBehaviorExtensionElement, OpenTelemetry.Instrumentation.Wcf" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="telemetry">
<telemetryExtension />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="basicHttpConfig">
<security mode="None" />
</binding>
</basicHttpBinding>
<netTcpBinding>
<binding name="netTCPConfig">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="http://localhost:9009/Telemetry" binding="basicHttpBinding" bindingConfiguration="basicHttpConfig" behaviorConfiguration="telemetry" contract="TestApplication.Wcf.Client.NetFramework.IStatusServiceContract" name="StatusService_Http" />
</client>
</system.serviceModel>
</configuration>
```
Example project available in
[test/test-applications/integrations/TestApplication.Wcf.Client.NetFramework](../test/test-applications/integrations/TestApplication.Wcf.Client.NetFramework/)
folder.

View File

@ -3,7 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
// Auto-generated file, do not change it - generated by the IntegrationsJsonGenerator
#ifndef BYTECODE_INSTRUMENTATIONS_H
#define BYTECODE_INSTRUMENTATIONS_H
@ -11,7 +10,16 @@
namespace trace
{
inline std::unordered_map<WSTRING, WSTRING> trace_integration_names = {{WStr("StrongNamedValidation"), WStr("OTEL_DOTNET_AUTO_TRACES_STRONGNAMEDVALIDATION_INSTRUMENTATION_ENABLED")}, {WStr("StackExchangeRedis"), WStr("OTEL_DOTNET_AUTO_TRACES_STACKEXCHANGEREDIS_INSTRUMENTATION_ENABLED")}, {WStr("NServiceBus"), WStr("OTEL_DOTNET_AUTO_TRACES_NSERVICEBUS_INSTRUMENTATION_ENABLED")}, {WStr("MySqlData"), WStr("OTEL_DOTNET_AUTO_TRACES_MYSQLDATA_INSTRUMENTATION_ENABLED")}, {WStr("MongoDB"), WStr("OTEL_DOTNET_AUTO_TRACES_MONGODB_INSTRUMENTATION_ENABLED")}, {WStr("GraphQL"), WStr("OTEL_DOTNET_AUTO_TRACES_GRAPHQL_INSTRUMENTATION_ENABLED")}};
inline std::unordered_map<WSTRING, WSTRING> trace_integration_names =
{
{WStr("StrongNamedValidation"), WStr("OTEL_DOTNET_AUTO_TRACES_STRONGNAMEDVALIDATION_INSTRUMENTATION_ENABLED")},
{WStr("StackExchangeRedis"), WStr("OTEL_DOTNET_AUTO_TRACES_STACKEXCHANGEREDIS_INSTRUMENTATION_ENABLED")},
{WStr("NServiceBus"), WStr("OTEL_DOTNET_AUTO_TRACES_NSERVICEBUS_INSTRUMENTATION_ENABLED")},
{WStr("MySqlData"), WStr("OTEL_DOTNET_AUTO_TRACES_MYSQLDATA_INSTRUMENTATION_ENABLED")},
{WStr("MongoDB"), WStr("OTEL_DOTNET_AUTO_TRACES_MONGODB_INSTRUMENTATION_ENABLED")},
{WStr("GraphQL"), WStr("OTEL_DOTNET_AUTO_TRACES_GRAPHQL_INSTRUMENTATION_ENABLED")},
{WStr("WcfClient"), WStr("OTEL_DOTNET_AUTO_TRACES_WCFCLIENT_INSTRUMENTATION_ENABLED")}
};
inline std::unordered_map<WSTRING, WSTRING> metric_integration_names = {{WStr("NServiceBus"), WStr("OTEL_DOTNET_AUTO_METRICS_NSERVICEBUS_INSTRUMENTATION_ENABLED")}};
inline std::unordered_map<WSTRING, WSTRING> log_integration_names = {{WStr("ILogger"), WStr("OTEL_DOTNET_AUTO_LOGS_ILOGGER_INSTRUMENTATION_ENABLED")}};
}

View File

@ -24,6 +24,7 @@ OpenTelemetry.AutoInstrumentation.Instrumentations.NServiceBus.EndpointConfigura
OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegration
OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegrationAsync
OpenTelemetry.AutoInstrumentation.Instrumentations.Validations.StrongNamedValidation
OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf.WcfClientIntegration
override OpenTelemetry.AutoInstrumentation.CallTarget.CallTargetReturn<T>.ToString() -> string!
override OpenTelemetry.AutoInstrumentation.CallTarget.CallTargetState.ToString() -> string!
static OpenTelemetry.AutoInstrumentation.CallTarget.CallTargetInvoker.BeginMethod<TIntegration, TTarget, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8>(TTarget instance, ref TArg1 arg1, ref TArg2 arg2, ref TArg3 arg3, ref TArg4 arg4, ref TArg5 arg5, ref TArg6 arg6, ref TArg7 arg7, ref TArg8 arg8) -> OpenTelemetry.AutoInstrumentation.CallTarget.CallTargetState

View File

@ -106,6 +106,12 @@ internal enum TracerInstrumentation
/// <summary>
/// ASP.NET Core instrumentation.
/// </summary>
AspNetCore = 15
AspNetCore = 15,
#endif
#if NETFRAMEWORK
/// <summary>
/// WcfClient instrumentation.
/// </summary>
WcfClient = 16,
#endif
}

View File

@ -73,6 +73,12 @@ internal static partial class InstrumentationDefinitions
new("StackExchangeRedis", "Trace", "StackExchange.Redis", "StackExchange.Redis.ConnectionMultiplexer", "ConnectImplAsync", new[] { "System.Threading.Tasks.Task`1<StackExchange.Redis.ConnectionMultiplexer>", "System.Object", "System.IO.TextWriter" }, 2, 0, 0, 2, 65535, 65535, assemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegrationAsync"),
new("StackExchangeRedis", "Trace", "StackExchange.Redis", "StackExchange.Redis.ConnectionMultiplexer", "ConnectImplAsync", new[] { "System.Threading.Tasks.Task`1<StackExchange.Redis.ConnectionMultiplexer>", "StackExchange.Redis.ConfigurationOptions", "System.IO.TextWriter" }, 2, 0, 0, 2, 65535, 65535, assemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegrationAsync"),
new("StackExchangeRedis", "Trace", "StackExchange.Redis", "StackExchange.Redis.ConnectionMultiplexer", "ConnectImplAsync", new[] { "System.Threading.Tasks.Task`1<StackExchange.Redis.ConnectionMultiplexer>", "StackExchange.Redis.ConfigurationOptions", "System.IO.TextWriter", "System.Nullable`1[StackExchange.Redis.ServerType]" }, 2, 0, 0, 2, 65535, 65535, assemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegrationAsync"),
// WcfClient
new("WcfClient", "Trace", "System.ServiceModel", "System.ServiceModel.ChannelFactory", "InitializeEndpoint", new[] { "System.Void", "System.String", "System.ServiceModel.EndpointAddress" }, 4, 0, 0, 4, 65535, 65535, assemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf.WcfClientIntegration"),
new("WcfClient", "Trace", "System.ServiceModel", "System.ServiceModel.ChannelFactory", "InitializeEndpoint", new[] { "System.Void", "System.String", "System.ServiceModel.EndpointAddress", "System.Configuration.Configuration" }, 4, 0, 0, 4, 65535, 65535, assemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf.WcfClientIntegration"),
new("WcfClient", "Trace", "System.ServiceModel", "System.ServiceModel.ChannelFactory", "InitializeEndpoint", new[] { "System.Void", "System.ServiceModel.Description.ServiceEndpoint" }, 4, 0, 0, 4, 65535, 65535, assemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf.WcfClientIntegration"),
new("WcfClient", "Trace", "System.ServiceModel", "System.ServiceModel.ChannelFactory", "InitializeEndpoint", new[] { "System.Void", "System.ServiceModel.Channels.Binding", "System.ServiceModel.EndpointAddress" }, 4, 0, 0, 4, 65535, 65535, assemblyFullName, "OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf.WcfClientIntegration"),
};
// TODO: Generate this list using source generators

View File

@ -0,0 +1,34 @@
// <copyright file="WcfClientConstants.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
#if NETFRAMEWORK
using OpenTelemetry.AutoInstrumentation.Configurations;
namespace OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf;
internal static class WcfClientConstants
{
public const string ServiceModelAssemblyName = "System.ServiceModel";
public const string ChannelFactoryTypeName = "System.ServiceModel.ChannelFactory";
public const string InitializeEndpointMethodName = "InitializeEndpoint";
public const string MinVersion = "4.0.0";
public const string MaxVersion = "4.*.*";
public const string IntegrationName = nameof(TracerInstrumentation.WcfClient);
public const string EndpointAddressTypeName = "System.ServiceModel.EndpointAddress";
public const string ServiceEndpointTypeName = "System.ServiceModel.Description.ServiceEndpoint";
public const string BindingTypeName = "System.ServiceModel.Channels.Binding";
public const string ConfigurationTypeName = "System.Configuration.Configuration";
}
#endif

View File

@ -0,0 +1,58 @@
// <copyright file="WcfClientInitializer.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
#if NETFRAMEWORK
using OpenTelemetry.AutoInstrumentation.DuckTyping;
using OpenTelemetry.Instrumentation.Wcf;
namespace OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf;
internal static class WcfClientInitializer
{
private interface IChannelFactory
{
IEndpoint Endpoint { get; }
}
private interface IEndpoint
{
IKeyedCollection Behaviors { get; }
}
private interface IKeyedCollection
{
void Add(object o);
bool Contains(Type t);
}
public static void Initialize(object instance)
{
// WcfInstrumentationActivitySource.Options is initialized by WcfInitializer
// when targeted assembly loads. Remaining work to initialize instrumentation
// is to add telemetry behavior to the endpoint's collection.
if (!instance.TryDuckCast<IChannelFactory>(out var channelFactory))
{
return;
}
var behaviors = channelFactory.Endpoint.Behaviors;
if (!behaviors.Contains(typeof(TelemetryEndpointBehavior)))
{
behaviors.Add(new TelemetryEndpointBehavior());
}
}
}
#endif

View File

@ -0,0 +1,89 @@
// <copyright file="WcfClientIntegration.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
#if NETFRAMEWORK
using OpenTelemetry.AutoInstrumentation.CallTarget;
using OpenTelemetry.AutoInstrumentation.Configurations;
namespace OpenTelemetry.AutoInstrumentation.Instrumentations.Wcf;
/// <summary>
/// ChannelFactory instrumentation.
/// </summary>
[InstrumentMethod(
assemblyName: WcfClientConstants.ServiceModelAssemblyName,
typeName: WcfClientConstants.ChannelFactoryTypeName,
methodName: WcfClientConstants.InitializeEndpointMethodName,
returnTypeName: ClrNames.Void,
parameterTypeNames: new[] { ClrNames.String, WcfClientConstants.EndpointAddressTypeName },
minimumVersion: WcfClientConstants.MinVersion,
maximumVersion: WcfClientConstants.MaxVersion,
integrationName: WcfClientConstants.IntegrationName,
type: InstrumentationType.Trace)]
[InstrumentMethod(
assemblyName: WcfClientConstants.ServiceModelAssemblyName,
typeName: WcfClientConstants.ChannelFactoryTypeName,
methodName: WcfClientConstants.InitializeEndpointMethodName,
returnTypeName: ClrNames.Void,
parameterTypeNames: new[] { ClrNames.String, WcfClientConstants.EndpointAddressTypeName, WcfClientConstants.ConfigurationTypeName },
minimumVersion: WcfClientConstants.MinVersion,
maximumVersion: WcfClientConstants.MaxVersion,
integrationName: WcfClientConstants.IntegrationName,
type: InstrumentationType.Trace)]
[InstrumentMethod(
assemblyName: WcfClientConstants.ServiceModelAssemblyName,
typeName: WcfClientConstants.ChannelFactoryTypeName,
methodName: WcfClientConstants.InitializeEndpointMethodName,
returnTypeName: ClrNames.Void,
parameterTypeNames: new[] { WcfClientConstants.ServiceEndpointTypeName },
minimumVersion: WcfClientConstants.MinVersion,
maximumVersion: WcfClientConstants.MaxVersion,
integrationName: WcfClientConstants.IntegrationName,
type: InstrumentationType.Trace)]
[InstrumentMethod(
assemblyName: WcfClientConstants.ServiceModelAssemblyName,
typeName: WcfClientConstants.ChannelFactoryTypeName,
methodName: WcfClientConstants.InitializeEndpointMethodName,
returnTypeName: ClrNames.Void,
parameterTypeNames: new[] { WcfClientConstants.BindingTypeName, WcfClientConstants.EndpointAddressTypeName },
minimumVersion: WcfClientConstants.MinVersion,
maximumVersion: WcfClientConstants.MaxVersion,
integrationName: WcfClientConstants.IntegrationName,
type: InstrumentationType.Trace)]
public static class WcfClientIntegration
{
/// <summary>
/// OnMethodEnd callback
/// </summary>
/// <param name="instance">Instance value, aka `this` of the instrumented method.</param>
/// <param name="exception">Exception value</param>
/// <param name="state">CallTarget state</param>
/// <typeparam name="TTarget">Type of the target</typeparam>
/// <returns>A response value, in an async scenario will be T of Task of T</returns>
internal static CallTargetReturn OnMethodEnd<TTarget>(TTarget instance, Exception exception, in CallTargetState state)
{
if (Instrumentation.TracerSettings.Value.EnabledInstrumentations.Contains(TracerInstrumentation.WcfClient))
{
if (instance != null)
{
WcfClientInitializer.Initialize(instance);
}
}
return CallTargetReturn.GetDefault();
}
}
#endif

View File

@ -2,16 +2,8 @@
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="telemetryExtension" type="OpenTelemetry.Instrumentation.Wcf.TelemetryEndpointBehaviorExtensionElement, OpenTelemetry.Instrumentation.Wcf" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="telemetry">
<telemetryExtension />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
@ -26,8 +18,8 @@
</netTcpBinding>
</bindings>
<client>
<endpoint address="http://127.0.0.1:9009/Telemetry" binding="basicHttpBinding" bindingConfiguration="basicHttpConfig" behaviorConfiguration="telemetry" contract="TestApplication.Wcf.Client.NetFramework.IStatusServiceContract" name="StatusService_Http" />
<endpoint address="net.tcp://127.0.0.1:9090/Telemetry" binding="netTcpBinding" bindingConfiguration="netTCPConfig" behaviorConfiguration="telemetry" contract="TestApplication.Wcf.Client.NetFramework.IStatusServiceContract" name="StatusService_Tcp" />
<endpoint address="http://127.0.0.1:9009/Telemetry" binding="basicHttpBinding" bindingConfiguration="basicHttpConfig" contract="TestApplication.Wcf.Client.NetFramework.IStatusServiceContract" name="StatusService_Http" />
<endpoint address="net.tcp://127.0.0.1:9090/Telemetry" binding="netTcpBinding" bindingConfiguration="netTCPConfig" contract="TestApplication.Wcf.Client.NetFramework.IStatusServiceContract" name="StatusService_Tcp" />
</client>
</system.serviceModel>
</configuration>