enforce EOL=LF in CI (#1250)

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
Reiley Yang 2020-09-09 17:14:26 -07:00 committed by GitHub
parent a3731ea02d
commit 5e41c62327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 3231 additions and 3227 deletions

View File

@ -8,7 +8,7 @@ CR = b'\r'
CRLF = b'\r\n'
LF = b'\n'
def sanitycheck(pattern, allow_utf8 = False):
def sanitycheck(pattern, allow_utf8 = False, allow_eol = (CRLF, LF)):
error_count = 0
for filename in glob.glob(pattern, recursive=True):
@ -43,6 +43,10 @@ def sanitycheck(pattern, allow_utf8 = False):
elif line[-1:] == CR:
error.append(' CR found at Ln:{} {}'.format(lineno, line))
line = line[:-1]
if eol:
if eol not in allow_eol:
error.append(' Line ending {} not allowed at Ln:{}'.format(eol, lineno))
break
if line[-1:] == b' ' or line[-1:] == b'\t':
error.append(' Trailing space found at Ln:{} {}'.format(lineno, line))
lineno += 1
@ -58,19 +62,19 @@ def sanitycheck(pattern, allow_utf8 = False):
return error_count
retval = 0
retval += sanitycheck('**/*.cmd')
retval += sanitycheck('**/*.config', allow_utf8 = True)
retval += sanitycheck('**/*.cs', allow_utf8 = True)
retval += sanitycheck('**/*.cshtml', allow_utf8 = True)
retval += sanitycheck('**/*.cmd', allow_eol = (CRLF,))
retval += sanitycheck('**/*.config', allow_utf8 = True, allow_eol = (LF,))
retval += sanitycheck('**/*.cs', allow_utf8 = True, allow_eol = (LF,))
retval += sanitycheck('**/*.cshtml', allow_utf8 = True, allow_eol = (LF,))
retval += sanitycheck('**/*.csproj', allow_utf8 = True)
retval += sanitycheck('**/*.htm')
retval += sanitycheck('**/*.html')
retval += sanitycheck('**/*.htm', allow_eol = (LF,))
retval += sanitycheck('**/*.html', allow_eol = (LF,))
retval += sanitycheck('**/*.md')
retval += sanitycheck('**/*.proj')
retval += sanitycheck('**/*.props')
retval += sanitycheck('**/*.py')
retval += sanitycheck('**/*.ruleset', allow_utf8 = True)
retval += sanitycheck('**/*.sln', allow_utf8 = True)
retval += sanitycheck('**/*.xml')
retval += sanitycheck('**/*.proj', allow_eol = (LF,))
retval += sanitycheck('**/*.props', allow_eol = (LF,))
retval += sanitycheck('**/*.py', allow_eol = (LF,))
retval += sanitycheck('**/*.ruleset', allow_utf8 = True, allow_eol = (LF,))
retval += sanitycheck('**/*.sln', allow_utf8 = True, allow_eol = (LF,))
retval += sanitycheck('**/*.xml', allow_eol = (LF,))
sys.exit(retval)

View File

@ -1,62 +1,62 @@
// <copyright file="MyExporter.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>
using System;
using System.Diagnostics;
using System.Text;
using OpenTelemetry;
using OpenTelemetry.Trace;
internal class MyExporter : ActivityExporter
{
private readonly string name;
public MyExporter(string name = "MyExporter")
{
this.name = name;
}
public override ExportResult Export(in Batch<Activity> batch)
{
// SuppressInstrumentationScope should be used to prevent exporter
// code from generating telemetry and causing live-loop.
using var scope = SuppressInstrumentationScope.Begin();
var sb = new StringBuilder();
foreach (var activity in batch)
{
if (sb.Length > 0)
{
sb.Append(", ");
}
sb.Append(activity.DisplayName);
}
Console.WriteLine($"{this.name}.Export([{sb.ToString()}])");
return ExportResult.Success;
}
protected override void OnShutdown(int timeoutMilliseconds)
{
Console.WriteLine($"{this.name}.OnShutdown(timeoutMilliseconds={timeoutMilliseconds})");
}
protected override void Dispose(bool disposing)
{
Console.WriteLine($"{this.name}.Dispose({disposing})");
}
}
// <copyright file="MyExporter.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>
using System;
using System.Diagnostics;
using System.Text;
using OpenTelemetry;
using OpenTelemetry.Trace;
internal class MyExporter : ActivityExporter
{
private readonly string name;
public MyExporter(string name = "MyExporter")
{
this.name = name;
}
public override ExportResult Export(in Batch<Activity> batch)
{
// SuppressInstrumentationScope should be used to prevent exporter
// code from generating telemetry and causing live-loop.
using var scope = SuppressInstrumentationScope.Begin();
var sb = new StringBuilder();
foreach (var activity in batch)
{
if (sb.Length > 0)
{
sb.Append(", ");
}
sb.Append(activity.DisplayName);
}
Console.WriteLine($"{this.name}.Export([{sb.ToString()}])");
return ExportResult.Success;
}
protected override void OnShutdown(int timeoutMilliseconds)
{
Console.WriteLine($"{this.name}.OnShutdown(timeoutMilliseconds={timeoutMilliseconds})");
}
protected override void Dispose(bool disposing)
{
Console.WriteLine($"{this.name}.Dispose({disposing})");
}
}

View File

@ -1,56 +1,56 @@
// <copyright file="MyProcessor.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>
using System;
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
internal class MyProcessor : ActivityProcessor
{
private readonly string name;
public MyProcessor(string name = "MyProcessor")
{
this.name = name;
}
public override void OnStart(Activity activity)
{
Console.WriteLine($"{this.name}.OnStart({activity.DisplayName})");
}
public override void OnEnd(Activity activity)
{
Console.WriteLine($"{this.name}.OnEnd({activity.DisplayName})");
}
protected override bool OnForceFlush(int timeoutMilliseconds)
{
Console.WriteLine($"{this.name}.OnForceFlush({timeoutMilliseconds})");
return true;
}
protected override void OnShutdown(int timeoutMilliseconds)
{
Console.WriteLine($"{this.name}.OnShutdown({timeoutMilliseconds})");
}
protected override void Dispose(bool disposing)
{
Console.WriteLine($"{this.name}.Dispose({disposing})");
}
}
// <copyright file="MyProcessor.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>
using System;
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
internal class MyProcessor : ActivityProcessor
{
private readonly string name;
public MyProcessor(string name = "MyProcessor")
{
this.name = name;
}
public override void OnStart(Activity activity)
{
Console.WriteLine($"{this.name}.OnStart({activity.DisplayName})");
}
public override void OnEnd(Activity activity)
{
Console.WriteLine($"{this.name}.OnEnd({activity.DisplayName})");
}
protected override bool OnForceFlush(int timeoutMilliseconds)
{
Console.WriteLine($"{this.name}.OnForceFlush({timeoutMilliseconds})");
return true;
}
protected override void OnShutdown(int timeoutMilliseconds)
{
Console.WriteLine($"{this.name}.OnShutdown({timeoutMilliseconds})");
}
protected override void Dispose(bool disposing)
{
Console.WriteLine($"{this.name}.Dispose({disposing})");
}
}

View File

@ -1,29 +1,29 @@
// <copyright file="MySampler.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>
using System;
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
internal class MySampler : Sampler
{
public override SamplingResult ShouldSample(in SamplingParameters param)
{
Console.WriteLine($"MySampler.ShouldSample({param.Name})");
return new SamplingResult(SamplingDecision.RecordAndSampled);
}
}
// <copyright file="MySampler.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>
using System;
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
internal class MySampler : Sampler
{
public override SamplingResult ShouldSample(in SamplingParameters param)
{
Console.WriteLine($"MySampler.ShouldSample({param.Name})");
return new SamplingResult(SamplingDecision.RecordAndSampled);
}
}

View File

@ -1,46 +1,46 @@
// <copyright file="Program.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>
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
public class Program
{
private static readonly ActivitySource DemoSource = new ActivitySource("OTel.Demo");
public static void Main()
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(new MySampler())
.AddSource("OTel.Demo")
.AddProcessor(new MyProcessor("ProcessorA"))
.AddProcessor(new MyProcessor("ProcessorB"))
.AddProcessor(new SimpleExportActivityProcessor(new MyExporter("ExporterX")))
.AddMyExporter()
.Build();
using (var foo = DemoSource.StartActivity("Foo"))
{
using (var bar = DemoSource.StartActivity("Bar"))
{
using (var baz = DemoSource.StartActivity("Baz"))
{
}
}
}
}
}
// <copyright file="Program.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>
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
public class Program
{
private static readonly ActivitySource DemoSource = new ActivitySource("OTel.Demo");
public static void Main()
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(new MySampler())
.AddSource("OTel.Demo")
.AddProcessor(new MyProcessor("ProcessorA"))
.AddProcessor(new MyProcessor("ProcessorB"))
.AddProcessor(new SimpleExportActivityProcessor(new MyExporter("ExporterX")))
.AddMyExporter()
.Build();
using (var foo = DemoSource.StartActivity("Foo"))
{
using (var bar = DemoSource.StartActivity("Bar"))
{
using (var baz = DemoSource.StartActivity("Baz"))
{
}
}
}
}
}

View File

@ -1,40 +1,40 @@
// <copyright file="Program.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>
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
public class Program
{
private static readonly ActivitySource MyActivitySource = new ActivitySource(
"MyCompany.MyProduct.MyLibrary");
public static void Main()
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("MyCompany.MyProduct.MyLibrary")
.AddConsoleExporter()
.Build();
using (var activity = MyActivitySource.StartActivity("SayHello"))
{
activity?.SetTag("foo", 1);
activity?.SetTag("bar", "Hello, World!");
activity?.SetTag("baz", new int[] { 1, 2, 3 });
}
}
}
// <copyright file="Program.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>
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
public class Program
{
private static readonly ActivitySource MyActivitySource = new ActivitySource(
"MyCompany.MyProduct.MyLibrary");
public static void Main()
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("MyCompany.MyProduct.MyLibrary")
.AddConsoleExporter()
.Build();
using (var activity = MyActivitySource.StartActivity("SayHello"))
{
activity?.SetTag("foo", 1);
activity?.SetTag("bar", "Hello, World!");
activity?.SetTag("baz", new int[] { 1, 2, 3 });
}
}
}

View File

