LoggingContext.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. namespace Best.HTTP.Shared.Logger
  4. {
  5. /// <summary>
  6. /// Represents a logging context for categorizing and organizing log messages.
  7. /// </summary>
  8. /// <remarks>
  9. /// The LoggingContext class is used to provide additional context information
  10. /// to log messages, allowing for better categorization and organization of log output. It can be
  11. /// associated with specific objects or situations to enrich log entries with context-specific data.
  12. /// </remarks>
  13. public sealed class LoggingContext
  14. {
  15. /// <summary>
  16. /// Gets the unique hash value of this logging context.
  17. /// </summary>
  18. public string Hash { get; private set; }
  19. private enum LoggingContextFieldType
  20. {
  21. Long,
  22. Bool,
  23. String,
  24. AnotherContext
  25. }
  26. private struct LoggingContextField
  27. {
  28. public string key;
  29. public long longValue;
  30. public bool boolValue;
  31. public string stringValue;
  32. public LoggingContext loggingContextValue;
  33. public LoggingContextFieldType fieldType;
  34. public override string ToString()
  35. {
  36. object value = this.fieldType switch
  37. {
  38. LoggingContextFieldType.Bool => this.boolValue,
  39. LoggingContextFieldType.Long => this.longValue,
  40. LoggingContextFieldType.String => this.stringValue,
  41. _ => this.loggingContextValue
  42. };
  43. return $"[{this.key} => '{value}']";
  44. }
  45. }
  46. private List<LoggingContextField> fields = new List<LoggingContextField>(2);
  47. /// <summary>
  48. /// Initializes a new instance of the LoggingContext class associated with the specified object.
  49. /// </summary>
  50. /// <param name="boundto">The object to associate the context with.</param>
  51. public LoggingContext(object boundto)
  52. {
  53. var name = boundto.GetType().Name;
  54. Add("TypeName", name);
  55. UnityEngine.Hash128 hash = new UnityEngine.Hash128();
  56. hash.Append(name);
  57. hash.Append(boundto.GetHashCode());
  58. hash.Append(this.GetHashCode());
  59. this.Hash = hash.ToString();
  60. Add("Hash", this.Hash);
  61. }
  62. /// <summary>
  63. /// Adds a <c>long</c> value to the logging context.
  64. /// </summary>
  65. /// <param name="key">The key to associate with the value.</param>
  66. /// <param name="value">The <c>long</c> value to add.</param>
  67. public void Add(string key, long value) => Add(new LoggingContextField { fieldType = LoggingContextFieldType.Long, key = key, longValue = value });
  68. /// <summary>
  69. /// Adds a <c>bool</c> value to the logging context.
  70. /// </summary>
  71. /// <param name="key">The key to associate with the value.</param>
  72. /// <param name="value">The <c>bool</c> value to add.</param>
  73. public void Add(string key, bool value) => Add(new LoggingContextField { fieldType = LoggingContextFieldType.Bool, key = key, boolValue = value });
  74. /// <summary>
  75. /// Adds a <c>string</c> value to the logging context.
  76. /// </summary>
  77. /// <param name="key">The key to associate with the value.</param>
  78. /// <param name="value">The <c>string</c> value to add.</param>
  79. public void Add(string key, string value) => Add(new LoggingContextField { fieldType = LoggingContextFieldType.String, key = key, stringValue = value });
  80. /// <summary>
  81. /// Adds a <c>LoggingContext</c> value to the logging context.
  82. /// </summary>
  83. /// <param name="key">The key to associate with the value.</param>
  84. /// <param name="value">The <c>LoggingContext</c> value to add.</param>
  85. public void Add(string key, LoggingContext value) => Add(new LoggingContextField { fieldType = LoggingContextFieldType.AnotherContext, key = key, loggingContextValue = value });
  86. private void Add(LoggingContextField field)
  87. {
  88. Remove(field.key);
  89. this.fields.Add(field);
  90. }
  91. /// <summary>
  92. /// Gets the <c>string</c> field with the specified name from the logging context.
  93. /// </summary>
  94. /// <param name="fieldName">The name of the <c>string</c> field to retrieve.</param>
  95. /// <returns>The value of the <c>string</c> field or <c>null</c> if not found.</returns>
  96. public string GetStringField(string fieldName) => this.fields.FirstOrDefault(f => f.key == fieldName).stringValue;
  97. /// <summary>
  98. /// Removes a field from the logging context by its key.
  99. /// </summary>
  100. /// <param name="key">The key of the field to remove.</param>
  101. public void Remove(string key) => this.fields.RemoveAll(field => field.key == key);
  102. /// <summary>
  103. /// Converts the logging context and its associated fields to a JSON string representation.
  104. /// </summary>
  105. /// <param name="sb">A <see cref="System.Text.StringBuilder"/> instance to which the JSON string is appended.</param>
  106. /// <remarks>
  107. /// This method serializes the logging context and its associated fields into a JSON format
  108. /// for structured logging purposes. The resulting JSON string represents the context and its fields, making it
  109. /// suitable for inclusion in log entries for better analysis and debugging.
  110. /// </remarks>
  111. public void ToJson(System.Text.StringBuilder sb)
  112. {
  113. if (this.fields == null || this.fields.Count == 0)
  114. {
  115. sb.Append("null");
  116. return;
  117. }
  118. sb.Append("{");
  119. for (int i = 0; i < this.fields.Count; ++i)
  120. {
  121. var field = this.fields[i];
  122. if (field.fieldType != LoggingContextFieldType.AnotherContext)
  123. {
  124. if (i > 0)
  125. sb.Append(", ");
  126. sb.AppendFormat("\"{0}\": ", field.key);
  127. }
  128. switch (field.fieldType)
  129. {
  130. case LoggingContextFieldType.Long:
  131. sb.Append(field.longValue);
  132. break;
  133. case LoggingContextFieldType.Bool:
  134. sb.Append(field.boolValue ? "true" : "false");
  135. break;
  136. case LoggingContextFieldType.String:
  137. sb.AppendFormat("\"{0}\"", Escape(field.stringValue));
  138. break;
  139. }
  140. }
  141. sb.Append("}");
  142. for (int i = 0; i < this.fields.Count; ++i)
  143. {
  144. var field = this.fields[i];
  145. if (field.loggingContextValue == null)
  146. continue;
  147. switch (field.fieldType)
  148. {
  149. case LoggingContextFieldType.AnotherContext:
  150. sb.Append(", ");
  151. field.loggingContextValue.ToJson(sb);
  152. break;
  153. }
  154. }
  155. }
  156. public static string Escape(string original)
  157. {
  158. return Best.HTTP.Shared.PlatformSupport.Text.StringBuilderPool.ReleaseAndGrab(Best.HTTP.Shared.PlatformSupport.Text.StringBuilderPool.Get(1)
  159. .Append(original)
  160. .Replace("\\", "\\\\")
  161. .Replace("\"", "\\\"")
  162. .Replace("/", "\\/")
  163. .Replace("\b", "\\b")
  164. .Replace("\f", "\\f")
  165. .Replace("\n", "\\n")
  166. .Replace("\r", "\\r")
  167. .Replace("\t", "\\t"));
  168. }
  169. }
  170. }