Skip to content

Instantly share code, notes, and snippets.

@GeorgeTsiokos
Last active January 10, 2018 18:59
Show Gist options
  • Save GeorgeTsiokos/3128ff4d56b27b948fbb222f88f8d903 to your computer and use it in GitHub Desktop.
Save GeorgeTsiokos/3128ff4d56b27b948fbb222f88f8d903 to your computer and use it in GitHub Desktop.
Simple high performance c# JsonLogger with auto-flush
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