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