summaryrefslogtreecommitdiff
path: root/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs
blob: 38c1767462f418bab8d87067be6f5a52546230b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;

#if !ES_BUILD_AGAINST_DOTNET_V35
using Contract = System.Diagnostics.Contracts.Contract;
#else
using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
#endif

#if ES_BUILD_STANDALONE
namespace Microsoft.Diagnostics.Tracing
#else
namespace System.Diagnostics.Tracing
#endif
{
    /// <summary>
    /// Provides support for EventSource activities by marking the start and
    /// end of a particular operation.
    /// </summary>
    internal sealed class EventSourceActivity
        : IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the EventSourceActivity class that
        /// is attached to the specified event source. The new activity will
        /// not be attached to any related (parent) activity.
        /// The activity is created in the Initialized state.
        /// </summary>
        /// <param name="eventSource">
        /// The event source to which the activity information is written.
        /// </param>
        public EventSourceActivity(EventSource eventSource)
        {
            if (eventSource == null)
                throw new ArgumentNullException(nameof(eventSource));
            Contract.EndContractBlock();

            this.eventSource = eventSource;
        }

        /// <summary>
        /// You can make an activity out of just an EventSource.  
        /// </summary>
        public static implicit operator EventSourceActivity(EventSource eventSource) { return new EventSourceActivity(eventSource); }

        /* Properties */
        /// <summary>
        /// Gets the event source to which this activity writes events.
        /// </summary>
        public EventSource EventSource
        {
            get { return this.eventSource; }
        }

        /// <summary>
        /// Gets this activity's unique identifier, or the default Guid if the
        /// event source was disabled when the activity was initialized.
        /// </summary>
        public Guid Id
        {
            get { return this.activityId; }
        }

#if false // don't expose RelatedActivityId unless there is a need.   
        /// <summary>
        /// Gets the unique identifier of this activity's related (parent)
        /// activity.
        /// </summary>
        public Guid RelatedId
        {
            get { return this.relatedActivityId; }
        }
#endif

        /// <summary>
        /// Writes a Start event with the specified name and data.   If the start event is not active (because the provider 
        /// is not on or keyword-level indiates the event is off, then the returned activity is simply the 'this' poitner 
        /// and it is effectively like the Start d
        /// 
        /// A new activityID GUID is generated and the returned
        /// EventSourceActivity remembers this activity and will mark every event (including the start stop and any writes)
        /// with this activityID.   In addition the Start activity will log a 'relatedActivityID' that was the activity
        /// ID before the start event.   This way event processors can form a linked list of all the activities that
        /// caused this one (directly or indirectly).  
        /// </summary>
        /// <param name="eventName">
        /// The name to use for the event.   It is strongly suggested that this name end in 'Start' (e.g. DownloadStart).  
        /// If you do this, then the Stop() method will automatically replace the 'Start' suffix with a 'Stop' suffix.  
        /// </param>
        /// <param name="options">Allow options (keywords, level) to be set for the write associated with this start 
        /// These will also be used for the stop event.</param>
        /// <param name="data">The data to include in the event.</param>
        public EventSourceActivity Start<T>(string eventName, EventSourceOptions options, T data)
        {
            return this.Start(eventName, ref options, ref data);
        }
        /// <summary>
        /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords 
        /// and level==Info) Data payload is empty.  
        /// </summary>
        public EventSourceActivity Start(string eventName)
        {
            var options = new EventSourceOptions();
            var data = new EmptyStruct();
            return this.Start(eventName, ref options, ref data);
        }
        /// <summary>
        /// Shortcut version see Start(string eventName, EventSourceOptions options, T data).  Data payload is empty. 
        /// </summary>
        public EventSourceActivity Start(string eventName, EventSourceOptions options)
        {
            var data = new EmptyStruct();
            return this.Start(eventName, ref options, ref data);
        }
        /// <summary>
        /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords 
        /// and level==Info) 
        /// </summary>
        public EventSourceActivity Start<T>(string eventName, T data)
        {
            var options = new EventSourceOptions();
            return this.Start(eventName, ref options, ref data);
        }

        /// <summary>
        /// Writes a Stop event with the specified data, and sets the activity
        /// to the Stopped state.  The name is determined by the eventName used in Start.
        /// If that Start event name is suffixed with 'Start' that is removed, and regardless
        /// 'Stop' is appended to the result to form the Stop event name.  
        /// May only be called when the activity is in the Started state.
        /// </summary>
        /// <param name="data">The data to include in the event.</param>
        public void Stop<T>(T data)
        {
            this.Stop(null, ref data);
        }
        /// <summary>
        /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop')
        /// This can be useful to indicate unusual ways of stoping (but it is still STRONGLY recommeded that
        /// you start with the same prefix used for the start event and you end with the 'Stop' suffix.   
        /// </summary>
        public void Stop<T>(string eventName)
        {
            var data = new EmptyStruct();
            this.Stop(eventName, ref data);
        }
        /// <summary>
        /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop')
        /// This can be useful to indicate unusual ways of stoping (but it is still STRONGLY recommeded that
        /// you start with the same prefix used for the start event and you end with the 'Stop' suffix.   
        /// </summary>
        public void Stop<T>(string eventName, T data)
        {
            this.Stop(eventName, ref data);
        }

        /// <summary>
        /// Writes an event associated with this activity to the eventSource associted with this activity.  
        /// May only be called when the activity is in the Started state.
        /// </summary>
        /// <param name="eventName">
        /// The name to use for the event. If null, the name is determined from
        /// data's type.
        /// </param>
        /// <param name="options">
        /// The options to use for the event.
        /// </param>
        /// <param name="data">The data to include in the event.</param>
        public void Write<T>(string eventName, EventSourceOptions options, T data)
        {
            this.Write(this.eventSource, eventName, ref options, ref data);
        }
        /// <summary>
        /// Writes an event associated with this activity.
        /// May only be called when the activity is in the Started state.
        /// </summary>
        /// <param name="eventName">
        /// The name to use for the event. If null, the name is determined from
        /// data's type.
        /// </param>
        /// <param name="data">The data to include in the event.</param>
        public void Write<T>(string eventName, T data)
        {
            var options = new EventSourceOptions();
            this.Write(this.eventSource, eventName, ref options, ref data);
        }
        /// <summary>
        /// Writes a trivial event associated with this activity.
        /// May only be called when the activity is in the Started state.
        /// </summary>
        /// <param name="eventName">
        /// The name to use for the event. Must not be null.
        /// </param>
        /// <param name="options">
        /// The options to use for the event.
        /// </param>
        public void Write(string eventName, EventSourceOptions options)
        {
            var data = new EmptyStruct();
            this.Write(this.eventSource, eventName, ref options, ref data);
        }
        /// <summary>
        /// Writes a trivial event associated with this activity.
        /// May only be called when the activity is in the Started state.
        /// </summary>
        /// <param name="eventName">
        /// The name to use for the event. Must not be null.
        /// </param>
        public void Write(string eventName)
        {
            var options = new EventSourceOptions();
            var data = new EmptyStruct();
            this.Write(this.eventSource, eventName, ref options, ref data);
        }
        /// <summary>
        /// Writes an event to a arbitrary eventSource stamped with the activity ID of this activity.   
        /// </summary>
        public void Write<T>(EventSource source, string eventName, EventSourceOptions options, T data)
        {
            this.Write(source, eventName, ref options, ref data);
        }

        /// <summary>
        /// Releases any unmanaged resources associated with this object.
        /// If the activity is in the Started state, calls Stop().
        /// </summary>
        public void Dispose()
        {
            if (this.state == State.Started)
            {
                var data = new EmptyStruct();
                this.Stop(null, ref data);
            }
        }

        #region private
        private EventSourceActivity Start<T>(string eventName, ref EventSourceOptions options, ref T data)
        {
            if (this.state != State.Started)
                throw new InvalidOperationException();

            // If the source is not on at all, then we don't need to do anything and we can simply return ourselves.  
            if (!this.eventSource.IsEnabled())
                return this;

            var newActivity = new EventSourceActivity(eventSource);
            if (!this.eventSource.IsEnabled(options.Level, options.Keywords))
            {
                // newActivity.relatedActivityId = this.Id;
                Guid relatedActivityId = this.Id;
                newActivity.activityId = Guid.NewGuid();
                newActivity.startStopOptions = options;
                newActivity.eventName = eventName;
                newActivity.startStopOptions.Opcode = EventOpcode.Start;
                this.eventSource.Write(eventName, ref newActivity.startStopOptions, ref newActivity.activityId, ref relatedActivityId, ref data);
            }
            else
            {
                // If we are not active, we don't set the eventName, which basically also turns off the Stop event as well.  
                newActivity.activityId = this.Id;
            }

            return newActivity;
        }

        private void Write<T>(EventSource eventSource, string eventName, ref EventSourceOptions options, ref T data)
        {
            if (this.state != State.Started)
                throw new InvalidOperationException();      // Write after stop. 
            if (eventName == null)
                throw new ArgumentNullException();

            eventSource.Write(eventName, ref options, ref this.activityId, ref s_empty, ref data);
        }

        private void Stop<T>(string eventName, ref T data)
        {
            if (this.state != State.Started)
                throw new InvalidOperationException();

            // If start was not fired, then stop isn't as well.  
            if (!StartEventWasFired)
                return;

            this.state = State.Stopped;
            if (eventName == null)
            {
                eventName = this.eventName;
                if (eventName.EndsWith("Start"))
                    eventName = eventName.Substring(0, eventName.Length - 5);
                eventName = eventName + "Stop";
            }
            this.startStopOptions.Opcode = EventOpcode.Stop;
            this.eventSource.Write(eventName, ref this.startStopOptions, ref this.activityId, ref s_empty, ref data);
        }

        private enum State
        {
            Started,
            Stopped
        }

        /// <summary>
        /// If eventName is non-null then we logged a start event 
        /// </summary>
        private bool StartEventWasFired { get { return eventName != null; } }

        private readonly EventSource eventSource;
        private EventSourceOptions startStopOptions;
        internal Guid activityId;
        // internal Guid relatedActivityId;
        private State state;
        private string eventName;

        static internal Guid s_empty;
        #endregion
    }
}