Skip to content

Commit

Permalink
Adds ability to specify user values via NLog configuration (#336)
Browse files Browse the repository at this point in the history
  • Loading branch information
josh-degraw authored and bruno-garcia committed Jan 14, 2020
1 parent 385a1e4 commit dcea346
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 23 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Expand Up @@ -142,6 +142,7 @@ csharp_style_deconstructed_variable_declaration = true : suggestion
# Code style defaults
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
csharp_prefer_braces = true : suggestion

# Indentation
csharp_indent_block_contents = true
Expand Down Expand Up @@ -182,3 +183,4 @@ csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true

9 changes: 9 additions & 0 deletions samples/Sentry.Samples.NLog/NLog.config
Expand Up @@ -30,6 +30,15 @@
<includeEventDataOnBreadcrumbs>true</includeEventDataOnBreadcrumbs>
</options>

<!-- Optionally specify user properties via NLog (here using MappedDiagnosticsLogicalContext as an example) -->
<user id="${mdlc:item=id}"
username="${mdlc:item=username}"
email="${mdlc:item=email}"
>
<!-- You can also apply additional user properties here-->
<other name="mood" layout="joyous"/>
</user>

<!--Add any desired additional tags that will be sent with every message -->
<tag name="logger" layout="${logger}" />
</target>
Expand Down
70 changes: 49 additions & 21 deletions samples/Sentry.Samples.NLog/Program.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using NLog;
using NLog.Config;
using NLog.Targets;
using Sentry.NLog;

// ReSharper disable ConvertToConstant.Local
namespace Sentry.Samples.NLog
Expand Down Expand Up @@ -36,26 +38,39 @@ private static void Main(string[] args)

private static void DemoLogger(ILogger logger)
{
// Minimum Breadcrumb and Event log levels are set to levels higher than Trace.
// In this case, Trace messages are ignored
logger.Trace("Verbose message which is not sent.");
// Here is an example of how you can set user properties in code via NLog. The layout is configured to use these values for the sentry user
using (MappedDiagnosticsLogicalContext.SetScoped("id", "myId"))
using (MappedDiagnosticsLogicalContext.SetScoped("username", "userNumberOne"))
using (MappedDiagnosticsLogicalContext.SetScoped("email", "theCoolest@sample.com"))
{
// Minimum Breadcrumb and Event log levels are set to levels higher than Trace.
// In this case, Trace messages are ignored
logger.Trace("Verbose message which is not sent.");

// Minimum Breadcrumb level is set to Debug so the following message is stored in memory and sent
// with following events of the same Scope
logger.Debug("Debug message stored as breadcrumb.");
// Minimum Breadcrumb level is set to Debug so the following message is stored in memory and sent
// with following events of the same Scope
logger.Debug("Debug message stored as breadcrumb.");

// Sends an event and stores the message as a breadcrumb too, to be sent with any upcoming events.
logger.Error("Some event that includes the previous breadcrumbs. mood = {mood}", "happy that my error is reported");
// Sends an event and stores the message as a breadcrumb too, to be sent with any upcoming events.
logger.Error("Some event that includes the previous breadcrumbs. mood = {mood}", "happy that my error is reported");

try
{
DoWork(logger);
}
catch (Exception e)
{
e.Data.Add("details", "DoWork always throws.");
logger.Fatal(e, "Error: with exception. {data}", new { title = "compound data object", wowFactor = 11, errorReported = true });
LogManager.Flush();
try
{
DoWork(logger);
}
catch (Exception e)
{
e.Data.Add("details", "DoWork always throws.");
logger.Fatal(e,
"Error: with exception. {data}",
new
{
title = "compound data object",
wowFactor = 11,
errorReported = true
});
LogManager.Flush();
}
}
}

Expand All @@ -67,7 +82,7 @@ private static void DoWork(ILogger logger)

logger.Warn("a is 0");

_ = 10 / a;
_ = b / a;
}

private static void UsingNLogConfigFile()
Expand All @@ -76,14 +91,15 @@ private static void UsingNLogConfigFile()
// different file, you can call the following to load it for you: LogManager.Configuration = LogManager.LoadConfiguration("NLog.config").Configuration;

var logger = LogManager.GetCurrentClassLogger();

DemoLogger(logger);
}