@ -1,56 +1,56 @@
// <copyright file="AsyncLocalRuntimeContextSlot.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 !NET452
using System.Runtime.CompilerServices;
using System.Threading;
namespace OpenTelemetry.Context
{
/// <summary>
/// The async local implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class AsyncLocalRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
private readonly AsyncLocal<T> slot;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncLocalRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public AsyncLocalRuntimeContextSlot(string name)
: base(name)
{
this.slot = new AsyncLocal<T>();
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T Get()
{
return this.slot.Value;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Set(T value)
{
this.slot.Value = value;
}
}
}
#endif
// <copyright file="AsyncLocalRuntimeContextSlot.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 !NET452
using System.Runtime.CompilerServices;
using System.Threading;
namespace OpenTelemetry.Context
{
/// <summary>
/// The async local implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class AsyncLocalRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
private readonly AsyncLocal<T> slot;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncLocalRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public AsyncLocalRuntimeContextSlot(string name)
: base(name)
{
this.slot = new AsyncLocal<T>();
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T Get()
{
return this.slot.Value;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Set(T value)
{
this.slot.Value = value;
}
}
}
#endif

View File

@ -1,77 +1,77 @@
// <copyright file="RemotingRuntimeContextSlot.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 NET452
using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting.Messaging;
namespace OpenTelemetry.Context
{
/// <summary>
/// The .NET Remoting implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class RemotingRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
// A special workaround to suppress context propagation cross AppDomains.
//
// By default the value added to System.Runtime.Remoting.Messaging.CallContext
// will be marshalled/unmarshalled across AppDomain boundary. This will cause
// serious issue if the destination AppDomain doesn't have the corresponding type
// to unmarshal data.
// The worst case is AppDomain crash with ReflectionLoadTypeException.
//
// The workaround is to use a well known type that exists in all AppDomains, and
// put the actual payload as a non-public field so the field is ignored during
// marshalling.
private static readonly FieldInfo WrapperField = typeof(BitArray).GetField("_syncRoot", BindingFlags.Instance | BindingFlags.NonPublic);
/// <summary>
/// Initializes a new instance of the <see cref="RemotingRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public RemotingRuntimeContextSlot(string name)
: base(name)
{
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T Get()
{
var wrapper = CallContext.LogicalGetData(this.Name) as BitArray;
if (wrapper == null)
{
return default(T);
}
return (T)WrapperField.GetValue(wrapper);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Set(T value)
{
var wrapper = new BitArray(0);
WrapperField.SetValue(wrapper, value);
CallContext.LogicalSetData(this.Name, wrapper);
}
}
}
#endif
// <copyright file="RemotingRuntimeContextSlot.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 NET452
using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting.Messaging;
namespace OpenTelemetry.Context
{
/// <summary>
/// The .NET Remoting implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class RemotingRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
// A special workaround to suppress context propagation cross AppDomains.
//
// By default the value added to System.Runtime.Remoting.Messaging.CallContext
// will be marshalled/unmarshalled across AppDomain boundary. This will cause
// serious issue if the destination AppDomain doesn't have the corresponding type
// to unmarshal data.
// The worst case is AppDomain crash with ReflectionLoadTypeException.
//
// The workaround is to use a well known type that exists in all AppDomains, and
// put the actual payload as a non-public field so the field is ignored during
// marshalling.
private static readonly FieldInfo WrapperField = typeof(BitArray).GetField("_syncRoot", BindingFlags.Instance | BindingFlags.NonPublic);
/// <summary>
/// Initializes a new instance of the <see cref="RemotingRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public RemotingRuntimeContextSlot(string name)
: base(name)
{
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T Get()
{
var wrapper = CallContext.LogicalGetData(this.Name) as BitArray;
if (wrapper == null)
{
return default(T);
}
return (T)WrapperField.GetValue(wrapper);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Set(T value)
{
var wrapper = new BitArray(0);
WrapperField.SetValue(wrapper, value);
CallContext.LogicalSetData(this.Name, wrapper);
}
}
}
#endif

View File

@ -1,140 +1,140 @@
// <copyright file="RuntimeContext.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>
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
namespace OpenTelemetry.Context
{
/// <summary>
/// Generic runtime context management API.
/// </summary>
public static class RuntimeContext
{
private static readonly ConcurrentDictionary<string, object> Slots = new ConcurrentDictionary<string, object>();
/// <summary>
/// Gets or sets the actual context carrier implementation.
/// </summary>
#if !NET452
public static Type ContextSlotType { get; set; } = typeof(AsyncLocalRuntimeContextSlot<>);
#else
public static Type ContextSlotType { get; set; } = typeof(RemotingRuntimeContextSlot<>);
#endif
/// <summary>
/// Register a named context slot.
/// </summary>
/// <param name="slotName">The name of the context slot.</param>
/// <typeparam name="T">The type of the underlying value.</typeparam>
/// <returns>The slot registered.</returns>
public static RuntimeContextSlot<T> RegisterSlot<T>(string slotName)
{
if (string.IsNullOrEmpty(slotName))
{
throw new ArgumentException($"{nameof(slotName)} cannot be null or empty string.");
}
lock (Slots)
{
if (Slots.ContainsKey(slotName))
{
throw new InvalidOperationException($"The context slot {slotName} is already registered.");
}
var type = ContextSlotType.MakeGenericType(typeof(T));
var ctor = type.GetConstructor(new Type[] { typeof(string) });
var slot = (RuntimeContextSlot<T>)ctor.Invoke(new object[] { slotName });
Slots[slotName] = slot;
return slot;
}
}
/// <summary>
/// Get a registered slot from a given name.
/// </summary>
/// <param name="slotName">The name of the context slot.</param>
/// <typeparam name="T">The type of the underlying value.</typeparam>
/// <returns>The slot previously registered.</returns>
public static RuntimeContextSlot<T> GetSlot<T>(string slotName)
{
if (string.IsNullOrEmpty(slotName))
{
throw new ArgumentException($"{nameof(slotName)} cannot be null or empty string.");
}
Slots.TryGetValue(slotName, out var slot);
return slot as RuntimeContextSlot<T> ?? throw new ArgumentException($"The context slot {slotName} is not found.");
}
/*
public static void Apply(IDictionary<string, object> snapshot)
{
foreach (var entry in snapshot)
{
// TODO: revisit this part if we want Snapshot() to be used on critical paths
dynamic value = entry.Value;
SetValue(entry.Key, value);
}
}
public static IDictionary<string, object> Snapshot()
{
var retval = new Dictionary<string, object>();
foreach (var entry in Slots)
{
// TODO: revisit this part if we want Snapshot() to be used on critical paths
dynamic slot = entry.Value;
retval[entry.Key] = slot.Get();
}
return retval;
}
*/
/// <summary>
/// Sets the value to a registered slot.
/// </summary>
/// <param name="name">The name of the context slot.</param>
/// <param name="value">The value to be set.</param>
/// <typeparam name="T">The type of the value.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetValue<T>(string name, T value)
{
var slot = (RuntimeContextSlot<T>)Slots[name];
slot.Set(value);
}
/// <summary>
/// Gets the value from a registered slot.
/// </summary>
/// <param name="name">The name of the context slot.</param>
/// <typeparam name="T">The type of the value.</typeparam>
/// <returns>The value retrieved from the context slot.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T GetValue<T>(string name)
{
var slot = (RuntimeContextSlot<T>)Slots[name];
return slot.Get();
}
// For testing purpose
internal static void Clear()
{
Slots.Clear();
}
}
}
// <copyright file="RuntimeContext.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>
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
namespace OpenTelemetry.Context
{
/// <summary>
/// Generic runtime context management API.
/// </summary>
public static class RuntimeContext
{
private static readonly ConcurrentDictionary<string, object> Slots = new ConcurrentDictionary<string, object>();
/// <summary>
/// Gets or sets the actual context carrier implementation.
/// </summary>
#if !NET452
public static Type ContextSlotType { get; set; } = typeof(AsyncLocalRuntimeContextSlot<>);
#else
public static Type ContextSlotType { get; set; } = typeof(RemotingRuntimeContextSlot<>);
#endif
/// <summary>
/// Register a named context slot.
/// </summary>
/// <param name="slotName">The name of the context slot.</param>
/// <typeparam name="T">The type of the underlying value.</typeparam>
/// <returns>The slot registered.</returns>
public static RuntimeContextSlot<T> RegisterSlot<T>(string slotName)
{
if (string.IsNullOrEmpty(slotName))
{
throw new ArgumentException($"{nameof(slotName)} cannot be null or empty string.");
}
lock (Slots)
{
if (Slots.ContainsKey(slotName))
{
throw new InvalidOperationException($"The context slot {slotName} is already registered.");
}
var type = ContextSlotType.MakeGenericType(typeof(T));
var ctor = type.GetConstructor(new Type[] { typeof(string) });
var slot = (RuntimeContextSlot<T>)ctor.Invoke(new object[] { slotName });
Slots[slotName] = slot;
return slot;
}
}
/// <summary>
/// Get a registered slot from a given name.
/// </summary>
/// <param name="slotName">The name of the context slot.</param>
/// <typeparam name="T">The type of the underlying value.</typeparam>
/// <returns>The slot previously registered.</returns>
public static RuntimeContextSlot<T> GetSlot<T>(string slotName)
{
if (string.IsNullOrEmpty(slotName))
{
throw new ArgumentException($"{nameof(slotName)} cannot be null or empty string.");
}
Slots.TryGetValue(slotName, out var slot);
return slot as RuntimeContextSlot<T> ?? throw new ArgumentException($"The context slot {slotName} is not found.");
}
/*
public static void Apply(IDictionary<string, object> snapshot)
{
foreach (var entry in snapshot)
{
// TODO: revisit this part if we want Snapshot() to be used on critical paths
dynamic value = entry.Value;
SetValue(entry.Key, value);
}
}
public static IDictionary<string, object> Snapshot()
{
var retval = new Dictionary<string, object>();
foreach (var entry in Slots)
{
// TODO: revisit this part if we want Snapshot() to be used on critical paths
dynamic slot = entry.Value;
retval[entry.Key] = slot.Get();
}
return retval;
}
*/
/// <summary>
/// Sets the value to a registered slot.
/// </summary>
/// <param name="name">The name of the context slot.</param>
/// <param name="value">The value to be set.</param>
/// <typeparam name="T">The type of the value.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetValue<T>(string name, T value)
{
var slot = (RuntimeContextSlot<T>)Slots[name];
slot.Set(value);
}
/// <summary>
/// Gets the value from a registered slot.
/// </summary>
/// <param name="name">The name of the context slot.</param>
/// <typeparam name="T">The type of the value.</typeparam>
/// <returns>The value retrieved from the context slot.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T GetValue<T>(string name)
{
var slot = (RuntimeContextSlot<T>)Slots[name];
return slot.Get();
}
// For testing purpose
internal static void Clear()
{
Slots.Clear();
}
}
}

View File

@ -1,68 +1,68 @@
// <copyright file="RuntimeContextSlot.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>
using System;
namespace OpenTelemetry.Context
{
/// <summary>
/// The abstract context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public abstract class RuntimeContextSlot<T> : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
protected RuntimeContextSlot(string name)
{
this.Name = name;
}
/// <summary>
/// Gets the name of the context slot.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Get the value from the context slot.
/// </summary>
/// <returns>The value retrieved from the context slot.</returns>
public abstract T Get();
/// <summary>
/// Set the value to the context slot.
/// </summary>
/// <param name="value">The value to be set.</param>
public abstract void Set(T value);
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases the unmanaged resources used by this class and optionally releases the managed resources.
/// </summary>
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
}
}
}
// <copyright file="RuntimeContextSlot.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>
using System;
namespace OpenTelemetry.Context
{
/// <summary>
/// The abstract context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public abstract class RuntimeContextSlot<T> : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
protected RuntimeContextSlot(string name)
{
this.Name = name;
}
/// <summary>
/// Gets the name of the context slot.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Get the value from the context slot.
/// </summary>
/// <returns>The value retrieved from the context slot.</returns>
public abstract T Get();
/// <summary>
/// Set the value to the context slot.
/// </summary>
/// <param name="value">The value to be set.</param>
public abstract void Set(T value);
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases the unmanaged resources used by this class and optionally releases the managed resources.
/// </summary>
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
}
}
}

View File

