using System; using System.Collections.Generic; using System.Text; namespace BestHTTP.Logger { public sealed class LoggingContext { private enum LoggingContextFieldType { Long, Bool, String, AnotherContext } private struct LoggingContextField { public string key; public long longValue; public bool boolValue; public string stringValue; public LoggingContext loggingContextValue; public LoggingContextFieldType fieldType; } private List fields = null; private static System.Random random = new System.Random(); private LoggingContext() { } public LoggingContext(object boundto) { var name = boundto.GetType().Name; Add("TypeName", name); long hash = 0; lock (random) hash = ((long)boundto.GetHashCode() << 32 | (long)name.GetHashCode()) ^ ((long)this.GetHashCode() << 16) | (long)(random.Next(int.MaxValue) << 32) | (long)random.Next(int.MaxValue); Add("Hash", hash); } public void Add(string key, long value) { Add(new LoggingContextField { fieldType = LoggingContextFieldType.Long, key = key, longValue = value }); } public void Add(string key, bool value) { Add(new LoggingContextField { fieldType = LoggingContextFieldType.Bool, key = key, boolValue = value }); } public void Add(string key, string value) { Add(new LoggingContextField { fieldType = LoggingContextFieldType.String, key = key, stringValue = value }); } public void Add(string key, LoggingContext value) { Add(new LoggingContextField { fieldType = LoggingContextFieldType.AnotherContext, key = key, loggingContextValue = value }); } private void Add(LoggingContextField field) { if (this.fields == null) this.fields = new List(); Remove(field.key); this.fields.Add(field); } public void Remove(string key) { this.fields.RemoveAll(field => field.key == key); } public LoggingContext Clone() { LoggingContext newContext = new LoggingContext(); if (this.fields != null && this.fields.Count > 0) { newContext.fields = new List(this.fields.Count); for (int i = 0; i < this.fields.Count; ++i) { var field = this.fields[i]; switch (field.fieldType) { case LoggingContextFieldType.Long: case LoggingContextFieldType.Bool: case LoggingContextFieldType.String: newContext.fields.Add(field); break; case LoggingContextFieldType.AnotherContext: newContext.Add(field.key, field.loggingContextValue.Clone()); break; } } } return newContext; } public void ToJson(System.Text.StringBuilder sb) { if (this.fields == null || this.fields.Count == 0) { sb.Append("null"); return; } sb.Append("{"); for (int i = 0; i < this.fields.Count; ++i) { var field = this.fields[i]; if (field.fieldType != LoggingContextFieldType.AnotherContext) { if (i > 0) sb.Append(", "); sb.AppendFormat("\"{0}\": ", field.key); } switch (field.fieldType) { case LoggingContextFieldType.Long: sb.Append(field.longValue); break; case LoggingContextFieldType.Bool: sb.Append(field.boolValue ? "true" : "false"); break; case LoggingContextFieldType.String: sb.AppendFormat("\"{0}\"", Escape(field.stringValue)); break; } } sb.Append("}"); for (int i = 0; i < this.fields.Count; ++i) { var field = this.fields[i]; switch (field.fieldType) { case LoggingContextFieldType.AnotherContext: sb.Append(", "); field.loggingContextValue.ToJson(sb); break; } } } public static string Escape(string original) { return new StringBuilder(original) .Replace("\\", "\\\\") .Replace("\"", "\\\"") .Replace("/", "\\/") .Replace("\b", "\\b") .Replace("\f", "\\f") .Replace("\n", "\\n") .Replace("\r", "\\r") .Replace("\t", "\\t") .ToString(); } } }