diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index 4d5716f66ec..9ddf6c56b48 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -1,15 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Stackdriver", "src\OpenTelemetry.Exporter.Stackdriver\OpenTelemetry.Exporter.Stackdriver.csproj", "{D7F6B622-ED4B-4A3B-BAFD-24FB503666C2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Stackdriver", "src\OpenTelemetry.Exporter.Stackdriver\OpenTelemetry.Exporter.Stackdriver.csproj", "{D7F6B622-ED4B-4A3B-BAFD-24FB503666C2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2097345F-4DD3-477D-BC54-A922F9B2B402}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Exporter.Stackdriver.Tests", "test\OpenTelemetry.Exporter.Stackdriver.Tests\OpenTelemetry.Exporter.Stackdriver.Tests.csproj", "{39EF6946-9909-498D-9181-479DEF6CB4B7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Stackdriver.Tests", "test\OpenTelemetry.Exporter.Stackdriver.Tests\OpenTelemetry.Exporter.Stackdriver.Tests.csproj", "{39EF6946-9909-498D-9181-479DEF6CB4B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Instrumentation.Azure", "src\OpenTelemetry.Instrumentation.Azure\OpenTelemetry.Instrumentation.Azure.csproj", "{0E862C3A-89B1-4C3D-A2D5-579E38439C92}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -20,9 +22,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D7F6B622-ED4B-4A3B-BAFD-24FB503666C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7F6B622-ED4B-4A3B-BAFD-24FB503666C2}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -48,9 +47,28 @@ Global {39EF6946-9909-498D-9181-479DEF6CB4B7}.Release|x64.Build.0 = Release|Any CPU {39EF6946-9909-498D-9181-479DEF6CB4B7}.Release|x86.ActiveCfg = Release|Any CPU {39EF6946-9909-498D-9181-479DEF6CB4B7}.Release|x86.Build.0 = Release|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Debug|x64.Build.0 = Debug|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Debug|x86.Build.0 = Debug|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Release|Any CPU.Build.0 = Release|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Release|x64.ActiveCfg = Release|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Release|x64.Build.0 = Release|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Release|x86.ActiveCfg = Release|Any CPU + {0E862C3A-89B1-4C3D-A2D5-579E38439C92}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {D7F6B622-ED4B-4A3B-BAFD-24FB503666C2} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63} {39EF6946-9909-498D-9181-479DEF6CB4B7} = {2097345F-4DD3-477D-BC54-A922F9B2B402} + {0E862C3A-89B1-4C3D-A2D5-579E38439C92} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B0816796-CDB3-47D7-8C3C-946434DE3B66} EndGlobalSection EndGlobal diff --git a/src/OpenTelemetry.Exporter.Stackdriver/OpenTelemetry.Exporter.Stackdriver.csproj b/src/OpenTelemetry.Exporter.Stackdriver/OpenTelemetry.Exporter.Stackdriver.csproj index fcdc021bbbf..0822efdfe90 100644 --- a/src/OpenTelemetry.Exporter.Stackdriver/OpenTelemetry.Exporter.Stackdriver.csproj +++ b/src/OpenTelemetry.Exporter.Stackdriver/OpenTelemetry.Exporter.Stackdriver.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;net46 Stackdriver .NET Exporter for OpenTelemetry. $(PackageTags);Stackdriver;Google;GCP;distributed-tracing 8.0 diff --git a/src/OpenTelemetry.Instrumentation.Azure/AzureClientsInstrumentation.cs b/src/OpenTelemetry.Instrumentation.Azure/AzureClientsInstrumentation.cs new file mode 100644 index 00000000000..85e64bf69e6 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Azure/AzureClientsInstrumentation.cs @@ -0,0 +1,50 @@ +// +// 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. +// +using OpenTelemetry.Instrumentation.Azure.Implementation; +using System; + +namespace OpenTelemetry.Instrumentation.Azure +{ + /// + /// AzureClients instrumentation. + /// TODO: Azure specific listeners would be moved out of this repo. + /// I believe this was initially put here for quick validation. + /// There were no unit tests covering this feature, so + /// cannot validate after Span is replaced with Activity. + /// + public class AzureClientsInstrumentation : IDisposable + { + private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; + + /// + /// Initializes a new instance of the class. + /// + public AzureClientsInstrumentation() + { + this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber( + name => new AzureSdkDiagnosticListener(name), + listener => listener.Name.StartsWith("Azure."), + null); + this.diagnosticSourceSubscriber.Subscribe(); + } + + /// + public void Dispose() + { + this.diagnosticSourceSubscriber?.Dispose(); + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Azure/AzurePipelineInstrumentation.cs b/src/OpenTelemetry.Instrumentation.Azure/AzurePipelineInstrumentation.cs new file mode 100644 index 00000000000..ba75f49d9e6 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Azure/AzurePipelineInstrumentation.cs @@ -0,0 +1,47 @@ +// +// 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. +// +using OpenTelemetry.Instrumentation.Azure.Implementation; +using System; + +namespace OpenTelemetry.Instrumentation.Azure +{ + /// + /// AzurePipeline instrumentation. + /// TODO: Azure specific listeners would be moved out of this repo. + /// I believe this was initially put here for quick validation. + /// There were no unit tests covering this feature, so + /// cannot validate after Span is replaced with Activity. + /// + public class AzurePipelineInstrumentation : IDisposable + { + private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; + + /// + /// Initializes a new instance of the class. + /// + public AzurePipelineInstrumentation() + { + this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new AzureSdkDiagnosticListener("Azure.Pipeline"), null); + this.diagnosticSourceSubscriber.Subscribe(); + } + + /// + public void Dispose() + { + this.diagnosticSourceSubscriber?.Dispose(); + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Azure/Class1.cs b/src/OpenTelemetry.Instrumentation.Azure/Class1.cs new file mode 100644 index 00000000000..f119cb0d7ba --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Azure/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace OpenTelemetry.Instrumentation.Azure +{ + public class Class1 + { + } +} diff --git a/src/OpenTelemetry.Instrumentation.Azure/Implementation/AzureSdkDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Azure/Implementation/AzureSdkDiagnosticListener.cs new file mode 100644 index 00000000000..0fc0a4d7640 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Azure/Implementation/AzureSdkDiagnosticListener.cs @@ -0,0 +1,131 @@ +// +// 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. +// +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.Azure.Implementation +{ + internal class AzureSdkDiagnosticListener : ListenerHandler + { + internal const string ActivitySourceName = "AzureSDK"; + internal const string ActivityName = ActivitySourceName + ".HttpRequestOut"; + private static readonly Version Version = typeof(AzureSdkDiagnosticListener).Assembly.GetName().Version; + private static readonly ActivitySource AzureSDKActivitySource = new ActivitySource(ActivitySourceName, Version.ToString()); + + // all fetchers must not be reused between DiagnosticSources. + private readonly PropertyFetcher linksPropertyFetcher = new PropertyFetcher("Links"); + + public AzureSdkDiagnosticListener(string sourceName) + : base(sourceName, null) + { + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public override void OnStartActivity(Activity activity, object valueValue) + { + string operationName = null; + var activityKind = ActivityKind.Internal; + + foreach (var keyValuePair in activity.Tags) + { + if (keyValuePair.Key == "http.url") + { + operationName = keyValuePair.Value; + activityKind = ActivityKind.Client; + break; + } + + if (keyValuePair.Key == "kind") + { + if (Enum.TryParse(keyValuePair.Value, true, out ActivityKind parsedActivityKind)) + { + activityKind = parsedActivityKind; + } + } + } + + if (operationName == null) + { + operationName = this.GetOperationName(activity); + } + + List links = null; + if (this.linksPropertyFetcher.Fetch(valueValue) is IEnumerable activityLinks) + { + if (activityLinks.Any()) + { + links = new List(); + foreach (var link in activityLinks) + { + if (link != null) + { + links.Add(new ActivityLink(new ActivityContext(link.TraceId, link.ParentSpanId, link.ActivityTraceFlags))); + } + } + } + } + + // Ignore the activity and create a new one using ActivitySource. + // The new one will have Sampling decision made using extracted Links as well. + AzureSDKActivitySource.StartActivity(operationName, activityKind, activity.Id, activity.Tags, links); + } + + public override void OnStopActivity(Activity current, object valueValue) + { + // nothing to be done. + } + + public override void OnException(Activity activity, object valueValue) + { + Status status = Status.Unknown; + activity.AddTag(SpanAttributeConstants.StatusCodeKey, SpanHelper.GetCachedCanonicalCodeString(status.CanonicalCode)); + activity.AddTag(SpanAttributeConstants.StatusDescriptionKey, valueValue?.ToString()); + } + + private string GetOperationName(Activity activity) + { + // activity name looks like 'Azure.<...>..' + // as namespace is too verbose, we'll just take the last two nodes from the activity name as telemetry name + // this will change with https://github.com/Azure/azure-sdk-for-net/issues/9071 ~Feb 2020 + + string activityName = activity.OperationName; + int methodDotIndex = activityName.LastIndexOf('.'); + if (methodDotIndex <= 0) + { + return activityName; + } + + int classDotIndex = activityName.LastIndexOf('.', methodDotIndex - 1); + + if (classDotIndex == -1) + { + return activityName; + } + + return activityName.Substring(classDotIndex + 1, activityName.Length - classDotIndex - 1); + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Azure/Implementation/PropertyFetcher.cs b/src/OpenTelemetry.Instrumentation.Azure/Implementation/PropertyFetcher.cs new file mode 100644 index 00000000000..a7e90355a8c --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Azure/Implementation/PropertyFetcher.cs @@ -0,0 +1,99 @@ +// +// 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. +// +using System; +using System.Linq; +using System.Reflection; + +namespace OpenTelemetry.Instrumentation.Azure.Implementation +{ + internal class PropertyFetcher + { + private readonly string propertyName; + private PropertyFetch innerFetcher; + + public PropertyFetcher(string propertyName) + { + this.propertyName = propertyName; + } + + public object Fetch(object obj) + { + if (this.innerFetcher == null) + { + var type = obj.GetType().GetTypeInfo(); + var property = type.DeclaredProperties.FirstOrDefault(p => string.Equals(p.Name, this.propertyName, StringComparison.InvariantCultureIgnoreCase)); + if (property == null) + { + property = type.GetProperty(this.propertyName); + } + + this.innerFetcher = PropertyFetch.FetcherForProperty(property); + } + + return this.innerFetcher?.Fetch(obj); + } + + // see https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs + private class PropertyFetch + { + /// + /// Create a property fetcher from a .NET Reflection PropertyInfo class that + /// represents a property of a particular type. + /// + public static PropertyFetch FetcherForProperty(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + // returns null on any fetch. + return new PropertyFetch(); + } + + var typedPropertyFetcher = typeof(TypedFetchProperty<,>); + var instantiatedTypedPropertyFetcher = typedPropertyFetcher.GetTypeInfo().MakeGenericType( + propertyInfo.DeclaringType, propertyInfo.PropertyType); + return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, propertyInfo); + } + + /// + /// Given an object, fetch the property that this propertyFetch represents. + /// + public virtual object Fetch(object obj) + { + return null; + } + + private class TypedFetchProperty : PropertyFetch + { + private readonly Func propertyFetch; + + public TypedFetchProperty(PropertyInfo property) + { + this.propertyFetch = (Func)property.GetMethod.CreateDelegate(typeof(Func)); + } + + public override object Fetch(object obj) + { + if (obj is TObject o) + { + return this.propertyFetch(o); + } + + return null; + } + } + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Azure/OpenTelemetry.Instrumentation.Azure.csproj b/src/OpenTelemetry.Instrumentation.Azure/OpenTelemetry.Instrumentation.Azure.csproj new file mode 100644 index 00000000000..4c05aec8f8d --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Azure/OpenTelemetry.Instrumentation.Azure.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + OpenTelemetry instrumentation for Azure dependencies. + + + + + + + + + + + diff --git a/src/OpenTelemetry.Instrumentation.Azure/OpenTelemetryBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Azure/OpenTelemetryBuilderExtensions.cs new file mode 100644 index 00000000000..647ec000aee --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Azure/OpenTelemetryBuilderExtensions.cs @@ -0,0 +1,46 @@ +// +// 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. +// + +using System; +using OpenTelemetry.Instrumentation.Azure; +using OpenTelemetry.Instrumentation.Azure.Implementation; + +namespace OpenTelemetry.Trace.Configuration +{ + /// + /// Extension methods to simplify registering of dependency instrumentation. + /// + public static class OpenTelemetryBuilderExtensions + { + /// + /// Enables instrumentation for Azure clients. + /// + /// being configured. + /// The instance of to chain the calls. + public static OpenTelemetryBuilder AddAzureClientsDependencyInstrumentation( + this OpenTelemetryBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddActivitySource(AzureSdkDiagnosticListener.ActivitySourceName); + builder.AddInstrumentation((activitySource) => new AzureClientsInstrumentation()); + return builder; + } + } +}