@ -1,70 +1,70 @@
// <copyright file="ThreadLocalRuntimeContextSlot.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>
using System.Runtime.CompilerServices;
using System.Threading;
namespace OpenTelemetry.Context
{
/// <summary>
/// The thread local (TLS) implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class ThreadLocalRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
private readonly ThreadLocal<T> slot;
private bool disposedValue;
/// <summary>
/// Initializes a new instance of the <see cref="ThreadLocalRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public ThreadLocalRuntimeContextSlot(string name)
: base(name)
{
this.slot = new ThreadLocal<T>();
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T Get()
{
return this.slot.Value;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Set(T value)
{
this.slot.Value = value;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(true);
if (!this.disposedValue)
{
if (disposing)
{
this.slot.Dispose();
}
this.disposedValue = true;
}
}
}
}
// <copyright file="ThreadLocalRuntimeContextSlot.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>
using System.Runtime.CompilerServices;
using System.Threading;
namespace OpenTelemetry.Context
{
/// <summary>
/// The thread local (TLS) implementation of context slot.
/// </summary>
/// <typeparam name="T">The type of the underlying value.</typeparam>
public class ThreadLocalRuntimeContextSlot<T> : RuntimeContextSlot<T>
{
private readonly ThreadLocal<T> slot;
private bool disposedValue;
/// <summary>
/// Initializes a new instance of the <see cref="ThreadLocalRuntimeContextSlot{T}"/> class.
/// </summary>
/// <param name="name">The name of the context slot.</param>
public ThreadLocalRuntimeContextSlot(string name)
: base(name)
{
this.slot = new ThreadLocal<T>();
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override T Get()
{
return this.slot.Value;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Set(T value)
{
this.slot.Value = value;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(true);
if (!this.disposedValue)
{
if (disposing)
{
this.slot.Dispose();
}
this.disposedValue = true;
}
}
}
}

View File

@ -1,158 +1,158 @@
// <copyright file="SemanticConventions.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>
namespace OpenTelemetry.Trace
{
/// <summary>
/// Constants for semantic attribute names outlined by the OpenTelemetry specifications.
/// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/README.md"/>.
/// </summary>
internal static class SemanticConventions
{
// The set of constants matches the specification as of this commit.
// https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions
// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/exceptions.md
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public const string AttributeServiceName = "service.name";
public const string AttributeServiceNamespace = "service.namespace";
public const string AttributeServiceInstance = "service.instance.id";
public const string AttributeServiceVersion = "service.version";
public const string AttributeTelemetrySdkName = "telemetry.sdk.name";
public const string AttributeTelemetrySdkLanguage = "telemetry.sdk.language";
public const string AttributeTelemetrySdkVersion = "telemetry.sdk.version";
public const string AttributeContainerName = "container.name";
public const string AttributeContainerImage = "container.image.name";
public const string AttributeContainerTag = "container.image.tag";
public const string AttributeFaasName = "faas.name";
public const string AttributeFaasId = "faas.id";
public const string AttributeFaasVersion = "faas.version";
public const string AttributeFaasInstance = "faas.instance";
public const string AttributeK8sCluster = "k8s.cluster.name";
public const string AttributeK8sNamespace = "k8s.namespace.name";
public const string AttributeK8sPod = "k8s.pod.name";
public const string AttributeK8sDeployment = "k8s.deployment.name";
public const string AttributeHostHostname = "host.hostname";
public const string AttributeHostId = "host.id";
public const string AttributeHostName = "host.name";
public const string AttributeHostType = "host.type";
public const string AttributeHostImageName = "host.image.name";
public const string AttributeHostImageId = "host.image.id";
public const string AttributeHostImageVersion = "host.image.version";
public const string AttributeProcessId = "process.id";
public const string AttributeProcessExecutableName = "process.executable.name";
public const string AttributeProcessExecutablePath = "process.executable.path";
public const string AttributeProcessCommand = "process.command";
public const string AttributeProcessCommandLine = "process.command_line";
public const string AttributeProcessUsername = "process.username";
public const string AttributeCloudProvider = "cloud.provider";
public const string AttributeCloudAccount = "cloud.account.id";
public const string AttributeCloudRegion = "cloud.region";
public const string AttributeCloudZone = "cloud.zone";
public const string AttributeComponent = "component";
public const string AttributeNetTransport = "net.transport";
public const string AttributeNetPeerIp = "net.peer.ip";
public const string AttributeNetPeerPort = "net.peer.port";
public const string AttributeNetPeerName = "net.peer.name";
public const string AttributeNetHostIp = "net.host.ip";
public const string AttributeNetHostPort = "net.host.port";
public const string AttributeNetHostName = "net.host.name";
public const string AttributeEnduserId = "enduser.id";
public const string AttributeEnduserRole = "enduser.role";
public const string AttributeEnduserScope = "enduser.scope";
public const string AttributePeerService = "peer.service";
public const string AttributeHttpMethod = "http.method";
public const string AttributeHttpUrl = "http.url";
public const string AttributeHttpTarget = "http.target";
public const string AttributeHttpHost = "http.host";
public const string AttributeHttpScheme = "http.scheme";
public const string AttributeHttpStatusCode = "http.status_code";
public const string AttributeHttpStatusText = "http.status_text";
public const string AttributeHttpFlavor = "http.flavor";
public const string AttributeHttpServerName = "http.server_name";
public const string AttributeHttpHostName = "host.name";
public const string AttributeHttpHostPort = "host.port";
public const string AttributeHttpRoute = "http.route";
public const string AttributeHttpClientIP = "http.client_ip";
public const string AttributeHttpUserAgent = "http.user_agent";
public const string AttributeHttpRequestContentLength = "http.request_content_length";
public const string AttributeHttpRequestContentLengthUncompressed = "http.request_content_length_uncompressed";
public const string AttributeHttpResponseContentLength = "http.response_content_length";
public const string AttributeHttpResponseContentLengthUncompressed = "http.response_content_length_uncompressed";
public const string AttributeDbSystem = "db.system";
public const string AttributeDbConnectionString = "db.connection_string";
public const string AttributeDbUser = "db.user";
public const string AttributeDbMsSqlInstanceName = "db.mssql.instance_name";
public const string AttributeDbJdbcDriverClassName = "db.jdbc.driver_classname";
public const string AttributeDbName = "db.name";
public const string AttributeDbStatement = "db.statement";
public const string AttributeDbOperation = "db.operation";
public const string AttributeDbInstance = "db.instance";
public const string AttributeDbUrl = "db.url";
public const string AttributeDbCassandraKeyspace = "db.cassandra.keyspace";
public const string AttributeDbHBaseNamespace = "db.hbase.namespace";
public const string AttributeDbRedisDatabaseIndex = "db.redis.database_index";
public const string AttributeDbMongoDbCollection = "db.mongodb.collection";
public const string AttributeRpcSystem = "rpc.system";
public const string AttributeRpcService = "rpc.service";
public const string AttributeRpcMethod = "rpc.method";
public const string AttributeMessageType = "message.type";
public const string AttributeMessageId = "message.id";
public const string AttributeMessageCompressedSize = "message.compressed_size";
public const string AttributeMessageUncompressedSize = "message.uncompressed_size";
public const string AttributeFaasTrigger = "faas.trigger";
public const string AttributeFaasExecution = "faas.execution";
public const string AttributeFaasDocumentCollection = "faas.document.collection";
public const string AttributeFaasDocumentOperation = "faas.document.operation";
public const string AttributeFaasDocumentTime = "faas.document.time";
public const string AttributeFaasDocumentName = "faas.document.name";
public const string AttributeFaasTime = "faas.time";
public const string AttributeFaasCron = "faas.cron";
public const string AttributeMessagingSystem = "messaging.system";
public const string AttributeMessagingDestination = "messaging.destination";
public const string AttributeMessagingDestinationKind = "messaging.destination_kind";
public const string AttributeMessagingTempDestination = "messaging.temp_destination";
public const string AttributeMessagingProtocol = "messaging.protocol";
public const string AttributeMessagingProtocolVersion = "messaging.protocol_version";
public const string AttributeMessagingUrl = "messaging.url";
public const string AttributeMessagingMessageId = "messaging.message_id";
public const string AttributeMessagingConversationId = "messaging.conversation_id";
public const string AttributeMessagingPayloadSize = "messaging.message_payload_size_bytes";
public const string AttributeMessagingPayloadCompressedSize = "messaging.message_payload_compressed_size_bytes";
public const string AttributeMessagingOperation = "messaging.operation";
public const string AttributeExceptionEventName = "exception";
public const string AttributeExceptionType = "exception.type";
public const string AttributeExceptionMessage = "exception.message";
public const string AttributeExceptionStacktrace = "exception.stacktrace";
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
}
// <copyright file="SemanticConventions.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>
namespace OpenTelemetry.Trace
{
/// <summary>
/// Constants for semantic attribute names outlined by the OpenTelemetry specifications.
/// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/README.md"/>.
/// </summary>
internal static class SemanticConventions
{
// The set of constants matches the specification as of this commit.
// https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions
// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/exceptions.md
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public const string AttributeServiceName = "service.name";
public const string AttributeServiceNamespace = "service.namespace";
public const string AttributeServiceInstance = "service.instance.id";
public const string AttributeServiceVersion = "service.version";
public const string AttributeTelemetrySdkName = "telemetry.sdk.name";
public const string AttributeTelemetrySdkLanguage = "telemetry.sdk.language";
public const string AttributeTelemetrySdkVersion = "telemetry.sdk.version";
public const string AttributeContainerName = "container.name";
public const string AttributeContainerImage = "container.image.name";
public const string AttributeContainerTag = "container.image.tag";
public const string AttributeFaasName = "faas.name";
public const string AttributeFaasId = "faas.id";
public const string AttributeFaasVersion = "faas.version";
public const string AttributeFaasInstance = "faas.instance";
public const string AttributeK8sCluster = "k8s.cluster.name";
public const string AttributeK8sNamespace = "k8s.namespace.name";
public const string AttributeK8sPod = "k8s.pod.name";
public const string AttributeK8sDeployment = "k8s.deployment.name";
public const string AttributeHostHostname = "host.hostname";
public const string AttributeHostId = "host.id";
public const string AttributeHostName = "host.name";
public const string AttributeHostType = "host.type";
public const string AttributeHostImageName = "host.image.name";
public const string AttributeHostImageId = "host.image.id";
public const string AttributeHostImageVersion = "host.image.version";
public const string AttributeProcessId = "process.id";
public const string AttributeProcessExecutableName = "process.executable.name";
public const string AttributeProcessExecutablePath = "process.executable.path";
public const string AttributeProcessCommand = "process.command";
public const string AttributeProcessCommandLine = "process.command_line";
public const string AttributeProcessUsername = "process.username";
public const string AttributeCloudProvider = "cloud.provider";
public const string AttributeCloudAccount = "cloud.account.id";
public const string AttributeCloudRegion = "cloud.region";
public const string AttributeCloudZone = "cloud.zone";
public const string AttributeComponent = "component";
public const string AttributeNetTransport = "net.transport";
public const string AttributeNetPeerIp = "net.peer.ip";
public const string AttributeNetPeerPort = "net.peer.port";
public const string AttributeNetPeerName = "net.peer.name";
public const string AttributeNetHostIp = "net.host.ip";
public const string AttributeNetHostPort = "net.host.port";
public const string AttributeNetHostName = "net.host.name";
public const string AttributeEnduserId = "enduser.id";
public const string AttributeEnduserRole = "enduser.role";
public const string AttributeEnduserScope = "enduser.scope";
public const string AttributePeerService = "peer.service";
public const string AttributeHttpMethod = "http.method";
public const string AttributeHttpUrl = "http.url";
public const string AttributeHttpTarget = "http.target";
public const string AttributeHttpHost = "http.host";
public const string AttributeHttpScheme = "http.scheme";
public const string AttributeHttpStatusCode = "http.status_code";
public const string AttributeHttpStatusText = "http.status_text";
public const string AttributeHttpFlavor = "http.flavor";
public const string AttributeHttpServerName = "http.server_name";
public const string AttributeHttpHostName = "host.name";
public const string AttributeHttpHostPort = "host.port";
public const string AttributeHttpRoute = "http.route";
public const string AttributeHttpClientIP = "http.client_ip";
public const string AttributeHttpUserAgent = "http.user_agent";
public const string AttributeHttpRequestContentLength = "http.request_content_length";
public const string AttributeHttpRequestContentLengthUncompressed = "http.request_content_length_uncompressed";
public const string AttributeHttpResponseContentLength = "http.response_content_length";
public const string AttributeHttpResponseContentLengthUncompressed = "http.response_content_length_uncompressed";
public const string AttributeDbSystem = "db.system";
public const string AttributeDbConnectionString = "db.connection_string";
public const string AttributeDbUser = "db.user";
public const string AttributeDbMsSqlInstanceName = "db.mssql.instance_name";
public const string AttributeDbJdbcDriverClassName = "db.jdbc.driver_classname";
public const string AttributeDbName = "db.name";
public const string AttributeDbStatement = "db.statement";
public const string AttributeDbOperation = "db.operation";
public const string AttributeDbInstance = "db.instance";
public const string AttributeDbUrl = "db.url";
public const string AttributeDbCassandraKeyspace = "db.cassandra.keyspace";
public const string AttributeDbHBaseNamespace = "db.hbase.namespace";
public const string AttributeDbRedisDatabaseIndex = "db.redis.database_index";
public const string AttributeDbMongoDbCollection = "db.mongodb.collection";
public const string AttributeRpcSystem = "rpc.system";
public const string AttributeRpcService = "rpc.service";
public const string AttributeRpcMethod = "rpc.method";
public const string AttributeMessageType = "message.type";
public const string AttributeMessageId = "message.id";
public const string AttributeMessageCompressedSize = "message.compressed_size";
public const string AttributeMessageUncompressedSize = "message.uncompressed_size";
public const string AttributeFaasTrigger = "faas.trigger";
public const string AttributeFaasExecution = "faas.execution";
public const string AttributeFaasDocumentCollection = "faas.document.collection";
public const string AttributeFaasDocumentOperation = "faas.document.operation";
public const string AttributeFaasDocumentTime = "faas.document.time";
public const string AttributeFaasDocumentName = "faas.document.name";
public const string AttributeFaasTime = "faas.time";
public const string AttributeFaasCron = "faas.cron";
public const string AttributeMessagingSystem = "messaging.system";
public const string AttributeMessagingDestination = "messaging.destination";
public const string AttributeMessagingDestinationKind = "messaging.destination_kind";
public const string AttributeMessagingTempDestination = "messaging.temp_destination";
public const string AttributeMessagingProtocol = "messaging.protocol";
public const string AttributeMessagingProtocolVersion = "messaging.protocol_version";
public const string AttributeMessagingUrl = "messaging.url";
public const string AttributeMessagingMessageId = "messaging.message_id";
public const string AttributeMessagingConversationId = "messaging.conversation_id";
public const string AttributeMessagingPayloadSize = "messaging.message_payload_size_bytes";
public const string AttributeMessagingPayloadCompressedSize = "messaging.message_payload_compressed_size_bytes";
public const string AttributeMessagingOperation = "messaging.operation";
public const string AttributeExceptionEventName = "exception";
public const string AttributeExceptionType = "exception.type";
public const string AttributeExceptionMessage = "exception.message";
public const string AttributeExceptionStacktrace = "exception.stacktrace";
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
}

View File

@ -1,64 +1,64 @@
// <copyright file="Int128.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>
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace OpenTelemetry.Exporter.Jaeger.Implementation
{
internal readonly struct Int128
{
public static Int128 Empty;
private const int SpanIdBytes = 8;
private const int TraceIdBytes = 16;
public Int128(ActivitySpanId spanId)
{
Span<byte> bytes = stackalloc byte[SpanIdBytes];
spanId.CopyTo(bytes);
if (BitConverter.IsLittleEndian)
{
bytes.Reverse();
}
var longs = MemoryMarshal.Cast<byte, long>(bytes);
this.High = 0;
this.Low = longs[0];
}
public Int128(ActivityTraceId traceId)
{
Span<byte> bytes = stackalloc byte[TraceIdBytes];
traceId.CopyTo(bytes);
if (BitConverter.IsLittleEndian)
{
bytes.Reverse();
}
var longs = MemoryMarshal.Cast<byte, long>(bytes);
this.High = BitConverter.IsLittleEndian ? longs[1] : longs[0];
this.Low = BitConverter.IsLittleEndian ? longs[0] : longs[1];
}
public long High { get; }
public long Low { get; }
}
}
// <copyright file="Int128.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>
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace OpenTelemetry.Exporter.Jaeger.Implementation
{
internal readonly struct Int128
{
public static Int128 Empty;
private const int SpanIdBytes = 8;
private const int TraceIdBytes = 16;
public Int128(ActivitySpanId spanId)
{
Span<byte> bytes = stackalloc byte[SpanIdBytes];
spanId.CopyTo(bytes);
if (BitConverter.IsLittleEndian)
{
bytes.Reverse();
}
var longs = MemoryMarshal.Cast<byte, long>(bytes);
this.High = 0;
this.Low = longs[0];
}
public Int128(ActivityTraceId traceId)
{
Span<byte> bytes = stackalloc byte[TraceIdBytes];
traceId.CopyTo(bytes);
if (BitConverter.IsLittleEndian)
{
bytes.Reverse();
}
var longs = MemoryMarshal.Cast<byte, long>(bytes);
this.High = BitConverter.IsLittleEndian ? longs[1] : longs[0];
this.Low = BitConverter.IsLittleEndian ? longs[0] : longs[1];
}
public long High { get; }
public long Low { get; }
}
}

View File

@ -1,320 +1,320 @@
// <copyright file="JaegerUdpBatcher.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Thrift.Protocol;
using Thrift.Transport;
namespace OpenTelemetry.Exporter.Jaeger.Implementation
{
internal class JaegerUdpBatcher : IDisposable
{
private readonly int maxPacketSize;
private readonly TProtocolFactory protocolFactory;
private readonly TTransport clientTransport;
private readonly JaegerThriftClient thriftClient;
private readonly InMemoryTransport memoryTransport;
private readonly TProtocol memoryProtocol;
private readonly SemaphoreSlim flushLock = new SemaphoreSlim(1);
private readonly TimeSpan maxFlushInterval;
private readonly System.Timers.Timer maxFlushIntervalTimer;
private Dictionary<string, Process> processCache;
private int batchByteSize;
private bool disposed;
public JaegerUdpBatcher(JaegerExporterOptions options, TTransport clientTransport = null)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.MaxFlushInterval <= TimeSpan.Zero)
{
options.MaxFlushInterval = TimeSpan.FromSeconds(10);
}
this.maxPacketSize = (!options.MaxPacketSize.HasValue || options.MaxPacketSize <= 0) ? JaegerExporterOptions.DefaultMaxPacketSize : options.MaxPacketSize.Value;
this.protocolFactory = new TCompactProtocol.Factory();
this.clientTransport = clientTransport ?? new JaegerThriftClientTransport(options.AgentHost, options.AgentPort);
this.thriftClient = new JaegerThriftClient(this.protocolFactory.GetProtocol(this.clientTransport));
this.memoryTransport = new InMemoryTransport(16000);
this.memoryProtocol = this.protocolFactory.GetProtocol(this.memoryTransport);
this.Process = new Process(options.ServiceName, options.ProcessTags);
this.maxFlushInterval = options.MaxFlushInterval;
this.maxFlushIntervalTimer = new System.Timers.Timer
{
AutoReset = false,
Enabled = false,
Interval = this.maxFlushInterval.TotalMilliseconds,
};
this.maxFlushIntervalTimer.Elapsed += async (sender, args) =>
{
bool lockTaken = this.flushLock.Wait(0);
try
{
if (!lockTaken)
{
// If the lock was already held, it means a flush is already executing.
return;
}
await this.FlushAsyncInternal(lockAlreadyHeld: true, CancellationToken.None).ConfigureAwait(false);
}
finally
{
if (lockTaken)
{
this.flushLock.Release();
}
}
};
}
public Process Process { get; internal set; }
internal Dictionary<string, Batch> CurrentBatches { get; } = new Dictionary<string, Batch>();
public async ValueTask<int> AppendBatchAsync(IEnumerable<Activity> activityBatch, CancellationToken cancellationToken)
{
await this.flushLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
int recordsFlushed = 0;
foreach (var activity in activityBatch)
{
recordsFlushed += await this.AppendInternalAsync(activity.ToJaegerSpan(), cancellationToken).ConfigureAwait(false);
}
return recordsFlushed;
}
finally
{
this.flushLock.Release();
}
}
public async ValueTask<int> AppendAsync(Activity activity, CancellationToken cancellationToken)
{
await this.flushLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
return await this.AppendInternalAsync(activity.ToJaegerSpan(), cancellationToken).ConfigureAwait(false);
}
finally
{
this.flushLock.Release();
}
}
public ValueTask<int> FlushAsync(CancellationToken cancellationToken) => this.FlushAsyncInternal(lockAlreadyHeld: false, cancellationToken);
public ValueTask<int> CloseAsync(CancellationToken cancellationToken) => this.FlushAsyncInternal(lockAlreadyHeld: false, cancellationToken);
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected async ValueTask<int> AppendInternalAsync(JaegerSpan jaegerSpan, CancellationToken cancellationToken)
{
if (this.processCache == null)
{
this.Process.Message = this.BuildThriftMessage(this.Process).ToArray();
this.processCache = new Dictionary<string, Process>
{
[this.Process.ServiceName] = this.Process,
};
}
var spanServiceName = jaegerSpan.PeerServiceName ?? this.Process.ServiceName;
if (!this.processCache.TryGetValue(spanServiceName, out var spanProcess))
{
spanProcess = new Process(spanServiceName, this.Process.Tags);
spanProcess.Message = this.BuildThriftMessage(spanProcess).ToArray();
this.processCache.Add(spanServiceName, spanProcess);
}
var spanMessage = this.BuildThriftMessage(jaegerSpan);
jaegerSpan.Return();
if (spanMessage.Count + spanProcess.Message.Length > this.maxPacketSize)
{
throw new JaegerExporterException($"ThriftSender received a span that was too large, size = {spanMessage.Count + spanProcess.Message.Length}, max = {this.maxPacketSize}", null);
}
var spanTotalBytesNeeded = spanMessage.Count;
var flushedSpanCount = 0;
if (!this.CurrentBatches.TryGetValue(spanServiceName, out var spanBatch))
{
spanBatch = new Batch(spanProcess)
{
SpanMessages = new List<BufferWriterMemory>(),
};
this.CurrentBatches.Add(spanServiceName, spanBatch);
spanTotalBytesNeeded += spanProcess.Message.Length;
}
// flush if current batch size plus new span size equals or exceeds max batch size
if (this.batchByteSize + spanTotalBytesNeeded >= this.maxPacketSize)
{
flushedSpanCount = await this.FlushAsyncInternal(lockAlreadyHeld: true, cancellationToken).ConfigureAwait(false);
// Flushing effectively erases the spanBatch we were working on, so we have to rebuild it.
spanBatch.SpanMessages.Clear();
spanTotalBytesNeeded = spanMessage.Count + spanProcess.Message.Length;
this.CurrentBatches.Add(spanServiceName, spanBatch);
}
else
{
this.maxFlushIntervalTimer.Enabled = true;
}
// add span to batch and wait for more spans
spanBatch.SpanMessages.Add(spanMessage);
this.batchByteSize += spanTotalBytesNeeded;
return flushedSpanCount;
}
protected async Task SendAsync(Dictionary<string, Batch> batches, CancellationToken cancellationToken)
{
try
{
foreach (var batch in batches)
{
await this.thriftClient.WriteBatchAsync(
batch.Value.Process.Message,
batch.Value.SpanMessages,
cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
{
throw new JaegerExporterException($"Could not send {batches.Select(b => b.Value.SpanMessages.Count).Sum()} spans", ex);
}
}
protected virtual void Dispose(bool disposing)
{
try
{
_ = this.CloseAsync(CancellationToken.None).GetAwaiter().GetResult();
}
catch (Exception ex)
{
JaegerExporterEventSource.Log.FailedClose(ex);
}
if (disposing && !this.disposed)
{
this.maxFlushIntervalTimer.Dispose();
this.thriftClient.Dispose();
this.clientTransport.Dispose();
this.memoryTransport.Dispose();
this.memoryProtocol.Dispose();
this.flushLock.Dispose();
this.disposed = true;
}
}
private async ValueTask<int> FlushAsyncInternal(bool lockAlreadyHeld, CancellationToken cancellationToken)
{
if (!lockAlreadyHeld)
{
await this.flushLock.WaitAsync(cancellationToken).ConfigureAwait(false);
}
try
{
this.maxFlushIntervalTimer.Enabled = false;
int n = this.CurrentBatches.Sum(b => b.Value.SpanMessages.Count);
if (n == 0)
{
return 0;
}
try
{
await this.SendAsync(this.CurrentBatches, cancellationToken).ConfigureAwait(false);
}
finally
{
this.CurrentBatches.Clear();
this.batchByteSize = 0;
this.memoryTransport.Reset();
}
return n;
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
JaegerExporterEventSource.Log.FailedFlush(ex);
return 0;
}
finally
{
if (!lockAlreadyHeld)
{
this.flushLock.Release();
}
}
}
private BufferWriterMemory BuildThriftMessage(Process process)
{
var task = process.WriteAsync(this.memoryProtocol, CancellationToken.None);
#if DEBUG
if (task.Status != TaskStatus.RanToCompletion)
{
throw new InvalidOperationException();
}
#endif
return this.memoryTransport.ToBuffer();
}
// Prevents boxing of JaegerSpan struct.
private BufferWriterMemory BuildThriftMessage(in JaegerSpan jaegerSpan)
{
var task = jaegerSpan.WriteAsync(this.memoryProtocol, CancellationToken.None);
#if DEBUG
if (task.Status != TaskStatus.RanToCompletion)
{
throw new InvalidOperationException();
}
#endif
return this.memoryTransport.ToBuffer();
}
}
}
// <copyright file="JaegerUdpBatcher.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Thrift.Protocol;
using Thrift.Transport;
namespace OpenTelemetry.Exporter.Jaeger.Implementation
{
internal class JaegerUdpBatcher : IDisposable
{
private readonly int maxPacketSize;
private readonly TProtocolFactory protocolFactory;
private readonly TTransport clientTransport;
private readonly JaegerThriftClient thriftClient;
private readonly InMemoryTransport memoryTransport;
private readonly TProtocol memoryProtocol;
private readonly SemaphoreSlim flushLock = new SemaphoreSlim(1);
private readonly TimeSpan maxFlushInterval;
private readonly System.Timers.Timer maxFlushIntervalTimer;
private Dictionary<string, Process> processCache;
private int batchByteSize;
private bool disposed;
public JaegerUdpBatcher(JaegerExporterOptions options, TTransport clientTransport = null)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
if (options.MaxFlushInterval <= TimeSpan.Zero)
{
options.MaxFlushInterval = TimeSpan.FromSeconds(10);
}
this.maxPacketSize = (!options.MaxPacketSize.HasValue || options.MaxPacketSize <= 0) ? JaegerExporterOptions.DefaultMaxPacketSize : options.MaxPacketSize.Value;
this.protocolFactory = new TCompactProtocol.Factory();
this.clientTransport = clientTransport ?? new JaegerThriftClientTransport(options.AgentHost, options.AgentPort);
this.thriftClient = new JaegerThriftClient(this.protocolFactory.GetProtocol(this.clientTransport));
this.memoryTransport = new InMemoryTransport(16000);
this.memoryProtocol = this.protocolFactory.GetProtocol(this.memoryTransport);
this.Process = new Process(options.ServiceName, options.ProcessTags);
this.maxFlushInterval = options.MaxFlushInterval;
this.maxFlushIntervalTimer = new System.Timers.Timer
{
AutoReset = false,
Enabled = false,
Interval = this.maxFlushInterval.TotalMilliseconds,
};
this.maxFlushIntervalTimer.Elapsed += async (sender, args) =>
{
bool lockTaken = this.flushLock.Wait(0);
try
{
if (!lockTaken)
{
// If the lock was already held, it means a flush is already executing.
return;
}
await this.FlushAsyncInternal(lockAlreadyHeld: true, CancellationToken.None).ConfigureAwait(false);
}
finally
{
if (lockTaken)
{
this.flushLock.Release();
}
}
};
}
public Process Process { get; internal set; }
internal Dictionary<string, Batch> CurrentBatches { get; } = new Dictionary<string, Batch>();
public async ValueTask<int> AppendBatchAsync(IEnumerable<Activity> activityBatch, CancellationToken cancellationToken)
{
await this.flushLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
int recordsFlushed = 0;
foreach (var activity in activityBatch)
{
recordsFlushed += await this.AppendInternalAsync(activity.ToJaegerSpan(), cancellationToken).ConfigureAwait(false);
}
return recordsFlushed;
}
finally
{
this.flushLock.Release();
}
}
public async ValueTask<int> AppendAsync(Activity activity, CancellationToken cancellationToken)
{
await this.flushLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
return await this.AppendInternalAsync(activity.ToJaegerSpan(), cancellationToken).ConfigureAwait(false);
}
finally
{
this.flushLock.Release();
}
}
public ValueTask<int> FlushAsync(CancellationToken cancellationToken) => this.FlushAsyncInternal(lockAlreadyHeld: false, cancellationToken);
public ValueTask<int> CloseAsync(CancellationToken cancellationToken) => this.FlushAsyncInternal(lockAlreadyHeld: false, cancellationToken);
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected async ValueTask<int> AppendInternalAsync(JaegerSpan jaegerSpan, CancellationToken cancellationToken)
{
if (this.processCache == null)
{
this.Process.Message = this.BuildThriftMessage(this.Process).ToArray();
this.processCache = new Dictionary<string, Process>
{
[this.Process.ServiceName] = this.Process,
};
}
var spanServiceName = jaegerSpan.PeerServiceName ?? this.Process.ServiceName;
if (!this.processCache.TryGetValue(spanServiceName, out var spanProcess))
{
spanProcess = new Process(spanServiceName, this.Process.Tags);
spanProcess.Message = this.BuildThriftMessage(spanProcess).ToArray();
this.processCache.Add(spanServiceName, spanProcess);
}
var spanMessage = this.BuildThriftMessage(jaegerSpan);
jaegerSpan.Return();
if (spanMessage.Count + spanProcess.Message.Length > this.maxPacketSize)
{
throw new JaegerExporterException($"ThriftSender received a span that was too large, size = {spanMessage.Count + spanProcess.Message.Length}, max = {this.maxPacketSize}", null);
}
var spanTotalBytesNeeded = spanMessage.Count;
var flushedSpanCount = 0;
if (!this.CurrentBatches.TryGetValue(spanServiceName, out var spanBatch))
{
spanBatch = new Batch(spanProcess)
{
SpanMessages = new List<BufferWriterMemory>(),
};
this.CurrentBatches.Add(spanServiceName, spanBatch);
spanTotalBytesNeeded += spanProcess.Message.Length;
}
// flush if current batch size plus new span size equals or exceeds max batch size
if (this.batchByteSize + spanTotalBytesNeeded >= this.maxPacketSize)
{
flushedSpanCount = await this.FlushAsyncInternal(lockAlreadyHeld: true, cancellationToken).ConfigureAwait(false);
// Flushing effectively erases the spanBatch we were working on, so we have to rebuild it.
spanBatch.SpanMessages.Clear();
spanTotalBytesNeeded = spanMessage.Count + spanProcess.Message.Length;
this.CurrentBatches.Add(spanServiceName, spanBatch);
}
else
{
this.maxFlushIntervalTimer.Enabled = true;
}
// add span to batch and wait for more spans
spanBatch.SpanMessages.Add(spanMessage);
this.batchByteSize += spanTotalBytesNeeded;
return flushedSpanCount;
}
protected async Task SendAsync(Dictionary<string, Batch> batches, CancellationToken cancellationToken)
{
try
{
foreach (var batch in batches)
{
await this.thriftClient.WriteBatchAsync(
batch.Value.Process.Message,
batch.Value.SpanMessages,
cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
{
throw new JaegerExporterException($"Could not send {batches.Select(b => b.Value.SpanMessages.Count).Sum()} spans", ex);
}
}
protected virtual void Dispose(bool disposing)
{
try
{
_ = this.CloseAsync(CancellationToken.None).GetAwaiter().GetResult();
}
catch (Exception ex)
{
JaegerExporterEventSource.Log.FailedClose(ex);
}
if (disposing && !this.disposed)
{
this.maxFlushIntervalTimer.Dispose();
this.thriftClient.Dispose();
this.clientTransport.Dispose();
this.memoryTransport.Dispose();
this.memoryProtocol.Dispose();
this.flushLock.Dispose();
this.disposed = true;
}
}
private async ValueTask<int> FlushAsyncInternal(bool lockAlreadyHeld, CancellationToken cancellationToken)
{
if (!lockAlreadyHeld)
{
await this.flushLock.WaitAsync(cancellationToken).ConfigureAwait(false);
}
try
{
this.maxFlushIntervalTimer.Enabled = false;
int n = this.CurrentBatches.Sum(b => b.Value.SpanMessages.Count);
if (n == 0)
{
return 0;
}
try
{
await this.SendAsync(this.CurrentBatches, cancellationToken).ConfigureAwait(false);
}
finally
{
this.CurrentBatches.Clear();
this.batchByteSize = 0;
this.memoryTransport.Reset();
}
return n;
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
JaegerExporterEventSource.Log.FailedFlush(ex);
return 0;
}
finally
{
if (!lockAlreadyHeld)
{
this.flushLock.Release();
}
}
}
private BufferWriterMemory BuildThriftMessage(Process process)
{
var task = process.WriteAsync(this.memoryProtocol, CancellationToken.None);
#if DEBUG
if (task.Status != TaskStatus.RanToCompletion)
{
throw new InvalidOperationException();
}
#endif
return this.memoryTransport.ToBuffer();
}
// Prevents boxing of JaegerSpan struct.
private BufferWriterMemory BuildThriftMessage(in JaegerSpan jaegerSpan)
{
var task = jaegerSpan.WriteAsync(this.memoryProtocol, CancellationToken.None);
#if DEBUG
if (task.Status != TaskStatus.RanToCompletion)
{
throw new InvalidOperationException();
}
#endif
return this.memoryTransport.ToBuffer();
}
}
}

View File

@ -1,49 +1,49 @@
// <copyright file="OtlpExporterOptions.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>
using System.Collections.Generic;
using Grpc.Core;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol
{
/// <summary>
/// Configuration options for the OpenTelemetry Protocol (OTLP) exporter.
/// </summary>
public class OtlpExporterOptions
{
/// <summary>
/// Gets or sets the target to which the exporter is going to send traces or metrics.
/// The valid syntax is described at https://github.com/grpc/grpc/blob/master/doc/naming.md.
/// </summary>
public string Endpoint { get; set; } = "localhost:55680";
/// <summary>
/// Gets or sets the client-side channel credentials. Used for creation of a secure channel.
/// The default is "insecure". See detais at https://grpc.io/docs/guides/auth/#credential-types.
/// </summary>
public ChannelCredentials Credentials { get; set; } = ChannelCredentials.Insecure;
/// <summary>
/// Gets or sets optional headers for the connection.
/// </summary>
public Metadata Headers { get; set; } = new Metadata();
/// <summary>
/// Gets or sets the gRPC channel options.
/// </summary>
public IEnumerable<ChannelOption> ChannelOptions { get; set; }
}
}
// <copyright file="OtlpExporterOptions.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>
using System.Collections.Generic;
using Grpc.Core;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol
{
/// <summary>
/// Configuration options for the OpenTelemetry Protocol (OTLP) exporter.
/// </summary>
public class OtlpExporterOptions
{
/// <summary>
/// Gets or sets the target to which the exporter is going to send traces or metrics.
/// The valid syntax is described at https://github.com/grpc/grpc/blob/master/doc/naming.md.
/// </summary>
public string Endpoint { get; set; } = "localhost:55680";
/// <summary>
/// Gets or sets the client-side channel credentials. Used for creation of a secure channel.
/// The default is "insecure". See detais at https://grpc.io/docs/guides/auth/#credential-types.
/// </summary>
public ChannelCredentials Credentials { get; set; } = ChannelCredentials.Insecure;
/// <summary>
/// Gets or sets optional headers for the connection.
/// </summary>
public Metadata Headers { get; set; } = new Metadata();
/// <summary>
/// Gets or sets the gRPC channel options.
/// </summary>
public IEnumerable<ChannelOption> ChannelOptions { get; set; }
}
}

View File

@ -1,227 +1,227 @@
// <copyright file="HttpInListener.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web;
using System.Web.Routing;
using OpenTelemetry.Context;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.AspNet.Implementation
{
internal class HttpInListener : ListenerHandler
{
public const string RequestCustomPropertyName = "OTel.AspNet.Request";
public const string ResponseCustomPropertyName = "OTel.AspNet.Response";
private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener";
private static readonly Func<HttpRequest, string, IEnumerable<string>> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name);
private readonly PropertyFetcher routeFetcher = new PropertyFetcher("Route");
private readonly PropertyFetcher routeTemplateFetcher = new PropertyFetcher("RouteTemplate");
private readonly AspNetInstrumentationOptions options;
private readonly ActivitySourceAdapter activitySource;
public HttpInListener(string name, AspNetInstrumentationOptions options, ActivitySourceAdapter activitySource)
: base(name)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.activitySource = activitySource;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
public override void OnStartActivity(Activity activity, object payload)
{
var context = HttpContext.Current;
if (context == null)
{
AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity));
return;
}
try
{
if (this.options.Filter?.Invoke(context) == false)
{
AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false;
return;
}
}
catch (Exception ex)
{
AspNetInstrumentationEventSource.Log.RequestFilterException(ex);
activity.IsAllDataRequested = false;
return;
}
var request = context.Request;
var requestValues = request.Unvalidated;
if (!(this.options.Propagator is TextMapPropagator))
{
var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter);
if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context)
{
// Create a new activity with its parent set from the extracted context.
// This makes the new activity as a "sibling" of the activity created by
// ASP.NET.
Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one.
newOne.Start();
// Both new activity and old one store the other activity
// inside them. This is required in the Stop step to
// correctly stop and restore Activity.Current.
newOne.SetCustomProperty("OTel.ActivityByAspNet", activity);
activity.SetCustomProperty("OTel.ActivityByHttpInListener", newOne);
activity = newOne;
}
if (ctx.Baggage != default)
{
Baggage.Current = ctx.Baggage;
}
}
// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
var path = requestValues.Path;
activity.DisplayName = path;
this.activitySource.Start(activity, ActivityKind.Server);
if (activity.IsAllDataRequested)
{
activity.SetCustomProperty(RequestCustomPropertyName, request);
if (request.Url.Port == 80 || request.Url.Port == 443)
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host);
}
else
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host + ":" + request.Url.Port);
}
activity.SetTag(SemanticConventions.AttributeHttpMethod, request.HttpMethod);
activity.SetTag(SpanAttributeConstants.HttpPathKey, path);
activity.SetTag(SemanticConventions.AttributeHttpUserAgent, request.UserAgent);
activity.SetTag(SemanticConventions.AttributeHttpUrl, request.Url.ToString());
}
}
public override void OnStopActivity(Activity activity, object payload)
{
Activity activityToEnrich = activity;
Activity createdActivity = null;
if (!(this.options.Propagator is TextMapPropagator))
{
// If using custom context propagator, then the activity here
// could be either the one from Asp.Net, or the one
// this instrumentation created in Start.
// This is because Asp.Net, under certain circumstances, restores Activity.Current
// to its own activity.
if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start"))
{
// This block is hit if Asp.Net did restore Current to its own activity,
// and we need to retrieve the one created by HttpInListener,
// or an additional activity was never created.
createdActivity = (Activity)activity.GetCustomProperty("OTel.ActivityByHttpInListener");
activityToEnrich = createdActivity ?? activity;
}
}
if (activityToEnrich.IsAllDataRequested)
{
var context = HttpContext.Current;
if (context == null)
{
AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity));
return;
}
var response = context.Response;
activityToEnrich.SetCustomProperty(ResponseCustomPropertyName, response);
activityToEnrich.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode);
activityToEnrich.SetStatus(
SpanHelper
.ResolveSpanStatusForHttpStatusCode(response.StatusCode)
.WithDescription(response.StatusDescription));
var routeData = context.Request.RequestContext.RouteData;
string template = null;
if (routeData.Values.TryGetValue("MS_SubRoutes", out object msSubRoutes))
{
// WebAPI attribute routing flows here. Use reflection to not take a dependency on microsoft.aspnet.webapi.core\[version]\lib\[framework]\System.Web.Http.
if (msSubRoutes is Array attributeRouting && attributeRouting.Length == 1)
{
var subRouteData = attributeRouting.GetValue(0);
var route = this.routeFetcher.Fetch(subRouteData);
template = this.routeTemplateFetcher.Fetch(route) as string;
}
}
else if (routeData.Route is Route route)
{
// MVC + WebAPI traditional routing & MVC attribute routing flow here.
template = route.Url;
}
if (!string.IsNullOrEmpty(template))
{
// Override the name that was previously set to the path part of URL.
activityToEnrich.DisplayName = template;
activityToEnrich.SetTag(SemanticConventions.AttributeHttpRoute, template);
}
}
if (!(this.options.Propagator is TextMapPropagator))
{
if (activity.OperationName.Equals(ActivityNameByHttpInListener))
{
// If instrumentation started a new Activity, it must
// be stopped here.
activity.Stop();
// Restore the original activity as Current.
var activityByAspNet = (Activity)activity.GetCustomProperty("OTel.ActivityByAspNet");
Activity.Current = activityByAspNet;
}
else if (createdActivity != null)
{
// This block is hit if Asp.Net did restore Current to its own activity,
// then we need to retrieve the one created by HttpInListener
// and stop it.
createdActivity.Stop();
// Restore current back to the one created by Asp.Net
Activity.Current = activity;
}
}
this.activitySource.Stop(activityToEnrich);
}
}
}
// <copyright file="HttpInListener.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web;
using System.Web.Routing;
using OpenTelemetry.Context;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.AspNet.Implementation
{
internal class HttpInListener : ListenerHandler
{
public const string RequestCustomPropertyName = "OTel.AspNet.Request";
public const string ResponseCustomPropertyName = "OTel.AspNet.Response";
private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener";
private static readonly Func<HttpRequest, string, IEnumerable<string>> HttpRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name);
private readonly PropertyFetcher routeFetcher = new PropertyFetcher("Route");
private readonly PropertyFetcher routeTemplateFetcher = new PropertyFetcher("RouteTemplate");
private readonly AspNetInstrumentationOptions options;
private readonly ActivitySourceAdapter activitySource;
public HttpInListener(string name, AspNetInstrumentationOptions options, ActivitySourceAdapter activitySource)
: base(name)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.activitySource = activitySource;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
public override void OnStartActivity(Activity activity, object payload)
{
var context = HttpContext.Current;
if (context == null)
{
AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity));
return;
}
try
{
if (this.options.Filter?.Invoke(context) == false)
{
AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false;
return;
}
}
catch (Exception ex)
{
AspNetInstrumentationEventSource.Log.RequestFilterException(ex);
activity.IsAllDataRequested = false;
return;
}
var request = context.Request;
var requestValues = request.Unvalidated;
if (!(this.options.Propagator is TextMapPropagator))
{
var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter);
if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context)
{
// Create a new activity with its parent set from the extracted context.
// This makes the new activity as a "sibling" of the activity created by
// ASP.NET.
Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one.
newOne.Start();
// Both new activity and old one store the other activity
// inside them. This is required in the Stop step to
// correctly stop and restore Activity.Current.
newOne.SetCustomProperty("OTel.ActivityByAspNet", activity);
activity.SetCustomProperty("OTel.ActivityByHttpInListener", newOne);
activity = newOne;
}
if (ctx.Baggage != default)
{
Baggage.Current = ctx.Baggage;
}
}
// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
var path = requestValues.Path;
activity.DisplayName = path;
this.activitySource.Start(activity, ActivityKind.Server);
if (activity.IsAllDataRequested)
{
activity.SetCustomProperty(RequestCustomPropertyName, request);
if (request.Url.Port == 80 || request.Url.Port == 443)
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host);
}
else
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Url.Host + ":" + request.Url.Port);
}
activity.SetTag(SemanticConventions.AttributeHttpMethod, request.HttpMethod);
activity.SetTag(SpanAttributeConstants.HttpPathKey, path);
activity.SetTag(SemanticConventions.AttributeHttpUserAgent, request.UserAgent);
activity.SetTag(SemanticConventions.AttributeHttpUrl, request.Url.ToString());
}
}
public override void OnStopActivity(Activity activity, object payload)
{
Activity activityToEnrich = activity;
Activity createdActivity = null;
if (!(this.options.Propagator is TextMapPropagator))
{
// If using custom context propagator, then the activity here
// could be either the one from Asp.Net, or the one
// this instrumentation created in Start.
// This is because Asp.Net, under certain circumstances, restores Activity.Current
// to its own activity.
if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start"))
{
// This block is hit if Asp.Net did restore Current to its own activity,
// and we need to retrieve the one created by HttpInListener,
// or an additional activity was never created.
createdActivity = (Activity)activity.GetCustomProperty("OTel.ActivityByHttpInListener");
activityToEnrich = createdActivity ?? activity;
}
}
if (activityToEnrich.IsAllDataRequested)
{
var context = HttpContext.Current;
if (context == null)
{
AspNetInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity));
return;
}
var response = context.Response;
activityToEnrich.SetCustomProperty(ResponseCustomPropertyName, response);
activityToEnrich.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode);
activityToEnrich.SetStatus(
SpanHelper
.ResolveSpanStatusForHttpStatusCode(response.StatusCode)
.WithDescription(response.StatusDescription));
var routeData = context.Request.RequestContext.RouteData;
string template = null;
if (routeData.Values.TryGetValue("MS_SubRoutes", out object msSubRoutes))
{
// WebAPI attribute routing flows here. Use reflection to not take a dependency on microsoft.aspnet.webapi.core\[version]\lib\[framework]\System.Web.Http.
if (msSubRoutes is Array attributeRouting && attributeRouting.Length == 1)
{
var subRouteData = attributeRouting.GetValue(0);
var route = this.routeFetcher.Fetch(subRouteData);
template = this.routeTemplateFetcher.Fetch(route) as string;
}
}
else if (routeData.Route is Route route)
{
// MVC + WebAPI traditional routing & MVC attribute routing flow here.
template = route.Url;
}
if (!string.IsNullOrEmpty(template))
{
// Override the name that was previously set to the path part of URL.
activityToEnrich.DisplayName = template;
activityToEnrich.SetTag(SemanticConventions.AttributeHttpRoute, template);
}
}
if (!(this.options.Propagator is TextMapPropagator))
{
if (activity.OperationName.Equals(ActivityNameByHttpInListener))
{
// If instrumentation started a new Activity, it must
// be stopped here.
activity.Stop();
// Restore the original activity as Current.
var activityByAspNet = (Activity)activity.GetCustomProperty("OTel.ActivityByAspNet");
Activity.Current = activityByAspNet;
}
else if (createdActivity != null)
{
// This block is hit if Asp.Net did restore Current to its own activity,
// then we need to retrieve the one created by HttpInListener
// and stop it.
createdActivity.Stop();
// Restore current back to the one created by Asp.Net
Activity.Current = activity;
}
}
this.activitySource.Stop(activityToEnrich);
}
}
}

