Skip to content

Commit

Permalink
Merge pull request serilog#751 from nblumhardt/f-factorymethods
Browse files Browse the repository at this point in the history
Add BindMessageTemplate() and BindProperty() to ILogger
  • Loading branch information
khellang committed May 26, 2016
2 parents 2b45b91 + 4736691 commit 3734491
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 11 deletions.
71 changes: 65 additions & 6 deletions src/Serilog/Core/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ public ILogger ForContext(IEnumerable<ILogEventEnricher> enrichers)
/// <summary>
/// Create a logger that enriches log events with the specified property.
/// </summary>
/// <param name="propertyName">The name of the property. Must be non-empty.</param>
/// <param name="value">The property value.</param>
/// <param name="destructureObjects">If true, the value will be serialized as a structured
/// object if possible; if false, the object will be recorded as a scalar or simple array.</param>
/// <returns>A logger that will enrich log events as specified.</returns>
public ILogger ForContext(string propertyName, object value, bool destructureObjects = false)
{
Expand Down Expand Up @@ -331,21 +335,19 @@ public void Write<T0, T1, T2>(LogEventLevel level, Exception exception, string m
[MessageTemplateFormatMethod("messageTemplate")]
public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues)
{
if (messageTemplate == null) return;
if (!IsEnabled(level)) return;
if (messageTemplate == null) return;

// Catch a common pitfall when a single non-object array is cast to object[]
if (propertyValues != null &&
propertyValues.GetType() != typeof(object[]))
propertyValues = new object[] { propertyValues };

var now = DateTimeOffset.Now;

MessageTemplate parsedTemplate;
IEnumerable<LogEventProperty> properties;
_messageTemplateProcessor.Process(messageTemplate, propertyValues, out parsedTemplate, out properties);
IEnumerable<LogEventProperty> boundProperties;
_messageTemplateProcessor.Process(messageTemplate, propertyValues, out parsedTemplate, out boundProperties);

var logEvent = new LogEvent(now, level, exception, parsedTemplate, properties);
var logEvent = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, boundProperties);
Dispatch(logEvent);
}

Expand Down Expand Up @@ -1276,6 +1278,63 @@ public void Fatal(Exception exception, string messageTemplate, params object[] p
Write(LogEventLevel.Fatal, exception, messageTemplate, propertyValues);
}

/// <summary>
/// Uses configured scalar conversion and destructuring rules to bind a set of properties to a
/// message template. Returns false if the template or values are invalid (<summary>ILogger</summary>
/// methods never throw exceptions).
/// </summary>
/// <param name="messageTemplate">Message template describing an event.</param>
/// <param name="propertyValues">Objects positionally formatted into the message template.</param>
/// <param name="parsedTemplate">The internal representation of the template, which may be used to
/// render the <paramref name="boundProperties"/> as text.</param>
/// <param name="boundProperties">Captured properties from the template and <paramref name="propertyValues"/>.</param>
/// <example>
/// MessageTemplate template;
/// IEnumerable&lt;LogEventProperty&gt; properties>;
/// if (Log.BindMessageTemplate("Hello, {Name}!", new[] { "World" }, out template, out properties)
/// {
/// var propsByName = properties.ToDictionary(p => p.Name, p => p.Value);
/// Console.WriteLine(template.Render(propsByName, null));
/// // -> "Hello, World!"
/// }
/// </example>
[MessageTemplateFormatMethod("messageTemplate")]
public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, out IEnumerable<LogEventProperty> boundProperties)
{
if (messageTemplate == null)
{
parsedTemplate = null;
boundProperties = null;
return false;
}

_messageTemplateProcessor.Process(messageTemplate, propertyValues, out parsedTemplate, out boundProperties);
return true;
}

/// <summary>
/// Uses configured scalar conversion and destructuring rules to bind a property value to its captured
/// representation.
/// </summary>
/// <returns>True if the property could be bound, otherwise false (<summary>ILogger</summary>
/// <param name="propertyName">The name of the property. Must be non-empty.</param>
/// <param name="value">The property value.</param>
/// <param name="destructureObjects">If true, the value will be serialized as a structured
/// object if possible; if false, the object will be recorded as a scalar or simple array.</param>
/// <param name="property">The resulting property.</param>
/// methods never throw exceptions).</returns>
public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property)
{
if (!LogEventProperty.IsValidName(propertyName))
{
property = null;
return false;
}

property =_messageTemplateProcessor.CreateProperty(propertyName, value, destructureObjects);
return true;
}

