From 486bb629d8050751913363c5df5e78ee5f8ca271 Mon Sep 17 00:00:00 2001 From: Lucas Zimerman Fraulob Date: Sun, 9 Aug 2020 10:19:56 -0300 Subject: [PATCH 1/7] Added SpanStatus and Span handlers for Requests. Flexibilized SentrySdk. Added Disabled Tracing and Span to make the flow for non existing scenarios easier. --- .../Enums/ESpanRequest.cs | 11 +++ .../Enums/ESpanStatus.cs | 49 ++++++++++ .../Extensibility/DisabledSpan.cs | 32 +++++++ .../Extensibility/DisabledTracing.cs | 29 ++++++ .../ITransactionEvent.cs | 12 --- .../Interface/ISentryTracing.cs | 22 +++++ .../Interface/ISpanBase.cs | 24 +++++ .../Internals/SpanStatus.cs | 79 +++++++++++++++ .../SentryTracing.cs | 38 +++++--- .../SentryTracingEvent.cs | 18 ++-- .../SentryTracingEventProcessor.cs | 2 +- .../SentryTracingSDK.cs | 62 ++++++++++-- sentry-dotnet-transaction-addon/Span.cs | 95 ++++++++++++++++--- .../sentry-dotnet-transaction-addon.csproj | 2 +- 14 files changed, 414 insertions(+), 61 deletions(-) create mode 100644 sentry-dotnet-transaction-addon/Enums/ESpanRequest.cs create mode 100644 sentry-dotnet-transaction-addon/Enums/ESpanStatus.cs create mode 100644 sentry-dotnet-transaction-addon/Extensibility/DisabledSpan.cs create mode 100644 sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs delete mode 100644 sentry-dotnet-transaction-addon/ITransactionEvent.cs create mode 100644 sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs create mode 100644 sentry-dotnet-transaction-addon/Interface/ISpanBase.cs create mode 100644 sentry-dotnet-transaction-addon/Internals/SpanStatus.cs diff --git a/sentry-dotnet-transaction-addon/Enums/ESpanRequest.cs b/sentry-dotnet-transaction-addon/Enums/ESpanRequest.cs new file mode 100644 index 0000000..68291c2 --- /dev/null +++ b/sentry-dotnet-transaction-addon/Enums/ESpanRequest.cs @@ -0,0 +1,11 @@ +namespace sentry_dotnet_transaction_addon.Enums +{ + public enum ESpanRequest + { + Get, + Post, + Put, + Delete, + Patch + } +} diff --git a/sentry-dotnet-transaction-addon/Enums/ESpanStatus.cs b/sentry-dotnet-transaction-addon/Enums/ESpanStatus.cs new file mode 100644 index 0000000..2f73a57 --- /dev/null +++ b/sentry-dotnet-transaction-addon/Enums/ESpanStatus.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace sentry_dotnet_transaction_addon.Enums +{ + + /// + /// Ok + ///
The operation completed successfully.
+ ///
+ internal enum ESpanStatus + { + /// The operation completed successfully. + Ok, + /// Deadline expired before operation could complete. + DeadlineExceeded, + /// 401 Unauthorized (actually does mean unauthenticated according to RFC 7235). + Unauthenticated, + /// 403 Forbidden + PermissionDenied, + /// 404 Not Found. Some requested entity (file or directory) was not found. + NotFound, + /// 429 Too Many Requests + ResourceExhausted, + /// Client specified an invalid argument. 4xx. + InvalidArgument, + /// 501 Not Implemented + Unimplemented, + /// 503 Service Unavailable + Unavailable, + /// Other/generic 5xx. + InternalError, + /// Unknown. Any non-standard HTTP status code. + UnknownError, + /// The operation was cancelled (typically by the user). + Cancelled, + /// Already exists (409). + AlreadyExists, + /// Operation was rejected because the system is not in a state required for the operation's + FailedPrecondition, + /// The operation was aborted, typically due to a concurrency issue. + Aborted, + /// Operation was attempted past the valid range. + OutOfRange, + /// Unrecoverable data loss or corruption + DataLoss, + } +} diff --git a/sentry-dotnet-transaction-addon/Extensibility/DisabledSpan.cs b/sentry-dotnet-transaction-addon/Extensibility/DisabledSpan.cs new file mode 100644 index 0000000..cf23140 --- /dev/null +++ b/sentry-dotnet-transaction-addon/Extensibility/DisabledSpan.cs @@ -0,0 +1,32 @@ +using sentry_dotnet_transaction_addon.Enums; +using sentry_dotnet_transaction_addon.Interface; +using System; +using System.Collections.Generic; + +namespace sentry_dotnet_transaction_addon.Extensibility +{ + public partial class DisabledSpan : ISpanBase + { + + internal static DisabledSpan Instance = new DisabledSpan(null, null); + + public DisabledSpan(string description, string op = null) { } + + public string Description => null; + public string Op => null; + public string SpanId => null; + public string ParentSpanId => null; + public DateTimeOffset? StartTimestamp => null; + public DateTimeOffset? Timestamp => null; + public string TraceId => null; + + public void Dispose() { } + + public void Finish() { } + public void Finish(int? httpStatus) { } + public void GetParentSpans(List spans) { } + + public ISpanBase StartChild(string description, string op = null) => Instance; + public ISpanBase StartChild(string url, ESpanRequest requestType) => Instance; + } +} diff --git a/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs b/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs new file mode 100644 index 0000000..879fc79 --- /dev/null +++ b/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs @@ -0,0 +1,29 @@ +using sentry_dotnet_transaction_addon.Enums; +using sentry_dotnet_transaction_addon.Interface; +using sentry_dotnet_transaction_addon.Internals; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Text; + +namespace sentry_dotnet_transaction_addon.Extensibility +{ + public class DisabledTracing : ISentryTracing + { + public static DisabledTracing Instance = new DisabledTracing(); + public List Spans => null; + + public DateTimeOffset StartTimestamp { get; } + + public void Dispose() { } + public void Finish() { } + + public ISpanBase GetCurrentSpan() => DisabledSpan.Instance; + + public ISpanBase GetSpan(string op) => DisabledSpan.Instance; + + public ISpanBase StartChild(string description, string op = null) => null; + + public ISpanBase StartChild(string url, ESpanRequest requestType) => DisabledSpan.Instance; + } +} diff --git a/sentry-dotnet-transaction-addon/ITransactionEvent.cs b/sentry-dotnet-transaction-addon/ITransactionEvent.cs deleted file mode 100644 index 94101bd..0000000 --- a/sentry-dotnet-transaction-addon/ITransactionEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace sentry_dotnet_transaction_addon -{ - public interface ITransactionEvent - { - List Spans { get; } - - DateTimeOffset StartTimestamp { get; } - } -} diff --git a/sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs b/sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs new file mode 100644 index 0000000..2e4e76a --- /dev/null +++ b/sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs @@ -0,0 +1,22 @@ +using sentry_dotnet_transaction_addon.Enums; +using sentry_dotnet_transaction_addon.Interface; +using System; +using System.Collections.Generic; + +namespace sentry_dotnet_transaction_addon.Internals +{ + public interface ISentryTracing : IDisposable + { + List Spans { get; } + + DateTimeOffset StartTimestamp { get; } + + ISpanBase GetSpan(string op); + + ISpanBase GetCurrentSpan(); + + ISpanBase StartChild(string description, string op = null); + ISpanBase StartChild(string url, ESpanRequest requestType); + void Finish(); + } +} diff --git a/sentry-dotnet-transaction-addon/Interface/ISpanBase.cs b/sentry-dotnet-transaction-addon/Interface/ISpanBase.cs new file mode 100644 index 0000000..e468bff --- /dev/null +++ b/sentry-dotnet-transaction-addon/Interface/ISpanBase.cs @@ -0,0 +1,24 @@ +using sentry_dotnet_transaction_addon.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace sentry_dotnet_transaction_addon.Interface +{ + public interface ISpanBase : IDisposable + { + string Description { get; } + string Op { get; } + string SpanId { get; } + string ParentSpanId { get; } + DateTimeOffset? StartTimestamp { get; } + DateTimeOffset? Timestamp { get; } + string TraceId { get; } + + void Finish(); + void Finish(int? httpStatus); + void GetParentSpans(List spans); + ISpanBase StartChild(string url, ESpanRequest requestType); + ISpanBase StartChild(string description, string op = null); + } +} diff --git a/sentry-dotnet-transaction-addon/Internals/SpanStatus.cs b/sentry-dotnet-transaction-addon/Internals/SpanStatus.cs new file mode 100644 index 0000000..5c114dd --- /dev/null +++ b/sentry-dotnet-transaction-addon/Internals/SpanStatus.cs @@ -0,0 +1,79 @@ +using sentry_dotnet_transaction_addon.Enums; +using System.Collections.Generic; + +namespace sentry_dotnet_transaction_addon.Internals +{ + internal static class SpanStatus + { + internal static Dictionary SpanStatusDictionary = new Dictionary(){ + { ESpanStatus.Ok, "ok"}, + { ESpanStatus.DeadlineExceeded, "deadline_exceeded"}, + { ESpanStatus.Unauthenticated, "unauthenticated"}, + { ESpanStatus.PermissionDenied, "permission_denied"}, + { ESpanStatus.NotFound, "not_found"}, + { ESpanStatus.ResourceExhausted, "resource_exhausted"}, + { ESpanStatus.InvalidArgument, "invalid_argument"}, + { ESpanStatus.Unimplemented, "unimplemented"}, + { ESpanStatus.Unavailable, "unavailable"}, + { ESpanStatus.InternalError, "internal_error"}, + { ESpanStatus.UnknownError, "unknown_error"}, + { ESpanStatus.Cancelled, "cancelled"}, + { ESpanStatus.AlreadyExists, "already_exists"}, + { ESpanStatus.FailedPrecondition, "failed_precondition"}, + { ESpanStatus.Aborted, "aborted"}, + { ESpanStatus.OutOfRange, "out_of_range"}, + { ESpanStatus.DataLoss, "data_loss"}, + }; + + + internal static ESpanStatus FromHttpStatusCode(int? httpStatus) + { + if (httpStatus == null) + return ESpanStatus.UnknownError; + + if (httpStatus < 400) + { + return ESpanStatus.Ok; + } + + if (httpStatus >= 400 && httpStatus < 500) + { + switch (httpStatus) + { + case 401: + return ESpanStatus.Unauthenticated; + case 403: + return ESpanStatus.PermissionDenied; + case 404: + return ESpanStatus.NotFound; + case 409: + return ESpanStatus.AlreadyExists; + case 413: + return ESpanStatus.FailedPrecondition; + case 429: + return ESpanStatus.ResourceExhausted; + default: + return ESpanStatus.InvalidArgument; + } + } + + if (httpStatus >= 500 && httpStatus < 600) + { + switch (httpStatus) + { + case 501: + return ESpanStatus.Unimplemented; + case 503: + return ESpanStatus.Unavailable; + case 504: + return ESpanStatus.DeadlineExceeded; + default: + return ESpanStatus.InternalError; + } + } + + return ESpanStatus.UnknownError; + } + + } +} diff --git a/sentry-dotnet-transaction-addon/SentryTracing.cs b/sentry-dotnet-transaction-addon/SentryTracing.cs index 962f636..5d28241 100644 --- a/sentry-dotnet-transaction-addon/SentryTracing.cs +++ b/sentry-dotnet-transaction-addon/SentryTracing.cs @@ -1,13 +1,17 @@ using Sentry; +using sentry_dotnet_transaction_addon.Enums; +using sentry_dotnet_transaction_addon.Extensibility; +using sentry_dotnet_transaction_addon.Interface; +using sentry_dotnet_transaction_addon.Internals; using System; using System.Collections.Generic; using System.Linq; namespace sentry_dotnet_transaction_addon { - public class SentryTracing : ITransactionEvent, IDisposable + public class SentryTracing : ISentryTracing { - public List Spans { get; private set; } + public List Spans { get; private set; } public DateTimeOffset StartTimestamp { get; private set; } internal Trace Trace { get; private set; } @@ -17,36 +21,42 @@ public SentryTracing(string name) Trace = new Trace(); Transaction = name; StartTimestamp = DateTimeOffset.Now; - Spans = new List(); + Spans = new List(); } public string Id => Guid.NewGuid().ToString(); - public Span GetSpan(string op) + public ISpanBase GetSpan(string op) { return Spans.FirstOrDefault(s => s.Op == op); } - public Span StartChild(string description, string op = null) + public ISpanBase GetCurrentSpan() { - var span = new Span(description, op); - SetTraceToSpan(span); + return Spans.LastOrDefault(s => s.Timestamp == s.StartTimestamp && s.ParentSpanId == Trace.SpanId) ?? DisabledSpan.Instance; + } + + public ISpanBase StartChild(string description, string op = null) + { + var span = new Span(Trace.TraceId, Trace.SpanId, description, op); + span.GetParentSpans(Spans); Spans.Add(span); return span; } - - private void SetTraceToSpan(Span span) + public ISpanBase StartChild(string url, ESpanRequest requestType) { - span.TraceId = Trace.TraceId; - span.ParentSpanId = Trace.SpanId; + var span = new Span(Trace.TraceId, Trace.SpanId, url, requestType); + span.Spans = Spans; + Spans.Add(span); + return span; } public void Finish() { - if (SentryTracingSDK.IsEnabled() && new Random().NextDouble() <= SentryTracingSDK.TracingOptions.TracesSampleRate) + if (SentryTracingSdk.IsEnabled() && new Random().NextDouble() <= SentryTracingSdk.TracingOptions.TracesSampleRate) { var @event = new SentryTracingEvent(this); - if (SentryTracingSDK.TracingOptions.RegisterTracingBreadcrmub) + if (SentryTracingSdk.TracingOptions.RegisterTracingBreadcrmub) { SentrySdk.AddBreadcrumb(@event.EventId.ToString(), "sentry.transaction"); } @@ -55,7 +65,7 @@ public void Finish() SentrySdk.CaptureEvent(@event); }); } - SentryTracingSDK.DisposeTracingEvent(this); + SentryTracingSdk.DisposeTracingEvent(this); } public void Dispose() diff --git a/sentry-dotnet-transaction-addon/SentryTracingEvent.cs b/sentry-dotnet-transaction-addon/SentryTracingEvent.cs index 88a9e0a..07f203d 100644 --- a/sentry-dotnet-transaction-addon/SentryTracingEvent.cs +++ b/sentry-dotnet-transaction-addon/SentryTracingEvent.cs @@ -2,6 +2,7 @@ using Sentry; using sentry_dotnet_transaction_addon.Converters; using sentry_dotnet_transaction_addon.Extensions; +using sentry_dotnet_transaction_addon.Interface; using sentry_dotnet_transaction_addon.Internals; using System; using System.Collections.Generic; @@ -10,13 +11,13 @@ namespace sentry_dotnet_transaction_addon { - public class SentryTracingEvent : SentryEvent, ITransactionEvent + public class SentryTracingEvent : SentryEvent { [JsonProperty("type")] public string Type { get; private set; } [JsonProperty("spans")] - public List Spans { get; private set; } + public List Spans { get; private set; } [JsonProperty("start_timestamp")] public DateTimeOffset StartTimestamp { get; private set; } @@ -25,14 +26,7 @@ internal SentryTracingEvent(SentryTracing transactionEvent) { Transaction = transactionEvent.Transaction; Type = "transaction"; - try - { - Contexts.AddOrUpdate("trace", transactionEvent.Trace, (id, trace) => trace); - } - catch (Exception e) - { - - } + Contexts.AddOrUpdate("trace", transactionEvent.Trace, (id, trace) => trace); Spans = transactionEvent.Spans; StartTimestamp = transactionEvent.StartTimestamp; } @@ -52,9 +46,9 @@ public void SendTransaction() EventId = EventId.ToString(), SentAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:MM:ss.ffZ") }); - var @type = "{\"type\"=\"transaction\"}"; + var @type = "{\"type\":\"transaction\"}"; var content = new StringContent(@event + '\n' + @type + '\n' + json); - var url = SentryTracingSDK.TracingOptions.Dsn.GetTracingUrl(); + var url = SentryTracingSdk.TracingOptions.Dsn.GetTracingUrl(); var @return = await client.PostAsync(url, content); } } diff --git a/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs b/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs index 2e936bc..9193046 100644 --- a/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs +++ b/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs @@ -15,4 +15,4 @@ public SentryEvent Process(SentryEvent @event) return @event; } } -} +} \ No newline at end of file diff --git a/sentry-dotnet-transaction-addon/SentryTracingSDK.cs b/sentry-dotnet-transaction-addon/SentryTracingSDK.cs index 03065f9..e101feb 100644 --- a/sentry-dotnet-transaction-addon/SentryTracingSDK.cs +++ b/sentry-dotnet-transaction-addon/SentryTracingSDK.cs @@ -1,12 +1,17 @@ using Sentry; +using sentry_dotnet_transaction_addon.Enums; +using sentry_dotnet_transaction_addon.Extensibility; +using sentry_dotnet_transaction_addon.Interface; +using sentry_dotnet_transaction_addon.Internals; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace sentry_dotnet_transaction_addon { - public static class SentryTracingSDK + public static class SentryTracingSdk { - internal static List> _transactionStorage = new List>(); + internal static List> _transactionStorage = new List>(); internal static SentryTracingOptions TracingOptions { get; set; } @@ -43,26 +48,65 @@ public static void Close() TracingOptions = null; } - public static SentryTracing StartTransaction(string name) + public static ISentryTracing StartTransaction(string name) { var tracing = new SentryTracing(name); - _transactionStorage.Add(new KeyValuePair(name, tracing)); + _transactionStorage.Add(new KeyValuePair(Task.CurrentId, tracing)); return tracing; } - public static SentryTracing RetreiveTransactionById(string id) + public static ISentryTracing RetreiveTransactionById(string id) { - return _transactionStorage.FirstOrDefault(p => p.Value.Id == id).Value;//<- it could go kaboom + return _transactionStorage.FirstOrDefault(p => p.Value.Trace.TraceId == id).Value ?? (ISentryTracing)DisabledTracing.Instance; } - public static SentryTracing RetreiveTransactionByName(string name) + public static ISentryTracing RetreiveTransactionByName(string name) { - return _transactionStorage.FirstOrDefault(p => p.Key == name).Value;//<- it could go kaboom + return _transactionStorage.FirstOrDefault(p => p.Value.Transaction == name).Value ?? (ISentryTracing)DisabledTracing.Instance; + } + + /// + /// Gets the Last Transaction from the current Task + /// + /// + public static ISentryTracing GetCurrentTransaction() + { + if (!IsEnabled() || _transactionStorage.Count() == 0) + return DisabledTracing.Instance; + var keyPair = _transactionStorage.LastOrDefault(p => p.Key == Task.CurrentId); + return keyPair.Value ?? (ISentryTracing)DisabledTracing.Instance; + } + + public static ISpanBase GetCurrentTracingSpan() + { + return GetCurrentTransaction().GetCurrentSpan(); + } + + public static ISpanBase StartChild(string url, ESpanRequest requestType) + { + var transaction = GetCurrentTransaction(); + var span = transaction.GetCurrentSpan(); + if(span is DisabledSpan) + { + return transaction.StartChild(url, requestType); + } + return span.StartChild(url, requestType); + } + + public static ISpanBase StartChild(string description, string op = null) + { + var transaction = GetCurrentTransaction(); + var span = transaction.GetCurrentSpan(); + if (span is DisabledSpan) + { + return transaction.StartChild(description, op); + } + return span.StartChild(description, op); } internal static void DisposeTracingEvent(SentryTracing tracing) { - _transactionStorage.Remove(_transactionStorage.First(p => p.Key == tracing.Transaction && p.Value.Equals(tracing))); + _transactionStorage.Remove(_transactionStorage.First(p => p.Value.Equals(tracing))); } } } diff --git a/sentry-dotnet-transaction-addon/Span.cs b/sentry-dotnet-transaction-addon/Span.cs index 5ce2f45..3c479d4 100644 --- a/sentry-dotnet-transaction-addon/Span.cs +++ b/sentry-dotnet-transaction-addon/Span.cs @@ -1,44 +1,115 @@ -using Newtonsoft.Json; -using System; +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using sentry_dotnet_transaction_addon.Enums; using sentry_dotnet_transaction_addon.Extensions; +using sentry_dotnet_transaction_addon.Interface; +using sentry_dotnet_transaction_addon.Internals; namespace sentry_dotnet_transaction_addon { - public class Span + public class Span : ISpanBase, IDisposable { #region Properties + + [JsonIgnore] + private bool _isRequest; + [JsonIgnore] + private bool _finished; + + [JsonIgnore] + internal List Spans { get; set; } + [JsonProperty("description")] - public string Description; + public string Description { get; private set; } [JsonProperty("op")] - public string Op; + public string Op { get; private set; } [JsonProperty("span_id")] - public string SpanId; + public string SpanId { get; private set; } [JsonProperty("parent_span_id")] - public string ParentSpanId; + public string ParentSpanId { get; private set; } [JsonProperty("start_timestamp")] - public DateTimeOffset StartTimestamp; + public DateTimeOffset? StartTimestamp { get; private set; } [JsonProperty("timestamp")] - public DateTimeOffset Timestamp; + public DateTimeOffset? Timestamp { get; private set; } [JsonProperty("trace_id")] - public string TraceId; + public string TraceId { get; private set; } + + /// + /// If set, it'll be based on the ESpanStatus result + /// + [JsonProperty("status")] + public string Status { get; private set; } + #endregion - public Span(string description, string op = null) + public Span(string traceId, string spanId, string description, string op = null) { StartTimestamp = DateTimeOffset.Now; + Timestamp = StartTimestamp; //In case of not closing the equal time will indicate a problem. Description = description; Op = op ?? description; SpanId = Guid.NewGuid().LimitLength(); + TraceId = traceId; + ParentSpanId = spanId; + } + + public Span(string traceId, string spanId, string url, ESpanRequest requestType) + { + StartTimestamp = DateTimeOffset.Now; + Timestamp = StartTimestamp; //In case of not closing the equal time will indicate a problem. + Description = url; + Op = requestType.ToString().ToUpper(); + SpanId = Guid.NewGuid().LimitLength(); + TraceId = traceId; + ParentSpanId = spanId; + _isRequest = true; + } + + public ISpanBase StartChild(string description, string op = null) + { + var span = new Span(TraceId, SpanId, description, op); + span.Spans = Spans; + Spans.Add(span); + return span; + } + + public ISpanBase StartChild(string url, ESpanRequest requestType) + { + var span = new Span(TraceId, SpanId, url, requestType); + span.Spans = Spans; + Spans.Add(span); + return span; } public void Finish() { - Timestamp = DateTimeOffset.Now; + if (_isRequest) + Finish(null); + else + Timestamp = DateTimeOffset.Now; + } + + public void Finish(int? httpCode) + { + if (!_finished) + { + _finished = true; + Status = SpanStatus.SpanStatusDictionary[SpanStatus.FromHttpStatusCode(httpCode)]; + Timestamp = DateTimeOffset.Now; + } + } + + public void Dispose() => Finish(); + + public void GetParentSpans(List spans) + { + Spans = spans; } } } diff --git a/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj b/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj index b7e8982..114a6bc 100644 --- a/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj +++ b/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 From 246f19a79ed6aee37eaea3d14eeec1e337f71b8f Mon Sep 17 00:00:00 2001 From: LucasZF Date: Sun, 9 Aug 2020 10:35:31 -0300 Subject: [PATCH 2/7] Update README.md Readme updated according to the new code. --- README.md | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f4f95a2..3c4a701 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,45 @@ # sentry dotnet transaction addon ### Unnoficial addon for adding Peformance support to Sentry-dotnet +# Status +Currently in Alpha, not all features were implemented and you may experience errors or lose of Performance events. + Official Docs: https://docs.sentry.io/performance-monitoring/getting-started/ -##### Usage: +# Configuration +To initialize the performance addon you'll need to call SentryTracingSdk.Init ```C# -SentryTracingSDK.Init(dsn, tracesSampleRate); can be called before or after SentrySdk.Init. +SentryTracingSDK.Init(dsn); +``` +Also you will need to attach to your Sentry options the SentryTracingEventProcessor so the addon can consume the Tracing event when it's ready +# Usage +You can start/finish a Transaction by creating an transaction Object or by 'using' +```C# var transaction = SentryTracingSDK.StartTransaction( name );// return a new transaction. var child = transaction.StartChild( name );// return a new child +... code to be measured child.Finish();// finishes the child // You can add as many childs as you wish on a transaction transaction.Finish();// finishes and sends the transaction ``` +```C# +using(var transaction = SentryTracingSDK.StartTransaction( name )) +{ + var child = transaction.StartChild( name );// return a new child + ... code to be measured + child.Finish();// finishes the child + // You can add as many childs as you wish on a transaction +} +``` -##### You'll need to add an additional code inside of your BeforeSend code +You can also start a child anywhere in the code, as long as there's an active Transaction running at the current Thread, else +the child will be discarted ```C# -if (arg is SentryTransaction transaction) +using(var child = SentryTracingSDK.StartChild( url, Post )) { - transaction.SendTransaction(); - return null; +... your http request here + child.Finish(httpstatuscode);// child finished with the current status code } ``` From 4b8ba26d89a591acdbee535eee740a00a962ee60 Mon Sep 17 00:00:00 2001 From: Lucas Zimerman Fraulob Date: Sun, 9 Aug 2020 17:01:12 -0300 Subject: [PATCH 3/7] Added testing functions and fixed some issues found on DisabledTransactions and also with concurrence --- Testing/Helpers/DsnHelper.cs | 17 ++ Testing/StartChildTest.cs | 151 ++++++++++++++++++ Testing/Testing.csproj | 20 +++ sentry-dotnet-transaction-addon.sln | 8 +- .../Extensibility/DisabledTracing.cs | 2 +- .../SentryTracingSDK.cs | 31 +++- 6 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 Testing/Helpers/DsnHelper.cs create mode 100644 Testing/StartChildTest.cs create mode 100644 Testing/Testing.csproj diff --git a/Testing/Helpers/DsnHelper.cs b/Testing/Helpers/DsnHelper.cs new file mode 100644 index 0000000..135d651 --- /dev/null +++ b/Testing/Helpers/DsnHelper.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Testing.Helpers +{ + /// + /// Based on DsnSamples from Sentry.Net + /// + public class DsnHelper + { + /// + /// Sentry has dropped the use of secrets + /// + public const string ValidDsnWithoutSecret = "https://d4d82fc1c2c4032a83f3a29aa3a3aff@fake-sentry.io:65535/2147483647"; + } +} diff --git a/Testing/StartChildTest.cs b/Testing/StartChildTest.cs new file mode 100644 index 0000000..fd93c0a --- /dev/null +++ b/Testing/StartChildTest.cs @@ -0,0 +1,151 @@ +using sentry_dotnet_transaction_addon; +using sentry_dotnet_transaction_addon.Enums; +using sentry_dotnet_transaction_addon.Extensibility; +using sentry_dotnet_transaction_addon.Interface; +using sentry_dotnet_transaction_addon.Internals; +using System.Threading.Tasks; +using Testing.Helpers; +using Xunit; + +namespace Testing +{ + public class StartChildTest + { + public ISpanBase AddSpan(string name) => SentryTracingSdk.StartChild(name); + + [Fact] + public void SentryTracingSdkStartChildConcurrenceFindsCorrectParents() + { + SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); + ISentryTracing a = null, b = null, c = null; + ISpanBase sa = null, sb = null, sc = null; + Task []tasks = new Task[3]; + tasks[0] = new Task(() => + { + a = SentryTracingSdk.StartTransaction("a"); + sa = AddSpan("a"); + sa.Finish(); + }); + tasks[1] = new Task(() => + { + b = SentryTracingSdk.StartTransaction("b"); + sb = AddSpan("b"); + sb.Finish(); + }); + tasks[2] = new Task(() => + { + c = SentryTracingSdk.StartTransaction("c"); + sc = AddSpan("c"); + sc.Finish(); + }); + + tasks[1].Start(); + tasks[0].Start(); + tasks[2].Start(); + + Task.WaitAll(tasks); + + //Check if all Tracings have only one Span + Assert.True(a.Spans.Count.Equals(1)); + Assert.True(b.Spans.Count.Equals(1)); + Assert.True(c.Spans.Count.Equals(1)); + + //Check if all Tracing have the correct Spans + Assert.Equal(sa, a.Spans[0]); + Assert.Equal(sb, b.Spans[0]); + Assert.Equal(sc, c.Spans[0]); + a.Finish(); + b.Finish(); + c.Finish(); + } + + [Fact] + public void SentryTracingSdkStartChildWithoutTransactionReturnDisabledSpan() + { + new Task(() => + { + Assert.IsType(AddSpan("_")); + }).Start(); + } + + [Fact] + public void SentryTracingSdkStartChildCanDispose() + { + SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); + ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); + using (var span = transaction.StartChild("span")) + { + Task.Delay(100).Wait(); + } + Assert.NotEqual(transaction.Spans[0].StartTimestamp, transaction.Spans[0].Timestamp); + transaction.Finish(); + } + + [Fact] + public void SentryTracingSdkStartSubChild() + { + SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); + ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); + using (var span = transaction.StartChild("span")) + { + Task.Delay(100).Wait(); + + using (var subSpan = span.StartChild("subspan")) + { + Task.Delay(100).Wait(); + } + } + Assert.NotEqual(transaction.Spans[0].StartTimestamp, transaction.Spans[0].Timestamp); + Assert.NotEqual(transaction.Spans[1].StartTimestamp, transaction.Spans[1].Timestamp); + Assert.Equal(transaction.Spans[0].SpanId, transaction.Spans[1].ParentSpanId); + transaction.Finish(); + } + + [Fact] + public void SentryTracingSdkStartRequestChildOkStatus() + { + SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); + ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); + using (var span = transaction.StartChild("span")) + { + Task.Delay(100).Wait(); + + using (var subSpan = span.StartChild("subspan", ESpanRequest.Post)) + { + Task.Delay(100).Wait(); + subSpan.Finish(200); + } + } + Assert.NotEqual(transaction.Spans[0].StartTimestamp, transaction.Spans[0].Timestamp); + Assert.NotEqual(transaction.Spans[1].StartTimestamp, transaction.Spans[1].Timestamp); + Assert.Equal(transaction.Spans[0].SpanId, transaction.Spans[1].ParentSpanId); + var statusSpan = transaction.Spans[1] as Span; + Assert.Equal("ok", statusSpan.Status); + transaction.Finish(); + } + + [Fact] + public void SentryTracingSdkStartRequestChildErrorStatus() + { + SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); + ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); + using (var span = transaction.StartChild("span")) + { + Task.Delay(100).Wait(); + + using (var subSpan = span.StartChild("subspan", ESpanRequest.Post)) + { + Task.Delay(100).Wait(); + subSpan.Finish(500); + } + } + Assert.NotEqual(transaction.Spans[0].StartTimestamp, transaction.Spans[0].Timestamp); + Assert.NotEqual(transaction.Spans[1].StartTimestamp, transaction.Spans[1].Timestamp); + Assert.Equal(transaction.Spans[0].SpanId, transaction.Spans[1].ParentSpanId); + var statusSpan = transaction.Spans[1] as Span; + Assert.Equal("internal_error", statusSpan.Status); + transaction.Finish(); + } + + } +} diff --git a/Testing/Testing.csproj b/Testing/Testing.csproj new file mode 100644 index 0000000..cc9dc8e --- /dev/null +++ b/Testing/Testing.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/sentry-dotnet-transaction-addon.sln b/sentry-dotnet-transaction-addon.sln index daf2577..0a237b7 100644 --- a/sentry-dotnet-transaction-addon.sln +++ b/sentry-dotnet-transaction-addon.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30204.135 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sentry-dotnet-transaction-addon", "sentry-dotnet-transaction-addon\sentry-dotnet-transaction-addon.csproj", "{BDA414FB-FACE-4867-BB3D-8B3DE73B79D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sentry-dotnet-transaction-addon", "sentry-dotnet-transaction-addon\sentry-dotnet-transaction-addon.csproj", "{BDA414FB-FACE-4867-BB3D-8B3DE73B79D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testing", "Testing\Testing.csproj", "{9C294F90-9E37-442C-A963-AABFFEE3A66F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {BDA414FB-FACE-4867-BB3D-8B3DE73B79D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {BDA414FB-FACE-4867-BB3D-8B3DE73B79D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {BDA414FB-FACE-4867-BB3D-8B3DE73B79D2}.Release|Any CPU.Build.0 = Release|Any CPU + {9C294F90-9E37-442C-A963-AABFFEE3A66F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C294F90-9E37-442C-A963-AABFFEE3A66F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C294F90-9E37-442C-A963-AABFFEE3A66F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C294F90-9E37-442C-A963-AABFFEE3A66F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs b/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs index 879fc79..1ff78dc 100644 --- a/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs +++ b/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs @@ -22,7 +22,7 @@ public void Finish() { } public ISpanBase GetSpan(string op) => DisabledSpan.Instance; - public ISpanBase StartChild(string description, string op = null) => null; + public ISpanBase StartChild(string description, string op = null) => DisabledSpan.Instance; public ISpanBase StartChild(string url, ESpanRequest requestType) => DisabledSpan.Instance; } diff --git a/sentry-dotnet-transaction-addon/SentryTracingSDK.cs b/sentry-dotnet-transaction-addon/SentryTracingSDK.cs index e101feb..abac761 100644 --- a/sentry-dotnet-transaction-addon/SentryTracingSDK.cs +++ b/sentry-dotnet-transaction-addon/SentryTracingSDK.cs @@ -51,18 +51,27 @@ public static void Close() public static ISentryTracing StartTransaction(string name) { var tracing = new SentryTracing(name); - _transactionStorage.Add(new KeyValuePair(Task.CurrentId, tracing)); + lock (_transactionStorage) + { + _transactionStorage.Add(new KeyValuePair(Task.CurrentId, tracing)); + } return tracing; } public static ISentryTracing RetreiveTransactionById(string id) { - return _transactionStorage.FirstOrDefault(p => p.Value.Trace.TraceId == id).Value ?? (ISentryTracing)DisabledTracing.Instance; + lock (_transactionStorage) + { + return _transactionStorage.FirstOrDefault(p => p.Value.Trace.TraceId == id).Value ?? (ISentryTracing)DisabledTracing.Instance; + } } public static ISentryTracing RetreiveTransactionByName(string name) { - return _transactionStorage.FirstOrDefault(p => p.Value.Transaction == name).Value ?? (ISentryTracing)DisabledTracing.Instance; + lock (_transactionStorage) + { + return _transactionStorage.FirstOrDefault(p => p.Value.Transaction == name).Value ?? (ISentryTracing)DisabledTracing.Instance; + } } /// @@ -71,10 +80,13 @@ public static ISentryTracing RetreiveTransactionByName(string name) /// public static ISentryTracing GetCurrentTransaction() { - if (!IsEnabled() || _transactionStorage.Count() == 0) - return DisabledTracing.Instance; - var keyPair = _transactionStorage.LastOrDefault(p => p.Key == Task.CurrentId); - return keyPair.Value ?? (ISentryTracing)DisabledTracing.Instance; + lock (_transactionStorage) + { + if (!IsEnabled() || _transactionStorage.Count() == 0) + return DisabledTracing.Instance; + var keyPair = _transactionStorage.LastOrDefault(p => p.Key == Task.CurrentId); + return keyPair.Value ?? (ISentryTracing)DisabledTracing.Instance; + } } public static ISpanBase GetCurrentTracingSpan() @@ -106,7 +118,10 @@ public static ISpanBase StartChild(string description, string op = null) internal static void DisposeTracingEvent(SentryTracing tracing) { - _transactionStorage.Remove(_transactionStorage.First(p => p.Value.Equals(tracing))); + lock (_transactionStorage) + { + _transactionStorage.Remove(_transactionStorage.First(p => p.Value.Equals(tracing))); + } } } } From 9144265b9aca37d987d9567ea2732982a3a25ae9 Mon Sep 17 00:00:00 2001 From: Lucas Zimerman Fraulob Date: Tue, 11 Aug 2020 09:21:44 -0300 Subject: [PATCH 4/7] Id was causing issues when removing from the stack --- sentry-dotnet-transaction-addon/SentryTracing.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/sentry-dotnet-transaction-addon/SentryTracing.cs b/sentry-dotnet-transaction-addon/SentryTracing.cs index 5d28241..7447643 100644 --- a/sentry-dotnet-transaction-addon/SentryTracing.cs +++ b/sentry-dotnet-transaction-addon/SentryTracing.cs @@ -24,8 +24,6 @@ public SentryTracing(string name) Spans = new List(); } - public string Id => Guid.NewGuid().ToString(); - public ISpanBase GetSpan(string op) { return Spans.FirstOrDefault(s => s.Op == op); From 5fc70601b990640bcaec742e523e2685d43f7a47 Mon Sep 17 00:00:00 2001 From: LucasZF Date: Sun, 16 Aug 2020 14:43:12 -0300 Subject: [PATCH 5/7] Feat/better thread handling (#1) * -Added IsolatedTracking for isolating the code to be measured. -Added ThreadTracking for Isolating the SentryTracingId. -Reworked how the Sdk is initialized. -More test case * Increased version due to breaking changes * cleaned using files and removed required line --- Testing/Helpers/DsnHelper.cs | 6 +- Testing/Initializer.cs | 19 ++ Testing/Internals/ThreadTrackingTests.cs | 196 ++++++++++++++++++ Testing/StartChildTest.cs | 54 +---- Testing/Testing.csproj | 2 +- Testing/UseCasesTests/SimpleScenarioTests.cs | 85 ++++++++ sentry-dotnet-transaction-addon.sln | 2 +- .../Extensibility/DisabledTracing.cs | 10 + .../Interface/ISentryTracing.cs | 8 +- .../Internals/ThreadTracking.cs | 48 +++++ .../SentryTracing.cs | 13 +- .../SentryTracingEventProcessor.cs | 2 +- .../SentryTracingOptions.cs | 5 +- .../SentryTracingSDK.cs | 51 ++--- .../SentryTracingSdkIntegration.cs | 28 +++ sentry-dotnet-transaction-addon/Trace.cs | 1 + .../sentry-dotnet-transaction-addon.csproj | 1 + 17 files changed, 443 insertions(+), 88 deletions(-) create mode 100644 Testing/Initializer.cs create mode 100644 Testing/Internals/ThreadTrackingTests.cs create mode 100644 Testing/UseCasesTests/SimpleScenarioTests.cs create mode 100644 sentry-dotnet-transaction-addon/Internals/ThreadTracking.cs create mode 100644 sentry-dotnet-transaction-addon/SentryTracingSdkIntegration.cs diff --git a/Testing/Helpers/DsnHelper.cs b/Testing/Helpers/DsnHelper.cs index 135d651..00a7ba3 100644 --- a/Testing/Helpers/DsnHelper.cs +++ b/Testing/Helpers/DsnHelper.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Testing.Helpers +namespace Testing.Helpers { /// /// Based on DsnSamples from Sentry.Net diff --git a/Testing/Initializer.cs b/Testing/Initializer.cs new file mode 100644 index 0000000..6f47d70 --- /dev/null +++ b/Testing/Initializer.cs @@ -0,0 +1,19 @@ +using Sentry; +using sentry_dotnet_transaction_addon; +using Testing.Helpers; + +namespace Testing +{ + public static class Initializer + { + public static void Init() + { + if (!SentryTracingSdk.IsEnabled()) + { + var integration = new SentryTracingSdkIntegration(); + integration.Register(null, new SentryOptions()); + SentryTracingSdk.SetDsn(new Dsn(DsnHelper.ValidDsnWithoutSecret)); + } + } + } +} diff --git a/Testing/Internals/ThreadTrackingTests.cs b/Testing/Internals/ThreadTrackingTests.cs new file mode 100644 index 0000000..36ba304 --- /dev/null +++ b/Testing/Internals/ThreadTrackingTests.cs @@ -0,0 +1,196 @@ +using sentry_dotnet_transaction_addon.Internals; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Testing.Internals +{ + public class ThreadTrackingTests + { + private readonly ITestOutputHelper _testOutputHelper; + public ThreadTrackingTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + private async Task TaskWaiter(int? expectedId, ThreadTracking tracking) + { + await Task.Delay(30); + Assert.True(tracking.Created); + Assert.Equal(expectedId, tracking.Id); + } + + [Fact] + public async Task ThreadTracking_CreateUnique_Numbers() + { + var tracker = new ThreadTracking(); + var numbers = new int[1000]; + for (int i = 0; i < 300; i++) + { + numbers[i] = ThreadTracking.InternalNewId(); + } + var task = new Task(() => + { + new Task(() => + { + for (int i = 300; i < 600; i += 2) + numbers[i] = ThreadTracking.InternalNewId(); + }).Start(); + new Task(() => + { + for (int i = 301; i < 600; i += 2) + numbers[i] = ThreadTracking.InternalNewId(); + }).Start(); + }); + var task2 = new Thread(() => + { + new Thread(() => + { + for (int i = 600; i < 1000; i += 2) + numbers[i] = ThreadTracking.InternalNewId(); + }).Start(); + new Thread(() => + { + for (int i = 601; i < 800; i += 2) + numbers[i] = ThreadTracking.InternalNewId(); + }).Start(); + }); + task.Start(); + task2.Start(); + await Task.Delay(100); + Assert.True(numbers.Distinct().Count() > 0); + } + + + [Fact] + public void ThreadTracking_UnsafeTracking_Not_Created() + { + var tracker = new ThreadTracking(); + var id = tracker.StartUnsafeTrackingId(); + Assert.False(tracker.Created); + Assert.Null(tracker.Id); + } + + + + [Fact] + public async Task ThreadTracking_CreateTrackTask_newTask_ReturnSameId() + { + var tracker = new ThreadTracking(); + await tracker.StartCallbackTrackingIdAsync(async () => + { + Assert.True(tracker.Created); + var idEqual = tracker.Id; + Task task = new Task(async () => + { + await Task.Delay(10); + Assert.True(tracker.Created); + Assert.Equal(idEqual, tracker.Id); + }); + task.Start(); + await task; + + }); + Assert.False(tracker.Created); + } + + [Fact] + public async Task ThreadTracking_CreateTrackTask_Using_UnsafeId() + { + var tracker = new ThreadTracking(); + var unsafeId = tracker.StartUnsafeTrackingId(); + await tracker.StartCallbackTrackingIdAsync(async () => + { + Assert.True(tracker.Created); + var idEqual = tracker.Id; + Task task = new Task(async () => + { + await Task.Delay(10); + Assert.True(tracker.Created); + Assert.Equal(idEqual, tracker.Id); + Assert.Equal(unsafeId, tracker.Id); + }); + task.Start(); + await task; + + }, unsafeId); + Assert.False(tracker.Created); + } + + [Fact] + public void ThreadTracking_Different_ThreadsCallbacks_Async_Return_DifferentIds() + { + var tracker = new ThreadTracking(); + int?[] ids = new int?[3]; + var Semaphores = new Semaphore[3] { new Semaphore(0, 1), new Semaphore(0, 1), new Semaphore(0, 1) }; + new Thread(async () => + { + await tracker.StartCallbackTrackingIdAsync(async () => + { + + ids[0] = tracker.Id; + await TaskWaiter(ids[0], tracker); + Assert.Equal(ids[0], tracker.Id); + Semaphores[0].Release(); + }); + }).Start(); + new Thread(async () => + { + await tracker.StartCallbackTrackingIdAsync(async () => + { + + ids[1] = tracker.Id; + await TaskWaiter(ids[1], tracker); + Assert.Equal(ids[1], tracker.Id); + Semaphores[1].Release(); + }); + }).Start(); + new Thread(async () => + { + await tracker.StartCallbackTrackingIdAsync(async () => + { + + ids[2] = tracker.Id; + await TaskWaiter(ids[2], tracker); + Assert.Equal(ids[2], tracker.Id); + Semaphores[2].Release(); + }); + }).Start(); + Semaphores[0].WaitOne(); + Semaphores[1].WaitOne(); + Semaphores[2].WaitOne(); + + Assert.False(tracker.Created); + Assert.NotNull(ids[0]); + Assert.NotNull(ids[1]); + Assert.NotNull(ids[2]); + Assert.NotEqual(ids[0], ids[1]); + Assert.NotEqual(ids[2], ids[1]); + } + + [Fact] + private async Task ThreadTracking_On_Function_Error() + { + var tracker = new ThreadTracking(); + bool receivedError = false; + try + { + await tracker.StartCallbackTrackingIdAsync(() => + { + throw new Exception("."); + }); + } + catch + { + receivedError = true; + } + finally + { + Assert.True(receivedError); + } + } + } +} diff --git a/Testing/StartChildTest.cs b/Testing/StartChildTest.cs index fd93c0a..b045c27 100644 --- a/Testing/StartChildTest.cs +++ b/Testing/StartChildTest.cs @@ -4,61 +4,21 @@ using sentry_dotnet_transaction_addon.Interface; using sentry_dotnet_transaction_addon.Internals; using System.Threading.Tasks; -using Testing.Helpers; using Xunit; namespace Testing { public class StartChildTest { - public ISpanBase AddSpan(string name) => SentryTracingSdk.StartChild(name); - - [Fact] - public void SentryTracingSdkStartChildConcurrenceFindsCorrectParents() - { - SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); - ISentryTracing a = null, b = null, c = null; - ISpanBase sa = null, sb = null, sc = null; - Task []tasks = new Task[3]; - tasks[0] = new Task(() => - { - a = SentryTracingSdk.StartTransaction("a"); - sa = AddSpan("a"); - sa.Finish(); - }); - tasks[1] = new Task(() => - { - b = SentryTracingSdk.StartTransaction("b"); - sb = AddSpan("b"); - sb.Finish(); - }); - tasks[2] = new Task(() => - { - c = SentryTracingSdk.StartTransaction("c"); - sc = AddSpan("c"); - sc.Finish(); - }); - - tasks[1].Start(); - tasks[0].Start(); - tasks[2].Start(); - - Task.WaitAll(tasks); - //Check if all Tracings have only one Span - Assert.True(a.Spans.Count.Equals(1)); - Assert.True(b.Spans.Count.Equals(1)); - Assert.True(c.Spans.Count.Equals(1)); - //Check if all Tracing have the correct Spans - Assert.Equal(sa, a.Spans[0]); - Assert.Equal(sb, b.Spans[0]); - Assert.Equal(sc, c.Spans[0]); - a.Finish(); - b.Finish(); - c.Finish(); + public StartChildTest() + { + Initializer.Init(); } + public ISpanBase AddSpan(string name) => SentryTracingSdk.StartChild(name); + [Fact] public void SentryTracingSdkStartChildWithoutTransactionReturnDisabledSpan() { @@ -71,7 +31,6 @@ public void SentryTracingSdkStartChildWithoutTransactionReturnDisabledSpan() [Fact] public void SentryTracingSdkStartChildCanDispose() { - SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); using (var span = transaction.StartChild("span")) { @@ -84,7 +43,6 @@ public void SentryTracingSdkStartChildCanDispose() [Fact] public void SentryTracingSdkStartSubChild() { - SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); using (var span = transaction.StartChild("span")) { @@ -104,7 +62,6 @@ public void SentryTracingSdkStartSubChild() [Fact] public void SentryTracingSdkStartRequestChildOkStatus() { - SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); using (var span = transaction.StartChild("span")) { @@ -127,7 +84,6 @@ public void SentryTracingSdkStartRequestChildOkStatus() [Fact] public void SentryTracingSdkStartRequestChildErrorStatus() { - SentryTracingSdk.Init(DsnHelper.ValidDsnWithoutSecret); ISentryTracing transaction = SentryTracingSdk.StartTransaction("tran"); using (var span = transaction.StartChild("span")) { diff --git a/Testing/Testing.csproj b/Testing/Testing.csproj index cc9dc8e..ac2ecbd 100644 --- a/Testing/Testing.csproj +++ b/Testing/Testing.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/Testing/UseCasesTests/SimpleScenarioTests.cs b/Testing/UseCasesTests/SimpleScenarioTests.cs new file mode 100644 index 0000000..305fd5e --- /dev/null +++ b/Testing/UseCasesTests/SimpleScenarioTests.cs @@ -0,0 +1,85 @@ +using sentry_dotnet_transaction_addon; +using sentry_dotnet_transaction_addon.Enums; +using sentry_dotnet_transaction_addon.Internals; +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Testing.UseCasesTests +{ + public class SimpleScenarioTests + { + public SimpleScenarioTests() + { + Initializer.Init(); + } + + private async Task GetRequest(int fixedDelay, string url) + { + using (SentryTracingSdk.StartChild(url, ESpanRequest.Get)) + { + var random = new Random(); + await Task.Delay(fixedDelay); + } + } + + [Fact] + public async Task ScenarioFinishPaymentTests() + { + ISentryTracing _tCreditCardValid; + ISentryTracing _tStorageRequest; + ISentryTracing _tAnalytics; + ISentryTracing _tFrontEnd; + var creditCardValidUrl = "https://validcard.com"; + var storageUrl = "https://storage.com"; + var analyticsUrl = "https://analytics.com"; + var frontUrl = "https://front.com"; + + using (_tCreditCardValid = SentryTracingSdk.StartTransaction("CreditCardValidation")) + { + await _tCreditCardValid.IsolateTracking(async () => + { + await Task.Delay(55); + await GetRequest(500, creditCardValidUrl); + }); + } + //lets assume it's a valid credit card + using (_tStorageRequest = SentryTracingSdk.StartTransaction("Storage Request")) + { + await _tStorageRequest.IsolateTracking(async () => + { + await Task.Delay(50); + await GetRequest(1500, storageUrl); + }); + } + + var tasks = new Task[2]; + + _tAnalytics = SentryTracingSdk.StartTransaction("analytics"); + tasks[0] = _tAnalytics.IsolateTracking(async () => + { + await Task.Delay(50); + _ = GetRequest(60, analyticsUrl); + }); + await Task.Delay(50); + + _tFrontEnd = SentryTracingSdk.StartTransaction("front"); + tasks[1] = _tFrontEnd.IsolateTracking(async () => + { + await Task.Delay(50); + _ = GetRequest(30, frontUrl); + }); + + Task.WaitAll(tasks); + + _tAnalytics.Finish(); + _tFrontEnd.Finish(); + + Assert.NotNull(_tCreditCardValid.Spans.FirstOrDefault(p => p.Description.Contains(creditCardValidUrl))); + Assert.NotNull(_tStorageRequest.Spans.FirstOrDefault(p => p.Description.Contains(storageUrl))); + Assert.NotNull(_tAnalytics.Spans.FirstOrDefault(p => p.Description.Contains(analyticsUrl))); + Assert.NotNull(_tFrontEnd.Spans.FirstOrDefault(p => p.Description.Contains(frontUrl))); + } + } +} diff --git a/sentry-dotnet-transaction-addon.sln b/sentry-dotnet-transaction-addon.sln index 0a237b7..6483ddd 100644 --- a/sentry-dotnet-transaction-addon.sln +++ b/sentry-dotnet-transaction-addon.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 16.0.30204.135 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sentry-dotnet-transaction-addon", "sentry-dotnet-transaction-addon\sentry-dotnet-transaction-addon.csproj", "{BDA414FB-FACE-4867-BB3D-8B3DE73B79D2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testing", "Testing\Testing.csproj", "{9C294F90-9E37-442C-A963-AABFFEE3A66F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testing", "Testing\Testing.csproj", "{9C294F90-9E37-442C-A963-AABFFEE3A66F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs b/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs index 1ff78dc..bc2fb8a 100644 --- a/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs +++ b/sentry-dotnet-transaction-addon/Extensibility/DisabledTracing.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Text; +using System.Threading.Tasks; namespace sentry_dotnet_transaction_addon.Extensibility { @@ -25,5 +26,14 @@ public void Finish() { } public ISpanBase StartChild(string description, string op = null) => DisabledSpan.Instance; public ISpanBase StartChild(string url, ESpanRequest requestType) => DisabledSpan.Instance; + + /// + /// Despite being disabled we must execute the user code + /// + /// A task where the user code is running + public Task IsolateTracking(Func trackedCode) + { + return trackedCode.Invoke(); + } } } diff --git a/sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs b/sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs index 2e4e76a..175e509 100644 --- a/sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs +++ b/sentry-dotnet-transaction-addon/Interface/ISentryTracing.cs @@ -2,6 +2,7 @@ using sentry_dotnet_transaction_addon.Interface; using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace sentry_dotnet_transaction_addon.Internals { @@ -10,13 +11,18 @@ public interface ISentryTracing : IDisposable List Spans { get; } DateTimeOffset StartTimestamp { get; } - ISpanBase GetSpan(string op); ISpanBase GetCurrentSpan(); ISpanBase StartChild(string description, string op = null); ISpanBase StartChild(string url, ESpanRequest requestType); + + /// + /// Invoke the user code on an isolated environment so that you can interact with Tracing from anywhere. + /// + /// A task where the user code is running. + Task IsolateTracking(Func trackedCode); void Finish(); } } diff --git a/sentry-dotnet-transaction-addon/Internals/ThreadTracking.cs b/sentry-dotnet-transaction-addon/Internals/ThreadTracking.cs new file mode 100644 index 0000000..6b4dd6c --- /dev/null +++ b/sentry-dotnet-transaction-addon/Internals/ThreadTracking.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace sentry_dotnet_transaction_addon.Internals +{ + /// + /// Used for attaching an unique Id for each Callback + /// + public class ThreadTracking + { + + private AsyncLocal _tracingIds = new AsyncLocal(); + + internal static object @lock = new object(); + internal static int _value; + public static int InternalNewId() + { + lock (@lock) + { + int i = _value++; + return i; + } + } + + public async Task StartCallbackTrackingIdAsync(Func test, int? unsafeId = null) + { + if(_tracingIds.Value != null) + { + return; + } + + _tracingIds.Value = unsafeId ?? InternalNewId(); + await test().ConfigureAwait(false); + _tracingIds.Value = null; + } + + public bool Created => _tracingIds.Value != null; + + public int StartUnsafeTrackingId() + { + return InternalNewId(); + } + + public int? Id => _tracingIds.Value; + + } +} diff --git a/sentry-dotnet-transaction-addon/SentryTracing.cs b/sentry-dotnet-transaction-addon/SentryTracing.cs index 7447643..96b6063 100644 --- a/sentry-dotnet-transaction-addon/SentryTracing.cs +++ b/sentry-dotnet-transaction-addon/SentryTracing.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace sentry_dotnet_transaction_addon { @@ -13,14 +14,17 @@ public class SentryTracing : ISentryTracing { public List Spans { get; private set; } public DateTimeOffset StartTimestamp { get; private set; } + public int TrackerId { get; private set; } + internal Trace Trace { get; private set; } public string Transaction { get; private set; } - public SentryTracing(string name) + public SentryTracing(string name, int trackerId) { Trace = new Trace(); Transaction = name; StartTimestamp = DateTimeOffset.Now; + TrackerId = trackerId; Spans = new List(); } @@ -44,7 +48,7 @@ public ISpanBase StartChild(string description, string op = null) public ISpanBase StartChild(string url, ESpanRequest requestType) { var span = new Span(Trace.TraceId, Trace.SpanId, url, requestType); - span.Spans = Spans; + span.GetParentSpans(Spans); Spans.Add(span); return span; } @@ -70,5 +74,10 @@ public void Dispose() { Finish(); } + + public Task IsolateTracking(Func trackedCode) + { + return SentryTracingSdk.Tracker.StartCallbackTrackingIdAsync(trackedCode, TrackerId); + } } } diff --git a/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs b/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs index 9193046..820870b 100644 --- a/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs +++ b/sentry-dotnet-transaction-addon/SentryTracingEventProcessor.cs @@ -3,7 +3,7 @@ namespace sentry_dotnet_transaction_addon { - public class SentryTracingEventProcessor : ISentryEventProcessor + internal class SentryTracingEventProcessor : ISentryEventProcessor { public SentryEvent Process(SentryEvent @event) { diff --git a/sentry-dotnet-transaction-addon/SentryTracingOptions.cs b/sentry-dotnet-transaction-addon/SentryTracingOptions.cs index fc14764..56f2b66 100644 --- a/sentry-dotnet-transaction-addon/SentryTracingOptions.cs +++ b/sentry-dotnet-transaction-addon/SentryTracingOptions.cs @@ -4,14 +4,13 @@ namespace sentry_dotnet_transaction_addon { public class SentryTracingOptions { - public SentryTracingOptions(Dsn dsn, double tracesSampleRate = 1.0, bool registerTracingBreadcrumb = true) + public SentryTracingOptions(double tracesSampleRate = 1.0, bool registerTracingBreadcrumb = true) { - Dsn = dsn; TracesSampleRate = tracesSampleRate; RegisterTracingBreadcrmub = registerTracingBreadcrumb; } - public Dsn Dsn { get; set; } + internal Dsn Dsn { get; set; } /// /// the rate of sending events where /// 1.0 you always send a performance Event. diff --git a/sentry-dotnet-transaction-addon/SentryTracingSDK.cs b/sentry-dotnet-transaction-addon/SentryTracingSDK.cs index abac761..6bf9b5f 100644 --- a/sentry-dotnet-transaction-addon/SentryTracingSDK.cs +++ b/sentry-dotnet-transaction-addon/SentryTracingSDK.cs @@ -3,8 +3,10 @@ using sentry_dotnet_transaction_addon.Extensibility; using sentry_dotnet_transaction_addon.Interface; using sentry_dotnet_transaction_addon.Internals; +using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace sentry_dotnet_transaction_addon @@ -12,50 +14,37 @@ namespace sentry_dotnet_transaction_addon public static class SentryTracingSdk { internal static List> _transactionStorage = new List>(); + internal static ThreadTracking Tracker; internal static SentryTracingOptions TracingOptions { get; set; } /// - /// Initialize the Tracing Sdk. - /// - public static void Init(string dsn) - { - Init(new SentryTracingOptions(new Dsn(dsn))); - } - - /// - /// Initialize the Tracing Sdk. + /// Change the Dsn for SentryTracingSdk, it's optional since the Dsn is taken from SentrySdk. /// - public static void Init(Dsn dsn) + public static void SetDsn(Dsn dsn) { - Init(new SentryTracingOptions(dsn)); + if (IsEnabled()) + { + TracingOptions.Dsn = dsn; + } } /// /// Initialize the Tracing Sdk. /// - public static void Init(SentryTracingOptions options) + internal static void Init(SentryTracingOptions options) { TracingOptions = options; + Tracker = new ThreadTracking(); } public static bool IsEnabled() => TracingOptions != null; public static void Close() { - _transactionStorage.Clear(); _transactionStorage = null; TracingOptions = null; - } - - public static ISentryTracing StartTransaction(string name) - { - var tracing = new SentryTracing(name); - lock (_transactionStorage) - { - _transactionStorage.Add(new KeyValuePair(Task.CurrentId, tracing)); - } - return tracing; + Tracker = null; } public static ISentryTracing RetreiveTransactionById(string id) @@ -82,9 +71,10 @@ public static ISentryTracing GetCurrentTransaction() { lock (_transactionStorage) { - if (!IsEnabled() || _transactionStorage.Count() == 0) + if (!IsEnabled() || _transactionStorage.Count() == 0 || !Tracker.Created) return DisabledTracing.Instance; - var keyPair = _transactionStorage.LastOrDefault(p => p.Key == Task.CurrentId); + var id = Tracker.Id; + var keyPair = _transactionStorage.LastOrDefault(p => p.Key == id); return keyPair.Value ?? (ISentryTracing)DisabledTracing.Instance; } } @@ -94,6 +84,17 @@ public static ISpanBase GetCurrentTracingSpan() return GetCurrentTransaction().GetCurrentSpan(); } + public static ISentryTracing StartTransaction(string name) + { + var id = Tracker.StartUnsafeTrackingId(); + var tracing = new SentryTracing(name, id); + lock (_transactionStorage) + { + _transactionStorage.Add(new KeyValuePair(id, tracing)); + } + return tracing; + } + public static ISpanBase StartChild(string url, ESpanRequest requestType) { var transaction = GetCurrentTransaction(); diff --git a/sentry-dotnet-transaction-addon/SentryTracingSdkIntegration.cs b/sentry-dotnet-transaction-addon/SentryTracingSdkIntegration.cs new file mode 100644 index 0000000..c4196eb --- /dev/null +++ b/sentry-dotnet-transaction-addon/SentryTracingSdkIntegration.cs @@ -0,0 +1,28 @@ +using Sentry; +using Sentry.Integrations; + + +namespace sentry_dotnet_transaction_addon +{ + public class SentryTracingSdkIntegration : ISdkIntegration + { + internal SentryTracingOptions _options; + public SentryTracingSdkIntegration() + { + _options = new SentryTracingOptions(); + } + + public SentryTracingSdkIntegration(SentryTracingOptions options) + { + _options = options; + } + + public void Register(IHub hub, SentryOptions options) + { + _options.Dsn = options.Dsn; + SentryTracingSdk.Init(_options); + _options = null; + options.AddEventProcessor(new SentryTracingEventProcessor()); + } + } +} diff --git a/sentry-dotnet-transaction-addon/Trace.cs b/sentry-dotnet-transaction-addon/Trace.cs index aa92207..162210c 100644 --- a/sentry-dotnet-transaction-addon/Trace.cs +++ b/sentry-dotnet-transaction-addon/Trace.cs @@ -16,6 +16,7 @@ internal class Trace [JsonProperty("trace_id")] public string TraceId { get; private set; } + #endregion public Trace() { diff --git a/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj b/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj index 114a6bc..98fd663 100644 --- a/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj +++ b/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj @@ -3,6 +3,7 @@ netstandard2.0 sentry_dotnet_transaction_addon + 2.0.0 From 9ca5b65d0f6ad56f2a3c81f0daae0d78481ee5c4 Mon Sep 17 00:00:00 2001 From: LucasZF Date: Sun, 16 Aug 2020 14:58:10 -0300 Subject: [PATCH 6/7] Update README.md Updated use for 2.0 Sdk --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3c4a701..d0ee60b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# sentry dotnet transaction addon +# sentry dotnet transaction addon V2.0.0 ### Unnoficial addon for adding Peformance support to Sentry-dotnet # Status @@ -8,11 +8,11 @@ Official Docs: https://docs.sentry.io/performance-monitoring/getting-started/ # Configuration -To initialize the performance addon you'll need to call SentryTracingSdk.Init +To initialize the performance addon you'll need add the SentryTracingSdkIntegration to your SentryOptions integration. ```C# -SentryTracingSDK.Init(dsn); +sentryOptions.AddIntegration(new SentryTracingSdkIntegration()); ``` -Also you will need to attach to your Sentry options the SentryTracingEventProcessor so the addon can consume the Tracing event when it's ready +you'll of course need to initialize SentrySdk giving passing the SentryOptions where you added the Integration. # Usage You can start/finish a Transaction by creating an transaction Object or by 'using' @@ -34,7 +34,7 @@ using(var transaction = SentryTracingSDK.StartTransaction( name )) } ``` -You can also start a child anywhere in the code, as long as there's an active Transaction running at the current Thread, else +You can also start a child anywhere in the code, as long as there's an active Isolated Transaction, else the child will be discarted ```C# using(var child = SentryTracingSDK.StartChild( url, Post )) @@ -43,3 +43,13 @@ using(var child = SentryTracingSDK.StartChild( url, Post )) child.Finish(httpstatuscode);// child finished with the current status code } ``` + +To isolate a Transaction if you would like to start a child by not referencing the Tracing object you'll need to run the following code +```C# +var transaction = SentryTracingSDK.StartTransaction( name ); +await transaction.IsolateTracking(async ()=>{ + // your code here +}); +transaction.Finish(); +``` +That way, if the code SentryTracingSDK.StartChild is called and the stack trace is inside of an isolated Transaction block, the Span will be attached to the Isolated Transaction. From d84656c1df5769a49d2b588ca661083520d0790a Mon Sep 17 00:00:00 2001 From: LucasZF Date: Thu, 3 Sep 2020 09:26:05 -0300 Subject: [PATCH 7/7] Feat/session addon (#2) Added Session Sdk Update README.md --- .assets/nugget-logo.png | Bin 0 -> 18653 bytes README.md | 57 +--- SessionSdk.Test/Helpers/DsnHelper.cs | 13 + SessionSdk.Test/Initializer.cs | 23 ++ SessionSdk.Test/SessionSdk.Test.csproj | 21 ++ SessionSdk.Test/SessionTest.cs | 80 ++++++ .../Enums/ESentryType.cs | 11 + .../Enums/SessionState.cs | 10 + .../Extensibility/DisabledSession.cs | 36 +++ .../Extensions/DateTimeExtensions.cs | 21 ++ .../Extensions/DsnExtensions.cs | 12 + .../Extensions/ESentryTypeExtensions.cs | 23 ++ .../Extensions/SessionStateExtensions.cs | 20 ++ sentry-dotnet-health-addon/ISession.cs | 21 ++ .../Internals/DistinctiveId.cs | 38 +++ .../Internals/ISessionContainer.cs | 12 + .../Internals/SentryEnvelope.cs | 40 +++ .../Internals/SentryEnvelopeHeader.cs | 29 ++ .../Internals/SentryEnvelopeItem.cs | 30 +++ .../Internals/SentryItemType.cs | 20 ++ .../Internals/Session.cs | 248 ++++++++++++++++++ .../Internals/SessionContainerAsyncLocal.cs | 23 ++ .../Internals/SessionContainerGlobal.cs | 23 ++ .../SentryContrib.SessionSdk.csproj | 28 ++ .../SentryHealthEventProcessor.cs | 30 +++ .../SentrySessionOptions.cs | 21 ++ .../SentrySessionSdk.cs | 82 ++++++ .../SentrySessionSdkIntegration.cs | 29 ++ sentry-dotnet-health-addon/Serializer.cs | 62 +++++ .../SessionAttributes.cs | 31 +++ .../Transport/HttpTransport.cs | 24 ++ sentry-dotnet-transaction-addon.sln | 13 +- .../Enums/ESentryType.cs | 15 ++ .../sentry-dotnet-transaction-addon.csproj | 2 +- 34 files changed, 1092 insertions(+), 56 deletions(-) create mode 100644 .assets/nugget-logo.png create mode 100644 SessionSdk.Test/Helpers/DsnHelper.cs create mode 100644 SessionSdk.Test/Initializer.cs create mode 100644 SessionSdk.Test/SessionSdk.Test.csproj create mode 100644 SessionSdk.Test/SessionTest.cs create mode 100644 sentry-dotnet-health-addon/Enums/ESentryType.cs create mode 100644 sentry-dotnet-health-addon/Enums/SessionState.cs create mode 100644 sentry-dotnet-health-addon/Extensibility/DisabledSession.cs create mode 100644 sentry-dotnet-health-addon/Extensions/DateTimeExtensions.cs create mode 100644 sentry-dotnet-health-addon/Extensions/DsnExtensions.cs create mode 100644 sentry-dotnet-health-addon/Extensions/ESentryTypeExtensions.cs create mode 100644 sentry-dotnet-health-addon/Extensions/SessionStateExtensions.cs create mode 100644 sentry-dotnet-health-addon/ISession.cs create mode 100644 sentry-dotnet-health-addon/Internals/DistinctiveId.cs create mode 100644 sentry-dotnet-health-addon/Internals/ISessionContainer.cs create mode 100644 sentry-dotnet-health-addon/Internals/SentryEnvelope.cs create mode 100644 sentry-dotnet-health-addon/Internals/SentryEnvelopeHeader.cs create mode 100644 sentry-dotnet-health-addon/Internals/SentryEnvelopeItem.cs create mode 100644 sentry-dotnet-health-addon/Internals/SentryItemType.cs create mode 100644 sentry-dotnet-health-addon/Internals/Session.cs create mode 100644 sentry-dotnet-health-addon/Internals/SessionContainerAsyncLocal.cs create mode 100644 sentry-dotnet-health-addon/Internals/SessionContainerGlobal.cs create mode 100644 sentry-dotnet-health-addon/SentryContrib.SessionSdk.csproj create mode 100644 sentry-dotnet-health-addon/SentryHealthEventProcessor.cs create mode 100644 sentry-dotnet-health-addon/SentrySessionOptions.cs create mode 100644 sentry-dotnet-health-addon/SentrySessionSdk.cs create mode 100644 sentry-dotnet-health-addon/SentrySessionSdkIntegration.cs create mode 100644 sentry-dotnet-health-addon/Serializer.cs create mode 100644 sentry-dotnet-health-addon/SessionAttributes.cs create mode 100644 sentry-dotnet-health-addon/Transport/HttpTransport.cs create mode 100644 sentry-dotnet-transaction-addon/Enums/ESentryType.cs diff --git a/.assets/nugget-logo.png b/.assets/nugget-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e9121955989440dd945acccc395a5d9dcc36a7df GIT binary patch literal 18653 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

QSS??IX|=m%vUc0zqjB=oN}}hh7z3qM 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 AhX4Qo literal 0 HcmV?d00001 diff --git a/README.md b/README.md index d0ee60b..46b45b6 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,4 @@ -# sentry dotnet transaction addon V2.0.0 -### Unnoficial addon for adding Peformance support to Sentry-dotnet +# SentryContrib +### Unnoficial addons for Sentry.Net SDK -# Status -Currently in Alpha, not all features were implemented and you may experience errors or lose of Performance events. - -Official Docs: https://docs.sentry.io/performance-monitoring/getting-started/ - -# Configuration - -To initialize the performance addon you'll need add the SentryTracingSdkIntegration to your SentryOptions integration. -```C# -sentryOptions.AddIntegration(new SentryTracingSdkIntegration()); -``` -you'll of course need to initialize SentrySdk giving passing the SentryOptions where you added the Integration. - -# Usage -You can start/finish a Transaction by creating an transaction Object or by 'using' -```C# -var transaction = SentryTracingSDK.StartTransaction( name );// return a new transaction. -var child = transaction.StartChild( name );// return a new child -... code to be measured -child.Finish();// finishes the child -// You can add as many childs as you wish on a transaction -transaction.Finish();// finishes and sends the transaction -``` -```C# -using(var transaction = SentryTracingSDK.StartTransaction( name )) -{ - var child = transaction.StartChild( name );// return a new child - ... code to be measured - child.Finish();// finishes the child - // You can add as many childs as you wish on a transaction -} -``` - -You can also start a child anywhere in the code, as long as there's an active Isolated Transaction, else -the child will be discarted -```C# -using(var child = SentryTracingSDK.StartChild( url, Post )) -{ -... your http request here - child.Finish(httpstatuscode);// child finished with the current status code -} -``` - -To isolate a Transaction if you would like to start a child by not referencing the Tracing object you'll need to run the following code -```C# -var transaction = SentryTracingSDK.StartTransaction( name ); -await transaction.IsolateTracking(async ()=>{ - // your code here -}); -transaction.Finish(); -``` -That way, if the code SentryTracingSDK.StartChild is called and the stack trace is inside of an isolated Transaction block, the Span will be attached to the Isolated Transaction. +For more information check the Wiki \ No newline at end of file diff --git a/SessionSdk.Test/Helpers/DsnHelper.cs b/SessionSdk.Test/Helpers/DsnHelper.cs new file mode 100644 index 0000000..0fa6b47 --- /dev/null +++ b/SessionSdk.Test/Helpers/DsnHelper.cs @@ -0,0 +1,13 @@ +namespace SessionSdk.Helpers +{ + ///

+ /// Based on DsnSamples from Sentry.Net + /// + public class DsnHelper + { + /// + /// Sentry has dropped the use of secrets + /// + public const string ValidDsnWithoutSecret = "https://d4d82fc1c2c4032a83f3a29aa3a3aff@fake-sentry.io:65535/2147483647"; + } +} diff --git a/SessionSdk.Test/Initializer.cs b/SessionSdk.Test/Initializer.cs new file mode 100644 index 0000000..b80fb1b --- /dev/null +++ b/SessionSdk.Test/Initializer.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Sentry; +using sentry_dotnet_health_addon; +using SessionSdk.Helpers; + +namespace SessionSdk.Test +{ + public static class Initializer + { + public const string TestRelease = "--1"; + public const string TestEnvironment = "test"; + public static void Init() + { + if (!SentrySessionSdk.IsEnabled) + { + var integration = new SentrySessionSdkIntegration(new SentrySessionOptions() { GlobalHubMode = true }); + integration.Register(null, new SentryOptions() { Release = TestRelease, Environment = TestEnvironment, Dsn = new Dsn(DsnHelper.ValidDsnWithoutSecret) }); + } + } + } +} \ No newline at end of file diff --git a/SessionSdk.Test/SessionSdk.Test.csproj b/SessionSdk.Test/SessionSdk.Test.csproj new file mode 100644 index 0000000..c790d90 --- /dev/null +++ b/SessionSdk.Test/SessionSdk.Test.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + diff --git a/SessionSdk.Test/SessionTest.cs b/SessionSdk.Test/SessionTest.cs new file mode 100644 index 0000000..c420902 --- /dev/null +++ b/SessionSdk.Test/SessionTest.cs @@ -0,0 +1,80 @@ +using Moq; +using Sentry.Protocol; +using sentry_dotnet_health_addon; +using sentry_dotnet_health_addon.Enums; +using sentry_dotnet_health_addon.Internals; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace SessionSdk.Test +{ + public class SessionTest + { + [Fact] + public void Session_Start_Should_Set_Default_Parameters() + { + var user = new User() + { + Id = "123", + IpAddress = "127.0.0.1" + }; + var session = CreateSession(user); + Assert.Equal(user.IpAddress,session.Attributes.IpAddress); + Assert.Equal(Initializer.TestRelease, session.Attributes.Release); + Assert.Equal(Initializer.TestEnvironment, session.Attributes.Environment); + Assert.Null(session.DistinctId); + Assert.NotNull(session.Init); + Assert.Equal(SessionState.Ok, session.Status); + Assert.NotNull(session.SessionId); + } + + [Fact] + public async Task Session_End_Status_Is_Exited_And_Timestamp_Higher_Than_Start() + { + var user = new User() + { + Id = "123", + IpAddress = "127.0.0.1" + }; + var session = CreateSession(user); + await Task.Delay(10); + session.End(); + Assert.Equal(SessionState.Exited, session.Status); + Assert.True(session.Timestamp > session.Started); + } + + [Fact] + private void Session_Crashed_When_Ended_Has_Status_Crashed() + { + var user = new User() + { + Id = "123", + IpAddress = "127.0.0.1" + }; + var session = CreateSession(user); + session.Status = SessionState.Crashed; + session.End(null); + Assert.Equal(SessionState.Crashed, session.Status); + } + + [Fact] + private void Session_End_With_TimeStamp_Has_Timestamp() + { + var user = new User() + { + Id = "123", + IpAddress = "127.0.0.1" + }; + var session = CreateSession(user); + var date = DateTime.Now.AddSeconds(5); + session.End(date); + Assert.Equal(date, session.Timestamp); + } + + private Session CreateSession(User user) + { + return new Session(null, user, Initializer.TestEnvironment, Initializer.TestRelease); + } + } +} diff --git a/sentry-dotnet-health-addon/Enums/ESentryType.cs b/sentry-dotnet-health-addon/Enums/ESentryType.cs new file mode 100644 index 0000000..abf7fbe --- /dev/null +++ b/sentry-dotnet-health-addon/Enums/ESentryType.cs @@ -0,0 +1,11 @@ +namespace sentry_dotnet_health_addon.Enums +{ + public enum ESentryType + { + Session, + Event, + Attachment, + Transaction, + Unknown + } +} diff --git a/sentry-dotnet-health-addon/Enums/SessionState.cs b/sentry-dotnet-health-addon/Enums/SessionState.cs new file mode 100644 index 0000000..6c06e73 --- /dev/null +++ b/sentry-dotnet-health-addon/Enums/SessionState.cs @@ -0,0 +1,10 @@ +namespace sentry_dotnet_health_addon.Enums +{ + public enum SessionState + { + Ok, + Exited, + Crashed, + Abnormal // not currently used in this SDK. + } +} diff --git a/sentry-dotnet-health-addon/Extensibility/DisabledSession.cs b/sentry-dotnet-health-addon/Extensibility/DisabledSession.cs new file mode 100644 index 0000000..dfa08ac --- /dev/null +++ b/sentry-dotnet-health-addon/Extensibility/DisabledSession.cs @@ -0,0 +1,36 @@ +using sentry_dotnet_health_addon.Enums; +using System; + +namespace sentry_dotnet_health_addon.Extensibility +{ + public class DisabledSession : ISession + { + public static DisabledSession Instance = new DisabledSession(); + public DateTime? Started { get; private set; } + + public DateTime? Timestamp { get; private set; } + + public int? ErrorCount { get; private set; } + + public string DistinctId { get; private set; } + + public Guid SessionId { get; private set; } + + public bool? Init { get; private set; } + + public SessionState Status { get; set; } + + public long? Sequence { get; private set; } + + public long? Duration { get; private set; } + + public SessionAttributes Attributes { get; private set; } + + public void End(DateTime? timestamp) {} + + public void RegisterError() {} + + public DisabledSession() {} + + } +} diff --git a/sentry-dotnet-health-addon/Extensions/DateTimeExtensions.cs b/sentry-dotnet-health-addon/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000..ef8dfda --- /dev/null +++ b/sentry-dotnet-health-addon/Extensions/DateTimeExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace sentry_dotnet_health_addon.Extensions +{ + internal static class DateTimeExtensions + { + private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + internal static double TotalUtcSeconds(this DateTime date) + { + return (date - _epoch).TotalSeconds; + } + + internal static double TotalUtcMiliseconds(this DateTime date) + { + return (date - _epoch).TotalMilliseconds; + } + } +} diff --git a/sentry-dotnet-health-addon/Extensions/DsnExtensions.cs b/sentry-dotnet-health-addon/Extensions/DsnExtensions.cs new file mode 100644 index 0000000..17a48fc --- /dev/null +++ b/sentry-dotnet-health-addon/Extensions/DsnExtensions.cs @@ -0,0 +1,12 @@ +using Sentry; + +namespace sentry_dotnet_health_addon.Extensions +{ + internal static class DsnExtensions + { + internal static string GetTracingUrl(this Dsn dsn) + { + return $"{dsn.SentryUri.Scheme}://{dsn.SentryUri.Host}/api/{dsn.ProjectId}/envelope/?sentry_key={dsn.PublicKey}&sentry_version=7"; + } + } +} diff --git a/sentry-dotnet-health-addon/Extensions/ESentryTypeExtensions.cs b/sentry-dotnet-health-addon/Extensions/ESentryTypeExtensions.cs new file mode 100644 index 0000000..af6fa8a --- /dev/null +++ b/sentry-dotnet-health-addon/Extensions/ESentryTypeExtensions.cs @@ -0,0 +1,23 @@ +using sentry_dotnet_health_addon.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace sentry_dotnet_health_addon.Extensions +{ + internal static class ESentryTypeExtensions + { + public static string ConvertString(this ESentryType type) + { + if (type == ESentryType.Attachment) + return "attachment"; + if (type == ESentryType.Event) + return "event"; + if (type == ESentryType.Session) + return "session"; + if (type == ESentryType.Transaction) + return "transaction"; + return "unknown"; + } + } +} diff --git a/sentry-dotnet-health-addon/Extensions/SessionStateExtensions.cs b/sentry-dotnet-health-addon/Extensions/SessionStateExtensions.cs new file mode 100644 index 0000000..72b5a09 --- /dev/null +++ b/sentry-dotnet-health-addon/Extensions/SessionStateExtensions.cs @@ -0,0 +1,20 @@ +using sentry_dotnet_health_addon.Enums; + +namespace sentry_dotnet_health_addon.Extensions +{ + public static class SessionStateExtensions + { + public static string ConvertString(this SessionState state) + { + if (state == SessionState.Ok) + return "ok"; + if (state == SessionState.Exited) + return "exited"; + if (state == SessionState.Crashed) + return "crashed"; + if( state == SessionState.Abnormal) + return "abnormal"; + return null; + } + } +} diff --git a/sentry-dotnet-health-addon/ISession.cs b/sentry-dotnet-health-addon/ISession.cs new file mode 100644 index 0000000..a7b1ab2 --- /dev/null +++ b/sentry-dotnet-health-addon/ISession.cs @@ -0,0 +1,21 @@ +using sentry_dotnet_health_addon.Enums; +using System; + +namespace sentry_dotnet_health_addon +{ + public interface ISession + { + DateTime? Started { get; } + DateTime? Timestamp { get; } + int? ErrorCount { get; } + string DistinctId { get; } + Guid SessionId { get; } + bool? Init { get; } + SessionState Status { get; set; } + long? Sequence { get; } + long? Duration { get; } + SessionAttributes Attributes { get; } + void End(DateTime? timestamp); + void RegisterError(); + } +} diff --git a/sentry-dotnet-health-addon/Internals/DistinctiveId.cs b/sentry-dotnet-health-addon/Internals/DistinctiveId.cs new file mode 100644 index 0000000..fd3695e --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/DistinctiveId.cs @@ -0,0 +1,38 @@ +using Sentry.Protocol; +using System; +using System.Security.Cryptography; +using System.Text; + +namespace sentry_dotnet_health_addon.Internals +{ + internal class DistinctiveId + { + internal string GetDistinctiveId(string identifier) + { + return HashString(identifier); + } + internal string GetDistinctiveId(User user) + { + if(user?.Id != null) + return HashString(user.Id); + if (user?.Email != null) + return HashString(user.Email); + if (user?.Username != null) + return HashString(user.Username); + return null; + } + + private string HashString(string @string) + { + byte[] bytes = Encoding.UTF8.GetBytes(@string); + SHA256Managed hashstring = new SHA256Managed(); + byte[] hash = hashstring.ComputeHash(bytes); + string hashString = string.Empty; + foreach (byte x in hash) + { + hashString += String.Format("{0:x2}", x); + } + return hashString; + } + } +} diff --git a/sentry-dotnet-health-addon/Internals/ISessionContainer.cs b/sentry-dotnet-health-addon/Internals/ISessionContainer.cs new file mode 100644 index 0000000..f9246b0 --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/ISessionContainer.cs @@ -0,0 +1,12 @@ + +namespace sentry_dotnet_health_addon.Internals +{ + internal interface ISessionContainer + { + Session GetCurrent(); + + void CreateNewSession(Session session); + + void Clear(); + } +} diff --git a/sentry-dotnet-health-addon/Internals/SentryEnvelope.cs b/sentry-dotnet-health-addon/Internals/SentryEnvelope.cs new file mode 100644 index 0000000..1b17fea --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/SentryEnvelope.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using Sentry.Protocol; +using System.Collections.Generic; + +namespace sentry_dotnet_health_addon.Internals +{ + + public class SentryEnvelope + { + [JsonExtensionData] + public SentryEnvelopeHeader Header { get; private set; } + [JsonExtensionData] + public List Items { get; private set; } + + public SentryEnvelope(SentryEnvelopeHeader header, List items) + { + Header = header; + Items = items; + } + + public SentryEnvelope(SentryId eventId, SdkVersion sdkVersion, + List items) + { + Header = new SentryEnvelopeHeader(eventId, sdkVersion); + Items = items; + } + public SentryEnvelope(SentryId eventId, SdkVersion sdkVersion, + SentryEnvelopeItem item) + { + Header = new SentryEnvelopeHeader(eventId, sdkVersion); + Items = new List() { item }; + } + + public static SentryEnvelope FromSession(ISession session, SdkVersion sdkVersion, + Serializer serializer) + { + return new SentryEnvelope(SentryId.Empty, sdkVersion, SentryEnvelopeItem.FromSession(session, serializer)); + } + } +} diff --git a/sentry-dotnet-health-addon/Internals/SentryEnvelopeHeader.cs b/sentry-dotnet-health-addon/Internals/SentryEnvelopeHeader.cs new file mode 100644 index 0000000..4b79fc6 --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/SentryEnvelopeHeader.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; +using Sentry.Protocol; +using System; + +namespace sentry_dotnet_health_addon.Internals +{ + public class SentryEnvelopeHeader + { + /// + ///Event Id must be set if the envelope holds an event, or an item that is related to the event.
+ /// (e.g: attachments, user feedback) + ///
+ [JsonProperty("eventId")] + public SentryId EventId { get; private set; } + + [JsonProperty("sdkVersion")] + public SdkVersion SdkVersion { get; private set; } + + [JsonProperty("sentAt")] + public string SentAt { get; private set; } + + internal SentryEnvelopeHeader(SentryId eventId, SdkVersion version) + { + EventId = eventId; + SdkVersion = version; + SentAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:MM:ss.ffZ"); + } + } +} diff --git a/sentry-dotnet-health-addon/Internals/SentryEnvelopeItem.cs b/sentry-dotnet-health-addon/Internals/SentryEnvelopeItem.cs new file mode 100644 index 0000000..cf394c7 --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/SentryEnvelopeItem.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using sentry_dotnet_health_addon.Enums; +using System.IO; + +namespace sentry_dotnet_health_addon.Internals +{ + public class SentryEnvelopeItem + { + [JsonIgnore] + internal SentryItemType Type {get; private set; } + + [JsonExtensionData] + internal byte[] Data { get; private set; } + + public SentryEnvelopeItem(SentryItemType type, byte[] data) + { + Type = type; + Data = data; + } + + public static SentryEnvelopeItem FromSession(ISession session, Serializer serializer) + { + var memoryStream = new MemoryStream(); + serializer.Serialize(session, memoryStream); + var array = memoryStream.ToArray(); + memoryStream.Close(); + return new SentryEnvelopeItem(new SentryItemType(ESentryType.Session), array); + } + } +} \ No newline at end of file diff --git a/sentry-dotnet-health-addon/Internals/SentryItemType.cs b/sentry-dotnet-health-addon/Internals/SentryItemType.cs new file mode 100644 index 0000000..262048e --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/SentryItemType.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using sentry_dotnet_health_addon.Enums; +using sentry_dotnet_health_addon.Extensions; + +namespace sentry_dotnet_health_addon.Internals +{ + public class SentryItemType + { + [JsonProperty("type")] + private string _type => Type.ConvertString(); + + [JsonIgnore] + public ESentryType Type { get; private set; } + + public SentryItemType(ESentryType type) + { + Type = type; + } + } +} diff --git a/sentry-dotnet-health-addon/Internals/Session.cs b/sentry-dotnet-health-addon/Internals/Session.cs new file mode 100644 index 0000000..6acb852 --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/Session.cs @@ -0,0 +1,248 @@ +using Newtonsoft.Json; +using Sentry.Protocol; +using sentry_dotnet_health_addon.Enums; +using sentry_dotnet_health_addon.Extensions; +using System; + +namespace sentry_dotnet_health_addon.Internals +{ + public class Session : ISession + { + /** started timestamp */ + [JsonProperty("started")] + public DateTime? Started { get; private set; } + + /** the timestamp */ + [JsonProperty("timestamp")] + public DateTime? Timestamp { get; private set; } + + /** the number of errors on the session */ + [JsonProperty("errors ")] + public int? ErrorCount { get; private set; } + + /** The distinctId, did */ + [JsonProperty("did")] + public string DistinctId { get; private set; } + + /** the SessionId, sid */ + [JsonProperty("sid")] + public Guid SessionId { get; private set; } + + /** The session init flag */ + [JsonProperty("init")] + public bool? Init { get; private set; } + + /** The session state */ + [JsonIgnore] + public SessionState Status { get; set; } + + [JsonProperty("status")] + internal string _statusJson => Status.ConvertString(); + + /** The session sequence */ + [JsonIgnore] + public long? Sequence { get; private set; } + + /** The session duration (timestamp - started) */ + [JsonProperty("duration")] + public long? Duration { get; private set; } + + /** User Attributes. **/ + [JsonProperty("attrs")] + public SessionAttributes Attributes { get; private set; } + /** The session lock, ops should be atomic */ + [JsonIgnore] + internal object _sessionLock = new object(); + + public Session( + SessionState status, + DateTime? started, + DateTime? timestamp, + int errorCount, + string distinctId, + Guid sessionId, + bool? init, + long? sequence, + long? duration, + string ipAddress, + string userAgent, + string environment, + string release) + { + Status = status; + Started = started; + Timestamp = timestamp; + ErrorCount = errorCount; + DistinctId = distinctId; + SessionId = sessionId; + Init = init; + Sequence = sequence; + Duration = duration; + Attributes = new SessionAttributes(ipAddress, userAgent, environment, release); + } + + public Session( + string distinctId, + User user, + string environment, + string release) + { + Start(distinctId, user, environment, release); + } + + public void Start(string distinctId, + User user, + string environment, + string release) + { + Status = SessionState.Ok; + Started = DateTime.Now; + Timestamp = Started; + ErrorCount = 0; + DistinctId = distinctId; + SessionId = Guid.NewGuid(); + Init = true; + Sequence = null; + Duration = null; + Attributes = new SessionAttributes(user != null ? user.IpAddress : null, + null, environment, release); + } + + internal string SentryUsertoUserAgent(User user) + { + if (user?.Id != null) + return user.Id; + if (user?.Email != null) + return user.Email; + if (user?.Username != null) + return user.Username; + return null; + } + + /// + /// Ends a session and update its values. + /// + /// the timestamp or null + public void End(DateTime? timestamp = null) + { + lock (_sessionLock) + { + // _init = null; + + // at this state it might be Crashed already, so we don't check for it. + if (Status == SessionState.Ok) + { + Status = SessionState.Exited; + } + + if (timestamp != null) + { + Timestamp = timestamp; + } + else + { + Timestamp = DateTime.Now; + } + + if (timestamp != null) + { + Duration = CalculateDurationTime(Timestamp.Value); + Sequence = GetSequenceTimestamp(Timestamp.Value); + } + } + } + + /// + /// Calculates the duration time in seconds timestamp (last update) - started + /// + /// the timestamp + /// duration in seconds + private long CalculateDurationTime(DateTime timestamp) + { + return (long)(timestamp.TotalUtcSeconds() - Started.Value.TotalUtcSeconds()); + } + + /// + /// Updates the current session and set its values. + /// + /// the status. + /// the userAgent. + /// true if should increase error count or not. + /// true if the session has been updated. + internal bool Update(SessionState? status, string userAgent, bool addErrorsCount) + { + lock (_sessionLock) + { + bool sessionHasBeenUpdated = false; + if (status != null) + { + Status = status.Value; + sessionHasBeenUpdated = true; + } + + if (userAgent != null) + { + Attributes.UserAgent = userAgent; + sessionHasBeenUpdated = true; + } + if (addErrorsCount) + { + ErrorCount++; + sessionHasBeenUpdated = true; + } + + if (sessionHasBeenUpdated) + { + Init = null; + Timestamp = DateTime.Now; + if (Timestamp != null) + { + Sequence = GetSequenceTimestamp(Timestamp.Value); + } + } + return sessionHasBeenUpdated; + } + } + + + /// + /// Returns a logical clock. + /// + /// The timestamp + /// time stamp in milliseconds UTC + private long GetSequenceTimestamp(DateTime timestamp) + { + long sequence = (long)timestamp.ToUniversalTime().TotalUtcMiliseconds(); + // if device has wrong date and time and it is nearly at the beginning of the epoch time. + // when converting GMT to UTC may give a negative value. + if (sequence < 0) + { + sequence = Math.Abs(sequence); + } + return sequence; + } + + public void RegisterError() + { + ErrorCount++; + } + + internal Session Clone() + { + return new Session( + Status, + Started, + Timestamp, + ErrorCount.GetValueOrDefault(), + DistinctId, + SessionId, + Init, + Sequence, + Duration, + Attributes.IpAddress, + Attributes.UserAgent, + Attributes.Environment, + Attributes.Release); + } + } +} diff --git a/sentry-dotnet-health-addon/Internals/SessionContainerAsyncLocal.cs b/sentry-dotnet-health-addon/Internals/SessionContainerAsyncLocal.cs new file mode 100644 index 0000000..6c66593 --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/SessionContainerAsyncLocal.cs @@ -0,0 +1,23 @@ +using System.Threading; + +namespace sentry_dotnet_health_addon.Internals +{ + internal class SessionContainerAsyncLocal : ISessionContainer + { + internal AsyncLocal Sessions = new AsyncLocal(); + public void CreateNewSession(Session session) + { + Sessions.Value = session; + } + + public Session GetCurrent() + { + return Sessions.Value; + } + + public void Clear() + { + //TODO: Clear AsyncLocal? + } + } +} diff --git a/sentry-dotnet-health-addon/Internals/SessionContainerGlobal.cs b/sentry-dotnet-health-addon/Internals/SessionContainerGlobal.cs new file mode 100644 index 0000000..f5c37de --- /dev/null +++ b/sentry-dotnet-health-addon/Internals/SessionContainerGlobal.cs @@ -0,0 +1,23 @@ +namespace sentry_dotnet_health_addon.Internals +{ + internal class SessionContainerGlobal : ISessionContainer + { + internal Session Session; + + public void CreateNewSession(Session session) + { + Session = session; + } + + public Session GetCurrent() + { + System.Console.WriteLine("Estou 3"); + return Session; + } + + public void Clear() + { + Session = null; + } + } +} diff --git a/sentry-dotnet-health-addon/SentryContrib.SessionSdk.csproj b/sentry-dotnet-health-addon/SentryContrib.SessionSdk.csproj new file mode 100644 index 0000000..c5c41b0 --- /dev/null +++ b/sentry-dotnet-health-addon/SentryContrib.SessionSdk.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.0 + sentry_dotnet_health_addon + Lucas Zimerman + Unnoficial Session Addon for Sentry.Net SDK + https://github.com/lucas-zimerman/sentry-dotnet-performance-addon + nugget-logo.png + https://github.com/lucas-zimerman/sentry-dotnet-performance-addon + true + Still in Beta + ContribSentry.SessionSdk + ContribSentry.SessionSdk + MIT + + + + + + + + + + + + + diff --git a/sentry-dotnet-health-addon/SentryHealthEventProcessor.cs b/sentry-dotnet-health-addon/SentryHealthEventProcessor.cs new file mode 100644 index 0000000..0d737ab --- /dev/null +++ b/sentry-dotnet-health-addon/SentryHealthEventProcessor.cs @@ -0,0 +1,30 @@ +using Sentry; +using Sentry.Extensibility; +using Sentry.Protocol; +using sentry_dotnet_health_addon.Enums; +using System; +using System.Linq; + +namespace sentry_dotnet_health_addon +{ + internal class SentryHealthEventProcessor : ISentryEventProcessor + { + public SentryEvent Process(SentryEvent @event) + { + if (@event.Level == SentryLevel.Error || + @event.Level == SentryLevel.Fatal) + { + var session = SentrySessionSdk.GetCurrent(); + session?.RegisterError(); + if (session != null && @event.SentryExceptions.Any(e => e.Mechanism?.Handled == false)) + { + //crash, must close the session + session.End(DateTime.Now); + session.Status = SessionState.Crashed; + SentrySessionSdk.CaptureSession(session); + } + } + return @event; + } + } +} \ No newline at end of file diff --git a/sentry-dotnet-health-addon/SentrySessionOptions.cs b/sentry-dotnet-health-addon/SentrySessionOptions.cs new file mode 100644 index 0000000..d63ffea --- /dev/null +++ b/sentry-dotnet-health-addon/SentrySessionOptions.cs @@ -0,0 +1,21 @@ +using Sentry; + +namespace sentry_dotnet_health_addon +{ + public class SentrySessionOptions + { + internal Dsn Dsn { get; set; } + internal string Environment { get; set; } + internal string Release { get; set; } + + /// + /// True for single user applications like Apps, Otherwise, False. + /// + public bool GlobalHubMode { get; set; } + + /// + /// The Device Id or the unique id that represents an user. + /// + public string DistinctId { get; set; } + } +} diff --git a/sentry-dotnet-health-addon/SentrySessionSdk.cs b/sentry-dotnet-health-addon/SentrySessionSdk.cs new file mode 100644 index 0000000..3f8c405 --- /dev/null +++ b/sentry-dotnet-health-addon/SentrySessionSdk.cs @@ -0,0 +1,82 @@ +using Sentry.Extensibility; +using Sentry.Protocol; +using sentry_dotnet_health_addon.Enums; +using sentry_dotnet_health_addon.Internals; +using sentry_dotnet_health_addon.Transport; +using System; + +namespace sentry_dotnet_health_addon +{ + public static class SentrySessionSdk + { + internal static ISessionContainer HealthContainer; + + internal static SentrySessionOptions Options; + + internal static DistinctiveId IdHandler; + + internal static Serializer @Serializer; + + public static bool IsEnabled => Options != null; + + public static ISession GetCurrent() + { + if (!IsEnabled) + return null; + return HealthContainer.GetCurrent(); + } + + internal static void Init(SentrySessionOptions options) + { + if (options.GlobalHubMode) + HealthContainer = new SessionContainerGlobal(); + else + HealthContainer = new SessionContainerAsyncLocal(); + Options = options; + IdHandler = new DistinctiveId(); + @Serializer = new Serializer(); + } + + public static void Close() + { + if (IsEnabled) + { + EndSession(); + } + HealthContainer = null; + Options = null; + IdHandler = null; + @Serializer = null; + } + + public static void StartSession(User user) + { + HealthContainer.CreateNewSession(new Session(ResolveDistinctId(user), user, Options.Environment, Options.Release)); + } + + public static void EndSession() + { + var session = HealthContainer.GetCurrent(); + if (session == null || session is DisabledHub || session.Status == SessionState.Exited) + return; + session.End(DateTime.Now); + CaptureSession(session); + } + + internal static void CaptureSession(ISession session) + { + var envelope = SentryEnvelope.FromSession(session, + new SdkVersion() { Name = "LucasSdk", Version = "1.0.0" }, + @Serializer); + //Todo: SOLVE THIS!!! + _ = HttpTransport.Send(envelope, @Serializer); + + } + private static string ResolveDistinctId(User user) + { + if (Options?.DistinctId != null) + return IdHandler.GetDistinctiveId(Options.DistinctId); + return IdHandler.GetDistinctiveId(user); + } + } +} diff --git a/sentry-dotnet-health-addon/SentrySessionSdkIntegration.cs b/sentry-dotnet-health-addon/SentrySessionSdkIntegration.cs new file mode 100644 index 0000000..7c91029 --- /dev/null +++ b/sentry-dotnet-health-addon/SentrySessionSdkIntegration.cs @@ -0,0 +1,29 @@ +using Sentry; +using Sentry.Integrations; + +namespace sentry_dotnet_health_addon +{ + public class SentrySessionSdkIntegration : ISdkIntegration + { + internal SentrySessionOptions _options; + public SentrySessionSdkIntegration() + { + _options = new SentrySessionOptions(); + } + + public SentrySessionSdkIntegration(SentrySessionOptions options) + { + _options = options; + } + + public void Register(IHub hub, SentryOptions options) + { + _options.Dsn = options.Dsn; + _options.Environment = options.Environment; + _options.Release = options.Release; + SentrySessionSdk.Init(_options); + _options = null; + options.AddEventProcessor(new SentryHealthEventProcessor()); + } + } +} diff --git a/sentry-dotnet-health-addon/Serializer.cs b/sentry-dotnet-health-addon/Serializer.cs new file mode 100644 index 0000000..c36ea88 --- /dev/null +++ b/sentry-dotnet-health-addon/Serializer.cs @@ -0,0 +1,62 @@ +using Newtonsoft.Json; +using Sentry.Protocol; +using sentry_dotnet_health_addon.Internals; +using System; +using System.IO; +using System.Text; + +namespace sentry_dotnet_health_addon +{ + public class Serializer + { + internal Encoding utf8 = Encoding.UTF8; + + JsonSerializerSettings jsonSettings; + + public Serializer() + { + jsonSettings = new JsonSerializerSettings() + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore + }; + } + + public void Serialize(ISession session, Stream writer) + { + var json = JsonConvert.SerializeObject(session, jsonSettings); + writer.Write(utf8.GetBytes(json), 0, json.Length); + writer.Flush(); + } + + internal void Serialize(SentryEnvelope envelope, Stream writer) + { + var nextLineArray = new Byte[1] { 10 }; + var header = (SentryId.Empty.Equals(envelope.Header.EventId) ? "{}" : JsonConvert.SerializeObject(envelope.Header, jsonSettings)); + writer.Write(utf8.GetBytes(header), 0, header.Length); + writer.Write(nextLineArray, 0, 1); + foreach (var item in envelope.Items) + { + var itemTypeJson = JsonConvert.SerializeObject(item.Type, jsonSettings); + writer.Write(utf8.GetBytes(itemTypeJson), 0, itemTypeJson.Length); + writer.Write(nextLineArray, 0, 1); + CopyBytesByKb(item.Data, writer); + writer.Write(nextLineArray, 0, 1); + } + writer.Flush(); + } + + + private void CopyBytesByKb(byte[] data, Stream writer) + { + long size = data.Length - 1024; + int offset = 0; + for (; offset < size; offset += 1024) + { + writer.Write(data, offset, 1024); + } + writer.Write(data, offset, data.Length - offset); + } + } +} diff --git a/sentry-dotnet-health-addon/SessionAttributes.cs b/sentry-dotnet-health-addon/SessionAttributes.cs new file mode 100644 index 0000000..c7d487a --- /dev/null +++ b/sentry-dotnet-health-addon/SessionAttributes.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; + +namespace sentry_dotnet_health_addon +{ + public class SessionAttributes + { + /** the user's ip address */ + [JsonProperty("ip_address")] + public string IpAddress { get; private set; } + + /** the user Agent */ + [JsonProperty("user_agent")] + internal string UserAgent { get; set; } + + /** the environment */ + [JsonProperty("environment")] + public string Environment { get; private set; } + + /** the App's release */ + [JsonProperty("release")] + public string Release { get; private set; } + + public SessionAttributes(string ipAddress, string userAgent, string environment, string release) + { + IpAddress = ipAddress; + UserAgent = userAgent; + Environment = environment; + Release = release; + } + } +} diff --git a/sentry-dotnet-health-addon/Transport/HttpTransport.cs b/sentry-dotnet-health-addon/Transport/HttpTransport.cs new file mode 100644 index 0000000..b6fe8c7 --- /dev/null +++ b/sentry-dotnet-health-addon/Transport/HttpTransport.cs @@ -0,0 +1,24 @@ +using sentry_dotnet_health_addon.Extensions; +using sentry_dotnet_health_addon.Internals; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace sentry_dotnet_health_addon.Transport +{ + public static class HttpTransport + { + internal static HttpClient Client = new HttpClient(); + public static async Task Send(SentryEnvelope envelope, Serializer serializer) + { + var memoryStream = new MemoryStream(); + serializer.Serialize(envelope, memoryStream); + var content = new ByteArrayContent(memoryStream.ToArray()); + memoryStream.Close(); + content.Headers.ContentType = new MediaTypeHeaderValue("application/x-sentry-envelope"); + var url = SentrySessionSdk.Options.Dsn.GetTracingUrl(); + await Client.PostAsync(url, content); + } + } +} \ No newline at end of file diff --git a/sentry-dotnet-transaction-addon.sln b/sentry-dotnet-transaction-addon.sln index 6483ddd..ba04ceb 100644 --- a/sentry-dotnet-transaction-addon.sln +++ b/sentry-dotnet-transaction-addon.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30204.135 @@ -7,6 +6,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sentry-dotnet-transaction-a EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Testing", "Testing\Testing.csproj", "{9C294F90-9E37-442C-A963-AABFFEE3A66F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SentryContrib.SessionSdk", "sentry-dotnet-health-addon\SentryContrib.SessionSdk.csproj", "{BD802D38-3F09-4F2D-867B-C94AED25CE84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SessionSdk.Test", "SessionSdk.Test\SessionSdk.Test.csproj", "{F304CDA6-B501-4A25-94F7-6C1C19FA0DFC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +24,14 @@ Global {9C294F90-9E37-442C-A963-AABFFEE3A66F}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C294F90-9E37-442C-A963-AABFFEE3A66F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C294F90-9E37-442C-A963-AABFFEE3A66F}.Release|Any CPU.Build.0 = Release|Any CPU + {BD802D38-3F09-4F2D-867B-C94AED25CE84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD802D38-3F09-4F2D-867B-C94AED25CE84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD802D38-3F09-4F2D-867B-C94AED25CE84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD802D38-3F09-4F2D-867B-C94AED25CE84}.Release|Any CPU.Build.0 = Release|Any CPU + {F304CDA6-B501-4A25-94F7-6C1C19FA0DFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F304CDA6-B501-4A25-94F7-6C1C19FA0DFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F304CDA6-B501-4A25-94F7-6C1C19FA0DFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F304CDA6-B501-4A25-94F7-6C1C19FA0DFC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/sentry-dotnet-transaction-addon/Enums/ESentryType.cs b/sentry-dotnet-transaction-addon/Enums/ESentryType.cs new file mode 100644 index 0000000..b168a5a --- /dev/null +++ b/sentry-dotnet-transaction-addon/Enums/ESentryType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace sentry_dotnet_transaction_addon.Enums +{ + internal enum ESentryType + { + Session, + Event, + Attachment, + Transaction, + Unknown + } +} diff --git a/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj b/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj index 98fd663..e446105 100644 --- a/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj +++ b/sentry-dotnet-transaction-addon/sentry-dotnet-transaction-addon.csproj @@ -9,7 +9,7 @@ - +