View File

@ -1,272 +1,272 @@
// <copyright file="HttpInListener.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using OpenTelemetry.Context;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Instrumentation.GrpcNetClient;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
{
internal class HttpInListener : ListenerHandler
{
public const string RequestCustomPropertyName = "OTel.AspNetCore.Request";
public const string ResponseCustomPropertyName = "OTel.AspNetCore.Response";
private const string UnknownHostName = "UNKNOWN-HOST";
private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener";
private static readonly Func<HttpRequest, string, IEnumerable<string>> HttpRequestHeaderValuesGetter = (request, name) => request.Headers[name];
private readonly PropertyFetcher startContextFetcher = new PropertyFetcher("HttpContext");
private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext");
private readonly PropertyFetcher beforeActionActionDescriptorFetcher = new PropertyFetcher("actionDescriptor");
private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new PropertyFetcher("AttributeRouteInfo");
private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template");
private readonly bool hostingSupportsW3C;
private readonly AspNetCoreInstrumentationOptions options;
private readonly ActivitySourceAdapter activitySource;
public HttpInListener(string name, AspNetCoreInstrumentationOptions options, ActivitySourceAdapter activitySource)
: base(name)
{
this.hostingSupportsW3C = typeof(HttpRequest).Assembly.GetName().Version.Major >= 3;
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.activitySource = activitySource;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
public override void OnStartActivity(Activity activity, object payload)
{
if (!(this.startContextFetcher.Fetch(payload) is HttpContext context))
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity));
return;
}
try
{
if (this.options.Filter?.Invoke(context) == false)
{
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false;
return;
}
}
catch (Exception ex)
{
AspNetCoreInstrumentationEventSource.Log.RequestFilterException(ex);
activity.IsAllDataRequested = false;
return;
}
var request = context.Request;
if (!this.hostingSupportsW3C || !(this.options.Propagator is TextMapPropagator))
{
var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter);
if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context)
{
// Create a new activity with its parent set from the extracted context.
// This makes the new activity as a "sibling" of the activity created by
// Asp.Net Core.
Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one.
newOne.Start();
activity = newOne;
}
if (ctx.Baggage != default)
{
Baggage.Current = ctx.Baggage;
}
}
this.activitySource.Start(activity, ActivityKind.Server);
if (activity.IsAllDataRequested)
{
activity.SetCustomProperty(RequestCustomPropertyName, request);
var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
activity.DisplayName = path;
// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
if (request.Host.Port == 80 || request.Host.Port == 443)
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host);
}
else
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host + ":" + request.Host.Port);
}
activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method);
activity.SetTag(SpanAttributeConstants.HttpPathKey, path);
activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUri(request));
var userAgent = request.Headers["User-Agent"].FirstOrDefault();
if (!string.IsNullOrEmpty(userAgent))
{
activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent);
}
}
}
public override void OnStopActivity(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
{
if (!(this.stopContextFetcher.Fetch(payload) is HttpContext context))
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity));
return;
}
var response = context.Response;
activity.SetCustomProperty(ResponseCustomPropertyName, response);
activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode);
if (TryGetGrpcMethod(activity, out var grpcMethod))
{
AddGrpcAttributes(activity, grpcMethod, context);
}
else
{
Status status = SpanHelper.ResolveSpanStatusForHttpStatusCode(response.StatusCode);
activity.SetStatus(status.WithDescription(response.HttpContext.Features.Get<IHttpResponseFeature>()?.ReasonPhrase));
}
}
if (activity.OperationName.Equals(ActivityNameByHttpInListener))
{
// If instrumentation started a new Activity, it must
// be stopped here.
activity.Stop();
// After the activity.Stop() code, Activity.Current becomes null.
// If Asp.Net Core uses Activity.Current?.Stop() - it'll not stop the activity
// it created.
// Currently Asp.Net core does not use Activity.Current, instead it stores a
// reference to its activity, and calls .Stop on it.
// TODO: Should we still restore Activity.Current here?
// If yes, then we need to store the asp.net core activity inside
// the one created by the instrumentation.
// And retrieve it here, and set it to Current.
}
this.activitySource.Stop(activity);
}
public override void OnCustom(string name, Activity activity, object payload)
{
if (name == "Microsoft.AspNetCore.Mvc.BeforeAction")
{
if (activity.IsAllDataRequested)
{
// See https://github.com/aspnet/Mvc/blob/2414db256f32a047770326d14d8b0e2afd49ba49/src/Microsoft.AspNetCore.Mvc.Core/MvcCoreDiagnosticSourceExtensions.cs#L36-L44
// Reflection accessing: ActionDescriptor.AttributeRouteInfo.Template
// The reason to use reflection is to avoid a reference on MVC package.
// This package can be used with non-MVC apps and this logic simply wouldn't run.
// Taking reference on MVC will increase size of deployment for non-MVC apps.
var actionDescriptor = this.beforeActionActionDescriptorFetcher.Fetch(payload);
var attributeRouteInfo = this.beforeActionAttributeRouteInfoFetcher.Fetch(actionDescriptor);
var template = this.beforeActionTemplateFetcher.Fetch(attributeRouteInfo) as string;
if (!string.IsNullOrEmpty(template))
{
// override the span name that was previously set to the path part of URL.
activity.DisplayName = template;
activity.SetTag(SemanticConventions.AttributeHttpRoute, template);
}
// TODO: Should we get values from RouteData?
// private readonly PropertyFetcher beforActionRouteDataFetcher = new PropertyFetcher("routeData");
// var routeData = this.beforActionRouteDataFetcher.Fetch(payload) as RouteData;
}
}
}
private static string GetUri(HttpRequest request)
{
var builder = new StringBuilder();
builder.Append(request.Scheme).Append("://");
if (request.Host.HasValue)
{
builder.Append(request.Host.Value);
}
else
{
// HTTP 1.0 request with NO host header would result in empty Host.
// Use placeholder to avoid incorrect URL like "http:///"
builder.Append(UnknownHostName);
}
if (request.PathBase.HasValue)
{
builder.Append(request.PathBase.Value);
}
if (request.Path.HasValue)
{
builder.Append(request.Path.Value);
}
if (request.QueryString.HasValue)
{
builder.Append(request.QueryString);
}
return builder.ToString();
}
private static bool TryGetGrpcMethod(Activity activity, out string grpcMethod)
{
grpcMethod = GrpcTagHelper.GetGrpcMethodFromActivity(activity);
return !string.IsNullOrEmpty(grpcMethod);
}
private static void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext context)
{
// TODO: Should the leading slash be trimmed? Spec seems to suggest no leading slash: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md#span-name
// Client instrumentation is trimming the leading slash. Whatever we decide here, should we apply the same to the client side?
// activity.DisplayName = grpcMethod?.Trim('/');
activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc);
if (GrpcTagHelper.TryParseRpcServiceAndRpcMethod(grpcMethod, out var rpcService, out var rpcMethod))
{
activity.SetTag(SemanticConventions.AttributeRpcService, rpcService);
activity.SetTag(SemanticConventions.AttributeRpcMethod, rpcMethod);
}
activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString());
activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort);
activity.SetStatus(GrpcTagHelper.GetGrpcStatusCodeFromActivity(activity));
}
}
}
// <copyright file="HttpInListener.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using OpenTelemetry.Context;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Instrumentation.GrpcNetClient;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
{
internal class HttpInListener : ListenerHandler
{
public const string RequestCustomPropertyName = "OTel.AspNetCore.Request";
public const string ResponseCustomPropertyName = "OTel.AspNetCore.Response";
private const string UnknownHostName = "UNKNOWN-HOST";
private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener";
private static readonly Func<HttpRequest, string, IEnumerable<string>> HttpRequestHeaderValuesGetter = (request, name) => request.Headers[name];
private readonly PropertyFetcher startContextFetcher = new PropertyFetcher("HttpContext");
private readonly PropertyFetcher stopContextFetcher = new PropertyFetcher("HttpContext");
private readonly PropertyFetcher beforeActionActionDescriptorFetcher = new PropertyFetcher("actionDescriptor");
private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new PropertyFetcher("AttributeRouteInfo");
private readonly PropertyFetcher beforeActionTemplateFetcher = new PropertyFetcher("Template");
private readonly bool hostingSupportsW3C;
private readonly AspNetCoreInstrumentationOptions options;
private readonly ActivitySourceAdapter activitySource;
public HttpInListener(string name, AspNetCoreInstrumentationOptions options, ActivitySourceAdapter activitySource)
: base(name)
{
this.hostingSupportsW3C = typeof(HttpRequest).Assembly.GetName().Version.Major >= 3;
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.activitySource = activitySource;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
public override void OnStartActivity(Activity activity, object payload)
{
if (!(this.startContextFetcher.Fetch(payload) is HttpContext context))
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity));
return;
}
try
{
if (this.options.Filter?.Invoke(context) == false)
{
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false;
return;
}
}
catch (Exception ex)
{
AspNetCoreInstrumentationEventSource.Log.RequestFilterException(ex);
activity.IsAllDataRequested = false;
return;
}
var request = context.Request;
if (!this.hostingSupportsW3C || !(this.options.Propagator is TextMapPropagator))
{
var ctx = this.options.Propagator.Extract(default, request, HttpRequestHeaderValuesGetter);
if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context)
{
// Create a new activity with its parent set from the extracted context.
// This makes the new activity as a "sibling" of the activity created by
// Asp.Net Core.
Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one.
newOne.Start();
activity = newOne;
}
if (ctx.Baggage != default)
{
Baggage.Current = ctx.Baggage;
}
}
this.activitySource.Start(activity, ActivityKind.Server);
if (activity.IsAllDataRequested)
{
activity.SetCustomProperty(RequestCustomPropertyName, request);
var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
activity.DisplayName = path;
// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
if (request.Host.Port == 80 || request.Host.Port == 443)
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host);
}
else
{
activity.SetTag(SemanticConventions.AttributeHttpHost, request.Host.Host + ":" + request.Host.Port);
}
activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method);
activity.SetTag(SpanAttributeConstants.HttpPathKey, path);
activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUri(request));
var userAgent = request.Headers["User-Agent"].FirstOrDefault();
if (!string.IsNullOrEmpty(userAgent))
{
activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent);
}
}
}
public override void OnStopActivity(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
{
if (!(this.stopContextFetcher.Fetch(payload) is HttpContext context))
{
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity));
return;
}
var response = context.Response;
activity.SetCustomProperty(ResponseCustomPropertyName, response);
activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode);
if (TryGetGrpcMethod(activity, out var grpcMethod))
{
AddGrpcAttributes(activity, grpcMethod, context);
}
else
{
Status status = SpanHelper.ResolveSpanStatusForHttpStatusCode(response.StatusCode);
activity.SetStatus(status.WithDescription(response.HttpContext.Features.Get<IHttpResponseFeature>()?.ReasonPhrase));
}
}
if (activity.OperationName.Equals(ActivityNameByHttpInListener))
{
// If instrumentation started a new Activity, it must
// be stopped here.
activity.Stop();
// After the activity.Stop() code, Activity.Current becomes null.
// If Asp.Net Core uses Activity.Current?.Stop() - it'll not stop the activity
// it created.
// Currently Asp.Net core does not use Activity.Current, instead it stores a
// reference to its activity, and calls .Stop on it.
// TODO: Should we still restore Activity.Current here?
// If yes, then we need to store the asp.net core activity inside
// the one created by the instrumentation.
// And retrieve it here, and set it to Current.
}
this.activitySource.Stop(activity);
}
public override void OnCustom(string name, Activity activity, object payload)
{
if (name == "Microsoft.AspNetCore.Mvc.BeforeAction")
{
if (activity.IsAllDataRequested)
{
// See https://github.com/aspnet/Mvc/blob/2414db256f32a047770326d14d8b0e2afd49ba49/src/Microsoft.AspNetCore.Mvc.Core/MvcCoreDiagnosticSourceExtensions.cs#L36-L44
// Reflection accessing: ActionDescriptor.AttributeRouteInfo.Template
// The reason to use reflection is to avoid a reference on MVC package.
// This package can be used with non-MVC apps and this logic simply wouldn't run.
// Taking reference on MVC will increase size of deployment for non-MVC apps.
var actionDescriptor = this.beforeActionActionDescriptorFetcher.Fetch(payload);
var attributeRouteInfo = this.beforeActionAttributeRouteInfoFetcher.Fetch(actionDescriptor);
var template = this.beforeActionTemplateFetcher.Fetch(attributeRouteInfo) as string;
if (!string.IsNullOrEmpty(template))
{
// override the span name that was previously set to the path part of URL.
activity.DisplayName = template;
activity.SetTag(SemanticConventions.AttributeHttpRoute, template);
}
// TODO: Should we get values from RouteData?
// private readonly PropertyFetcher beforActionRouteDataFetcher = new PropertyFetcher("routeData");
// var routeData = this.beforActionRouteDataFetcher.Fetch(payload) as RouteData;
}
}
}
private static string GetUri(HttpRequest request)
{
var builder = new StringBuilder();
builder.Append(request.Scheme).Append("://");
if (request.Host.HasValue)
{
builder.Append(request.Host.Value);
}
else
{
// HTTP 1.0 request with NO host header would result in empty Host.
// Use placeholder to avoid incorrect URL like "http:///"
builder.Append(UnknownHostName);
}
if (request.PathBase.HasValue)
{
builder.Append(request.PathBase.Value);
}
if (request.Path.HasValue)
{
builder.Append(request.Path.Value);
}
if (request.QueryString.HasValue)
{
builder.Append(request.QueryString);
}
return builder.ToString();
}
private static bool TryGetGrpcMethod(Activity activity, out string grpcMethod)
{
grpcMethod = GrpcTagHelper.GetGrpcMethodFromActivity(activity);
return !string.IsNullOrEmpty(grpcMethod);
}
private static void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext context)
{
// TODO: Should the leading slash be trimmed? Spec seems to suggest no leading slash: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md#span-name
// Client instrumentation is trimming the leading slash. Whatever we decide here, should we apply the same to the client side?
// activity.DisplayName = grpcMethod?.Trim('/');
activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc);
if (GrpcTagHelper.TryParseRpcServiceAndRpcMethod(grpcMethod, out var rpcService, out var rpcMethod))
{
activity.SetTag(SemanticConventions.AttributeRpcService, rpcService);
activity.SetTag(SemanticConventions.AttributeRpcMethod, rpcMethod);
}
activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString());
activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort);
activity.SetStatus(GrpcTagHelper.GetGrpcStatusCodeFromActivity(activity));
}
}
}