/// <summary>
/// Close and flush the logging pipeline.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/Serilog/Core/Pipeline/SilentLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,5 +333,19 @@ public void Fatal<T0, T1, T2>(Exception exception, string messageTemplate, T0 pr
public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues)
{
}

[MessageTemplateFormatMethod("messageTemplate")]
public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, out IEnumerable<LogEventProperty> boundProperties)
{
parsedTemplate = null;
boundProperties = null;
return false;
}

public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property)
{
property = null;
return false;
}
}
}
41 changes: 41 additions & 0 deletions src/Serilog/ILogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ public interface ILogger
/// <summary>
/// Create a logger that enriches log events with the specified property.
/// </summary>
/// <param name="propertyName">The name of the property. Must be non-empty.</param>
/// <param name="value">The property value.</param>
/// <param name="destructureObjects">If true, the value will be serialized as a structured
/// object if possible; if false, the object will be recorded as a scalar or simple array.</param>
/// <returns>A logger that will enrich log events as specified.</returns>
ILogger ForContext(string propertyName, object value, bool destructureObjects = false);

Expand Down Expand Up @@ -899,5 +903,42 @@ public interface ILogger
/// </example>
[MessageTemplateFormatMethod("messageTemplate")]
void Fatal(Exception exception, string messageTemplate, params object[] propertyValues);

/// <summary>
/// Uses configured scalar conversion and destructuring rules to bind a set of properties to a
/// message template. Returns false if the template or values are invalid (<summary>ILogger</summary>
/// methods never throw exceptions).
/// </summary>
/// <param name="messageTemplate">Message template describing an event.</param>
/// <param name="propertyValues">Objects positionally formatted into the message template.</param>
/// <param name="parsedTemplate">The internal representation of the template, which may be used to
/// render the <paramref name="boundProperties"/> as text.</param>
/// <param name="boundProperties">Captured properties from the template and <paramref name="propertyValues"/>.</param>
/// <example>
/// MessageTemplate template;
/// IEnumerable&lt;LogEventProperty&gt; properties>;
/// if (Log.BindMessageTemplate("Hello, {Name}!", new[] { "World" }, out template, out properties)
/// {
/// var propsByName = properties.ToDictionary(p => p.Name, p => p.Value);
/// Console.WriteLine(template.Render(propsByName, null));
/// // -> "Hello, World!"
/// }
/// </example>
[MessageTemplateFormatMethod("messageTemplate")]
bool BindMessageTemplate(string messageTemplate, object[] propertyValues,
out MessageTemplate parsedTemplate, out IEnumerable<LogEventProperty> boundProperties);