private static void UsingCodeConfiguration()
{
// Other overloads exist, for example, configure the SDK with only the DSN or no parameters at all.
var config = LogManager.Configuration = new LoggingConfiguration();
config
_ = config
.AddSentry(o =>
{
o.Layout = "${message}";
Expand All @@ -102,6 +118,18 @@ private static void UsingCodeConfiguration()
o.IncludeEventDataOnBreadcrumbs = true; // Optionally include event properties with breadcrumbs
o.ShutdownTimeoutSeconds = 5;
//Optionally specify user properties via NLog (here using MappedDiagnosticsLogicalContext as an example)
o.User = new SentryNLogUser
{
Id = "${mdlc:item=id}",
Username = "${mdlc:item=username}",
Email = "${mdlc:item=email}",
Other =
{
new TargetPropertyWithContext("mood", "joyous")
},
};
o.AddTag("logger", "${logger}"); // Send the logger name as a tag
// Other configuration
Expand All @@ -115,8 +143,8 @@ private static void UsingCodeConfiguration()

LogManager.Configuration = config;

var Log = LogManager.GetCurrentClassLogger();
DemoLogger(Log);
var log = LogManager.GetCurrentClassLogger();
DemoLogger(log);
}
}

Expand Down
16 changes: 15 additions & 1 deletion src/Sentry.NLog/PublicAPI.Unshipped.txt
@@ -1 +1,15 @@

Sentry.NLog.SentryNLogOptions.User.get -> Sentry.NLog.SentryNLogUser
Sentry.NLog.SentryNLogOptions.User.set -> void
Sentry.NLog.SentryNLogUser
Sentry.NLog.SentryNLogUser.Email.get -> NLog.Layouts.Layout
Sentry.NLog.SentryNLogUser.Email.set -> void
Sentry.NLog.SentryNLogUser.Id.get -> NLog.Layouts.Layout
Sentry.NLog.SentryNLogUser.Id.set -> void
Sentry.NLog.SentryNLogUser.IpAddress.get -> NLog.Layouts.Layout
Sentry.NLog.SentryNLogUser.IpAddress.set -> void
Sentry.NLog.SentryNLogUser.Other.get -> System.Collections.Generic.IList<NLog.Targets.TargetPropertyWithContext>
Sentry.NLog.SentryNLogUser.SentryNLogUser() -> void
Sentry.NLog.SentryNLogUser.Username.get -> NLog.Layouts.Layout
Sentry.NLog.SentryNLogUser.Username.set -> void
Sentry.NLog.SentryTarget.User.get -> Sentry.NLog.SentryNLogUser
Sentry.NLog.SentryTarget.User.set -> void
6 changes: 6 additions & 0 deletions src/Sentry.NLog/SentryNLogOptions.cs
Expand Up @@ -89,5 +89,11 @@ public int ShutdownTimeoutSeconds
/// This might be not ideal when using multiple integrations in case you want another one doing the Init.
/// </remarks>
public bool InitializeSdk { get; set; } = true;

/// <summary>
/// Optionally configure one or more parts of the user information to be rendered dynamically from an NLog layout
/// </summary>
[NLogConfigurationIgnoreProperty] // Configure this directly on the target in XML config.
public SentryNLogUser User { get; set; } = new SentryNLogUser();
}
}
43 changes: 43 additions & 0 deletions src/Sentry.NLog/SentryNLogUser.cs
@@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.Generic;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;

namespace Sentry.NLog
{
/// <summary>
/// A helper class used to configure Sentry user properties using NLog layouts
/// </summary>
public class SentryNLogUser
{
/// <summary>
/// A <see cref="Layout"/> used to dynamically specify the id of a user for a sentry event.
/// </summary>
public Layout Id { get; set; }

/// <summary>
/// A <see cref="Layout"/> used to dynamically specify the username of a user for a sentry event.
/// </summary>
public Layout Username { get; set; }

/// <summary>
/// A <see cref="Layout"/> used to dynamically specify the email of a user for a sentry event.
/// </summary>
public Layout Email { get; set; }

/// <summary>
/// A <see cref="Layout"/> used to dynamically specify the ip address of a user for a sentry event.
/// </summary>
public Layout IpAddress { get; set; }

/// <summary>
/// Additional information about the user
/// </summary>
/// <summary>
/// Add any desired additional tags that will be sent with every message.
/// </summary>
[ArrayParameter(typeof(TargetPropertyWithContext), "other")]
public IList<TargetPropertyWithContext> Other { get; } = new List<TargetPropertyWithContext>();
}
}
29 changes: 29 additions & 0 deletions src/Sentry.NLog/SentryTarget.cs
Expand Up @@ -194,6 +194,15 @@ public int FlushTimeoutSeconds
set => Options.FlushTimeout = TimeSpan.FromSeconds(value);
}