View File

@ -1,189 +1,189 @@
// <copyright file="HttpHandlerDiagnosticListener.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using OpenTelemetry.Context;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.Http.Implementation
{
internal class HttpHandlerDiagnosticListener : ListenerHandler
{
public const string RequestCustomPropertyName = "OTel.HttpHandler.Request";
public const string ResponseCustomPropertyName = "OTel.HttpHandler.Response";
public const string ExceptionCustomPropertyName = "OTel.HttpHandler.Exception";
private static readonly Func<HttpRequestMessage, string, IEnumerable<string>> HttpRequestMessageHeaderValuesGetter = (request, name) =>
{
if (request.Headers.TryGetValues(name, out var values))
{
return values;
}
return null;
};
private static readonly Action<HttpRequestMessage, string, string> HttpRequestMessageHeaderValueSetter = (request, name, value) => request.Headers.Add(name, value);
private static readonly Regex CoreAppMajorVersionCheckRegex = new Regex("^\\.NETCoreApp,Version=v(\\d+)\\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly ActivitySourceAdapter activitySource;
private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request");
private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response");
private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception");
private readonly PropertyFetcher stopRequestStatusFetcher = new PropertyFetcher("RequestTaskStatus");
private readonly bool httpClientSupportsW3C;
private readonly HttpClientInstrumentationOptions options;
public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options, ActivitySourceAdapter activitySource)
: base("HttpHandlerDiagnosticListener")
{
var framework = Assembly
.GetEntryAssembly()?
.GetCustomAttribute<TargetFrameworkAttribute>()?
.FrameworkName;
// Depending on the .NET version/flavor this will look like
// '.NETCoreApp,Version=v3.0', '.NETCoreApp,Version = v2.2' or '.NETFramework,Version = v4.7.1'
if (framework != null)
{
var match = CoreAppMajorVersionCheckRegex.Match(framework);
this.httpClientSupportsW3C = match.Success && int.Parse(match.Groups[1].Value) >= 3;
}
this.options = options;
this.activitySource = activitySource;
}
public override void OnStartActivity(Activity activity, object payload)
{
if (!(this.startRequestFetcher.Fetch(payload) is HttpRequestMessage request))
{
HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnStartActivity));
return;
}
if (this.options.Propagator.Extract(default, request, HttpRequestMessageHeaderValuesGetter) != default)
{
// this request is already instrumented, we should back off
activity.IsAllDataRequested = false;
return;
}
activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method);
this.activitySource.Start(activity, ActivityKind.Client);
if (activity.IsAllDataRequested)
{
activity.SetCustomProperty(RequestCustomPropertyName, request);
activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method));
activity.SetTag(SemanticConventions.AttributeHttpHost, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri));
activity.SetTag(SemanticConventions.AttributeHttpUrl, request.RequestUri.OriginalString);
if (this.options.SetHttpFlavor)
{
activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version));
}
}
if (!(this.httpClientSupportsW3C && this.options.Propagator is TextMapPropagator))
{
this.options.Propagator.Inject(new PropagationContext(activity.Context, Baggage.Current), request, HttpRequestMessageHeaderValueSetter);
}
}
public override void OnStopActivity(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
{
var requestTaskStatus = this.stopRequestStatusFetcher.Fetch(payload) as TaskStatus?;
if (requestTaskStatus.HasValue)
{
if (requestTaskStatus != TaskStatus.RanToCompletion)
{
if (requestTaskStatus == TaskStatus.Canceled)
{
activity.SetStatus(Status.Cancelled);
}
else if (requestTaskStatus != TaskStatus.Faulted)
{
// Faults are handled in OnException and should already have a span.Status of Unknown w/ Description.
activity.SetStatus(Status.Unknown);
}
}
}
if (this.stopResponseFetcher.Fetch(payload) is HttpResponseMessage response)
{
activity.SetCustomProperty(ResponseCustomPropertyName, response);
activity.SetTag(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode);
activity.SetStatus(
SpanHelper
.ResolveSpanStatusForHttpStatusCode((int)response.StatusCode)
.WithDescription(response.ReasonPhrase));
}
}
this.activitySource.Stop(activity);
}
public override void OnException(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
{
if (!(this.stopExceptionFetcher.Fetch(payload) is Exception exc))
{
HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnException));
return;
}
activity.SetCustomProperty(ExceptionCustomPropertyName, exc);
if (exc is HttpRequestException)
{
if (exc.InnerException is SocketException exception)
{
switch (exception.SocketErrorCode)
{
case SocketError.HostNotFound:
activity.SetStatus(Status.InvalidArgument.WithDescription(exc.Message));
return;
}
}
if (exc.InnerException != null)
{
activity.SetStatus(Status.Unknown.WithDescription(exc.Message));
}
}
}
}
}
}
// <copyright file="HttpHandlerDiagnosticListener.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using OpenTelemetry.Context;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.Http.Implementation
{
internal class HttpHandlerDiagnosticListener : ListenerHandler
{
public const string RequestCustomPropertyName = "OTel.HttpHandler.Request";
public const string ResponseCustomPropertyName = "OTel.HttpHandler.Response";
public const string ExceptionCustomPropertyName = "OTel.HttpHandler.Exception";
private static readonly Func<HttpRequestMessage, string, IEnumerable<string>> HttpRequestMessageHeaderValuesGetter = (request, name) =>
{
if (request.Headers.TryGetValues(name, out var values))
{
return values;
}
return null;
};
private static readonly Action<HttpRequestMessage, string, string> HttpRequestMessageHeaderValueSetter = (request, name, value) => request.Headers.Add(name, value);
private static readonly Regex CoreAppMajorVersionCheckRegex = new Regex("^\\.NETCoreApp,Version=v(\\d+)\\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly ActivitySourceAdapter activitySource;
private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request");
private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response");
private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception");
private readonly PropertyFetcher stopRequestStatusFetcher = new PropertyFetcher("RequestTaskStatus");
private readonly bool httpClientSupportsW3C;
private readonly HttpClientInstrumentationOptions options;
public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options, ActivitySourceAdapter activitySource)
: base("HttpHandlerDiagnosticListener")
{
var framework = Assembly
.GetEntryAssembly()?
.GetCustomAttribute<TargetFrameworkAttribute>()?
.FrameworkName;
// Depending on the .NET version/flavor this will look like
// '.NETCoreApp,Version=v3.0', '.NETCoreApp,Version = v2.2' or '.NETFramework,Version = v4.7.1'
if (framework != null)
{
var match = CoreAppMajorVersionCheckRegex.Match(framework);
this.httpClientSupportsW3C = match.Success && int.Parse(match.Groups[1].Value) >= 3;
}
this.options = options;
this.activitySource = activitySource;
}
public override void OnStartActivity(Activity activity, object payload)
{
if (!(this.startRequestFetcher.Fetch(payload) is HttpRequestMessage request))
{
HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnStartActivity));
return;
}
if (this.options.Propagator.Extract(default, request, HttpRequestMessageHeaderValuesGetter) != default)
{
// this request is already instrumented, we should back off
activity.IsAllDataRequested = false;
return;
}
activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method);
this.activitySource.Start(activity, ActivityKind.Client);
if (activity.IsAllDataRequested)
{
activity.SetCustomProperty(RequestCustomPropertyName, request);
activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method));
activity.SetTag(SemanticConventions.AttributeHttpHost, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri));
activity.SetTag(SemanticConventions.AttributeHttpUrl, request.RequestUri.OriginalString);
if (this.options.SetHttpFlavor)
{
activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version));
}
}
if (!(this.httpClientSupportsW3C && this.options.Propagator is TextMapPropagator))
{
this.options.Propagator.Inject(new PropagationContext(activity.Context, Baggage.Current), request, HttpRequestMessageHeaderValueSetter);
}
}
public override void OnStopActivity(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
{
var requestTaskStatus = this.stopRequestStatusFetcher.Fetch(payload) as TaskStatus?;
if (requestTaskStatus.HasValue)
{
if (requestTaskStatus != TaskStatus.RanToCompletion)
{
if (requestTaskStatus == TaskStatus.Canceled)
{
activity.SetStatus(Status.Cancelled);
}
else if (requestTaskStatus != TaskStatus.Faulted)
{
// Faults are handled in OnException and should already have a span.Status of Unknown w/ Description.
activity.SetStatus(Status.Unknown);
}
}
}
if (this.stopResponseFetcher.Fetch(payload) is HttpResponseMessage response)
{
activity.SetCustomProperty(ResponseCustomPropertyName, response);
activity.SetTag(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode);
activity.SetStatus(
SpanHelper
.ResolveSpanStatusForHttpStatusCode((int)response.StatusCode)
.WithDescription(response.ReasonPhrase));
}
}
this.activitySource.Stop(activity);
}
public override void OnException(Activity activity, object payload)
{
if (activity.IsAllDataRequested)
{
if (!(this.stopExceptionFetcher.Fetch(payload) is Exception exc))
{
HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnException));
return;
}
activity.SetCustomProperty(ExceptionCustomPropertyName, exc);
if (exc is HttpRequestException)
{
if (exc.InnerException is SocketException exception)
{
switch (exception.SocketErrorCode)
{
case SocketError.HostNotFound:
activity.SetStatus(Status.InvalidArgument.WithDescription(exc.Message));
return;
}
}
if (exc.InnerException != null)
{
activity.SetStatus(Status.Unknown.WithDescription(exc.Message));
}
}
}
}
}
}

