Last active
January 10, 2018 18:59
-
-
Save GeorgeTsiokos/3128ff4d56b27b948fbb222f88f8d903 to your computer and use it in GitHub Desktop.
Simple high performance c# JsonLogger with auto-flush
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public sealed class JsonLogger : IDisposable | |
{ | |
readonly string _fileName; | |
readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings | |
{ | |
NullValueHandling = NullValueHandling.Ignore, | |
Formatting = Formatting.None, | |
Converters = new JsonConverter[] {new StringEnumConverter()} | |
}); | |
readonly BlockingCollection<Line> _lines = new BlockingCollection<Line>(); | |
readonly Thread _thread; | |
/// <summary> | |
/// Creates a new JSON logger with the specified file name | |
/// </summary> | |
/// <param name="fileName">file to create</param> | |
public JsonLogger(string fileName) | |
{ | |
if (string.IsNullOrWhiteSpace(fileName)) | |
throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName)); | |
_fileName = fileName; | |
_thread = new Thread(Worker); | |
} | |
public void Dispose() | |
{ | |
if (!_lines.IsAddingCompleted) | |
_lines.CompleteAdding(); | |
if (_thread.IsAlive) | |
_thread.Join(); | |
} | |
/// <summary> | |
/// Logs the specified value | |
/// </summary> | |
/// <param name="value">value to log</param> | |
/// <param name="memberName">caller member name</param> | |
/// <param name="sourceFilePath">source file path</param> | |
/// <param name="sourceLineNumber">source line number</param> | |
/// <exception cref="T:System.ObjectDisposedException">The <see cref="JsonLogger"></see> has been disposed.</exception> | |
/// <exception cref="T:System.InvalidOperationException">The <see cref="JsonLogger"></see> is disposing.</exception> | |
public void Log(object value = null, [CallerMemberName] string memberName = null, [CallerFilePath] string sourceFilePath = null, [CallerLineNumber] int sourceLineNumber = 0) | |
{ | |
var line = value is null ? new Line() : new Line(JObject.FromObject(value, _jsonSerializer)); | |
if (null != memberName) | |
line.Data.Add("caller", JObject.FromObject(new | |
{ | |
memberName, | |
sourceFilePath = Path.GetFileName(sourceFilePath), | |
sourceLineNumber | |
})); | |
_lines.Add(line); | |
} | |
void Worker() | |
{ | |
using (var textWriter = new StreamWriter(_fileName, true, Encoding.UTF8, 32768) | |
{ | |
AutoFlush = false | |
}) | |
{ | |
if (_lines.IsCompleted) | |
return; | |
textWriter.Write("["); | |
try | |
{ | |
textWriter.Write("\r\n "); | |
textWriter.Write(_lines.Take()); | |
while (!_lines.IsCompleted) | |
if (_lines.TryTake(out var value, 1000)) | |
{ | |
textWriter.Write(",\r\n "); | |
textWriter.Write(value); | |
} | |
else | |
{ | |
textWriter.Flush(); | |
} | |
} | |
catch (OperationCanceledException) | |
{ | |
// ignore | |
} | |
finally | |
{ | |
textWriter.WriteLine("\r\n]"); | |
textWriter.Flush(); | |
} | |
} | |
} | |
class Line | |
{ | |
static readonly object _lock = new object(); | |
static long __t = Stopwatch.GetTimestamp(); | |
static long __i; | |
readonly long _i; | |
readonly long _t; | |
public Line(IDictionary<string, JToken> data) | |
{ | |
_i = Interlocked.Increment(ref __i); | |
_t = GetTicks(); | |
Data = data; | |
} | |
public Line() : this(new Dictionary<string, JToken>()) { } | |
public IDictionary<string, JToken> Data { get; } | |
IEnumerable<KeyValuePair<string, JToken>> Values | |
{ | |
get | |
{ | |
yield return new KeyValuePair<string, JToken>("I", _i); | |
yield return new KeyValuePair<string, JToken>("T", _t); | |
} | |
} | |
public override string ToString() => JsonConvert.SerializeObject(Values.Concat(Data).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); | |
static long GetTicks() | |
{ | |
long t; | |
var timestamp = Stopwatch.GetTimestamp(); | |
lock (_lock) | |
{ | |
t = __t; | |
__t = timestamp; | |
} | |
return timestamp - t; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment