Allow names for scopes with recording and rendering

parent 6b4f30c2
Pipeline #107 passed with stages
in 9 minutes and 39 seconds
using Microsoft.VisualStudio.TestTools.UnitTesting;
using KSharp.NChronicle.Core.Model;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace KSharp.NChronicle.Core.Tests.ForChronicle
{
......@@ -19,7 +18,7 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicle
[TestMethod]
[DynamicData(nameof(_chronicleLevel))]
public void ThenVerbosityIncreases(ChronicleLevel level)
public void ThenRecordVerbosityIncreases(ChronicleLevel level)
{
using (this._chronicle.ScopeIn())
using (this._chronicle.ScopeIn())
......@@ -34,7 +33,7 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicle
[TestMethod]
[DynamicData(nameof(_chronicleLevel))]
public void ThenVerbosityIncreasesInOtherChronicleInstances(ChronicleLevel level)
public void ThenRecordVerbosityIncreasesInOtherChronicleInstances(ChronicleLevel level)
{
using (this._chronicle.ScopeIn())
{
......@@ -47,6 +46,44 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicle
Assert.AreEqual(1, this._lastReceivedRecord.Verbosity);
}
[TestMethod]
public void ThenCurrentScopeIsTheNewScope()
{
var scope = this._chronicle.ScopeIn();
// Assert
Assert.AreEqual(scope, this._chronicle.CurrentScope);
}
[TestMethod]
public void ThenCurrentScopesNameIsAsGiven()
{
this._chronicle.ScopeIn("New Scope Name");
// Assert
Assert.AreEqual("New Scope Name", this._chronicle.CurrentScope.Name);
}
[TestMethod]
public void ThenCurrentScopesParentIsThePreviousScope()
{
var parentScope = this._chronicle.ScopeIn("Parent Scope Name");
this._chronicle.ScopeIn("New Scope Name");
// Assert
Assert.AreEqual(parentScope, this._chronicle.CurrentScope.Parent);
}
[TestMethod]
public void ThenCurrentScopesParentNameIsAsGiven()
{
this._chronicle.ScopeIn("Parent Scope Name");
this._chronicle.ScopeIn("New Scope Name");
// Assert
Assert.AreEqual("Parent Scope Name", this._chronicle.CurrentScope.Parent.Name);
}
}
}
......
......@@ -3,7 +3,6 @@ using KSharp.NChronicle.Core.Model;
using KSharp.NChronicle.Core.Abstractions;
using System;
using System.IO;
using System.Threading;
namespace KSharp.NChronicle.Core.Tests.ForChronicleLibrary
{
......@@ -36,14 +35,14 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleLibrary
{
this._exception = e;
}
this._record = new ChronicleRecord(ChronicleLevel.Critical, "This is a test message", this._exception, 3, "Tag1", "Tag2");
this._record = new ChronicleRecord(ChronicleLevel.Critical, "This is a test message", this._exception, new[] { "ascope", "innerscope" }, "Tag1", "Tag2");
}
[TestMethod]
public void ThenTheDefaultPatternIsAsExpected()
{
var result = this._library.ResolveMessageOutputWithDefaultPattern(this._record, TimeZoneInfo.Utc);
Assert.AreEqual($"{this._record.UtcTime.ToString("yyyy/MM/dd HH:mm:ss.fff")} [{this._record.ThreadId}] {this._record.Message} \n{this._record.Exception}\n[{String.Join(", ", this._record.Tags)}]", result);
Assert.AreEqual($"{this._record.UtcTime.ToString("yyyy/MM/dd HH:mm:ss.fff")} [{this._record.ThreadId}] [{String.Join(" › ", this._record.ScopeStack)}] {this._record.Message} \n{this._record.Exception}\n[{String.Join(", ", this._record.Tags)}]", result);
}
[TestMethod]
......@@ -74,6 +73,35 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleLibrary
Assert.AreEqual(this._record.Message, result);
}
[TestMethod]
public void ThenRecordVerbosityCanBeRendered()
{
var result = this._library.ResolveMessageOutput(this._record, TimeZoneInfo.Utc, "{VERBOSITY}");
Assert.AreEqual(this._record.Verbosity.ToString(), result);
}
[TestMethod]
public void ThenRecordScopeStackCanBeRendered()
{
var result = this._library.ResolveMessageOutput(this._record, TimeZoneInfo.Utc, "{SCOPE}");
Assert.AreEqual(string.Join(" › ", this._record.ScopeStack), result);
}
[TestMethod]
public void ThenRecordScopeStackCanBeRenderedWithAnAlternateDelimiter()
{
var result = this._library.ResolveMessageOutput(this._record, TimeZoneInfo.Utc, "{SCOPE|.}");
Assert.AreEqual(string.Join(".", this._record.ScopeStack), result);
}
[TestMethod]
public void ThenRecordScopeStackRendersUsingVerbosityForStacksWithNoName()
{
this._record.ScopeStack = new[] { "ascope", "innerscope", null, "currentscope" };
var result = this._library.ResolveMessageOutput(this._record, TimeZoneInfo.Utc, "{SCOPE}");
Assert.AreEqual("ascope › innerscope › 3 › currentscope", result);
}
[TestMethod]
public void ThenRecordTagsCanBeRendered()
{
......
......@@ -82,6 +82,13 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
Assert.IsFalse(this._recordOne.Equals(this._recordTwo), "The records are incorrectly considered equal.");
}
[TestMethod]
public void AndAllButTheScopeStackAreEqualThenTheChronicleRecordsAreNotEqual()
{
this._recordOne.ScopeStack = new string[] { "random1", "World 50" };
Assert.IsFalse(this._recordOne.Equals(this._recordTwo), "The records are incorrectly considered equal.");
}
[TestMethod]
public void AndAllButTheTagsAreEqualThenTheChronicleRecordsAreNotEqual()
{
......
......@@ -22,7 +22,7 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
private Exception _exception;
private string[] _tags;
private ChronicleLevel _level;
private int _verbosity;
private string[] _scopeStack;
[TestInitialize]
public void Init()
......@@ -31,8 +31,8 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
this._exception = new IOException();
this._tags = new[] { "Tag1", "Tag2" };
this._level = ChronicleLevel.Critical;
this._verbosity = 3;
this._chronicleRecord = new ChronicleRecord(this._level, this._message, this._exception, this._verbosity, this._tags);
this._scopeStack = new[] { "ascope", "innerscope" };
this._chronicleRecord = new ChronicleRecord(this._level, this._message, this._exception, this._scopeStack, this._tags);
}
[TestMethod]
......@@ -53,11 +53,30 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
Assert.AreEqual(this._message, this._chronicleRecord.Message, "The message is not as given.");
}
[TestMethod]
public void ThenTheScopeStackIsAsGiven()
{
Assert.IsTrue(new HashSet<string>(this._scopeStack).SetEquals(this._chronicleRecord.ScopeStack), "The scope stack is not as given.");
}
[TestMethod]
public void ThenTheScopeStackIsReadOnly()
{
Assert.IsInstanceOfType(this._chronicleRecord.ScopeStack, typeof(IReadOnlyCollection<string>), "The scope stack is not a type of IReadOnlyCollection.");
}
[TestMethod]
public void ThenTheScopeStackIsEmptyIfGivenNull()
{
this._chronicleRecord = new ChronicleRecord(this._level, this._message, this._exception, null, null);
Assert.IsFalse(this._chronicleRecord.ScopeStack.Any(), "The scope stack is not empty.");
}
[TestMethod]
public void ThenTheVerbosityIsAsGiven()
public void ThenTheVerbosityIsTheDepthOfTheScopeStack()
{
Assert.AreEqual(this._verbosity, this._chronicleRecord.Verbosity, "The verbosity is not as given.");
Assert.AreEqual(this._chronicleRecord.ScopeStack.Count(), this._chronicleRecord.Verbosity, "The verbosity is not the depth/length of the call stack.");
}
[TestMethod]
......@@ -75,7 +94,7 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
[TestMethod]
public void ThenTheTagsAreEmptyIfGivenNull()
{
this._chronicleRecord = new ChronicleRecord(this._level, this._message, this._exception, 0, null);
this._chronicleRecord = new ChronicleRecord(this._level, this._message, this._exception, null, null);
Assert.IsFalse(this._chronicleRecord.Tags.Any(), "The tags are not empty.");
}
......@@ -83,7 +102,7 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
[TestMethod]
public void ThenTheMessageIsNullIfGivenNull()
{
this._chronicleRecord = new ChronicleRecord(this._level, null, this._exception, 0, this._tags);
this._chronicleRecord = new ChronicleRecord(this._level, null, this._exception, null, this._tags);
Assert.IsNull(this._chronicleRecord.Message, "The message is not null.");
}
......@@ -91,7 +110,7 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
[TestMethod]
public void ThenTheExceptionIsNullIfGivenNull()
{
this._chronicleRecord = new ChronicleRecord(this._level, this._message, null, 0, this._tags);
this._chronicleRecord = new ChronicleRecord(this._level, this._message, null, null, this._tags);
Assert.IsNull(this._chronicleRecord.Exception, "The exception is not null.");
}
......
......@@ -46,6 +46,12 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
Assert.IsNull(this._chronicleRecord.Message, "The message is not null");
}
[TestMethod]
public void ThenTheScopeStackIsEmpty()
{
Assert.IsFalse(this._chronicleRecord.Tags.Any(), "The scope stack is not empty");
}
[TestMethod]
public void ThenTheTagsAreEmpty()
{
......
......@@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Collections;
namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
{
......@@ -51,15 +52,13 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
Assert.IsNotNull(deserializedRecord.Exception, "The exception was not deserialized.");
Assert.IsNotNull(deserializedRecord.Tags, "The tags were not deserialized.");
Assert.AreEqual(originalRecord.Verbosity, deserializedRecord.Verbosity, "The message was deserialized but incorrectly; the verbosity was incorrect.");
Assert.AreEqual(originalRecord.ThreadId, deserializedRecord.ThreadId, "The message was deserialized but incorrectly; the ThreadId was incorrect.");
Assert.AreEqual(originalRecord.Message, deserializedRecord.Message, "The message was deserialized but incorrectly; the message was incorrect.");
Assert.AreEqual(originalRecord.UtcTime, deserializedRecord.UtcTime, "The time was not deserialized correctly; the time was incorrect.");
Assert.AreEqual(originalRecord.Level, deserializedRecord.Level, "The level was not deserialized correctly; the level was incorrect.");
Assert.AreEqual(originalRecord.Tags.Count(), deserializedRecord.Tags.Count(), "The tags were not deserialized correctly.");
foreach (string tag in originalRecord.Tags)
Assert.IsTrue(deserializedRecord.Tags.Contains(tag), "The tags were not deserialized correctly.");
Assert.AreEqual(originalRecord.Verbosity, deserializedRecord.Verbosity, "The message was deserialized but incorrectly; the verbosity was incorrect.");
CollectionAssert.AreEqual(originalRecord.ScopeStack as ICollection, deserializedRecord.ScopeStack as ICollection, "The message was deserialized but incorrectly; the scope stack was incorrect.");
CollectionAssert.AreEquivalent(originalRecord.Tags as ICollection, deserializedRecord.Tags as ICollection, "The message was deserialized but incorrectly; the tags were incorrect.");
Assert.AreEqual(originalRecord.Exception.Message, deserializedRecord.Exception.Message, "The exception was deserialized but incorrectly; the message is incorrect.");
Assert.AreEqual(originalRecord.Exception.HResult, deserializedRecord.Exception.HResult, "The exception was deserialized but incorrectly; the HResult is incorrect.");
......@@ -107,7 +106,7 @@ namespace KSharp.NChronicle.Core.Tests.ForChronicleRecord
string[] tags = new[] { "Tag1", "Tag2" };
ChronicleLevel level = ChronicleLevel.Critical;
ChronicleRecord chronicleRecord = new ChronicleRecord(level, message, exception, 2, tags);
ChronicleRecord chronicleRecord = new ChronicleRecord(level, message, exception, new [] { "ascope", "aninnerscope" }, tags);
return (JsonConvert.SerializeObject(chronicleRecord), chronicleRecord);
}
......
using Microsoft.VisualStudio.TestTools.UnitTesting;
using KSharp.NChronicle.Core.Model;
namespace KSharp.NChronicle.Core.Tests.ForChronicleScope
{
public partial class WhenUsingAChronicleScope
{
[TestClass]
public partial class AndCreatingANewChronicleScope
{
private Chronicle _chronicle;
private string _parentScopeName;
private ChronicleScope _parentScope;
private string _scopeName;
private ChronicleScope _scope;
[TestInitialize]
public void Init()
{
this._chronicle = new Chronicle();
this._scopeName = "Nested scope name";
this._parentScopeName = "Scope name";
this._parentScope = new ChronicleScope(this._chronicle, null, this._parentScopeName);
this._scope = new ChronicleScope(this._chronicle, this._parentScope, this._scopeName);
}
[TestMethod]
public void ThenTheParentScopeIsAsGiven()
{
Assert.AreEqual(this._parentScope, this._scope.Parent);
}
[TestMethod]
public void ThenTheScopeNameIsAsGiven()
{
Assert.AreEqual(this._scopeName, this._scope.Name);
}
[TestMethod]
public void AndScopeNameNotGivenThenTheScopeNameIsNull()
{
this._scope = new ChronicleScope(this._chronicle, this._parentScope);
Assert.AreEqual(null, this._scope.Name);
}
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
using KSharp.NChronicle.Core.Model;
using System.Linq;
namespace KSharp.NChronicle.Core.Tests.ForChronicleScope
{
public partial class WhenUsingAChronicleScope
{
[TestClass]
public partial class AndWorkingWithTheScopeStack
{
private Chronicle _chronicle;
private string _parentParentScopeName;
private ChronicleScope _parentParentScope;
private ChronicleScope _parentScope;
private string _scopeName;
private ChronicleScope _scope;
[TestInitialize]
public void Init()
{
this._chronicle = new Chronicle();
this._scopeName = "Nested scope name";
this._parentParentScopeName = "Scope name";
this._parentParentScope = new ChronicleScope(this._chronicle, null, this._parentParentScopeName);
this._parentScope = new ChronicleScope(this._chronicle, this._parentParentScope, null);
this._scope = new ChronicleScope(this._chronicle, this._parentScope, this._scopeName);
}
[TestMethod]
public void ThenItCanBeEnumerated()
{
// Act
var count = 0;
foreach (var scope in this._scope)
{
count++;
}
// Assert
Assert.AreEqual(3, count, "Incorrect count of scopes when enumerating.");
}
[TestMethod]
public void ThenItEnumeratesLowestScopeFirst()
{
// Act
var bottomScope = this._scope.Last();
// Assert
Assert.AreEqual(this._parentParentScope, bottomScope, "The first scope when enumerating is not the lowest scope.");
}
[TestMethod]
public void ThenItEnumeratesHighestScopeLast()
{
// Act
var topScope = this._scope.First();
// Assert
Assert.AreEqual(this._scope, topScope, "The last scope when enumerating is not the top scope.");
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
......@@ -47,6 +48,7 @@ namespace KSharp.NChronicle.Core.Abstractions
protected ChronicleLibrary()
{
this.FunctionalKeywordHandlers = new Dictionary<string, FunctionalKeyworkdHandler> {
{"SCOPE", this.ScopeMethodHandler},
{"TAGS", this.TagsMethodHandler}
};
this.StandardKeywordHandlers = new Dictionary<string, StandardKeywordHandler> {
......@@ -54,6 +56,8 @@ namespace KSharp.NChronicle.Core.Abstractions
{"EXC", this.ExceptionKeyHandler},
{"EMSG", this.ExceptionMessageKeyHandler},
{"TH", this.ThreadKeyHandler},
{"VERBOSITY", this.VerbosityKeyHandler},
{"SCOPE", this.ScopeKeyHandler},
{"TAGS", this.TagsKeyHandler},
{"LVL", this.LevelKeyHandler}
};
......@@ -92,12 +96,14 @@ namespace KSharp.NChronicle.Core.Abstractions
/// Standard keywords available are:
/// </para>
/// <list>
/// <c>LVL</c> The level of this record.
/// <c>TAGS</c> The tags for the record delimited by a comma and a space (<c>, </c>).
/// <c>TH</c> The thread ID the record was created in.
/// <c>MSG</c> The developer message for the record if any. May be absent.
/// <c>EMSG</c> The exception message for the record if any. May be absent.
/// <c>EXC</c> The full exception for the record if any. May be absent.
/// <c>LVL</c> The level of this record.
/// <c>VERBOSITY</c> The scope stack depth for the record.
/// <c>SCOPE</c> The scope stack for the record with scopes delimited by a spaced single right pointing angle (<c> › </c>).
/// <c>TAGS</c> The tags for the record delimited by a comma and a space (<c>, </c>).
/// <c>TH</c> The thread ID the record was created in.
/// <c>MSG</c> The developer message for the record if any. May be absent.
/// <c>EMSG</c> The exception message for the record if any. May be absent.
/// <c>EXC</c> The full exception for the record if any. May be absent.
/// </list>
/// <para>
/// Functional tokens are tokens which may take in extra arguments to render; these
......@@ -108,6 +114,7 @@ namespace KSharp.NChronicle.Core.Abstractions
/// Functional keywords available are:
/// </para>
/// <list>
/// <c>SCOPE</c> The scope stack for the record, taking 1 string argument to be used as the delimiter.
/// <c>TAGS</c> Prints all the tags for the record, taking 1 string argument to be used as the delimiter.
/// </list>
/// <para>
......@@ -122,7 +129,7 @@ namespace KSharp.NChronicle.Core.Abstractions
/// The default output pattern is:
/// </para>
/// <code>
/// "{%yyyy/MM/dd HH:mm:ss.fff} [{TH}] {MSG?{MSG} {EXC?\n}}{EXC?{EXC}\n}{TAGS?[{TAGS}]}"
/// "{%yyyy/MM/dd HH:mm:ss.fff} [{TH}] {SCOPE?[{SCOPE}]} {MSG?{MSG} {EXC?\n}}{EXC?{EXC}\n}{TAGS?[{TAGS}]}"
/// </code>
/// </remarks>
/// <example>
......@@ -180,7 +187,7 @@ namespace KSharp.NChronicle.Core.Abstractions
/// <param name="pattern">The output pattern in which to render the record (see Remarks).</param>
/// <param name="timeZone">The time zone to which any date/times should be localised and rendered.</param>
/// <returns></returns>
protected string ResolveOutput(ChronicleRecord record, TimeZoneInfo timeZone, string pattern = "{%yyyy/MM/dd HH:mm:ss.fff} [{TH}] {MSG?{MSG} {EXC?\n}}{EXC?{EXC}\n}{TAGS?[{TAGS}]}")
protected string ResolveOutput(ChronicleRecord record, TimeZoneInfo timeZone, string pattern = "{%yyyy/MM/dd HH:mm:ss.fff} [{TH}] {SCOPE?[{SCOPE}]} {MSG?{MSG} {EXC?\n}}{EXC?{EXC}\n}{TAGS?[{TAGS}]}")
{
var output = pattern;
var currentTime = TimeZoneInfo.ConvertTime(record.UtcTime, TimeZoneInfo.Utc, timeZone);
......@@ -264,6 +271,11 @@ namespace KSharp.NChronicle.Core.Abstractions
return output;
}
private string ScopeMethodHandler(ChronicleRecord record, params string[] parameters)
{
return parameters.Length < 1 ? string.Empty : string.Join(parameters[0], record.ScopeStack.Select((s, i) => s ?? (i + 1).ToString()));
}
private string TagsMethodHandler(ChronicleRecord record, params string[] parameters)
{
return parameters.Length < 1 ? string.Empty : string.Join(parameters[0], record.Tags);
......@@ -289,6 +301,16 @@ namespace KSharp.NChronicle.Core.Abstractions
return record.ThreadId.ToString();
}
private string VerbosityKeyHandler(ChronicleRecord record)
{
return record.Verbosity.ToString();
}
private string ScopeKeyHandler(ChronicleRecord record)
{
return this.ScopeMethodHandler(record, " › ");
}
private string TagsKeyHandler(ChronicleRecord record)
{
return this.TagsMethodHandler(record, ", ");
......
......@@ -9,12 +9,31 @@ namespace KSharp.NChronicle.Core.Abstractions
public interface IChronicle
{
/// <summary>
/// The current <see cref="IChronicleScope"/> for the calling thread.
/// </summary>
IChronicleScope CurrentScope { get; }
IChronicleScope ScopeIn();
/// <summary>
/// Create a new child scope with the given <paramref name="scopeName"/> (optional)
/// from the current scope, and set it as the current scope, increasing the
/// verbosity level of records created on this thread.
/// </summary>
/// <param name="scopeName">The name for the new scope.</param>
IChronicleScope ScopeIn(string scopeName = null);
/// <summary>
/// Restore the current scope to the given <paramref name="scope"/>, setting
/// the verbosity level of records created on this thread to that of the given <paramref name="scope"/>.
/// Use this when using scopes in a multi-threaded and/or asynchronous context.
/// </summary>
/// <param name="scope">The name for the new scope.</param>
void ScopeIn(IChronicleScope scope);
/// <summary>
/// Restore the parent of the current scope as the new current scope,
/// decreasing the verbosity level of records created on this thread.
/// </summary>
void ScopeOut();
/// <summary>
......
......@@ -13,9 +13,15 @@ namespace KSharp.NChronicle.Core.Abstractions
/// <summary>
/// The scope depth from which this record was created.
/// Equivilent to <c>ScopeStack.Length</c>.
/// </summary>
int Verbosity { get; }
/// <summary>
/// The stack of scopes by name from which this record was created.
/// </summary>
IEnumerable<string> ScopeStack { get; }
/// <summary>
/// The managed thread Id for the thread on which this record was created.
/// </summary>
......
using System;
using System.Collections.Generic;
namespace KSharp.NChronicle.Core.Abstractions
{
public interface IChronicleScope : IDisposable {
/// <summary>
/// A scope in which to create more verbose <see cref="IChronicleRecord"/>s,
/// representing a context or area of execution. A scope can be apart
/// of a scope stack, navigatable via the <see cref="Parent"/> property
/// or by enumerating on this <see cref="IChronicleScope"/>.
/// </summary>
public interface IChronicleScope : IDisposable, IEnumerable<IChronicleScope>
{
/// <summary>
/// The name of this scope.
/// </summary>
string Name { get; }
/// <summary>
/// The verbosity level of this scope, equivilent to the
/// number of scopes in this stack of scopes.
/// </summary>
int Verbosity { get; }
/// <summary>
/// The immediately enclosing scope of this scope;
/// the next scope down the stack of scopes.
/// </summary>
IChronicleScope Parent { get; }
IChronicle Chronicle { get; }
}
}
\ No newline at end of file
......@@ -18,6 +18,9 @@ namespace KSharp.NChronicle.Core
[ThreadStatic]
private static IChronicleScope _currentScope;
/// <summary>
/// The current <see cref="IChronicleScope"/> for the calling thread.
/// </summary>
public IChronicleScope CurrentScope => _currentScope;
private ChronicleConfiguration _configuration;
......@@ -33,16 +36,32 @@ namespace KSharp.NChronicle.Core
this._tags = new ConcurrentBag<string>();
}
public IChronicleScope ScopeIn()
/// <summary>
/// Create a new child scope with the given <paramref name="scopeName"/> (optional)
/// from the current scope, and set it as the current scope, increasing the
/// verbosity level of records created on this thread.
/// </summary>
/// <param name="scopeName">The name for the new scope.</param>
public IChronicleScope ScopeIn(string scopeName = null)
{
return _currentScope = new ChronicleScope(this, CurrentScope);
return _currentScope = new ChronicleScope(this, CurrentScope, scopeName);
}
/// <summary>
/// Restore the current scope to the given <paramref name="scope"/>, setting
/// the verbosity level of records created on this thread to that of the given <paramref name="scope"/>.
/// Use this when using scopes in a multi-threaded and/or asynchronous context.
/// </summary>
/// <param name="scope">The name for the new scope.</param>
public void ScopeIn(IChronicleScope scope)
{
_currentScope = scope;
}
/// <summary>
/// Restore the parent of the current scope as the new current scope,
/// decreasing the verbosity level of records created on this thread.
/// </summary>
public void ScopeOut()
{
_currentScope = CurrentScope?.Parent;
......@@ -351,7 +370,8 @@ namespace KSharp.NChronicle.Core
private IChronicleRecord BuildRecord(ChronicleLevel level, string message, Exception exception, IEnumerable<string> tags)
{
IEnumerable<string> allTags = tags.Concat(this._tags);
return new ChronicleRecord(level, message, exception, CurrentScope?.Verbosity ?? 0, allTags.ToArray());
var scopeStack = CurrentScope?.Reverse().Select(s => s.Name).ToArray();
return new ChronicleRecord(level, message, exception, scopeStack, allTags.ToArray());
}
private void SendToLibraries(IChronicleRecord record)
......
using System;
using System.Collections.ObjectModel;
using System.Linq;
using KSharp.NChronicle.Core.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
......@@ -21,7 +22,7 @@ namespace KSharp.NChronicle.Core.Converters
var exception = record["exception"];
var level = record["level"];
var message = record["message"];
var verbosity = record["verbosity"];
var scopeStack = record["scopeStack"];
var tags = record["tags"];
if (threadId != null)
......@@ -34,10 +35,19 @@ namespace KSharp.NChronicle.Core.Converters
chronicleRecord.Level = level.ToObject<ChronicleLevel>();
if (message != null)
chronicleRecord.Message = message.Value<string>();
if (verbosity != null)
chronicleRecord.Verbosity = verbosity.Value<int>();
if (scopeStack != null)
{
var scopeStackValue = scopeStack.ToObject<ReadOnlyCollection<string>>();
if (scopeStackValue != null)
chronicleRecord.ScopeStack = scopeStackValue;
chronicleRecord.Verbosity = chronicleRecord.ScopeStack.Count();
}
if (tags != null)
chronicleRecord.Tags = tags.ToObject<ReadOnlyCollection<string>>();
{
var tagsValue = tags.ToObject<ReadOnlyCollection<string>>();
if (tagsValue != null)
chronicleRecord.Tags = tagsValue;
}
return chronicleRecord;
}
......@@ -49,12 +59,13 @@ namespace KSharp.NChronicle.Core.Converters
json.Add("threadId", JToken.FromObject(record.ThreadId));
json.Add("utcTime", JToken.FromObject(record.UtcTime));
json.Add("verbosity", JToken.FromObject(record.Verbosity));
json.Add("level", JToken.FromObject(record.Level));
if (record.Exception != null)
json.Add("exception", JToken.FromObject(record.Exception));
if (record.Message != null)
json.Add("message", JToken.FromObject(record.Message));
if (record.ScopeStack != null)
json.Add("scopeStack", JToken.FromObject(record.ScopeStack));