/// <summary>
/// Optionally configure one or more parts of the user information to be rendered dynamically from an NLog layout
/// </summary>
public SentryNLogUser User
{
get => Options.User;
set => Options.User = value;
}

/// <inheritdoc />
protected override void CloseTarget()
{
Expand Down Expand Up @@ -284,6 +293,7 @@ protected override void Write(LogEventInfo logEvent)
Level = logEvent.Level.ToSentryLevel(),
Release = Options.Release,
Environment = Options.Environment,
User = GetUser(logEvent),
};

evt.Sdk.AddPackage(ProtocolPackageName, NameAndVersion.Version);
Expand Down Expand Up @@ -345,6 +355,23 @@ protected override void Write(LogEventInfo logEvent)
}
}

private User GetUser(LogEventInfo logEvent)
{
if (User == null)
{
return null;
}

return new User
{
Email = User.Email?.Render(logEvent),
Id = User.Id?.Render(logEvent),
IpAddress = User.IpAddress?.Render(logEvent),
Username = User.Username?.Render(logEvent),
Other = User.Other.ToDictionary(a => a.Name, b => b.Layout.Render(logEvent)),
};
}

private IEnumerable<KeyValuePair<string, string>> GetTagsFromLogEvent(LogEventInfo logEvent)
{
if (SendEventPropertiesAsTags)
Expand All @@ -364,7 +391,9 @@ protected override void Write(LogEventInfo logEvent)
{
var tagValue = RenderLogEvent(tag.Layout, logEvent);
if (!tag.IncludeEmptyValue && string.IsNullOrEmpty(tagValue))
{
continue;
}

yield return new KeyValuePair<string, string>(tag.Name, tagValue);
}
Expand Down
2 changes: 1 addition & 1 deletion test/Sentry.NLog.Tests/SentryNLogOptionsTests.cs
Expand Up @@ -3,7 +3,7 @@

namespace Sentry.NLog.Tests
{
public class SentrySerilogOptionsTests
public class SentryNLogOptionsTests
{
[Fact]
public void Ctor_MinimumBreadcrumbLevel_Information()
Expand Down
48 changes: 48 additions & 0 deletions test/Sentry.NLog.Tests/SentryTargetTests.cs
Expand Up @@ -114,6 +114,36 @@ public void Can_configure_from_xml_file()
Assert.Equal("Development", t.Options.Environment);
}

[Fact]
public void Can_configure_user_from_xml_file()
{
var configXml = $@"
<nlog throwConfigExceptions='true'>
<extensions>
<add type='{typeof(SentryTarget).AssemblyQualifiedName}' />
</extensions>
<targets>
<target type='Sentry' name='sentry' dsn='{ValidDsnWithoutSecret}'>
<user username=""myUser"">
<other name='mood' layout='joyous'/>
</user>
</target>
</targets>
</nlog>";

var stringReader = new System.IO.StringReader(configXml);
var xmlReader = System.Xml.XmlReader.Create(stringReader);
var c = new XmlLoggingConfiguration(xmlReader, null);

var t = c.FindTargetByName("sentry") as SentryTarget;
Assert.NotNull(t);
Assert.Equal(ValidDsnWithoutSecret, t.Options.Dsn.ToString());
Assert.Equal("'myUser'", t.User.Username.ToString());
Assert.NotEmpty(t.User.Other);
Assert.Equal("mood", t.User.Other[0].Name);
Assert.Equal("'joyous'", t.User.Other[0].Layout.ToString());
}

[Fact]
public void Shutdown_DisposesSdk()
{
Expand Down Expand Up @@ -685,6 +715,24 @@ public void GetTagsFromLogEvent_NullPropertiesMapped()
Assert.Null(b.Data["a"]);
}

[Fact]
public void GetUserFromLayouts_PropertiesMapped()
{
var factory = _fixture.GetLoggerFactory();
var sentryTarget = factory.Configuration.FindTargetByName<SentryTarget>("sentry");
sentryTarget.User = new SentryNLogUser
{
Username = "${logger:shortName=true}"
};

var logger = factory.GetLogger("sentry");

logger.Fatal(DefaultMessage);

_fixture.Hub.Received(1)
.CaptureEvent(Arg.Is<SentryEvent>(e => e.User.Username == "sentry"));
}

internal class LogLevelData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
Expand Down

0 comments on commit dcea346

Please sign in to comment.