1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Text; 7 using System.Globalization; 8 using System.IO; 9 using System.Collections; 10 11 namespace System.Diagnostics 12 { 13 public class DelimitedListTraceListener : TextWriterTraceListener 14 { 15 private string _delimiter = ";"; 16 private string _secondaryDelim = ","; 17 DelimitedListTraceListener(Stream stream)18 public DelimitedListTraceListener(Stream stream) : base(stream) 19 { 20 } 21 DelimitedListTraceListener(Stream stream, string name)22 public DelimitedListTraceListener(Stream stream, string name) : base(stream, name) 23 { 24 } 25 DelimitedListTraceListener(TextWriter writer)26 public DelimitedListTraceListener(TextWriter writer) : base(writer) 27 { 28 } 29 DelimitedListTraceListener(TextWriter writer, string name)30 public DelimitedListTraceListener(TextWriter writer, string name) : base(writer, name) 31 { 32 } 33 DelimitedListTraceListener(string fileName)34 public DelimitedListTraceListener(string fileName) : base(fileName) 35 { 36 } 37 DelimitedListTraceListener(string fileName, string name)38 public DelimitedListTraceListener(string fileName, string name) : base(fileName, name) 39 { 40 } 41 42 public string Delimiter 43 { 44 get 45 { 46 return _delimiter; 47 } 48 set 49 { 50 if (value == null) 51 throw new ArgumentNullException(nameof(Delimiter)); 52 53 if (value.Length == 0) 54 throw new ArgumentException(SR.Format(SR.Generic_ArgCantBeEmptyString, nameof(Delimiter))); 55 56 lock (this) 57 { 58 _delimiter = value; 59 } 60 61 if (_delimiter == ",") 62 _secondaryDelim = ";"; 63 else 64 _secondaryDelim = ","; 65 } 66 } 67 68 // base class method is protected internal but since its base class is in another assembly can't override it as protected internal because a CS0507 69 // warning would be hitted. GetSupportedAttributes()70 protected override string[] GetSupportedAttributes() => new string[] { "delimiter" }; 71 TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string format, params object[] args)72 public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string format, params object[] args) 73 { 74 if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null)) 75 return; 76 77 WriteHeader(source, eventType, id); 78 79 if (args != null) 80 WriteEscaped(String.Format(CultureInfo.InvariantCulture, format, args)); 81 else 82 WriteEscaped(format); 83 Write(Delimiter); // Use get_Delimiter 84 85 // one more delimiter for the data object 86 Write(Delimiter); // Use get_Delimiter 87 88 WriteFooter(eventCache); 89 } 90 TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string message)91 public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string message) 92 { 93 if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null)) 94 return; 95 96 WriteHeader(source, eventType, id); 97 98 WriteEscaped(message); 99 Write(Delimiter); // Use get_Delimiter 100 101 // one more delimiter for the data object 102 Write(Delimiter); // Use get_Delimiter 103 104 WriteFooter(eventCache); 105 } 106 TraceData(TraceEventCache eventCache, String source, TraceEventType eventType, int id, object data)107 public override void TraceData(TraceEventCache eventCache, String source, TraceEventType eventType, int id, object data) 108 { 109 if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, null, null, data, null)) 110 return; 111 112 WriteHeader(source, eventType, id); 113 114 // first a delimiter for the message 115 Write(Delimiter); // Use get_Delimiter 116 117 WriteEscaped(data.ToString()); 118 Write(Delimiter); // Use get_Delimiter 119 120 WriteFooter(eventCache); 121 } 122 TraceData(TraceEventCache eventCache, String source, TraceEventType eventType, int id, params object[] data)123 public override void TraceData(TraceEventCache eventCache, String source, TraceEventType eventType, int id, params object[] data) 124 { 125 if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, null, null, null, data)) 126 return; 127 128 WriteHeader(source, eventType, id); 129 130 // first a delimiter for the message 131 Write(Delimiter); // Use get_Delimiter 132 133 if (data != null) 134 { 135 for (int i = 0; i < data.Length; i++) 136 { 137 if (i != 0) 138 Write(_secondaryDelim); 139 WriteEscaped(data[i].ToString()); 140 } 141 } 142 Write(Delimiter); // Use get_Delimiter 143 144 WriteFooter(eventCache); 145 } 146 WriteHeader(String source, TraceEventType eventType, int id)147 private void WriteHeader(String source, TraceEventType eventType, int id) 148 { 149 WriteEscaped(source); 150 Write(Delimiter); // Use get_Delimiter 151 152 Write(eventType.ToString()); 153 Write(Delimiter); // Use get_Delimiter 154 155 Write(id.ToString(CultureInfo.InvariantCulture)); 156 Write(Delimiter); // Use get_Delimiter 157 } 158 WriteFooter(TraceEventCache eventCache)159 private void WriteFooter(TraceEventCache eventCache) 160 { 161 if (eventCache != null) 162 { 163 if (IsEnabled(TraceOptions.ProcessId)) 164 Write(eventCache.ProcessId.ToString(CultureInfo.InvariantCulture)); 165 Write(Delimiter); // Use get_Delimiter 166 167 if (IsEnabled(TraceOptions.LogicalOperationStack)) 168 WriteStackEscaped(eventCache.LogicalOperationStack); 169 Write(Delimiter); // Use get_Delimiter 170 171 if (IsEnabled(TraceOptions.ThreadId)) 172 WriteEscaped(eventCache.ThreadId); 173 Write(Delimiter); // Use get_Delimiter 174 175 if (IsEnabled(TraceOptions.DateTime)) 176 WriteEscaped(eventCache.DateTime.ToString("o", CultureInfo.InvariantCulture)); 177 Write(Delimiter); // Use get_Delimiter 178 179 if (IsEnabled(TraceOptions.Timestamp)) 180 Write(eventCache.Timestamp.ToString(CultureInfo.InvariantCulture)); 181 Write(Delimiter); // Use get_Delimiter 182 } 183 else 184 { 185 for (int i = 0; i < 5; i++) 186 Write(Delimiter); // Use get_Delimiter 187 } 188 189 WriteLine(""); 190 } 191 WriteEscaped(string message)192 private void WriteEscaped(string message) 193 { 194 if (!string.IsNullOrEmpty(message)) 195 { 196 StringBuilder sb = new StringBuilder("\""); 197 EscapeMessage(message, sb); 198 sb.Append("\""); 199 Write(sb.ToString()); 200 } 201 } 202 WriteStackEscaped(Stack stack)203 private void WriteStackEscaped(Stack stack) 204 { 205 StringBuilder sb = new StringBuilder("\""); 206 bool first = true; 207 foreach (object obj in stack) 208 { 209 if (!first) 210 { 211 sb.Append(", "); 212 } 213 else 214 { 215 first = false; 216 } 217 218 string operation = obj.ToString(); 219 EscapeMessage(operation, sb); 220 } 221 222 sb.Append("\""); 223 Write(sb.ToString()); 224 } 225 EscapeMessage(string message, StringBuilder sb)226 private void EscapeMessage(string message, StringBuilder sb) 227 { 228 int index; 229 int lastindex = 0; 230 while ((index = message.IndexOf('"', lastindex)) != -1) 231 { 232 sb.Append(message, lastindex, index - lastindex); 233 sb.Append("\"\""); 234 lastindex = index + 1; 235 } 236 237 sb.Append(message, lastindex, message.Length - lastindex); 238 } 239 IsEnabled(TraceOptions opts)240 private bool IsEnabled(TraceOptions opts) 241 { 242 return (opts & TraceOutputOptions) != 0; 243 } 244 } 245 } 246