├── .gitignore
├── Collector
├── IISLogCollector
│ ├── App.config
│ ├── EtwLogCollector.cs
│ ├── EtwLogCollector.csproj
│ ├── ILogCollectorService.cs
│ ├── Program.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ └── packages.config
├── TraceProcessor.Tests
│ ├── BaseEtwTraceTests.cs
│ ├── BasicPartitionKeyGeneratorTests.cs
│ ├── BatchTests.cs
│ ├── ComplexJsonConverterTests.cs
│ ├── FlatJsonConverterTests.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── TraceProcessor.Tests.csproj
│ ├── TraceSourceProcessorTests.cs
│ ├── iis.etl
│ └── packages.config
└── TraceProcessor
│ ├── Batch.cs
│ ├── BinHexConverter.cs
│ ├── IBatchSender.cs
│ ├── IBatchSenderFactory.cs
│ ├── IEtwEventProvider.cs
│ ├── IEventProcessor.cs
│ ├── IJsonConverter.cs
│ ├── IPartitionKeyGenerator.cs
│ ├── ISendNotify.cs
│ ├── ISettings.cs
│ ├── ITraceSourceProcessor.cs
│ ├── Impl
│ ├── AppSettings.cs
│ ├── BasicPartitionKeyGenerator.cs
│ ├── BatchedEventHubSender.cs
│ ├── BatchedEventHubSenderFactory.cs
│ ├── ComplexJsonConverter.cs
│ ├── EventHubEventProcessor.cs
│ ├── FlatJsonConverter.cs
│ ├── MultiBatchSender.cs
│ └── TraceSourceProcessor.cs
│ ├── JsonFormatAttribute.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── TraceParserExtensions.cs
│ ├── TraceProcessor.csproj
│ ├── app.config
│ └── packages.config
├── Common.proj
├── IISTraceEvent
├── IISEventProvider.cs
├── IISLogTraceData.cs
├── IISLogTraceEventParser.cs
├── IISTraceEvent.csproj
├── Microsoft-Windows-IIS-Logging.manifest.xml
├── Properties
│ └── AssemblyInfo.cs
└── packages.config
├── Readme.md
├── Samples
└── SampleLogger
│ ├── App.config
│ ├── Program.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── SampleLogger.csproj
│ └── packages.config
└── iis-etw-tracing.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | bin/
3 | obj/
4 | packages/
5 | TestResults/
6 | .vs/
7 | *.dll
8 | *.suo
9 | *.user
10 | appSettings.config
11 |
--------------------------------------------------------------------------------
/Collector/IISLogCollector/App.config:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Collector/IISLogCollector/EtwLogCollector.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.Diagnostics.Tracing.Session;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.Diagnostics;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using Winterdom.Diagnostics.TraceProcessor;
12 |
13 | namespace Winterdom.EtwCollector {
14 | [Export(typeof(ILogCollectorService))]
15 | public class EtwLogCollector : ILogCollectorService, IDisposable {
16 | public const String SessionName = "eh-etw-collector";
17 | private TraceEventSession bufferSession;
18 | private ETWTraceEventSource eventSource;
19 | private IList eventProviders;
20 | private IObservable observableStream;
21 | private ITraceSourceProcessor sourceProcessor;
22 | private String traceFolder;
23 | private String currentFilename;
24 | private EventWaitHandle shutdownEvent;
25 | private TimeSpan bufferPeriod;
26 |
27 | [ImportingConstructor]
28 | public EtwLogCollector(ITraceSourceProcessor processor, ISettings settings, [ImportMany]IEnumerable eventProviders) {
29 | this.sourceProcessor = processor;
30 | this.eventProviders = eventProviders.ToList();
31 | this.traceFolder = Path.GetTempPath();
32 | this.bufferPeriod = settings.GetTimeSpan("BufferPeriod", TimeSpan.FromMinutes(5));
33 | this.shutdownEvent = new EventWaitHandle(false, EventResetMode.ManualReset);
34 | }
35 |
36 | public void Start() {
37 | CreateBufferSession();
38 | // TODO: Configure how long this will take
39 | Task.Delay(this.bufferPeriod)
40 | .ContinueWith((task) => CreateEventSourceOnBufferFile());
41 | }
42 |
43 | public void Stop() {
44 | this.shutdownEvent.Set();
45 | if ( this.eventSource != null ) {
46 | this.eventSource.StopProcessing();
47 | }
48 | if ( this.sourceProcessor != null ) {
49 | this.sourceProcessor.Stop().Wait();
50 | }
51 | }
52 |
53 | public void Dispose() {
54 | this.ReleaseProcessingSession();
55 | if ( this.bufferSession != null ) {
56 | this.bufferSession.Dispose();
57 | this.bufferSession = null;
58 | }
59 | this.eventProviders = null;
60 | }
61 |
62 | private void CreateBufferSession() {
63 | this.currentFilename = Path.Combine(this.traceFolder, Guid.NewGuid() + ".etl");
64 | this.bufferSession = new TraceEventSession(
65 | SessionName, this.currentFilename, TraceEventSessionOptions.Create
66 | );
67 | foreach (var provider in eventProviders) {
68 | provider.EnableProvider(this.bufferSession);
69 | }
70 | Trace.WriteLine(String.Format("Starting buffering on: {0}", this.currentFilename));
71 | }
72 |
73 | private void CreateEventSourceOnBufferFile() {
74 | String oldFile = SwitchBufferFiles();
75 | Trace.WriteLine(String.Format("Starting processing on: {0}", oldFile));
76 | this.eventSource = new ETWTraceEventSource(
77 | oldFile, TraceEventSourceType.FileOnly
78 | );
79 |
80 | var sortedProvs = eventProviders.OrderBy(x => x.IsKernelProvider ? 0 : 1);
81 | foreach (var provider in sortedProvs) {
82 | provider.RegisterParser(this.eventSource);
83 | }
84 |
85 | this.observableStream = JoinStreams(sortedProvs);
86 | this.sourceProcessor.Start(this.observableStream);
87 | new Task(Process).Start();
88 | }
89 |
90 | private IObservable JoinStreams(IEnumerable providers) {
91 | return System.Reactive.Linq.Observable.Merge(providers.Select(x => x.GetEventStream()));
92 | }
93 |
94 | private String SwitchBufferFiles() {
95 | String oldFile = this.currentFilename;
96 | this.currentFilename = Path.Combine(this.traceFolder, Guid.NewGuid() + ".etl");
97 | Trace.WriteLine(String.Format("Switching buffering to: {0}", this.currentFilename));
98 | this.bufferSession.SetFileName(this.currentFilename);
99 | return oldFile;
100 | }
101 |
102 | private void ReleaseProcessingSession() {
103 | if ( this.eventSource != null ) {
104 | this.eventSource.Dispose();
105 | this.eventSource = null;
106 | }
107 | }
108 |
109 | private void Process() {
110 | Stopwatch timer = Stopwatch.StartNew();
111 | this.eventSource.Process();
112 | timer.Stop();
113 | Trace.WriteLine(String.Format("ETL file processed in {0}", timer.Elapsed));
114 |
115 | // release existing event source, if any
116 | // and then switch to the new buffer collected
117 | String oldFile = this.eventSource.LogFileName;
118 | this.ReleaseProcessingSession();
119 | File.Delete(oldFile);
120 |
121 | // if we processed the file before the buffering
122 | // period is done, it means we're processing events
123 | // faster than they are being collected
124 | // so wait for a while before switching the files again
125 | TimeSpan wait = this.bufferPeriod - timer.Elapsed;
126 | if ( wait < TimeSpan.Zero ) {
127 | wait = TimeSpan.Zero;
128 | }
129 | bool stopping = this.shutdownEvent.WaitOne(wait);
130 | if (!stopping) {
131 | CreateEventSourceOnBufferFile();
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/Collector/IISLogCollector/EtwLogCollector.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {F279711D-D760-4815-8750-E60B449ED50B}
8 | Exe
9 | Properties
10 | Winterdom.EtwCollector
11 | EtwLogCollector
12 | v4.5.1
13 | 512
14 | true
15 |
16 |
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 | ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.32\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll
38 | True
39 |
40 |
41 |
42 |
43 |
44 |
45 | ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll
46 | True
47 |
48 |
49 | ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll
50 | True
51 |
52 |
53 | ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll
54 | True
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | PreserveNewest
72 |
73 |
74 |
75 |
76 |
77 | {afbed2c0-5d28-449e-b047-077b533c1852}
78 | TraceProcessor
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
87 |
88 |
89 |
90 |
97 |
--------------------------------------------------------------------------------
/Collector/IISLogCollector/ILogCollectorService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Winterdom.EtwCollector {
4 | public interface ILogCollectorService : IDisposable {
5 | void Start();
6 | void Stop();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Collector/IISLogCollector/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.ComponentModel.Composition.Hosting;
5 | using System.ComponentModel.Composition.Primitives;
6 | using System.Linq;
7 | using Winterdom.Diagnostics.TraceProcessor;
8 |
9 | namespace Winterdom.EtwCollector {
10 | // TODO: Should be a service!
11 | class Program {
12 | [Import]
13 | public ILogCollectorService CollectorService { get; set; }
14 |
15 | static void Main(string[] args) {
16 | AggregateCatalog catalog = new AggregateCatalog();
17 | String baseDir = AppDomain.CurrentDomain.BaseDirectory;
18 | catalog.Catalogs.Add(new DirectoryCatalog(baseDir));
19 | catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));
20 | CompositionContainer container = new FlatCompositionContainer(catalog);
21 |
22 | Program program = new Program();
23 | container.SatisfyImportsOnce(program);
24 |
25 | program.Run();
26 | }
27 |
28 | public void Run() {
29 | using ( this.CollectorService ) {
30 | Console.WriteLine("Listening for events...");
31 | this.CollectorService.Start();
32 | Console.ReadLine();
33 | this.CollectorService.Stop();
34 | }
35 | }
36 | }
37 |
38 | class FlatCompositionContainer : CompositionContainer {
39 | public FlatCompositionContainer(AggregateCatalog catalog) : base(catalog) {
40 | }
41 |
42 | protected override IEnumerable GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition) {
43 | var exports = base.GetExportsCore(definition, atomicComposition);
44 | if ( definition.ContractName == typeof(IJsonConverter).FullName ) {
45 | object format;
46 | var r = from e in exports
47 | where e.Metadata.TryGetValue("Format", out format)
48 | && String.Equals(format, "Flat")
49 | select e;
50 | return r;
51 | }
52 | return exports;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Collector/IISLogCollector/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("IISLogCollector")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("IISLogCollector")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("08327d9a-e339-4f42-a0db-75d149a2120d")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Collector/IISLogCollector/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/BaseEtwTraceTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Winterdom.Diagnostics.Tracing.IISTraceEvent;
9 |
10 | namespace TraceProcessor.Tests {
11 | [DeploymentItem(TraceName)]
12 | public abstract class BaseEtwTraceTests {
13 | public const String TraceName = "iis.etl";
14 |
15 | protected ETWTraceEventSource LoadEventSource() {
16 | return new ETWTraceEventSource(TraceName);
17 | }
18 | protected IEnumerable LoadSampleTrace() {
19 | var traceSource = LoadEventSource();
20 | var parser = new IISLogTraceEventParser(traceSource);
21 | List events = new List();
22 | parser.All += e => {
23 | events.Add(e.Clone());
24 | };
25 | traceSource.Process();
26 | return events;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/BasicPartitionKeyGeneratorTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Winterdom.Diagnostics.TraceProcessor;
9 | using Winterdom.Diagnostics.TraceProcessor.Impl;
10 | using Winterdom.Diagnostics.Tracing.IISTraceEvent;
11 |
12 | namespace TraceProcessor.Tests {
13 | [TestClass]
14 | public class BasicPartitionKeyGeneratorTests : BaseEtwTraceTests {
15 |
16 | [TestMethod]
17 | public void PartitionKeyIncludesMachineName() {
18 | var traceEvent = LoadSampleTrace().First();
19 | IPartitionKeyGenerator gen = new BasicPartitionKeyGenerator();
20 | String key = gen.GetKey(traceEvent);
21 | Assert.IsTrue(key.IndexOf(Environment.MachineName) >= 0);
22 | }
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/BatchTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Winterdom.Diagnostics.TraceProcessor;
8 |
9 | namespace TraceProcessor.Tests {
10 | [TestClass]
11 | public class BatchTests {
12 | [TestMethod]
13 | public void TryAddReturnsTrueIfBatchIsEmpty() {
14 | var batch = Batch.Empty(128*1024);
15 | Assert.IsTrue(batch.TryAdd("1", 1024));
16 | }
17 | [TestMethod]
18 | public void TryAddJustToBatchSizeReturnsTrue() {
19 | var batch = Batch.Empty(128*1024);
20 | for ( int i = 0; i < 127; i++ ) {
21 | Assert.IsTrue(batch.TryAdd("1", 1024));
22 | }
23 | // should just reach batch size
24 | Assert.IsTrue(batch.TryAdd("1", 1024));
25 | }
26 | [TestMethod]
27 | public void TryAddAfterBatchIsFullReturnsFalse() {
28 | var batch = Batch.Empty(128*1024);
29 | for ( int i = 0; i < 128; i++ ) {
30 | Assert.IsTrue(batch.TryAdd("1", 1024));
31 | }
32 | Assert.IsFalse(batch.TryAdd("1", 1024));
33 | }
34 | [TestMethod]
35 | public void TryAddReturnsFalseIfBatchSizePlusNewItemIsGreaterThanMaxBatchSize() {
36 | var batch = Batch.Empty(128*1024);
37 | for ( int i = 0; i < 127; i++ ) {
38 | Assert.IsTrue(batch.TryAdd("1", 1024));
39 | }
40 | Assert.IsFalse(batch.TryAdd("1", 2048));
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/ComplexJsonConverterTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.Diagnostics.Tracing.Session;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Linq;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using System.Linq;
10 | using Winterdom.Diagnostics.Tracing.IISTraceEvent;
11 | using Winterdom.Diagnostics.TraceProcessor;
12 |
13 | namespace TraceProcessor.Tests {
14 | [TestClass]
15 | public class ComplexJsonConverterTests : BaseEtwTraceTests {
16 | [TestMethod]
17 | public void IisEventToJson() {
18 | var traceEvents = LoadSampleTrace();
19 | String json = ToJson(traceEvents.First());
20 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
21 | JObject obj = (JObject)JToken.ReadFrom(reader);
22 | Assert.IsNotNull(obj);
23 | }
24 | }
25 |
26 | [TestMethod]
27 | public void IisEventToJson_HasHeaderData() {
28 | var traceEvents = LoadSampleTrace();
29 | var te = traceEvents.First();
30 | String json = ToJson(te);
31 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
32 | JObject obj = (JObject)JToken.ReadFrom(reader);
33 | JObject header = (JObject)obj["header"];
34 |
35 | Assert.IsNotNull(header);
36 | Assert.AreEqual(te.TimeStampRelativeMSec, (double)header["msec"]);
37 | Assert.AreEqual(te.ProcessID, (int)header["processId"]);
38 | Assert.AreEqual(te.ThreadID, (int)header["threadId"]);
39 | Assert.AreEqual(te.EventName, (String)header["eventName"]);
40 | Assert.AreEqual((int)te.ID, (int)header["id"]);
41 | Assert.AreEqual(te.ProviderName, (String)header["providerName"]);
42 | Assert.AreEqual(te.ProcessorNumber, (int)header["cpu"]);
43 | }
44 | }
45 |
46 | [TestMethod]
47 | public void IisEventToJson_HasPayload() {
48 | var traceEvents = LoadSampleTrace();
49 | var te = traceEvents.First();
50 | String json = ToJson(te);
51 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
52 | JObject obj = (JObject)JToken.ReadFrom(reader);
53 | byte[] payload = (byte[])obj["payload"];
54 |
55 | Assert.IsNotNull(payload);
56 | Assert.AreEqual(232, payload.Length);
57 | }
58 | }
59 |
60 | [TestMethod]
61 | public void IisEventToJson_HasEventData() {
62 | var traceEvents = LoadSampleTrace();
63 | var te = traceEvents.First();
64 | String json = ToJson(te);
65 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
66 | JObject obj = (JObject)JToken.ReadFrom(reader);
67 | JObject data = (JObject)obj["event"];
68 |
69 | Assert.IsNotNull(data);
70 | Assert.AreEqual("GET", (String)data["cs_method"]);
71 | Assert.AreEqual("/", (String)data["cs_uri_stem"]);
72 | Assert.AreEqual(80, (int)data["s_port"]);
73 | }
74 | }
75 |
76 | private String ToJson(TraceEvent traceEvent) {
77 | IJsonConverter converter = new ComplexJsonConverter();
78 | return converter.ToJson(traceEvent);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/FlatJsonConverterTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.Diagnostics.Tracing.Session;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Linq;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using System.Linq;
10 | using Winterdom.Diagnostics.Tracing.IISTraceEvent;
11 | using Winterdom.Diagnostics.TraceProcessor;
12 |
13 | namespace TraceProcessor.Tests {
14 | [TestClass]
15 | public class FlatJsonConverterTests : BaseEtwTraceTests {
16 | [TestMethod]
17 | public void IisEventToJson() {
18 | var traceEvents = LoadSampleTrace();
19 | String json = ToJson(traceEvents.First());
20 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
21 | JObject obj = (JObject)JToken.ReadFrom(reader);
22 | Assert.IsNotNull(obj);
23 | }
24 | }
25 |
26 | [TestMethod]
27 | public void IisEventToJson_HasHeaderData() {
28 | var traceEvents = LoadSampleTrace();
29 | var te = traceEvents.First();
30 | String json = ToJson(te);
31 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
32 | JObject obj = (JObject)JToken.ReadFrom(reader);
33 | Assert.AreEqual(te.TimeStampRelativeMSec, (double)obj["header_msec"]);
34 | Assert.AreEqual(te.ProcessID, (int)obj["header_processId"]);
35 | Assert.AreEqual(te.ThreadID, (int)obj["header_threadId"]);
36 | Assert.AreEqual(te.EventName, (String)obj["header_eventName"]);
37 | Assert.AreEqual((int)te.ID, (int)obj["header_id"]);
38 | Assert.AreEqual(te.ProviderName, (String)obj["header_providerName"]);
39 | Assert.AreEqual(te.ProcessorNumber, (int)obj["header_cpu"]);
40 | }
41 | }
42 |
43 | [TestMethod]
44 | public void IisEventToJson_HasPayload() {
45 | var traceEvents = LoadSampleTrace();
46 | var te = traceEvents.First();
47 | String json = ToJson(te);
48 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
49 | JObject obj = (JObject)JToken.ReadFrom(reader);
50 | byte[] payload = (byte[])obj["payload"];
51 |
52 | Assert.IsNotNull(payload);
53 | Assert.AreEqual(232, payload.Length);
54 | }
55 | }
56 |
57 | [TestMethod]
58 | public void IisEventToJson_HasEventData() {
59 | var traceEvents = LoadSampleTrace();
60 | var te = traceEvents.First();
61 | String json = ToJson(te);
62 | using ( var reader = new JsonTextReader(new StringReader(json)) ) {
63 | JObject obj = (JObject)JToken.ReadFrom(reader);
64 |
65 | Assert.AreEqual("GET", (String)obj["event_cs_method"]);
66 | Assert.AreEqual("/", (String)obj["event_cs_uri_stem"]);
67 | Assert.AreEqual(80, (int)obj["event_s_port"]);
68 | }
69 | }
70 |
71 | private String ToJson(TraceEvent traceEvent) {
72 | IJsonConverter converter = new FlatJsonConverter();
73 | return converter.ToJson(traceEvent);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("TraceProcessor.Tests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("TraceProcessor.Tests")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("6d589150-8b71-43af-b280-ab0cd264cbf1")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/TraceProcessor.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {F06244D4-313C-4B2C-AC24-F7B479014482}
7 | Library
8 | Properties
9 | TraceProcessor.Tests
10 | TraceProcessor.Tests
11 | v4.5.1
12 | 512
13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 10.0
15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
17 | False
18 | UnitTest
19 |
20 |
21 |
22 |
23 | true
24 | full
25 | false
26 | bin\Debug\
27 | DEBUG;TRACE
28 | prompt
29 | 4
30 |
31 |
32 | pdbonly
33 | true
34 | bin\Release\
35 | TRACE
36 | prompt
37 | 4
38 |
39 |
40 |
41 | ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.32\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll
42 | True
43 |
44 |
45 | ..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
46 | True
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 | {006b5002-ee7e-46db-9345-80199ca7b2d4}
74 | IISTraceEvent
75 |
76 |
77 | {afbed2c0-5d28-449e-b047-077b533c1852}
78 | TraceProcessor
79 |
80 |
81 |
82 |
83 | Always
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | False
92 |
93 |
94 | False
95 |
96 |
97 | False
98 |
99 |
100 | False
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
111 |
112 |
113 |
114 |
121 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/TraceSourceProcessorTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using Winterdom.Diagnostics.TraceProcessor;
10 | using Winterdom.Diagnostics.TraceProcessor.Impl;
11 | using Winterdom.Diagnostics.Tracing.IISTraceEvent;
12 |
13 | namespace TraceProcessor.Tests {
14 | [TestClass]
15 | public class TraceSourceProcessorTests : BaseEtwTraceTests {
16 | [TestMethod]
17 | public void ObservesAllEvents() {
18 | var source = LoadEventSource();
19 | var parser = new IISLogTraceEventParser(source);
20 | var observable = parser.Observe(IISLogTraceEventParser.ProviderName, null);
21 |
22 | var counter = new CountingProcessor();
23 | var sourceProc = new TraceSourceProcessor(counter);
24 |
25 | sourceProc.Start(observable);
26 | source.Process();
27 | source.StopProcessing();
28 | sourceProc.Stop().Wait();
29 |
30 | Assert.AreEqual(9, counter.GetCount());
31 | Assert.IsTrue(counter.FlushCalled);
32 | Assert.IsTrue(counter.DisposeCalled);
33 | }
34 |
35 |
36 | class CountingProcessor : IEventProcessor {
37 |
38 | private int count = 0;
39 | public bool FlushCalled { get; private set; }
40 | public bool DisposeCalled { get; private set; }
41 |
42 | public void SetNotify(ISendNotify sink) {
43 | }
44 |
45 | public Task Process(TraceEvent traceEvent) {
46 | Interlocked.Increment(ref count);
47 | return Task.FromResult(traceEvent);
48 | }
49 |
50 | public int GetCount() {
51 | return Interlocked.Exchange(ref count, 0);
52 | }
53 | public Task Flush() {
54 | FlushCalled = true;
55 | return Task.FromResult(0);
56 | }
57 | public void Dispose() {
58 | DisposeCalled = true;
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/iis.etl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomasr/iis-etw-tracing/d558fe52e1924a190918274f96b65f1349f94729/Collector/TraceProcessor.Tests/iis.etl
--------------------------------------------------------------------------------
/Collector/TraceProcessor.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Batch.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Winterdom.Diagnostics.TraceProcessor {
8 | ///
9 | /// Used to keep track of event batches
10 | /// TODO: figure out a better way of handling it
11 | ///
12 | /// Type of item
13 | public class Batch {
14 | private readonly int maxBatchSize;
15 | private int batchSize;
16 | private Queue entries;
17 |
18 | public bool IsEmpty {
19 | get { return entries.Count == 0; }
20 | }
21 | public int Count {
22 | get { return entries.Count; }
23 | }
24 |
25 | private Batch(int maxBatchSize)
26 | : this(maxBatchSize, 0, new Queue()) {
27 | }
28 | private Batch(int maxBatchSize, int batchSize, Queue entries) {
29 | this.maxBatchSize = maxBatchSize;
30 | this.batchSize = batchSize;
31 | this.entries = entries;
32 | }
33 |
34 | public static Batch Empty(int maxBatchSize) {
35 | return new Batch(maxBatchSize);
36 | }
37 |
38 | public bool TryAdd(TEntry item, int itemSize) {
39 | int newBatchSize = this.batchSize + itemSize;
40 |
41 | if ( newBatchSize > maxBatchSize ) {
42 | // can't add, needs flushing
43 | return false;
44 | }
45 | entries.Enqueue(item);
46 | this.batchSize = newBatchSize;
47 | return true;
48 | }
49 |
50 | public IEnumerable Drain() {
51 | return this.entries;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/BinHexConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Winterdom.Diagnostics.TraceProcessor {
8 | public static class BinHexConverter {
9 | public static String ToBinHex(byte[] data) {
10 | if ( data.Length == 0 ) {
11 | return String.Empty;
12 | }
13 | StringBuilder sb = new StringBuilder(data.Length*2+2);
14 | sb.Append("0x");
15 | for ( int i = 0; i < data.Length; i++ ) {
16 | sb.AppendFormat("{0:x2}", data[i]);
17 | }
18 | return sb.ToString();
19 | }
20 |
21 | // TODO: Only used for testing, optimize later!
22 | public static byte[] FromBinHex(String data) {
23 | if ( String.IsNullOrEmpty(data) ) {
24 | return new byte[0];
25 | }
26 | if ( !data.StartsWith("0x") ) {
27 | throw new FormatException("Data is not in the binhex format");
28 | }
29 | byte[] bytes = new byte[(data.Length - 2) / 2];
30 | for ( int i = 2; i < data.Length; i += 2 ) {
31 | bytes[i] = Convert.ToByte(data.Substring(i, 2), 16);
32 | }
33 | return bytes;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/IBatchSender.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceBus.Messaging;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor {
9 | public interface IBatchSender {
10 | void SetNotify(ISendNotify sink);
11 | void Send(Batch batch);
12 | void Close();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/IBatchSenderFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Winterdom.Diagnostics.TraceProcessor {
8 | public interface IBatchSenderFactory {
9 | IBatchSender Create();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/IEtwEventProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.Diagnostics.Tracing.Session;
3 | using System;
4 |
5 | namespace Winterdom.Diagnostics.TraceProcessor {
6 | public interface IEtwEventProvider {
7 | bool IsKernelProvider { get; }
8 | void RegisterParser(TraceEventSource eventSource);
9 | void EnableProvider(TraceEventSession session);
10 | IObservable GetEventStream();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/IEventProcessor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor {
9 | ///
10 | /// Processes a single event at a time
11 | ///
12 | public interface IEventProcessor : IDisposable {
13 | void SetNotify(ISendNotify sink);
14 | Task Process(TraceEvent traceEvent);
15 | Task Flush();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/IJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace Winterdom.Diagnostics.TraceProcessor {
10 | public interface IJsonConverter {
11 | void ToJson(TraceEvent traceEvent, TextWriter writer);
12 | String ToJson(TraceEvent traceEvent);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/IPartitionKeyGenerator.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor {
9 | public interface IPartitionKeyGenerator {
10 | String GetKey(TraceEvent traceEvent);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/ISendNotify.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceBus.Messaging;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor {
9 | public interface ISendNotify {
10 | void OnSendComplete(Batch batch, Exception ex);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/ISettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace Winterdom.Diagnostics.TraceProcessor {
10 | public interface ISettings {
11 | String GetString(String name, String defaultValue = "");
12 | int GetInt32(String name, int defaultValue = default(int));
13 | TimeSpan GetTimeSpan(String name, TimeSpan defaultValue = default(TimeSpan));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/ITraceSourceProcessor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor {
9 | ///
10 | /// Processes an entire TraceSource
11 | ///
12 | public interface ITraceSourceProcessor {
13 | void Start(IObservable eventStream);
14 | Task Stop();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/AppSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Configuration;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace Winterdom.Diagnostics.TraceProcessor.Impl {
10 |
11 | [Export(typeof(ISettings))]
12 | public class AppSettings : ISettings {
13 | public String GetString(String name, String defaultValue = "") {
14 | String value = null;
15 | if ( !TryGetValue(name, out value) ) {
16 | return defaultValue;
17 | }
18 | return value;
19 | }
20 |
21 | public int GetInt32(String name, int defaultValue = default(int)) {
22 | String value = null;
23 | if ( !TryGetValue(name, out value) ) {
24 | return defaultValue;
25 | }
26 | return Convert.ToInt32(value);
27 | }
28 |
29 | public TimeSpan GetTimeSpan(String name, TimeSpan defaultValue = default(TimeSpan)) {
30 | String value = null;
31 | if ( !TryGetValue(name, out value) ) {
32 | return defaultValue;
33 | }
34 | TimeSpan result;
35 | if ( !TimeSpan.TryParse(value, out result) ) {
36 | return defaultValue;
37 | }
38 | return result;
39 | }
40 |
41 | private bool TryGetValue(String key, out String value) {
42 | var appSettings = ConfigurationManager.AppSettings;
43 | value = appSettings[key];
44 | return value != null;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/BasicPartitionKeyGenerator.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.ComponentModel.Composition;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace Winterdom.Diagnostics.TraceProcessor.Impl {
10 | ///
11 | /// A basic IPartitionKeyGenerator that just uses the machine name
12 | ///
13 | [Export(typeof(IPartitionKeyGenerator))]
14 | public class BasicPartitionKeyGenerator : IPartitionKeyGenerator {
15 | public String GetKey(TraceEvent traceEvent) {
16 | // Original code combined the Provider GUID with the machine name
17 | // but you cannot mix events with different partition keys in the same
18 | // batch.
19 | return Environment.MachineName;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/BatchedEventHubSender.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceBus.Messaging;
2 | using System;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 |
11 | namespace Winterdom.Diagnostics.TraceProcessor.Impl {
12 | public class BatchedEventHubSender : IBatchSender {
13 | private EventHubClient eventHubClient;
14 | private ISettings settings;
15 | private BlockingCollection> pendingFlushList;
16 | private Task flusher;
17 | private CancellationTokenSource done;
18 | private ISendNotify notifySink;
19 |
20 | [ImportingConstructor]
21 | public BatchedEventHubSender(ISettings settings) {
22 | this.settings = settings;
23 | this.pendingFlushList = new BlockingCollection>();
24 | this.done = new CancellationTokenSource();
25 | this.flusher = new Task(this.Flusher);
26 | this.flusher.Start();
27 | }
28 |
29 | public void SetNotify(ISendNotify sink) {
30 | this.notifySink = sink;
31 | }
32 |
33 | public void Send(Batch batch) {
34 | this.pendingFlushList.Add(batch);
35 | }
36 |
37 | public void Close() {
38 | // tell our code we're not taking in any more requests
39 | this.done.Cancel();
40 | // then wait until all pending batches are flushed
41 | this.flusher.Wait();
42 | if ( this.eventHubClient != null && !this.eventHubClient.IsClosed ) {
43 | this.eventHubClient.Close();
44 | }
45 | }
46 |
47 | private void Flusher() {
48 | var cancellationToken = this.done.Token;
49 | try {
50 | var enumerable = this.pendingFlushList.GetConsumingEnumerable(cancellationToken);
51 | foreach ( var batch in enumerable ) {
52 | FlushBatch(batch);
53 | }
54 | } catch ( OperationCanceledException ) {
55 | // expected error
56 | // need get any remaning entries
57 | foreach ( var batch in this.pendingFlushList ) {
58 | FlushBatch(batch);
59 | }
60 | }
61 | }
62 |
63 | private void FlushBatch(Batch batch) {
64 | var client = GetOrCreateClient();
65 | if ( !batch.IsEmpty ) {
66 | Exception error = null;
67 | try {
68 | client.SendBatch(batch.Drain());
69 | } catch ( Exception ex ) {
70 | error = ex;
71 | this.eventHubClient.Abort();
72 | this.eventHubClient = null;
73 | }
74 | if ( this.notifySink != null ) {
75 | this.notifySink.OnSendComplete(batch, error);
76 | }
77 | }
78 | }
79 |
80 | private EventHubClient GetOrCreateClient() {
81 | if ( this.eventHubClient == null || this.eventHubClient.IsClosed ) {
82 | String connectionString = this.settings.GetString("EtwHubConnectionString");
83 | String eventHubName = this.settings.GetString("EtwEventHubName");
84 | var factory =
85 | MessagingFactory.CreateFromConnectionString(connectionString);
86 | this.eventHubClient = factory.CreateEventHubClient(eventHubName);
87 | }
88 | return this.eventHubClient;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/BatchedEventHubSenderFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor.Impl {
9 | [Export(typeof(IBatchSenderFactory))]
10 | public class BatchedEventHubSenderFactory : IBatchSenderFactory {
11 | [Import]
12 | public ISettings Settings { get; set; }
13 |
14 | public IBatchSender Create() {
15 | return new BatchedEventHubSender(Settings);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/ComplexJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace Winterdom.Diagnostics.TraceProcessor {
12 | [Export(typeof(IJsonConverter))]
13 | [JsonFormat(Format="Complex")]
14 | public class ComplexJsonConverter : IJsonConverter {
15 | public void ToJson(TraceEvent traceEvent, TextWriter tw) {
16 | var writer = new JsonTextWriter(tw);
17 | writer.WriteStartObject();
18 | WriteHeader(writer, traceEvent);
19 | WriteEventData(writer, traceEvent);
20 | WritePayload(writer, traceEvent);
21 | writer.WriteEndObject();
22 | }
23 | public String ToJson(TraceEvent traceEvent) {
24 | using ( StringWriter sw = new StringWriter() ) {
25 | ToJson(traceEvent, sw);
26 | sw.Flush();
27 | return sw.ToString();
28 | }
29 | }
30 |
31 | private static void WriteHeader(JsonTextWriter writer, TraceEvent traceEvent) {
32 | writer.WritePropertyName("header");
33 | writer.WriteStartObject();
34 |
35 | WriteProperty(writer, "msec", traceEvent.TimeStampRelativeMSec);
36 | WriteProperty(writer, "processId", traceEvent.ProcessID);
37 | WriteProperty(writer, "processName", traceEvent.ProcessName);
38 | WriteProperty(writer, "threadId", traceEvent.ThreadID);
39 | WriteProperty(writer, "activityId", traceEvent.ActivityID);
40 | WriteProperty(writer, "relatedActivityId", traceEvent.RelatedActivityID);
41 | WriteProperty(writer, "eventName", traceEvent.EventName);
42 | WriteProperty(writer, "timeStamp", traceEvent.TimeStamp);
43 | WriteProperty(writer, "id", traceEvent.ID);
44 | WriteProperty(writer, "version", traceEvent.Version);
45 | WriteProperty(writer, "keywords", traceEvent.Keywords);
46 | WriteProperty(writer, "level", traceEvent.Level);
47 | WriteProperty(writer, "providerName", traceEvent.ProviderName);
48 | WriteProperty(writer, "providerGuid", traceEvent.ProviderGuid);
49 | WriteProperty(writer, "classicProvider", traceEvent.IsClassicProvider);
50 | WriteProperty(writer, "opcode", traceEvent.Opcode);
51 | WriteProperty(writer, "opcodeName", traceEvent.OpcodeName);
52 | WriteProperty(writer, "task", traceEvent.Task);
53 | WriteProperty(writer, "channel", traceEvent.Channel);
54 | WriteProperty(writer, "pointerSize", traceEvent.PointerSize);
55 | WriteProperty(writer, "cpu", traceEvent.ProcessorNumber);
56 | WriteProperty(writer, "eventIndex", traceEvent.EventIndex);
57 |
58 | writer.WriteEndObject();
59 | }
60 |
61 | private static void WritePayload(JsonTextWriter writer, TraceEvent traceEvent) {
62 | writer.WritePropertyName("payload");
63 | writer.WriteValue(traceEvent.EventData());
64 | }
65 |
66 | private static void WriteEventData(JsonTextWriter writer, TraceEvent traceEvent) {
67 | writer.WritePropertyName("event");
68 | writer.WriteStartObject();
69 | String[] names = traceEvent.PayloadNames;
70 | for ( int i = 0; i < names.Length; i++ ) {
71 | writer.WritePropertyName(names[i].ToLower());
72 | writer.WriteValue(traceEvent.PayloadValue(i));
73 | }
74 | writer.WriteEndObject();
75 | }
76 |
77 | private static void WriteProperty(JsonTextWriter writer, string name, T value) {
78 | writer.WritePropertyName(name);
79 | writer.WriteValue(value);
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/EventHubEventProcessor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.ServiceBus.Messaging;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.Concurrent;
6 | using System.Configuration;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Threading;
12 | using System.ComponentModel.Composition;
13 | using System.Diagnostics;
14 |
15 | namespace Winterdom.Diagnostics.TraceProcessor.Impl {
16 | [Export(typeof(IEventProcessor))]
17 | public class EventHubEventProcessor : IEventProcessor {
18 | public int MaxBatchSize { get; private set; }
19 | private IPartitionKeyGenerator keyGenerator;
20 | private IJsonConverter jsonConverter;
21 | private IBatchSender batchSender;
22 | private Batch currentBatch;
23 |
24 | [ImportingConstructor]
25 | public EventHubEventProcessor(
26 | IPartitionKeyGenerator generator,
27 | ISettings settings,
28 | IBatchSender batchSender,
29 | IJsonConverter jsonConverter) {
30 | this.MaxBatchSize = 1024 * settings.GetInt32("MaxBatchSizeKB", 192);
31 | this.keyGenerator = generator;
32 | this.batchSender = batchSender;
33 | this.jsonConverter = jsonConverter;
34 | this.currentBatch = Batch.Empty(MaxBatchSize);
35 | }
36 |
37 | public void SetNotify(ISendNotify sink) {
38 | this.batchSender.SetNotify(sink);
39 | }
40 |
41 | public Task Process(TraceEvent traceEvent) {
42 | byte[] eventBody = EventToBytes(traceEvent);
43 |
44 | //System.IO.File.WriteAllBytes(String.Format(@"c:\temp\etw\{0}.json", Guid.NewGuid()), eventBody);
45 |
46 | EventData eventData = new EventData(eventBody);
47 | eventData.PartitionKey = keyGenerator.GetKey(traceEvent);
48 |
49 | if ( !currentBatch.TryAdd(eventData, eventBody.Length) ) {
50 | // this needs to be an atomic operation!
51 | var batch = Interlocked.Exchange(ref this.currentBatch, Batch.Empty(MaxBatchSize));
52 | this.batchSender.Send(batch);
53 | }
54 | return Task.FromResult(traceEvent);
55 | }
56 |
57 | // TODO: Not needed, remove
58 | public Task Flush() {
59 | return Task.FromResult(0);
60 | }
61 |
62 | public void Dispose() {
63 | this.batchSender.Close();
64 | }
65 |
66 | private byte[] EventToBytes(TraceEvent traceEvent) {
67 | using ( MemoryStream ms = new MemoryStream() ) {
68 | using ( StreamWriter writer = new StreamWriter(ms, Encoding.UTF8) ) {
69 | jsonConverter.ToJson(traceEvent, writer);
70 | writer.Flush();
71 | return ms.ToArray();
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/FlatJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace Winterdom.Diagnostics.TraceProcessor {
12 | [Export(typeof(IJsonConverter))]
13 | [JsonFormat(Format="Flat")]
14 | public class FlatJsonConverter : IJsonConverter {
15 | public void ToJson(TraceEvent traceEvent, TextWriter tw) {
16 | var writer = new JsonTextWriter(tw);
17 | writer.WriteStartObject();
18 | WriteHeader(writer, traceEvent);
19 | WriteEventData(writer, traceEvent);
20 | WritePayload(writer, traceEvent);
21 | writer.WriteEndObject();
22 | }
23 | public String ToJson(TraceEvent traceEvent) {
24 | using ( StringWriter sw = new StringWriter() ) {
25 | ToJson(traceEvent, sw);
26 | sw.Flush();
27 | return sw.ToString();
28 | }
29 | }
30 |
31 | private static void WriteHeader(JsonTextWriter writer, TraceEvent traceEvent) {
32 | WriteProperty(writer, "header_msec", traceEvent.TimeStampRelativeMSec);
33 | WriteProperty(writer, "header_processId", traceEvent.ProcessID);
34 | WriteProperty(writer, "header_processName", traceEvent.ProcessName);
35 | WriteProperty(writer, "header_threadId", traceEvent.ThreadID);
36 | WriteProperty(writer, "header_activityId", traceEvent.ActivityID);
37 | WriteProperty(writer, "header_relatedActivityId", traceEvent.RelatedActivityID);
38 | WriteProperty(writer, "header_eventName", traceEvent.EventName);
39 | WriteProperty(writer, "header_timeStamp", traceEvent.TimeStamp);
40 | WriteProperty(writer, "header_id", traceEvent.ID);
41 | WriteProperty(writer, "header_version", traceEvent.Version);
42 | WriteProperty(writer, "header_keywords", traceEvent.Keywords);
43 | WriteProperty(writer, "header_level", traceEvent.Level);
44 | WriteProperty(writer, "header_providerName", traceEvent.ProviderName);
45 | WriteProperty(writer, "header_providerGuid", traceEvent.ProviderGuid);
46 | WriteProperty(writer, "header_classicProvider", traceEvent.IsClassicProvider);
47 | WriteProperty(writer, "header_opcode", traceEvent.Opcode);
48 | WriteProperty(writer, "header_opcodeName", traceEvent.OpcodeName);
49 | WriteProperty(writer, "header_task", traceEvent.Task);
50 | WriteProperty(writer, "header_channel", traceEvent.Channel);
51 | WriteProperty(writer, "header_pointerSize", traceEvent.PointerSize);
52 | WriteProperty(writer, "header_cpu", traceEvent.ProcessorNumber);
53 | WriteProperty(writer, "header_eventIndex", traceEvent.EventIndex);
54 | }
55 |
56 | private static void WritePayload(JsonTextWriter writer, TraceEvent traceEvent) {
57 | writer.WritePropertyName("payload");
58 | writer.WriteValue(traceEvent.EventData());
59 | }
60 |
61 | private static void WriteEventData(JsonTextWriter writer, TraceEvent traceEvent) {
62 | String[] names = traceEvent.PayloadNames;
63 | for ( int i = 0; i < names.Length; i++ ) {
64 | writer.WritePropertyName("event_" + names[i].ToLower());
65 | writer.WriteValue(traceEvent.PayloadValue(i));
66 | }
67 | }
68 |
69 | private static void WriteProperty(JsonTextWriter writer, string name, T value) {
70 | writer.WritePropertyName(name);
71 | writer.WriteValue(value);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/MultiBatchSender.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.ServiceBus.Messaging;
2 | using System;
3 | using System.Collections.Concurrent;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.Diagnostics;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace Winterdom.Diagnostics.TraceProcessor.Impl {
12 | [Export(typeof(IBatchSender))]
13 | public class MultiBatchSender : IBatchSender {
14 | private IBatchSender[] availableSenders;
15 | private IBatchSenderFactory senderFactory;
16 | private ISendNotify notifySink;
17 | private int senderCount;
18 | private int currentSender;
19 |
20 | [ImportingConstructor]
21 | public MultiBatchSender(ISettings settings, IBatchSenderFactory factory) {
22 | this.senderFactory = factory;
23 | this.currentSender = 0;
24 | this.senderCount = settings.GetInt32("EventHubSenderCount", Environment.ProcessorCount);
25 | this.availableSenders = new IBatchSender[senderCount];
26 |
27 | InitializeSenders();
28 | }
29 |
30 | public void SetNotify(ISendNotify sink) {
31 | this.notifySink = sink;
32 | foreach ( var sender in availableSenders ) {
33 | sender.SetNotify(sink);
34 | }
35 | }
36 |
37 | public void Send(Batch batch) {
38 | int senderIndex = (this.currentSender + 1) % this.senderCount;
39 | this.currentSender = senderIndex;
40 | this.availableSenders[this.currentSender].Send(batch);
41 | }
42 |
43 | public void Close() {
44 | foreach ( var sender in availableSenders ) {
45 | sender.Close();
46 | }
47 | }
48 |
49 | /*
50 | public async Task CloseAsync() {
51 | // TODO: This will miss some senders
52 | // if we have some "busy" senders
53 | Task[] closers = (from sender in availableSenders
54 | select sender.CloseAsync()).ToArray();
55 |
56 | await Task.WhenAll(closers);
57 | }
58 | */
59 |
60 | private void InitializeSenders() {
61 | for ( int i = 0; i < senderCount; i++ ) {
62 | var sender = senderFactory.Create();
63 | this.availableSenders[i] = sender;
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Impl/TraceSourceProcessor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.ServiceBus.Messaging;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.ComponentModel.Composition;
6 | using System.Diagnostics;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace Winterdom.Diagnostics.TraceProcessor.Impl {
13 | [Export(typeof(ITraceSourceProcessor))]
14 | public class TraceSourceProcessor : ITraceSourceProcessor, IObserver, ISendNotify {
15 | private IEventProcessor eventProcessor;
16 | private IDisposable subscription;
17 | private long eventCount;
18 | private long eventsProcessed;
19 | private object eplock;
20 |
21 | [ImportingConstructor]
22 | public TraceSourceProcessor(IEventProcessor processor) {
23 | this.eventProcessor = processor;
24 | this.eventCount = 0;
25 | this.eventsProcessed = 0;
26 | this.eplock = new object();
27 | processor.SetNotify(this);
28 | }
29 |
30 | public void Start(IObservable eventStream) {
31 | if ( this.subscription != null ) {
32 | this.subscription.Dispose();
33 | }
34 | this.subscription = eventStream.Subscribe(this);
35 | }
36 |
37 | public async Task Stop() {
38 | if ( this.subscription != null ) {
39 | this.subscription.Dispose();
40 | this.subscription = null;
41 | }
42 | if ( this.eventProcessor != null ) {
43 | var ep = this.eventProcessor;
44 | this.eventProcessor = null;
45 | // ensure all events are processed
46 | // and after that clean up around the processor
47 | await ep.Flush();
48 | ReleaseEventProcessor(ep);
49 | }
50 | }
51 |
52 | private void ReleaseEventProcessor(IEventProcessor ep) {
53 | // TODO: add tracing
54 | ep.Dispose();
55 | }
56 |
57 | void IObserver.OnCompleted() {
58 | }
59 |
60 | void ISendNotify.OnSendComplete(Batch batch, Exception error) {
61 | if ( error != null ) {
62 | Trace.WriteLine(String.Format("Batch send failed: {0}", error));
63 | } else {
64 | lock ( this.eplock ) {
65 | this.eventsProcessed += batch.Count;
66 | Trace.WriteLine(String.Format("Events flushed: {0}", this.eventsProcessed));
67 | }
68 | }
69 | }
70 |
71 | void IObserver.OnError(Exception error) {
72 | Trace.WriteLine(String.Format("Error observed: {0}", error));
73 | // TODO: implement
74 | }
75 |
76 | void IObserver.OnNext(TraceEvent traceEvent) {
77 | this.eventProcessor.Process(traceEvent)
78 | .ContinueWith(OnEventProcessingComplete);
79 | }
80 |
81 | private void OnEventProcessingComplete(Task task) {
82 | if ( task.IsFaulted ) {
83 | // TODO: figure out proper error handling here :)
84 | Console.WriteLine(task.Exception);
85 | } else {
86 | long count = Interlocked.Increment(ref this.eventCount);
87 | if ( count % 100 == 0 ) {
88 | Trace.WriteLine(String.Format("Processed {0} events", count));
89 | }
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/JsonFormatAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.Composition;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor {
9 | [MetadataAttribute]
10 | [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
11 | public class JsonFormatAttribute : Attribute {
12 | public String Format { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("TraceProcessor")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("TraceProcessor")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("55544414-9085-4f42-aee5-a53cbdb63858")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/TraceParserExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.TraceProcessor {
9 | public static class TraceParserExtensions {
10 | public static IObservable ObserveAll(this TraceEventParser parser) {
11 | return parser.Observe((providerName, eventName) => {
12 | return EventFilterResponse.AcceptEvent;
13 | });
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/TraceProcessor.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {AFBED2C0-5D28-449E-B047-077B533C1852}
8 | Library
9 | Properties
10 | Winterdom.Diagnostics.TraceProcessor
11 | Winterdom.Diagnostics.TraceProcessor
12 | v4.5.1
13 | 512
14 |
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 | ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.32\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll
35 | True
36 |
37 |
38 | ..\..\packages\WindowsAzure.ServiceBus.2.7.5\lib\net40-full\Microsoft.ServiceBus.dll
39 | True
40 |
41 |
42 | ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.1.0\lib\net40\Microsoft.WindowsAzure.Configuration.dll
43 | True
44 |
45 |
46 | ..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
47 | True
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 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
97 |
98 |
99 |
100 |
107 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
11 |
13 |
14 |
15 |
17 |
19 |
21 |
23 |
25 |
26 |
27 |
29 |
31 |
33 |
35 |
37 |
39 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
49 |
--------------------------------------------------------------------------------
/Collector/TraceProcessor/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Common.proj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildThisFileDirectory)build\$(Configuration)\
5 |
6 |
7 |
--------------------------------------------------------------------------------
/IISTraceEvent/IISEventProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Diagnostics.Tracing;
3 | using Microsoft.Diagnostics.Tracing.Session;
4 | using Winterdom.Diagnostics.TraceProcessor;
5 | using System.ComponentModel.Composition;
6 |
7 | namespace Winterdom.Diagnostics.Tracing.IISTraceEvent {
8 | [Export(typeof(IEtwEventProvider))]
9 | public class IISEventProvider : IEtwEventProvider {
10 | private IISLogTraceEventParser parser;
11 |
12 | public bool IsKernelProvider { get { return false; } }
13 |
14 | public void RegisterParser(TraceEventSource eventSource) {
15 | this.parser = new IISLogTraceEventParser(eventSource);
16 | }
17 | public void EnableProvider(TraceEventSession session) {
18 | session.EnableProvider(IISLogTraceEventParser.ProviderName);
19 | }
20 | public IObservable GetEventStream() {
21 | return parser.ObserveAll();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/IISTraceEvent/IISLogTraceData.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.Tracing.IISTraceEvent {
9 | public class IISLogTraceData : TraceEvent {
10 | public int EnabledFieldsFlags {
11 | get { return GetInt32At(GetOffsetForField(0)); }
12 | }
13 | public String Date {
14 | get { return GetUnicodeStringAt(GetOffsetForField(1)); }
15 | }
16 | public String Time {
17 | get { return GetUnicodeStringAt(GetOffsetForField(2)); }
18 | }
19 | public String C_ip {
20 | get { return GetUnicodeStringAt(GetOffsetForField(3)); }
21 | }
22 | public String Cs_username {
23 | get { return GetUnicodeStringAt(GetOffsetForField(4)); }
24 | }
25 | public String S_sitename {
26 | get { return GetUnicodeStringAt(GetOffsetForField(5)); }
27 | }
28 | public String S_computername {
29 | get { return GetUnicodeStringAt(GetOffsetForField(6)); }
30 | }
31 | public String S_ip {
32 | get { return GetUnicodeStringAt(GetOffsetForField(7)); }
33 | }
34 | public String Cs_method {
35 | get { return GetUTF8StringAt(GetOffsetForField(8)); }
36 | }
37 | public String Cs_uri_stem {
38 | get { return GetUnicodeStringAt(GetOffsetForField(9)); }
39 | }
40 | public String Cs_uri_query {
41 | get { return GetUTF8StringAt(GetOffsetForField(10)); }
42 | }
43 | public int Sc_status {
44 | get { return GetInt16At(GetOffsetForField(11)); }
45 | }
46 | public int Sc_win32_status {
47 | get { return GetInt32At(GetOffsetForField(12)); }
48 | }
49 | public long Sc_bytes {
50 | get { return GetInt64At(GetOffsetForField(13)); }
51 | }
52 | public long Cs_bytes {
53 | get { return GetInt64At(GetOffsetForField(14)); }
54 | }
55 | public long Time_taken {
56 | get { return GetInt64At(GetOffsetForField(15)); }
57 | }
58 | public int S_port {
59 | get { return GetInt16At(GetOffsetForField(16)); }
60 | }
61 | public String CsUser_agent {
62 | get { return GetUTF8StringAt(GetOffsetForField(17)); }
63 | }
64 | public String CsCookie {
65 | get { return GetUTF8StringAt(GetOffsetForField(18)); }
66 | }
67 | public String CsReferer {
68 | get { return GetUTF8StringAt(GetOffsetForField(19)); }
69 | }
70 | public String Cs_version {
71 | get { return GetUnicodeStringAt(GetOffsetForField(20)); }
72 | }
73 | public String Cs_host {
74 | get { return GetUTF8StringAt(GetOffsetForField(21)); }
75 | }
76 | public int Sc_substatus {
77 | get { return GetInt16At(GetOffsetForField(22)); }
78 | }
79 | public String CustomFields {
80 | get { return GetUnicodeStringAt(GetOffsetForField(23)); }
81 | }
82 |
83 | private static readonly String[] PayloadNamesCache;
84 | public override String[] PayloadNames {
85 | get { return PayloadNamesCache; }
86 | }
87 |
88 | private Action target;
89 | protected override Delegate Target {
90 | get { return target; }
91 | set { target = (Action)value; }
92 | }
93 |
94 | static IISLogTraceData() {
95 | var names = "EnabledFieldsFlags,Date,Time,C_ip,Cs_username,"
96 | + "S_sitename,S_computername,S_ip,Cs_method,Cs_uri_stem,"
97 | + "Cs_uri_query,Sc_status,Sc_win32_status,Sc_bytes,"
98 | + "Cs_bytes,Time_taken,S_port,CsUser_agent,CsCookie,CsReferer,"
99 | + "Cs_version,Cs_host,Sc_substatus,CustomFields";
100 | PayloadNamesCache = names.Split(',');
101 | }
102 |
103 | public IISLogTraceData(Action action, int eventID, int task, String taskName, Guid taskGuid, int opcode, String opcodeName, Guid providerGuid, String providerName)
104 | : base(eventID, task, taskName, taskGuid, opcode, opcodeName, providerGuid, providerName) {
105 | this.Target = action;
106 | }
107 |
108 | protected override void Dispatch() {
109 | var action = target;
110 | if ( action != null ) {
111 | action(this);
112 | }
113 | }
114 |
115 | public override object PayloadValue(int index)
116 | {
117 | switch ( index ) {
118 | case 0: return EnabledFieldsFlags;
119 | case 1: return Date;
120 | case 2: return Time;
121 | case 3: return C_ip;
122 | case 4: return Cs_username;
123 | case 5: return S_sitename;
124 | case 6: return S_computername;
125 | case 7: return S_ip;
126 | case 8: return Cs_method;
127 | case 9: return Cs_uri_stem;
128 | case 10: return Cs_uri_query;
129 | case 11: return Sc_status;
130 | case 12: return Sc_win32_status;
131 | case 13: return Sc_bytes;
132 | case 14: return Cs_bytes;
133 | case 15: return Time_taken;
134 | case 16: return S_port;
135 | case 17: return CsUser_agent;
136 | case 18: return CsCookie;
137 | case 19: return CsReferer;
138 | case 20: return Cs_version;
139 | case 21: return Cs_host;
140 | case 22: return Sc_substatus;
141 | case 23: return CustomFields;
142 | }
143 | return null;
144 | }
145 |
146 | public override StringBuilder ToXml(StringBuilder sb) {
147 | Prefix(sb);
148 | XmlAttrib(sb, "EnabledFieldsFlags", EnabledFieldsFlags.ToString("x"));
149 | XmlAttrib(sb, "Date", Date);
150 | XmlAttrib(sb, "Time", Time);
151 | XmlAttrib(sb, "C_ip", C_ip);
152 | XmlAttrib(sb, "Cs_username", Cs_username);
153 | XmlAttrib(sb, "S_sitename", S_sitename);
154 | XmlAttrib(sb, "S_computername", S_computername);
155 | XmlAttrib(sb, "S_ip", S_ip);
156 | XmlAttrib(sb, "Cs_method", Cs_method);
157 | XmlAttrib(sb, "Cs_uri_stem", Cs_uri_stem);
158 | XmlAttrib(sb, "Cs_uri_query", Cs_uri_query);
159 | XmlAttrib(sb, "Sc_status", Sc_status);
160 | XmlAttrib(sb, "Sc_win32_status", Sc_win32_status);
161 | XmlAttrib(sb, "Sc_bytes", Sc_bytes);
162 | XmlAttrib(sb, "Cs_bytes", Cs_bytes);
163 | XmlAttrib(sb, "Time_taken", Time_taken);
164 | XmlAttrib(sb, "S_port", S_port);
165 | XmlAttrib(sb, "CsUser_agent", CsUser_agent);
166 | XmlAttrib(sb, "CsCookie", CsCookie);
167 | XmlAttrib(sb, "CsReferer", CsReferer);
168 | XmlAttrib(sb, "Cs_version", Cs_version);
169 | XmlAttrib(sb, "Cs_host", Cs_host);
170 | XmlAttrib(sb, "Sc_substatus", Sc_substatus);
171 | XmlAttrib(sb, "CustomFields", CustomFields);
172 | sb.Append("/>");
173 | return sb;
174 | }
175 |
176 |
177 | enum Types {
178 | UInt16,
179 | UInt32,
180 | UInt64,
181 | UnicodeString,
182 | AnsiString
183 | }
184 |
185 | private static readonly Types[] FieldTypes = new Types[] {
186 | Types.UInt32,
187 | Types.UnicodeString,
188 | Types.UnicodeString,
189 | Types.UnicodeString,
190 | Types.UnicodeString,
191 | Types.UnicodeString,
192 | Types.UnicodeString,
193 | Types.UnicodeString,
194 | Types.AnsiString,
195 | Types.UnicodeString,
196 | Types.AnsiString,
197 | Types.UInt16,
198 | Types.UInt32,
199 | Types.UInt64,
200 | Types.UInt64,
201 | Types.UInt64,
202 | Types.UInt16,
203 | Types.AnsiString,
204 | Types.AnsiString,
205 | Types.AnsiString,
206 | Types.UnicodeString,
207 | Types.AnsiString,
208 | Types.UInt16,
209 | Types.UnicodeString
210 | };
211 |
212 | private int GetOffsetForField(int index) {
213 | int offset = 0;
214 | for ( int i = 1; i <= index; i++ ) {
215 | offset = AddSizeOf(FieldTypes[i - 1], offset);
216 | }
217 | return offset;
218 | }
219 |
220 | private int AddSizeOf(Types type, int offset) {
221 | switch ( type ) {
222 | case Types.UInt16: return offset + sizeof(short);
223 | case Types.UInt32: return offset + sizeof(int);
224 | case Types.UInt64: return offset + sizeof(long);
225 | case Types.UnicodeString: return SkipUnicodeString(offset);
226 | case Types.AnsiString: return SkipUTF8String(offset);
227 | default:
228 | throw new NotSupportedException("Data type not supported");
229 | }
230 | }
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/IISTraceEvent/IISLogTraceEventParser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Winterdom.Diagnostics.Tracing.IISTraceEvent {
9 | public class IISLogTraceEventParser : TraceEventParser {
10 | public static readonly Guid ProviderGuid = new Guid("7E8AD27F-B271-4EA2-A783-A47BDE29143B");
11 | public const String ProviderName = "Microsoft-Windows-IIS-Logging";
12 | public const int IISLogEventId = 6200;
13 |
14 | public IISLogTraceEventParser(TraceEventSource source)
15 | : base(source) {
16 | }
17 |
18 | public event Action IISLog {
19 | add { source.RegisterEventTemplate(IISLogTemplate(value)); }
20 | remove { source.UnregisterEventTemplate(value, IISLogEventId, ProviderGuid); }
21 | }
22 |
23 | protected override string GetProviderName() {
24 | return ProviderName;
25 | }
26 |
27 | private static IISLogTraceData IISLogTemplate(Action action) {
28 | return new IISLogTraceData(action, IISLogEventId, 0, "Logs", Guid.Empty, 0, "", ProviderGuid, ProviderName);
29 | }
30 | private static TraceEvent[] Templates = null;
31 | protected override void EnumerateTemplates(
32 | Func eventsToObserve,
33 | Action callback) {
34 |
35 | if ( Templates == null ) {
36 | Templates = new TraceEvent[] {
37 | IISLogTemplate(null)
38 | };
39 | }
40 |
41 | var templs = Templates;
42 | foreach ( var e in templs ) {
43 | callback(e);
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/IISTraceEvent/IISTraceEvent.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {006B5002-EE7E-46DB-9345-80199CA7B2D4}
8 | Library
9 | Properties
10 | Winterdom.Diagnostics.Tracing.IISTraceEvent
11 | Winterdom.Diagnostics.Tracing.IISTraceEvent
12 | v4.5.1
13 | 512
14 |
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 | ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.32\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll
35 | True
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {afbed2c0-5d28-449e-b047-077b533c1852}
58 | TraceProcessor
59 |
60 |
61 |
62 |
63 | Designer
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
72 |
73 |
74 |
75 |
82 |
--------------------------------------------------------------------------------
/IISTraceEvent/Microsoft-Windows-IIS-Logging.manifest.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/IISTraceEvent/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("IISTraceEvent")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("IISTraceEvent")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("4de3ceec-87b8-4748-8e9f-d76caa397778")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/IISTraceEvent/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # IIS 8.5 ETW Logging
2 |
3 | IIS 8.5 (Windows Server 2012 R2) supports writing W3SVC logs to ETW (Event Tracing for Windows) in addition (or as an alternative) to traditional text-based W3C log files.
4 |
5 | You can read more about this feature [here](http://www.iis.net/learn/get-started/whats-new-in-iis-85/logging-to-etw-in-iis-85).
6 |
7 | This offers a lot of interesting opportunities for processing log events in a more compact format, and also for processing log entries in near real-time!
8 |
9 | This sample contains a simple TraceEventParser that can be used with the [Microsoft TraceEvent library](https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent) to process ETW events generated by the Microsoft-Windows-IIS-Logging provider in IIS 8.5.
10 |
11 | ## Using the Code
12 | Included is also some sample code on how to use it. Here's how to create a real-time ETW session and log events from IIS as they come:
13 |
14 |
15 | ```C#
16 | class Program {
17 | const String SessionName = "iis-etw";
18 | static void Main(string[] args) {
19 | // create a new real-time ETW trace session
20 | using ( var session = new TraceEventSession(SessionName) ) {
21 | // enable IIS ETW provider and set up a new trace source on it
22 | session.EnableProvider(IISLogTraceEventParser.ProviderName, TraceEventLevel.Verbose);
23 |
24 | using ( var traceSource = new ETWTraceEventSource(SessionName, TraceEventSourceType.Session) ) {
25 | Console.WriteLine("Session started, listening for events...");
26 | var parser = new IISLogTraceEventParser(traceSource);
27 | parser.IISLog += OnIISRequest;
28 |
29 | traceSource.Process();
30 | Console.ReadLine();
31 | traceSource.StopProcessing();
32 | }
33 | }
34 | }
35 | private static void OnIISRequest(IISLogTraceData request) {
36 | Console.WriteLine("{0} {1}", request.Cs_method, request.Cs_uri_stem);
37 | }
38 | }
39 | ```
40 |
41 | ## Pushing events to Azure
42 | The second part of this sample is a more interesting ETW event collector. This currently is a simple console application (IISLogCollector), but will later turn into a proper Windows NT service.
43 |
44 | This collector will capture ETW events in near-real time and can push said events to an [Azure Event Hub](http://azure.microsoft.com/en-us/services/event-hubs/). To use this feature, you'll need to first configure a few things:
45 |
46 | * Create a new Event Hub in the azure portal. You should also add a new Shared Access Policy (SAS) for the collector, which only requires "Send" permissions.
47 | * Create an `appSettings.config` file to the IISLogCollector project and ensure it is copied to the output folder during the build.
48 | * Edit the `appSettings.config` file to configure your Event Hub connection information:
49 |
50 | ```XML
51 |
52 |
54 |
55 |
56 |
60 |
61 |
66 |
67 |
72 |
73 | ```
74 |
75 | **Note:** This is just a sample, lacking most error handling and won't be able to handle a large number of events without some work. It is, however, useful for playing with Azure services :).
76 |
77 | ## About the code
78 | The code for the TraceEventParser is hand-written. This was my first attempt at writing such a thing and I wanted to better understand how it all worked under the hood. I'm sure I missed a few things here and there.
79 |
80 | One of the interesting aspects of it, however, is that ETW events generated by the Microsoft-Windows-IIS-Logging provider include some string fields in unicode and others in ANSI.
81 |
82 | Note, however, that you wouldn't normally do it this way. Instead, the TraceParserGen tool can be used to generate this code directly from the ETW provider manifest. A pointer to the tool can be found [here](http://blogs.msdn.com/b/vancem/archive/2013/08/15/traceevent-etw-library-published-as-a-nuget-package.aspx#10473283), and the help for the tool is pretty self-explanatory. Note, however, that the generated code for the IIS provider is pretty ugly :).
83 |
84 | ## Update (July 22, 2015)
85 | I've now implemented support for enabling multiple ETW event providers at the same time, and pushing all their events into the EventHub.
86 |
87 | TraceParsers and ETW providers are now registered through MEF, by implementing the IEtwEventProvider interface, and adding an [Export] attribute to it. There are two methods:
88 |
89 | * RegisterParser: used to create the TraceEventParser for your events on the event source.
90 | * EnableProvider: used to enable the ETW provider on the event session.
91 |
92 | You can find a sample implementation for IIS in IISTraceEvent\IISEventProvider.cs.
93 |
94 | Once you've implemented this class, just drop the assembly in the output folder along the .EXE and it will be picked up when it starts automatically.
95 |
--------------------------------------------------------------------------------
/Samples/SampleLogger/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Samples/SampleLogger/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Diagnostics.Tracing;
2 | using Microsoft.Diagnostics.Tracing.Session;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Winterdom.Diagnostics.Tracing.IISTraceEvent;
9 |
10 | namespace SampleLogger {
11 | class Program {
12 | const String SessionName = "iis-etw";
13 |
14 | static void Main(string[] args) {
15 | // create a new real-time ETW trace session
16 | using ( var session = new TraceEventSession(SessionName) ) {
17 | // enable IIS ETW provider and set up a new trace source on it
18 | session.EnableProvider(IISLogTraceEventParser.ProviderName, TraceEventLevel.Verbose);
19 |
20 | using ( var traceSource = new ETWTraceEventSource(SessionName, TraceEventSourceType.Session) ) {
21 | Console.WriteLine("Session started, listening for events...");
22 | var parser = new IISLogTraceEventParser(traceSource);
23 | parser.IISLog += OnIISRequest;
24 |
25 | traceSource.Process();
26 | Console.ReadLine();
27 | traceSource.StopProcessing();
28 | }
29 | }
30 | }
31 | private static void OnIISRequest(IISLogTraceData request) {
32 | Console.WriteLine(request.Dump(true));
33 | Console.WriteLine("*******************");
34 | //Console.WriteLine("{0} {1}", request.Cs_method, request.Cs_uri_stem);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Samples/SampleLogger/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SampleLogger")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SampleLogger")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("b9b77aec-eea5-4c09-9170-a58632e7e35a")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Samples/SampleLogger/SampleLogger.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {59E42D48-4A46-4791-A05F-BA42759A6B29}
8 | Exe
9 | Properties
10 | SampleLogger
11 | SampleLogger
12 | v4.5.1
13 | 512
14 | true
15 |
16 |
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 | ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.32\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll
40 | True
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {006b5002-ee7e-46db-9345-80199ca7b2d4}
61 | IISTraceEvent
62 |
63 |
64 |
65 |
66 |
67 |
68 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
69 |
70 |
71 |
72 |
79 |
--------------------------------------------------------------------------------
/Samples/SampleLogger/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/iis-etw-tracing.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.23107.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IISTraceEvent", "IISTraceEvent\IISTraceEvent.csproj", "{006B5002-EE7E-46DB-9345-80199CA7B2D4}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E7CE26F3-65F0-4AA8-BF71-1E89BA8C8611}"
9 | ProjectSection(SolutionItems) = preProject
10 | Common.proj = Common.proj
11 | Readme.md = Readme.md
12 | EndProjectSection
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{98B25BDD-A119-4F08-8BD1-EA0E77FDEE41}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleLogger", "Samples\SampleLogger\SampleLogger.csproj", "{59E42D48-4A46-4791-A05F-BA42759A6B29}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Collector", "Collector", "{B6F8E600-49F3-416D-B0E7-0AC110F76D2B}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceProcessor", "Collector\TraceProcessor\TraceProcessor.csproj", "{AFBED2C0-5D28-449E-B047-077B533C1852}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceProcessor.Tests", "Collector\TraceProcessor.Tests\TraceProcessor.Tests.csproj", "{F06244D4-313C-4B2C-AC24-F7B479014482}"
23 | EndProject
24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EtwLogCollector", "Collector\IISLogCollector\EtwLogCollector.csproj", "{F279711D-D760-4815-8750-E60B449ED50B}"
25 | EndProject
26 | Global
27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
28 | Debug|Any CPU = Debug|Any CPU
29 | Release|Any CPU = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {006B5002-EE7E-46DB-9345-80199CA7B2D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {006B5002-EE7E-46DB-9345-80199CA7B2D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {006B5002-EE7E-46DB-9345-80199CA7B2D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {006B5002-EE7E-46DB-9345-80199CA7B2D4}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {59E42D48-4A46-4791-A05F-BA42759A6B29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {59E42D48-4A46-4791-A05F-BA42759A6B29}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {59E42D48-4A46-4791-A05F-BA42759A6B29}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {59E42D48-4A46-4791-A05F-BA42759A6B29}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {AFBED2C0-5D28-449E-B047-077B533C1852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {AFBED2C0-5D28-449E-B047-077B533C1852}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {AFBED2C0-5D28-449E-B047-077B533C1852}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {AFBED2C0-5D28-449E-B047-077B533C1852}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {F06244D4-313C-4B2C-AC24-F7B479014482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {F06244D4-313C-4B2C-AC24-F7B479014482}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {F06244D4-313C-4B2C-AC24-F7B479014482}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {F06244D4-313C-4B2C-AC24-F7B479014482}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {F279711D-D760-4815-8750-E60B449ED50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {F279711D-D760-4815-8750-E60B449ED50B}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {F279711D-D760-4815-8750-E60B449ED50B}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {F279711D-D760-4815-8750-E60B449ED50B}.Release|Any CPU.Build.0 = Release|Any CPU
52 | EndGlobalSection
53 | GlobalSection(SolutionProperties) = preSolution
54 | HideSolutionNode = FALSE
55 | EndGlobalSection
56 | GlobalSection(NestedProjects) = preSolution
57 | {59E42D48-4A46-4791-A05F-BA42759A6B29} = {98B25BDD-A119-4F08-8BD1-EA0E77FDEE41}
58 | {AFBED2C0-5D28-449E-B047-077B533C1852} = {B6F8E600-49F3-416D-B0E7-0AC110F76D2B}
59 | {F06244D4-313C-4B2C-AC24-F7B479014482} = {B6F8E600-49F3-416D-B0E7-0AC110F76D2B}
60 | {F279711D-D760-4815-8750-E60B449ED50B} = {B6F8E600-49F3-416D-B0E7-0AC110F76D2B}
61 | EndGlobalSection
62 | EndGlobal
63 |
--------------------------------------------------------------------------------