Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Don't expose internals #17

Merged
merged 1 commit into from
Oct 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 320 additions & 0 deletions src/Sentry.Protocol/BaseScopeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Sentry.Protocol;

// ReSharper disable once CheckNamespace
namespace Sentry
{
///
[EditorBrowsable(EditorBrowsableState.Never)]
public static class BaseScopeExtensions
{
#if HAS_VALUE_TUPLE
/// <summary>
/// Adds a breadcrumb to the scope
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="message">The message.</param>
/// <param name="type">The type.</param>
/// <param name="category">The category.</param>
/// <param name="dataPair">The data key-value pair.</param>
/// <param name="level">The level.</param>
public static void AddBreadcrumb(
this BaseScope scope,
string message,
string category,
string type,
in (string, string)? dataPair = null,
BreadcrumbLevel level = default)
{
Dictionary<string, string> data = null;
if (dataPair != null)
{
data = new Dictionary<string, string>
{
{dataPair.Value.Item1, dataPair.Value.Item2}
};
}

scope.AddBreadcrumb(
timestamp: null,
message: message,
category: category,
type: type,
data: data,
level: level);
}
#endif

/// <summary>
/// Adds a breadcrumb to the scope.
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="message">The message.</param>
/// <param name="category">The category.</param>
/// <param name="type">The type.</param>
/// <param name="data">The data.</param>
/// <param name="level">The level.</param>
public static void AddBreadcrumb(
this BaseScope scope,
string message,
string category = null,
string type = null,
Dictionary<string, string> data = null,
BreadcrumbLevel level = default)
{
scope.AddBreadcrumb(
timestamp: null,
message: message,
category: category,
type: type,
data: data,
level: level);
}

/// <summary>
/// Adds a breadcrumb to the scope
/// </summary>
/// <remarks>
/// This overload is used for testing.
/// </remarks>
/// <param name="scope">The scope.</param>
/// <param name="timestamp">The timestamp</param>
/// <param name="message">The message.</param>
/// <param name="category">The category.</param>
/// <param name="type">The type.</param>
/// <param name="data">The data</param>
/// <param name="level">The level.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void AddBreadcrumb(
this BaseScope scope,
DateTimeOffset? timestamp,
string message,
string category = null,
string type = null,
IReadOnlyDictionary<string, string> data = null,
BreadcrumbLevel level = default)
=> scope.AddBreadcrumb(new Breadcrumb(
timestamp: timestamp,
message: message,
type: type,
data: data,
category: category,
level: level));

/// <summary>
/// Adds a breadcrumb to the <see cref="BaseScope"/>
/// </summary>
/// <param name="scope">Scope</param>
/// <param name="breadcrumb">The breadcrumb.</param>
internal static void AddBreadcrumb(this BaseScope scope, Breadcrumb breadcrumb)
{
if (scope == null)
{
return;
}

if (scope.ScopeOptions?.BeforeBreadcrumb is Func<Breadcrumb, Breadcrumb> callback)
{
breadcrumb = callback(breadcrumb);

if (breadcrumb == null)
{
return;
}
}

var breadcrumbs = (ConcurrentQueue<Breadcrumb>)scope.Breadcrumbs;

var overflow = breadcrumbs.Count - (scope.ScopeOptions?.MaxBreadcrumbs
?? Constants.DefaultMaxBreadcrumbs) + 1;
if (overflow > 0)
{
breadcrumbs.TryDequeue(out _);
}

breadcrumbs.Enqueue(breadcrumb);
}

/// <summary>
/// Sets the fingerprint to the <see cref="BaseScope"/>
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="fingerprint">The fingerprint.</param>
public static void SetFingerprint(this BaseScope scope, IEnumerable<string> fingerprint)
=> scope.InternalFingerprint = fingerprint;

/// <summary>
/// Sets the extra key-value to the <see cref="BaseScope"/>
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
public static void SetExtra(this BaseScope scope, string key, object value)
=> ((ConcurrentDictionary<string, object>)scope.Extra).AddOrUpdate(key, value, (s, o) => value);

/// <summary>
/// Sets the extra key-value pairs to the <see cref="BaseScope"/>
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="values">The values.</param>
public static void SetExtras(this BaseScope scope, IEnumerable<KeyValuePair<string, object>> values)
{
var extra = (ConcurrentDictionary<string, object>)scope.Extra;
foreach (var keyValuePair in values)
{
extra.AddOrUpdate(keyValuePair.Key, keyValuePair.Value, (s, o) => keyValuePair.Value);
}
}

/// <summary>
/// Sets the tag to the <see cref="BaseScope"/>
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
public static void SetTag(this BaseScope scope, string key, string value)
=> ((ConcurrentDictionary<string, string>)scope.Tags).AddOrUpdate(key, value, (s, o) => value);

/// <summary>
/// Set all items as tags
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="tags"></param>
public static void SetTags(this BaseScope scope, IEnumerable<KeyValuePair<string, string>> tags)
{
var internalTags = (ConcurrentDictionary<string, string>)scope.Tags;
foreach (var keyValuePair in tags)
{
internalTags.AddOrUpdate(keyValuePair.Key, keyValuePair.Value, (s, o) => keyValuePair.Value);
}
}

/// <summary>
/// Removes a tag from the <see cref="BaseScope"/>
/// </summary>
/// <param name="scope">The scope.</param>
/// <param name="key"></param>
public static void UnsetTag(this BaseScope scope, string key)
=> scope.InternalTags?.TryRemove(key, out _);

/// <summary>
/// Applies the data from one scope to the other while
/// </summary>
/// <param name="from">The scope to data copy from.</param>
/// <param name="to">The scope to copy data to.</param>
/// <remarks>
/// Applies the data of 'from' into 'to'.
/// If data in 'from' is null, 'to' is unmodified.
/// Conflicting keys are not overriden
/// This is a shallow copy.
/// </remarks>
public static void Apply(this BaseScope from, BaseScope to)
{
if (from == null || to == null)
{
return;
}

// Fingerprint isn't combined. It's absolute.
// One set explicitly on target (i.e: event)
// takes precedence and is not overwritten
if (to.InternalFingerprint == null
&& from.InternalFingerprint != null)
{
to.InternalFingerprint = from.InternalFingerprint;
}

if (from.InternalBreadcrumbs != null)
{
((ConcurrentQueue<Breadcrumb>)to.Breadcrumbs).EnqueueAll(from.InternalBreadcrumbs);
}

if (from.InternalExtra != null)
{
foreach (var extra in from.Extra)
{
((ConcurrentDictionary<string, object>)to.Extra).TryAdd(extra.Key, extra.Value);
}
}

if (from.InternalTags != null)
{
foreach (var tag in from.Tags)
{
((ConcurrentDictionary<string, string>)to.Tags).TryAdd(tag.Key, tag.Value);
}
}

from.InternalContexts?.CopyTo(to.Contexts);
from.InternalRequest?.CopyTo(to.Request);
from.InternalUser?.CopyTo(to.User);

if (to.Environment == null)
{
to.Environment = from.Environment;
}

if (from.Sdk != null)
{
if (from.Sdk.Name != null && from.Sdk.Version != null)
{
to.Sdk.Name = from.Sdk.Name;
to.Sdk.Version = from.Sdk.Version;
}

if (from.Sdk.InternalPackages != null)
{
foreach (var package in from.Sdk.InternalPackages)
{
to.Sdk.AddPackage(package);
}
}
}
}

/// <summary>
/// Applies the state object into the scope
/// </summary>
/// <param name="scope">The scope to apply the data.</param>
/// <param name="state">The state object to apply.</param>
public static void Apply(this BaseScope scope, object state)
{
switch (state)
{
case string scopeString:
// TODO: find unique key to support multiple single-string scopes
scope.SetTag("scope", scopeString);
break;
case IEnumerable<KeyValuePair<string, string>> keyValStringString:
scope.SetTags(keyValStringString
.Where(kv => !string.IsNullOrEmpty(kv.Value)));
break;
case IEnumerable<KeyValuePair<string, object>> keyValStringObject:
{
scope.SetTags(keyValStringObject
.Select(k => new KeyValuePair<string, string>(
k.Key,
k.Value?.ToString()))
.Where(kv => !string.IsNullOrEmpty(kv.Value)));

break;
}
#if HAS_VALUE_TUPLE
case ValueTuple<string, string> tupleStringString:
if (!string.IsNullOrEmpty(tupleStringString.Item2))
{
scope.SetTag(tupleStringString.Item1, tupleStringString.Item2);
}
break;
#endif
default:
scope.SetExtra("state", state);
break;
}
}
}
}
6 changes: 5 additions & 1 deletion src/Sentry.Protocol/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ public static class Constants
public const string DisableSdkDsnValue = "";
public const int DefaultMaxBreadcrumbs = 100;
public const int ProtocolVersion = 7;
internal const string Platform = "csharp";

/// <summary>
/// Platform key that defines an events is coming from any .NET implementation
/// </summary>
public const string Platform = "csharp";
}
}
2 changes: 0 additions & 2 deletions src/Sentry.Protocol/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Sentry,PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]

[assembly: InternalsVisibleTo("Sentry.Protocol.Tests,PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
[assembly: InternalsVisibleTo("Sentry.Tests,PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
[assembly: InternalsVisibleTo("Sentry.Testing,PublicKey=002400000480000094000000060200000024000052534131000400000100010059964a931488bcdbd14657f1ee0df32df61b57b3d14d7290c262c2cc9ddaad6ec984044f761f778e1823049d2cb996a4f58c8ea5b46c37891414cb34b4036b1c178d7b582289d2eef3c0f1e9b692c229a306831ee3d371d9e883f0eb0f74aeac6c6ab8c85fd1ec04b267e15a31532c4b4e2191f5980459db4dce0081f1050fb8")]
Expand Down
4 changes: 4 additions & 0 deletions src/Sentry.Protocol/Sentry.Protocol.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@
<PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
</ItemGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<DefineConstants>HAS_VALUE_TUPLE;$(AdditionalConstants)</DefineConstants>
</PropertyGroup>

</Project>
13 changes: 11 additions & 2 deletions src/Sentry.Protocol/SentryEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,27 @@ namespace Sentry
/// An event to be sent to Sentry
/// </summary>
/// <seealso href="https://docs.sentry.io/clientdev/attributes/" />
/// <inheritdoc />
[DataContract]
[DebuggerDisplay("{GetType().Name,nq}: {" + nameof(EventId) + ",nq}")]
public class SentryEvent : BaseScope
{
internal Exception Exception { get; set; }

[DataMember(Name = "modules", EmitDefaultValue = false)]
internal IDictionary<string, string> InternalModules { get; set; }

[DataMember(Name = "event_id", EmitDefaultValue = false)]
private string SerializableEventId => EventId.ToString("N");

/// <summary>
/// The <see cref="System.Exception"/> used to create this event.
/// </summary>
/// <remarks>
/// The information from this exception is used by the Sentry SDK
/// to add the relevant data to the event prior to sending to Sentry.
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public Exception Exception { get; }

/// <summary>
/// The unique identifier of this event
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Sentry.Protocol/SentryEventExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Sentry.Protocol
{
public static class SentryEventExtensions
{
public static bool HasUser(this SentryEvent evt) => evt.InternalUser != null;
}
}
Loading