├── .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 | 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 | --------------------------------------------------------------------------------