View File

@ -1,261 +1,261 @@
// <copyright file="TracerProviderSdk.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using OpenTelemetry.Resources;
namespace OpenTelemetry.Trace
{
internal class TracerProviderSdk : TracerProvider
{
private readonly List<object> instrumentations = new List<object>();
private readonly ActivityListener listener;
private readonly Resource resource;
private readonly Sampler sampler;
private ActivityProcessor processor;
private ActivitySourceAdapter adapter;
static TracerProviderSdk()
{
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
}
internal TracerProviderSdk(
Resource resource,
IEnumerable<string> sources,
IEnumerable<TracerProviderBuilder.InstrumentationFactory> instrumentationFactories,
Sampler sampler,
List<ActivityProcessor> processors)
{
this.resource = resource;
this.sampler = sampler;
foreach (var processor in processors)
{
this.AddProcessor(processor);
}
if (instrumentationFactories.Any())
{
this.adapter = new ActivitySourceAdapter(sampler, this.processor, resource);
foreach (var instrumentationFactory in instrumentationFactories)
{
this.instrumentations.Add(instrumentationFactory.Factory(this.adapter));
}
}
var listener = new ActivityListener
{
// Callback when Activity is started.
ActivityStarted = (activity) =>
{
if (!activity.IsAllDataRequested)
{
return;
}
if (SuppressInstrumentationScope.IncrementIfTriggered() == 0)
{
activity.SetResource(this.resource);
this.processor?.OnStart(activity);
}
},
// Callback when Activity is stopped.
ActivityStopped = (activity) =>
{
if (!activity.IsAllDataRequested)
{
return;
}
if (SuppressInstrumentationScope.DecrementIfTriggered() == 0)
{
this.processor?.OnEnd(activity);
}
},
};
if (sampler is AlwaysOnSampler)
{
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ActivitySamplingResult.AllDataAndRecorded : ActivitySamplingResult.None;
}
else if (sampler is AlwaysOffSampler)
{
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? PropagateOrIgnoreData(options.Parent.TraceId) : ActivitySamplingResult.None;
}
else
{
// This delegate informs ActivitySource about sampling decision when the parent context is an ActivityContext.
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ComputeActivitySamplingResult(options, sampler) : ActivitySamplingResult.None;
}
if (sources.Any())
{
// Sources can be null. This happens when user
// is only interested in InstrumentationLibraries
// which do not depend on ActivitySources.
var wildcardMode = false;
// Validation of source name is already done in builder.
foreach (var name in sources)
{
if (name.Contains('*'))
{
wildcardMode = true;
}
}
if (wildcardMode)
{
var pattern = "^(" + string.Join("|", from name in sources select '(' + Regex.Escape(name).Replace("\\*", ".*") + ')') + ")$";
var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
// or not.
listener.ShouldListenTo = (activitySource) => regex.IsMatch(activitySource.Name);
}
else
{
var activitySources = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach (var name in sources)
{
activitySources[name] = true;
}
// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
// or not.
listener.ShouldListenTo = (activitySource) => activitySources.ContainsKey(activitySource.Name);
}
}
ActivitySource.AddActivityListener(listener);
this.listener = listener;
}
internal TracerProviderSdk AddProcessor(ActivityProcessor processor)
{
if (processor == null)
{
throw new ArgumentNullException(nameof(processor));
}
if (this.processor == null)
{
this.processor = processor;
}
else if (this.processor is CompositeActivityProcessor compositeProcessor)
{
compositeProcessor.AddProcessor(processor);
}
else
{
this.processor = new CompositeActivityProcessor(new[]
{
this.processor,
processor,
});
}
this.adapter?.UpdateProcessor(this.processor);
return this;
}
protected override void Dispose(bool disposing)
{
if (this.instrumentations != null)
{
foreach (var item in this.instrumentations)
{
(item as IDisposable)?.Dispose();
}
this.instrumentations.Clear();
}
(this.sampler as IDisposable)?.Dispose();
// Wait for up to 5 seconds grace period
this.processor?.Shutdown(5000);
this.processor?.Dispose();
// Shutdown the listener last so that anything created while instrumentation cleans up will still be processed.
// Redis instrumentation, for example, flushes during dispose which creates Activity objects for any profiling
// sessions that were open.
this.listener?.Dispose();
base.Dispose(disposing);
}
private static ActivitySamplingResult ComputeActivitySamplingResult(
in ActivityCreationOptions<ActivityContext> options,
Sampler sampler)
{
var samplingParameters = new SamplingParameters(
options.Parent,
options.TraceId,
options.Name,
options.Kind,
options.Tags,
options.Links);
var shouldSample = sampler.ShouldSample(samplingParameters);
var activitySamplingResult = shouldSample.Decision switch
{
SamplingDecision.RecordAndSampled => ActivitySamplingResult.AllDataAndRecorded,
SamplingDecision.Record => ActivitySamplingResult.AllData,
_ => ActivitySamplingResult.PropagationData
};
if (activitySamplingResult != ActivitySamplingResult.PropagationData)
{
foreach (var att in shouldSample.Attributes)
{
options.SamplingTags.Add(att.Key, att.Value);
}
return activitySamplingResult;
}
return PropagateOrIgnoreData(options.Parent.TraceId);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ActivitySamplingResult PropagateOrIgnoreData(ActivityTraceId traceId)
{
var isRootSpan = traceId == default;
// If it is the root span select PropagationData so the trace ID is preserved
// even if no activity of the trace is recorded (sampled per OpenTelemetry parlance).
return isRootSpan
? ActivitySamplingResult.PropagationData
: ActivitySamplingResult.None;
}
}
}
// <copyright file="TracerProviderSdk.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>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using OpenTelemetry.Resources;
namespace OpenTelemetry.Trace
{
internal class TracerProviderSdk : TracerProvider
{
private readonly List<object> instrumentations = new List<object>();
private readonly ActivityListener listener;
private readonly Resource resource;
private readonly Sampler sampler;
private ActivityProcessor processor;
private ActivitySourceAdapter adapter;
static TracerProviderSdk()
{
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
}
internal TracerProviderSdk(
Resource resource,
IEnumerable<string> sources,
IEnumerable<TracerProviderBuilder.InstrumentationFactory> instrumentationFactories,
Sampler sampler,
List<ActivityProcessor> processors)
{
this.resource = resource;
this.sampler = sampler;
foreach (var processor in processors)
{
this.AddProcessor(processor);
}
if (instrumentationFactories.Any())
{
this.adapter = new ActivitySourceAdapter(sampler, this.processor, resource);
foreach (var instrumentationFactory in instrumentationFactories)
{
this.instrumentations.Add(instrumentationFactory.Factory(this.adapter));
}
}
var listener = new ActivityListener
{
// Callback when Activity is started.
ActivityStarted = (activity) =>
{
if (!activity.IsAllDataRequested)
{
return;
}
if (SuppressInstrumentationScope.IncrementIfTriggered() == 0)
{
activity.SetResource(this.resource);
this.processor?.OnStart(activity);
}
},
// Callback when Activity is stopped.
ActivityStopped = (activity) =>
{
if (!activity.IsAllDataRequested)
{
return;
}
if (SuppressInstrumentationScope.DecrementIfTriggered() == 0)
{
this.processor?.OnEnd(activity);
}
},
};
if (sampler is AlwaysOnSampler)
{
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ActivitySamplingResult.AllDataAndRecorded : ActivitySamplingResult.None;
}
else if (sampler is AlwaysOffSampler)
{
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? PropagateOrIgnoreData(options.Parent.TraceId) : ActivitySamplingResult.None;
}
else
{
// This delegate informs ActivitySource about sampling decision when the parent context is an ActivityContext.
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ComputeActivitySamplingResult(options, sampler) : ActivitySamplingResult.None;
}
if (sources.Any())
{
// Sources can be null. This happens when user
// is only interested in InstrumentationLibraries
// which do not depend on ActivitySources.
var wildcardMode = false;
// Validation of source name is already done in builder.
foreach (var name in sources)
{
if (name.Contains('*'))
{
wildcardMode = true;
}
}
if (wildcardMode)
{
var pattern = "^(" + string.Join("|", from name in sources select '(' + Regex.Escape(name).Replace("\\*", ".*") + ')') + ")$";
var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
// or not.
listener.ShouldListenTo = (activitySource) => regex.IsMatch(activitySource.Name);
}
else
{
var activitySources = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach (var name in sources)
{
activitySources[name] = true;
}
// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
// or not.
listener.ShouldListenTo = (activitySource) => activitySources.ContainsKey(activitySource.Name);
}
}
ActivitySource.AddActivityListener(listener);
this.listener = listener;
}
internal TracerProviderSdk AddProcessor(ActivityProcessor processor)
{
if (processor == null)
{
throw new ArgumentNullException(nameof(processor));
}
if (this.processor == null)
{
this.processor = processor;
}
else if (this.processor is CompositeActivityProcessor compositeProcessor)
{
compositeProcessor.AddProcessor(processor);
}
else
{
this.processor = new CompositeActivityProcessor(new[]
{
this.processor,
processor,
});
}
this.adapter?.UpdateProcessor(this.processor);
return this;
}
protected override void Dispose(bool disposing)
{
if (this.instrumentations != null)
{
foreach (var item in this.instrumentations)
{
(item as IDisposable)?.Dispose();
}
this.instrumentations.Clear();
}
(this.sampler as IDisposable)?.Dispose();
// Wait for up to 5 seconds grace period
this.processor?.Shutdown(5000);
this.processor?.Dispose();
// Shutdown the listener last so that anything created while instrumentation cleans up will still be processed.
// Redis instrumentation, for example, flushes during dispose which creates Activity objects for any profiling
// sessions that were open.
this.listener?.Dispose();
base.Dispose(disposing);
}
private static ActivitySamplingResult ComputeActivitySamplingResult(
in ActivityCreationOptions<ActivityContext> options,
Sampler sampler)
{
var samplingParameters = new SamplingParameters(
options.Parent,
options.TraceId,
options.Name,
options.Kind,
options.Tags,
options.Links);
var shouldSample = sampler.ShouldSample(samplingParameters);
var activitySamplingResult = shouldSample.Decision switch
{
SamplingDecision.RecordAndSampled => ActivitySamplingResult.AllDataAndRecorded,
SamplingDecision.Record => ActivitySamplingResult.AllData,
_ => ActivitySamplingResult.PropagationData
};
if (activitySamplingResult != ActivitySamplingResult.PropagationData)
{
foreach (var att in shouldSample.Attributes)
{
options.SamplingTags.Add(att.Key, att.Value);
}
return activitySamplingResult;
}
return PropagateOrIgnoreData(options.Parent.TraceId);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ActivitySamplingResult PropagateOrIgnoreData(ActivityTraceId traceId)
{
var isRootSpan = traceId == default;
// If it is the root span select PropagationData so the trace ID is preserved
// even if no activity of the trace is recorded (sampled per OpenTelemetry parlance).
return isRootSpan
? ActivitySamplingResult.PropagationData
: ActivitySamplingResult.None;
}
}
}