/// <summary>
/// Uses configured scalar conversion and destructuring rules to bind a property value to its captured
/// representation.
/// </summary>
/// <returns>True if the property could be bound, otherwise false (<summary>ILogger</summary>
/// <param name="propertyName">The name of the property. Must be non-empty.</param>
/// <param name="value">The property value.</param>
/// <param name="destructureObjects">If true, the value will be serialized as a structured
/// object if possible; if false, the object will be recorded as a scalar or simple array.</param>
/// <param name="property">The resulting property.</param>
/// methods never throw exceptions).</returns>
bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property);
}
}
27 changes: 27 additions & 0 deletions src/Serilog/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Threading;
using Serilog.Core;
using Serilog.Core.Pipeline;
Expand Down Expand Up @@ -1158,5 +1159,31 @@ public static void Fatal(Exception exception, string messageTemplate, params obj
{
Logger.Fatal(exception, messageTemplate, propertyValues);
}

/// <summary>
/// Uses configured scalar conversion and destructuring rules to bind a set of properties to a
/// message template. Returns false if the template or values are invalid (<summary>ILogger</summary>
/// methods never throw exceptions).
/// </summary>
/// <param name="messageTemplate">Message template describing an event.</param>
/// <param name="propertyValues">Objects positionally formatted into the message template.</param>
/// <param name="parsedTemplate">The internal representation of the template, which may be used to
/// render the <paramref name="boundProperties"/> as text.</param>
/// <param name="boundProperties">Captured properties from the template and <paramref name="propertyValues"/>.</param>
/// <example>
/// MessageTemplate template;
/// IEnumerable&lt;LogEventProperty&gt; properties>;
/// if (Log.BindMessageTemplate("Hello, {Name}!", new[] { "World" }, out template, out properties)
/// {
/// var propsByName = properties.ToDictionary(p => p.Name, p => p.Value);
/// Console.WriteLine(template.Render(propsByName, null));
/// // -> "Hello, World!"
/// }
/// </example>
[MessageTemplateFormatMethod("messageTemplate")]
public static bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, out IEnumerable<LogEventProperty> boundProperties)
{
return Logger.BindMessageTemplate(messageTemplate, propertyValues, out parsedTemplate, out boundProperties);
}
}
}
39 changes: 34 additions & 5 deletions test/Serilog.Tests/Core/LoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ public void AnExceptionThrownByAnEnricherIsNotPropagated()

var l = new LoggerConfiguration()
.WriteTo.Sink(new StringSink())
.Enrich.With(new DelegatingEnricher((le, pf) => {
.Enrich.With(new DelegatingEnricher((le, pf) =>
{
thrown = true;
throw new Exception("No go, pal."); }))
throw new Exception("No go, pal.");
}))
.CreateLogger();

l.Information(Some.String());
Expand All @@ -32,7 +34,7 @@ public void AnExceptionThrownByAnEnricherIsNotPropagated()
public void AContextualLoggerAddsTheSourceTypeName()
{
var evt = DelegatingSink.GetLogEvent(l => l.ForContext<LoggerTests>()
.Information(Some.String()));
.Information(Some.String()));

var lv = evt.Properties[Constants.SourceContextPropertyName].LiteralValue();
Assert.Equal(typeof(LoggerTests).FullName, lv);
Expand All @@ -45,8 +47,8 @@ public void PropertiesInANestedContextOverrideParentContextValues()
var v1 = Some.Int();
var v2 = Some.Int();
var evt = DelegatingSink.GetLogEvent(l => l.ForContext(name, v1)
.ForContext(name, v2)
.Write(Some.InformationEvent()));
.ForContext(name, v2)
.Write(Some.InformationEvent()));

var pActual = evt.Properties[name];
Assert.Equal(v2, pActual.LiteralValue());
Expand Down Expand Up @@ -87,5 +89,32 @@ public void LoggingLevelSwitchDynamicallyChangesLevel()
Assert.Equal(4, events.Count);
Assert.True(events.All(evt => evt.RenderMessage() == "Emitted"));
}

[Fact]
public void MessageTemplatesCanBeBound()
{
var log = new LoggerConfiguration()
.CreateLogger();

MessageTemplate template;
IEnumerable<LogEventProperty> properties;
Assert.True(log.BindMessageTemplate("Hello, {Name}!", new object[] { "World" }, out template, out properties));

Assert.Equal("Hello, {Name}!", template.Text);
Assert.Equal("World", properties.Single().Value.LiteralValue());
}

[Fact]
public void PropertiesCanBeBound()
{
var log = new LoggerConfiguration()
.CreateLogger();

LogEventProperty property;
Assert.True(log.BindProperty("Name", "World", false, out property));

Assert.Equal("Name", property.Name);
Assert.Equal("World", property.Value.LiteralValue());
}
}
}
10 changes: 10 additions & 0 deletions test/Serilog.Tests/Support/DisposableLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,5 +398,15 @@ public void Fatal(Exception exception, string messageTemplate)
{
throw new NotImplementedException();
}

public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, out IEnumerable<LogEventProperty> boundProperties)
{
throw new NotImplementedException();
}

public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property)
{
throw new NotImplementedException();
}
}
}

0 comments on commit 3734491

Please sign in to comment.