enforce EOL=LF in CI (#1250)
Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
parent
a3731ea02d
commit
5e41c62327
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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})");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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})");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue