diff --git a/README.md b/README.md index 5645d80..5ab26d6 100644 --- a/README.md +++ b/README.md @@ -18,20 +18,28 @@ Log.Logger = new LoggerConfiguration() new[] { "globalTag1", "globalTag2" }, new[] { "ignoreField1", "ignoreField2" }, "CustomGroupKeyProperty", - "CustomTagsProperty") + "CustomTagsProperty", + "CustomUserInfoProperty") .CreateLogger(); ``` -### Required -#### applicationKey -`string` -### Optional -#### wrapperExceptions -`type: IEnumerable` +### applicationKey +`type: string` + +`required` + +Each application you create in Raygun will have an API Key which you can pass in here to specify where the crash reports will be sent to. Although this is required, you can set this to null or empty string which would result in crash reports not being sent. This can be useful if you want to configure your local environment to not send crash reports to Raygun and then use config transforms or the like to provide an API key for other environments. + +### wrapperExceptions +`type: IEnumerable` `default: null` -#### userNameProperty +This is a list of wrapper exception types that you're not interested in logging to Raygun. Whenever an undesired wrapper exception is logged, it will be discarded and only the inner exception(s) will be logged. + +For example, you may not be interested in the details of an AggregateException, so you could include `typeof(AggregateException)` in this list of wrapperExceptions. All inner exceptions of any logged AggregateException would then be sent to Raygun as separate crash reports. + +### userNameProperty `type: string` `default: UserName` @@ -40,50 +48,154 @@ Log.Logger = new LoggerConfiguration() Log.ForContext("CustomUserNameProperty", "John Doe").Error(new Exception("random error"), "other information"); ``` -#### applicationVersionProperty +### applicationVersionProperty `type: string` `default: ApplicationVersion` +By default, crash reports sent to Raygun will have an ApplicationVersion field based on the version of the entry assembly for your application. If this is not being picked up correctly, or if you want to provide a different version, then you can do so by including the desired value in the logging properties collection. + +You can specify the property key that you place the version in by using this applicationVersionProperty setting. Otherwise the version will be read from the "ApplicationVersion" key. + ```csharp Log.ForContext("CustomAppVersionProperty", "1.2.11").Error(new Exception("random error"), "other information"); ``` -#### restrictedToMinimumLevel +### restrictedToMinimumLevel `type: LogEventLevel` `default: LogEventLevel.Error` -#### formatProvider +### formatProvider `type: IFormatProvider` `default: null` -#### tags +### tags `type: IEnumerable` `default: null` -#### ignoredFormFieldNames +This is a list of global tags that will be included on every crash report sent with this Serilog sink. + +### ignoredFormFieldNames `type: IEnumerable` `default: null` -#### groupKeyProperty +Crash reports sent to Raygun from this Serilog sink will include HTTP context details if present. (Currently only supported in .NET Framework applications). This option lets you specify a list of form fields that you do not want to be sent to Raygun. + +Setting `ignoredFormFieldNames` to a list that only contains "*" will cause no form fields to be sent to Raygun. Placing * before, after or at both ends of an entry will perform an ends-with, starts-with or contains operation respectively. + +Note that HTTP headers, query parameters, cookies, server variables and raw request data can also be filtered out. Configuration to do so is described in the [RaygunSettings](#raygun4net-features-configured-via-raygunsettings) section further below. + +The `ignoreFormFieldNames` entries will also strip out specified values from the raw request payload if it is multipart/form-data. + +### groupKeyProperty `type: string` `default: GroupKey` +Crash reports sent to Raygun will be automatically grouped together based on stack trace and exception type information. The `groupKeyProperty` setting specifies a key in the logging properties collection where you can provide a grouping key. Crash reports containing a grouping key will not be grouped automatically by Raygun. Instead, crash reports with matching custom grouping keys will be grouped together. + ```csharp Log.ForContext("CustomGroupKeyProperty", "TransactionId-12345").Error(new Exception("random error"), "other information"); ``` -#### tagsProperty +### tagsProperty `type: string` `default: Tags` +This allows you to specify a key in the properties collection that contains a list of tags to include on crash reports. Note that these will be included in addition to any global tags [described above](#tags). If you set a list of tags in the properties collection multiple times (e.g. at different logging scopes) then only the latest list of tags will be used. + ```csharp Log.ForContext("CustomTagsProperty", new[] {"tag1", "tag2"}).Error(new Exception("random error"), "other information"); Log.Error(new Exception("random error"), "other information {@CustomTagsProperty}", new[] {"tag3", "tag4"}); -``` \ No newline at end of file +``` + +### userInfoProperty +`type: string` + +`default: null` + +This is null by default, so you need to configure the userInfoProperty name if you want to log more user information in this way. This will cause the provided RaygunIdentifierMessage to be included in the "User" section of the Raygun payload, allowing the information to be picked up by the "Users" section of the Raygun service. It's recommended to destructure the RaygunIdentifierMessage, but this feature will still work if you don't. Sending user information in this way will overwrite the use of the userNameProperty. + +The user identifier passed into the RaygunIdentifierMessage constructor could be the users name, email address, database id or whatever works best for you to identify unique users. + +```csharp +var userInfo = new RaygunIdentifierMessage("12345") +{ + FirstName = "John", + FullName = "John Doe", + Email = "johndoe@email.address" +}; + +Log.ForContext("CustomUserInfoProperty", userInfo, true).Error(new Exception("random error"), "other information"); +``` + +## Raygun4Net features configured via RaygunSettings + +This sink wraps the [Raygun4Net](https://github.com/MindscapeHQ/raygun4net) provider to build a crash report from an Exception and send it to Raygun. This makes the following Raygun4Net features available to you. To use these features, you need to add RaygunSettings to your configuration as explained below which is separate to the Serilog configuration. + +**.NET Core** + +Add a RaygunSettings block to your appsettings.config file where you can populate the settings that you want to use. + +``` +"RaygunSettings": { + "Setting": "Value" +} +``` + +**.NET Framework** + +Add the following section within the configSections element of your app.config or web.config file. + +```xml +
+``` + +Then add a RaygunSettings element containing the desired settings somewhere within the configuration element of the app.config or web.config file. + +```xml + +``` + +### ThrowOnError +`type: bool` + +`default: false` + +This is false by default, which means that any exception that occur within Raygun4Net itself will be silently caught. Setting this to true will allow any exceptions occurring in Raygun4Net to be thrown, which can help debug issues with Raygun4Net if crash reports aren't showing up in Raygun. + +### IgnoreSensitiveFieldNames +`type: string[]` + +`default: null` + +Crash reports sent to Raygun from this Serilog sink will include HTTP context details if present. (Currently only supported in .NET Framework applications). `IgnoreSensitiveFieldNames` lets you specify a list of HTTP query parameters, form fields, headers, cookies and server variables that you do not want to be sent to Raygun. Additionally, entries in this setting will be attempted to be stripped out of the raw request payload (more options for controlling this are explained in the `IsRawDataIgnored` section below). + +Setting `IgnoreSensitiveFieldNames` to a list that only contains "*" will cause none of these things to be sent to Raygun. Placing * before, after or at both ends of an entry will perform an ends-with, starts-with or contains operation respectively. + +Individual options are also available which function in the same way as IgnoreSensitiveFieldNames: `IgnoreQueryParameterNames`, `IgnoreFormFieldNames`, `IgnoreHeaderNames`, `IgnoreCookieNames` and `IgnoreServerVariableNames`. + +The `IgnoreFormFieldNames` entries will also strip out specified values from the raw request payload if it is multipart/form-data. + +### IsRawDataIgnored +`type: bool` + +`default: false` + +By default, Raygun crash reports will capture the raw request payload of the current HTTP context if present. (Currently only supported in .NET Framework applications). If you would not like to include raw request payloads on crash reports sent to Raygun, then you can set `IsRawDataIgnored` to true. + +If you do want to include the raw request payload, but want to filter out sensitive fields, then you can use the `IgnoreSensitiveFieldNames` options described above. You'll also need to specify how the fields should be stripped from the raw request payload. Set `UseXmlRawDataFilter` to true for XML payloads or/and set `UseKeyValuePairRawDataFilter` to true for payloads of the format "key1=value1&key2=value2". + +Setting `IsRawDataIgnoredWhenFilteringFailed` to true will cause the entire raw request payload to be ignored in cases where specified sensitive values fail to be stripped out. + +### CrashReportingOfflineStorageEnabled +`type: bool` + +`default: true` + +Only available in .NET Framework applications. This is true by default which will cause crash reports to be saved to isolated storage (if possible) in cases where they fail to be sent to Raygun. This option lets you disable this functionality by setting it to false. When enabled, a maximum of 64 crash reports can be saved. This limit can be set lower than 64 via the `MaxCrashReportsStoredOffline` option. diff --git a/appveyor.yml b/appveyor.yml index 0aca527..bbc8551 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,7 +12,7 @@ artifacts: deploy: - provider: NuGet api_key: - secure: eY0pNgTWxzEUVfkitD3R/g5Qfg2GVLYdiJiyvnwfiOLBd2FMRRrJKD89j8XMeK9A + secure: K3/810hkTO6rd2AEHVkUQAadjGmDREus9k96QHu6hxrA1/wRTuAJemHMKtVVgIvf skip_symbols: true on: branch: /^(master|dev)$/ diff --git a/src/Serilog.Sinks.Raygun/LoggerConfigurationRaygunExtensions.cs b/src/Serilog.Sinks.Raygun/LoggerConfigurationRaygunExtensions.cs index 8e68722..39b91ac 100644 --- a/src/Serilog.Sinks.Raygun/LoggerConfigurationRaygunExtensions.cs +++ b/src/Serilog.Sinks.Raygun/LoggerConfigurationRaygunExtensions.cs @@ -26,11 +26,11 @@ namespace Serilog public static class LoggerConfigurationRaygunExtensions { /// - /// Adds a sink that writes log events (defaults to error and up) to the Raygun.io webservice. Properties are being send as data and the level is used as a tag. + /// Adds a sink that writes log events (defaults to error and up) to the Raygun service. Properties and the log message are being attached as UserCustomData and the level is included as a Tag. /// Your message is part of the custom data. /// /// The logger configuration. - /// The application key as found on the Raygun.io website. + /// The application key as found on an application in your Raygun account. /// If you have common outer exceptions that wrap a valuable inner exception which you'd prefer to group by, you can specify these by providing a list. /// Specifies the property name to read the username from. By default it is UserName. Set to null if you do not want to use this feature. /// Specifies the property to use to retrieve the application version from. You can use an enricher to add the application version to all the log events. When you specify null, Raygun will use the assembly version. @@ -53,15 +53,13 @@ public static LoggerConfiguration Raygun( IEnumerable tags = null, IEnumerable ignoredFormFieldNames = null, string groupKeyProperty = "GroupKey", - string tagsProperty = "Tags") + string tagsProperty = "Tags", + string userInfoProperty = null) { if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration"); - if (string.IsNullOrWhiteSpace(applicationKey)) - throw new ArgumentNullException("applicationKey"); - return loggerConfiguration.Sink( - new RaygunSink(formatProvider, applicationKey, wrapperExceptions, userNameProperty, applicationVersionProperty, tags, ignoredFormFieldNames, groupKeyProperty, tagsProperty), + new RaygunSink(formatProvider, applicationKey, wrapperExceptions, userNameProperty, applicationVersionProperty, tags, ignoredFormFieldNames, groupKeyProperty, tagsProperty, userInfoProperty), restrictedToMinimumLevel); } } diff --git a/src/Serilog.Sinks.Raygun/Serilog.Sinks.Raygun.csproj b/src/Serilog.Sinks.Raygun/Serilog.Sinks.Raygun.csproj index 0d5d6b2..757fdfb 100644 --- a/src/Serilog.Sinks.Raygun/Serilog.Sinks.Raygun.csproj +++ b/src/Serilog.Sinks.Raygun/Serilog.Sinks.Raygun.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net46;net461 @@ -6,30 +6,27 @@ Michiel van Oudheusden Serilog Serilog.Sinks.Raygun - Send log events to custom topics in Azure Event Grid Apache-2.0 http://serilog.net http://serilog.net/images/serilog-sink-nuget.png git https://github.com/serilog/serilog-sinks-raygun serilog sink raygun - Copyright © Serilog Contributors 2017-2019 - Serilog event sink that writes to the Raygun.io service. - 4.0.0 - 4.0.0.0 - 4.0.0.0 + Copyright © Serilog Contributors 2017-2020 + Serilog event sink that writes to the Raygun service. + 5.0.1 - + - + - + diff --git a/src/Serilog.Sinks.Raygun/Sinks/Raygun/RaygunSink.cs b/src/Serilog.Sinks.Raygun/Sinks/Raygun/RaygunSink.cs index 0817f29..0a0635c 100644 --- a/src/Serilog.Sinks.Raygun/Sinks/Raygun/RaygunSink.cs +++ b/src/Serilog.Sinks.Raygun/Sinks/Raygun/RaygunSink.cs @@ -14,7 +14,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Reflection; using Mindscape.Raygun4Net; #if NETSTANDARD2_0 using Mindscape.Raygun4Net.AspNetCore; @@ -28,31 +30,36 @@ namespace Serilog.Sinks.Raygun { /// - /// Writes log events to the Raygun.com service. + /// Writes log events to the Raygun service. /// public class RaygunSink : ILogEventSink { - readonly IFormatProvider _formatProvider; - readonly string _userNameProperty; - readonly string _applicationVersionProperty; - readonly IEnumerable _tags; - readonly IEnumerable _ignoredFormFieldNames; - readonly string _groupKeyProperty; - readonly string _tagsProperty; - readonly RaygunClient _client; + private const string RenderedLogMessageProperty = "RenderedLogMessage"; + private const string LogMessageTemplateProperty = "LogMessageTemplate"; + private const string OccurredProperty = "RaygunSink_OccurredOn"; + + private readonly IFormatProvider _formatProvider; + private readonly string _userNameProperty; + private readonly string _applicationVersionProperty; + private readonly IEnumerable _tags; + private readonly string _groupKeyProperty; + private readonly string _tagsProperty; + private readonly string _userInfoProperty; + private readonly RaygunClient _client; /// - /// Construct a sink that saves errors to the Raygun.io service. Properties are being send as userdata and the level is included as tag. The message is included inside the userdata. + /// Construct a sink that saves errors to the Raygun service. Properties and the log message are being attached as UserCustomData and the level is included as a Tag. /// /// Supplies culture-specific formatting information, or null. - /// The application key as found on the Raygun website. + /// The application key as found on an application in your Raygun account. /// If you have common outer exceptions that wrap a valuable inner exception which you'd prefer to group by, you can specify these by providing a list. /// Specifies the property name to read the username from. By default it is UserName. Set to null if you do not want to use this feature. /// Specifies the property to use to retrieve the application version from. You can use an enricher to add the application version to all the log events. When you specify null, Raygun will use the assembly version. /// Specifies the tags to include with every log message. The log level will always be included as a tag. /// Specifies the form field names which to ignore when including request form data. /// The property containing the custom group key for the Raygun message. - /// The property where additional tags are stored when emitting log events + /// The property where additional tags are stored when emitting log events. + /// The property where a RaygunIdentifierMessage with more user information can optionally be provided. public RaygunSink(IFormatProvider formatProvider, string applicationKey, IEnumerable wrapperExceptions = null, @@ -61,22 +68,33 @@ public RaygunSink(IFormatProvider formatProvider, IEnumerable tags = null, IEnumerable ignoredFormFieldNames = null, string groupKeyProperty = "GroupKey", - string tagsProperty = "Tags") + string tagsProperty = "Tags", + string userInfoProperty = null) { - if (string.IsNullOrEmpty(applicationKey)) - throw new ArgumentNullException("applicationKey"); - _formatProvider = formatProvider; _userNameProperty = userNameProperty; _applicationVersionProperty = applicationVersionProperty; _tags = tags ?? new string[0]; - _ignoredFormFieldNames = ignoredFormFieldNames ?? Enumerable.Empty(); _groupKeyProperty = groupKeyProperty; _tagsProperty = tagsProperty; + _userInfoProperty = userInfoProperty; +#if NETSTANDARD2_0 _client = new RaygunClient(applicationKey); +#else + _client = string.IsNullOrWhiteSpace(applicationKey) ? new RaygunClient() : new RaygunClient(applicationKey); +#endif + + // Raygun4Net adds these two wrapper exceptions by default, but as there is no way to remove them through this Serilog sink, we replace them entirely with the configured wrapper exceptions. + _client.RemoveWrapperExceptions(typeof(TargetInvocationException), Type.GetType("System.Web.HttpUnhandledException, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")); + if (wrapperExceptions != null) _client.AddWrapperExceptions(wrapperExceptions.ToArray()); + + if(ignoredFormFieldNames != null) + _client.IgnoreFormFieldNames(ignoredFormFieldNames.ToArray()); + + _client.SendingMessage += OnSendingMessage; } /// @@ -85,68 +103,208 @@ public RaygunSink(IFormatProvider formatProvider, /// The log event to write. public void Emit(LogEvent logEvent) { - //Include the log level as a tag. + // Include the log level as a tag. var tags = _tags.Concat(new[] { logEvent.Level.ToString() }).ToList(); - var properties = logEvent.Properties - .Select(pv => new { Name = pv.Key, Value = RaygunPropertyFormatter.Simplify(pv.Value) }) - .ToDictionary(a => a.Name, b => b.Value); + var properties = logEvent.Properties.ToDictionary(kv => kv.Key, kv => kv.Value); - // Add the message - properties.Add("RenderedLogMessage", logEvent.RenderMessage(_formatProvider)); - properties.Add("LogMessageTemplate", logEvent.MessageTemplate.Text); + // Add the message and template to the properties + properties[RenderedLogMessageProperty] = new ScalarValue(logEvent.RenderMessage(_formatProvider)); + properties[LogMessageTemplateProperty] = new ScalarValue(logEvent.MessageTemplate.Text); + properties[OccurredProperty] = new ScalarValue(logEvent.Timestamp.UtcDateTime); - // Create new message - var raygunMessage = new RaygunMessage + // Add additional custom tags + if (properties.TryGetValue(_tagsProperty, out var eventTags) && eventTags is SequenceValue tagsSequence) { - OccurredOn = logEvent.Timestamp.UtcDateTime - }; + tags.AddRange(tagsSequence.Elements.Select(t => t.ToString("l", null))); - // Add exception when available, else use the message template so events can be grouped - raygunMessage.Details.Error = logEvent.Exception != null - ? RaygunErrorMessageBuilder.Build(logEvent.Exception) - : new RaygunErrorMessage() + properties.Remove(_tagsProperty); + } + + // Submit + if (logEvent.Level == LogEventLevel.Fatal) + { + _client.Send(logEvent.Exception, tags, properties); + } + else + { + _client.SendInBackground(logEvent.Exception, tags, properties); + } + } + + private void OnSendingMessage(object sender, RaygunSendingMessageEventArgs e) + { + if (e?.Message?.Details != null) + { + var details = e.Message.Details; + + details.Client = new RaygunClientMessage { - ClassName = logEvent.MessageTemplate.Text, - Message = logEvent.MessageTemplate.Text, - Data = logEvent.Properties.ToDictionary(k => k.Key, v => v.Value.ToString()) + Name = "RaygunSerilogSink", + Version = new AssemblyName(this.GetType().Assembly.FullName).Version.ToString(), + ClientUrl = "https://github.com/serilog/serilog-sinks-raygun" }; - // Add user when requested - if (!string.IsNullOrWhiteSpace(_userNameProperty) && - logEvent.Properties.ContainsKey(_userNameProperty) && - logEvent.Properties[_userNameProperty] != null) - { - raygunMessage.Details.User = new RaygunIdentifierMessage(logEvent.Properties[_userNameProperty].ToString()); + if (details.UserCustomData is Dictionary properties) + { + // If an Exception has not been provided, then use the log message/template to fill in the details and attach the current execution stack + if (details.Error == null) + { + details.Error = new RaygunErrorMessage + { + ClassName = properties[LogMessageTemplateProperty].ToString("l", null), + Message = properties[RenderedLogMessageProperty].ToString("l", null), + StackTrace = RaygunErrorMessageBuilder.BuildStackTrace(new StackTrace()) + }; + } + + if (properties.TryGetValue(OccurredProperty, out var occurredOnPropertyValue) && + occurredOnPropertyValue is ScalarValue occurredOnScalar && + occurredOnScalar.Value is DateTime occurredOn) + { + e.Message.OccurredOn = occurredOn; + + properties.Remove(OccurredProperty); + } + + // Add user information if provided + if (!string.IsNullOrWhiteSpace(_userInfoProperty) && + properties.TryGetValue(_userInfoProperty, out var userInfoPropertyValue) && + userInfoPropertyValue != null) + { + switch (userInfoPropertyValue) + { + case StructureValue userInfoStructure: + details.User = BuildUserInformationFromStructureValue(userInfoStructure); + break; + case ScalarValue userInfoScalar when userInfoScalar.Value is string userInfo: + details.User = ParseUserInformation(userInfo); + break; + } + + if (details.User != null) + { + details.UserCustomData.Remove(_userInfoProperty); + } + } + + // If user information is not set, then use the user-name if provided + if (details.User == null && + !string.IsNullOrWhiteSpace(_userNameProperty) && + properties.ContainsKey(_userNameProperty) && + properties[_userNameProperty] != null) + { + details.User = new RaygunIdentifierMessage(properties[_userNameProperty].ToString("l", null)); + + properties.Remove(_userNameProperty); + } + + // Add version if provided + if (!string.IsNullOrWhiteSpace(_applicationVersionProperty) && + properties.ContainsKey(_applicationVersionProperty) && + properties[_applicationVersionProperty] != null) + { + details.Version = properties[_applicationVersionProperty].ToString("l", null); + + properties.Remove(_applicationVersionProperty); + } + + // Add the custom group key if provided + if (properties.TryGetValue(_groupKeyProperty, out var customKey)) + { + details.GroupingKey = customKey.ToString("l", null); + + properties.Remove(_groupKeyProperty); + } + + // Simplify the remaining properties to be used as user-custom-data + details.UserCustomData = properties + .Select(pv => new { Name = pv.Key, Value = RaygunPropertyFormatter.Simplify(pv.Value) }) + .ToDictionary(a => a.Name, b => b.Value); + } } + } + + private static RaygunIdentifierMessage BuildUserInformationFromStructureValue(StructureValue userStructure) + { + RaygunIdentifierMessage userIdentifier = new RaygunIdentifierMessage(null); - // Add version when requested - if (!String.IsNullOrWhiteSpace(_applicationVersionProperty) && - logEvent.Properties.ContainsKey(_applicationVersionProperty) && - logEvent.Properties[_applicationVersionProperty] != null) + foreach (var property in userStructure.Properties) { - raygunMessage.Details.Version = logEvent.Properties[_applicationVersionProperty].ToString("l", null); + ScalarValue scalar = property.Value as ScalarValue; + switch (property.Name) + { + case nameof(RaygunIdentifierMessage.Identifier): + userIdentifier.Identifier = scalar?.Value != null ? property.Value.ToString("l", null) : null; + break; + case nameof(RaygunIdentifierMessage.IsAnonymous): + userIdentifier.IsAnonymous = "True".Equals(property.Value.ToString()); + break; + case nameof(RaygunIdentifierMessage.Email): + userIdentifier.Email = scalar?.Value != null ? property.Value.ToString("l", null) : null; + break; + case nameof(RaygunIdentifierMessage.FullName): + userIdentifier.FullName = scalar?.Value != null ? property.Value.ToString("l", null) : null; + break; + case nameof(RaygunIdentifierMessage.FirstName): + userIdentifier.FirstName = scalar?.Value != null ? property.Value.ToString("l", null) : null; + break; + case nameof(RaygunIdentifierMessage.UUID): + userIdentifier.UUID = scalar?.Value != null ? property.Value.ToString("l", null) : null; + break; + } } - // Build up the rest of the message - raygunMessage.Details.Environment = new RaygunEnvironmentMessage(); - raygunMessage.Details.Tags = tags; - raygunMessage.Details.UserCustomData = properties; - raygunMessage.Details.MachineName = Environment.MachineName; + return userIdentifier; + } - // Add the custom group key when provided - if (properties.TryGetValue(_groupKeyProperty, out var customKey)) - raygunMessage.Details.GroupingKey = customKey.ToString(); + private static RaygunIdentifierMessage ParseUserInformation(string userInfo) + { + RaygunIdentifierMessage userIdentifier = null; - // Add additional custom tags - if (properties.TryGetValue(_tagsProperty, out var eventTags) && eventTags is object[]) + // This is a parse of the ToString implementation of RaygunIdentifierMessage which uses the format: + // [RaygunIdentifierMessage: Identifier=X, IsAnonymous=X, Email=X, FullName=X, FirstName=X, UUID=X] + string[] properties = userInfo.Split(new[] { ',', ']' }, StringSplitOptions.RemoveEmptyEntries); + if (properties.Length == 6) { - foreach (var tag in (object[])eventTags) - raygunMessage.Details.Tags.Add(tag.ToString()); + string[] identifierSplit = properties[0].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (identifierSplit.Length == 2) + { + userIdentifier = new RaygunIdentifierMessage(identifierSplit[1]); + + string[] isAnonymousSplit = properties[1].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (isAnonymousSplit.Length == 2) + { + userIdentifier.IsAnonymous = "True".Equals(isAnonymousSplit[1]); + } + + string[] emailSplit = properties[2].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (emailSplit.Length == 2) + { + userIdentifier.Email = emailSplit[1]; + } + + string[] fullNameSplit = properties[3].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (fullNameSplit.Length == 2) + { + userIdentifier.FullName = fullNameSplit[1]; + } + + string[] firstNameSplit = properties[4].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (firstNameSplit.Length == 2) + { + userIdentifier.FirstName = firstNameSplit[1]; + } + + string[] uuidSplit = properties[5].Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (uuidSplit.Length == 2) + { + userIdentifier.UUID = uuidSplit[1]; + } + } } - // Submit - _client.SendInBackground(raygunMessage); + return userIdentifier; } } }