Implementation of RecordException in TelemetrySpan (#1116)

* Add RecordException method to TelemetrySpan

* reusing ToInvariantString already implemented

* updating changelog

* checking for null

* reusing method

* Adding activity extension to record exception

* adding aggressiveinlining

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
Eddy Nakamura 2020-08-21 11:07:44 -03:00 committed by GitHub
parent 83adf75f01
commit 1b6c39d879
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 1 deletions

View File

@ -2,6 +2,8 @@
## Unreleased
* Added `RecordException` in `TelemetrySpan`
([#1116](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1116))
* `PropagationContext` is now used instead of `ActivityContext` in the
`ITextFormat` API
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))

View File

@ -19,6 +19,7 @@ using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Trace
{
@ -84,6 +85,33 @@ namespace OpenTelemetry.Trace
SetKindProperty(activity, kind);
}
/// <summary>
/// Record Exception.
/// </summary>
/// <param name="activity">Activity instance.</param>
/// <param name="ex">Exception to be recorded.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RecordException(this Activity activity, Exception ex)
{
if (ex == null)
{
return;
}
var tagsCollection = new ActivityTagsCollection
{
{ SemanticConventions.AttributeExceptionType, ex.GetType().Name },
{ SemanticConventions.AttributeExceptionStacktrace, ex.ToInvariantString() },
};
if (!string.IsNullOrWhiteSpace(ex.Message))
{
tagsCollection.Add(SemanticConventions.AttributeExceptionMessage, ex.Message);
}
activity?.AddEvent(new ActivityEvent(SemanticConventions.AttributeExceptionEventName, default, tagsCollection));
}
#pragma warning disable SA1201 // Elements should appear in the correct order
private static readonly Action<Activity, ActivityKind> SetKindProperty = CreateActivityKindSetter();
#pragma warning restore SA1201 // Elements should appear in the correct order

View File

@ -23,7 +23,8 @@ namespace OpenTelemetry.Trace
internal static class SemanticConventions
{
// The set of constants matches the specification as of this commit.
// https://github.com/open-telemetry/opentelemetry-specification/tree/709293fe132709705f0e0dd4252992e87a6ec899/specification/trace/semantic_conventions
// 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";
@ -147,6 +148,11 @@ namespace OpenTelemetry.Trace
public const string AttributeMessagingPayloadSize = "messaging.message_payload_size_bytes";
public const string AttributeMessagingPayloadCompressedSize = "messaging.message_payload_compressed_size_bytes";
public const string AttributeMessagingOperation = "messaging.operation";
public const string AttributeExceptionEventName = "exception";
public const string AttributeExceptionType = "exception.type";
public const string AttributeExceptionMessage = "exception.message";
public const string AttributeExceptionStacktrace = "exception.stacktrace";
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
}

View File

@ -18,6 +18,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Trace
{
@ -303,6 +304,56 @@ namespace OpenTelemetry.Trace
return this;
}
/// <summary>
/// Record Exception.
/// </summary>
/// <param name="ex">Exception to be recorded.</param>
/// <returns>The <see cref="TelemetrySpan"/> instance for chaining.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TelemetrySpan RecordException(Exception ex)
{
if (ex == null)
{
return this;
}
return this.RecordException(ex.GetType().Name, ex.Message, ex.ToInvariantString());
}
/// <summary>
/// Record Exception.
/// </summary>
/// <param name="type">Type of the exception to be recorded.</param>
/// <param name="message">Message of the exception to be recorded.</param>
/// <param name="stacktrace">Stacktrace of the exception to be recorded.</param>
/// <returns>The <see cref="TelemetrySpan"/> instance for chaining.</returns>
public TelemetrySpan RecordException(string type, string message, string stacktrace)
{
Dictionary<string, object> attributes = new Dictionary<string, object>();
if (!string.IsNullOrWhiteSpace(type))
{
attributes.Add(SemanticConventions.AttributeExceptionType, type);
}
if (!string.IsNullOrWhiteSpace(stacktrace))
{
attributes.Add(SemanticConventions.AttributeExceptionStacktrace, stacktrace);
}
if (!string.IsNullOrWhiteSpace(message))
{
attributes.Add(SemanticConventions.AttributeExceptionMessage, message);
}
if (attributes.Count != 0)
{
this.AddEvent(SemanticConventions.AttributeExceptionEventName, attributes);
}
return this;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()

View File

@ -14,7 +14,9 @@
// limitations under the License.
// </copyright>
using System;
using System.Diagnostics;
using System.Linq;
using Xunit;
namespace OpenTelemetry.Trace.Tests
@ -114,5 +116,18 @@ namespace OpenTelemetry.Trace.Tests
Assert.Equal(inputOutput, activity.Kind);
}
[Fact]
public void CheckRecordException()
{
var message = "message";
var exception = new ArgumentNullException(message, new Exception(message));
var activity = new Activity("test-activity");
activity.RecordException(exception);
var @event = activity.Events.FirstOrDefault(e => e.Name == SemanticConventions.AttributeExceptionEventName);
Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
Assert.Equal(exception.GetType().Name, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
}
}
}

View File

@ -0,0 +1,72 @@
// <copyright file="TelemetrySpanTest.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.Linq;
using OpenTelemetry.Trace;
using Xunit;
namespace OpenTelemetry.Tests.Trace
{
public class TelemetrySpanTest
{
[Fact]
public void CheckRecordExceptionData()
{
string message = "message";
using Activity activity = new Activity("exception-test");
using TelemetrySpan telemetrySpan = new TelemetrySpan(activity);
telemetrySpan.RecordException(new ArgumentNullException(message, new Exception("new-exception")));
Assert.Single(activity.Events);
var @event = telemetrySpan.Activity.Events.FirstOrDefault(q => q.Name == SemanticConventions.AttributeExceptionEventName);
Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
Assert.Equal(typeof(ArgumentNullException).Name, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
}
[Fact]
public void CheckRecordExceptionData2()
{
string type = "ArgumentNullException";
string message = "message";
string stack = "stack";
using Activity activity = new Activity("exception-test");
using TelemetrySpan telemetrySpan = new TelemetrySpan(activity);
telemetrySpan.RecordException(type, message, stack);
Assert.Single(activity.Events);
var @event = telemetrySpan.Activity.Events.FirstOrDefault(q => q.Name == SemanticConventions.AttributeExceptionEventName);
Assert.Equal(message, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionMessage).Value);
Assert.Equal(type, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionType).Value);
Assert.Equal(stack, @event.Tags.FirstOrDefault(t => t.Key == SemanticConventions.AttributeExceptionStacktrace).Value);
}
[Fact]
public void CheckRecordExceptionEmpty()
{
using Activity activity = new Activity("exception-test");
using TelemetrySpan telemetrySpan = new TelemetrySpan(activity);
telemetrySpan.RecordException(string.Empty, string.Empty, string.Empty);
Assert.Empty(activity.Events);
telemetrySpan.RecordException(null);
Assert.Empty(activity.Events);
}
}
}