Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corefx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiudmila Molkova <lmolkova@microsoft.com>2017-01-07 04:57:52 +0300
committerLiudmila Molkova <lmolkova@microsoft.com>2017-02-08 03:22:52 +0300
commitcb468ec555ce4ca2e22c690b3a39191593cd97be (patch)
treed271fa0d19ca98742d24ce7ed957f39d39da17f8
parentb2ca211833d0c66ee427169b9517bb606ac03234 (diff)
Add Activity to DiagnosticSource #15023
-rw-r--r--src/System.Diagnostics.DiagnosticSource/ref/Configurations.props1
-rw-r--r--src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj1
-rw-r--r--src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs34
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md171
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/Configurations.props1
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj18
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs309
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net45.cs31
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net46.cs25
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs2
-rw-r--r--src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs45
-rw-r--r--src/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs259
-rw-r--r--src/System.Diagnostics.DiagnosticSource/tests/Configurations.props2
-rw-r--r--src/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs14
-rw-r--r--src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.builds4
-rw-r--r--src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj5
16 files changed, 913 insertions, 9 deletions
diff --git a/src/System.Diagnostics.DiagnosticSource/ref/Configurations.props b/src/System.Diagnostics.DiagnosticSource/ref/Configurations.props
index a66f1edba4..c6602850c6 100644
--- a/src/System.Diagnostics.DiagnosticSource/ref/Configurations.props
+++ b/src/System.Diagnostics.DiagnosticSource/ref/Configurations.props
@@ -2,6 +2,7 @@
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildConfigurations>
+ net45-Windows_NT;
netstandard;
netstandard1.1;
</BuildConfigurations>
diff --git a/src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj b/src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj
index cd326009af..027104fd2b 100644
--- a/src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj
+++ b/src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSource.csproj
@@ -13,6 +13,7 @@
<Compile Include="System.Diagnostics.DiagnosticSource.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netstandard1.1'">
+ <Compile Include="System.Diagnostics.DiagnosticSourceActivity.cs" />
<Reference Include="System.Runtime" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
diff --git a/src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
new file mode 100644
index 0000000000..ac8273cad4
--- /dev/null
+++ b/src/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the http://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+namespace System.Diagnostics {
+ public partial class Activity {
+ public Activity(string operationName) {}
+ public string OperationName { get { throw null; } }
+ public string Id {get { throw null; } private set {} }
+ public DateTime StartTimeUtc {get { throw null; } private set {} }
+ public Activity Parent {get { throw null; } private set {} }
+ public string ParentId {get { throw null; } private set {} }
+ public TimeSpan Duration {get { throw null; } private set {} }
+ public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string>> Tags { get { throw null; } }
+ public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string>> Baggage { get { throw null; } }
+ public string GetBaggageItem(string key) {throw null;}
+ public Activity AddTag(string key, string value) {throw null;}
+ public Activity AddBaggage(string key, string value) {throw null;}
+ public Activity SetParentId(string parentId) {throw null;}
+ public Activity SetStartTime(DateTime startTimeUtc) {throw null;}
+ public Activity SetEndTime(DateTime endTimeUtc) {throw null;}
+ public Activity Start() {throw null;}
+ public void Stop() {}
+ public static Activity Current {get { throw null; } private set {} }
+ }
+ public abstract partial class DiagnosticSource {
+ public Activity StartActivity(Activity activity, object args) {throw null;}
+ public void StopActivity(Activity activity, object args) {}
+ }
+}
+
diff --git a/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md b/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md
new file mode 100644
index 0000000000..0af2a365d3
--- /dev/null
+++ b/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md
@@ -0,0 +1,171 @@
+# Activity User Guide
+
+This document describes Activity, a class that allows storing and accessing diagnostics context and consuming it with logging system.
+
+This document provides Activity architecture [overview](#overview) and [usage](#activity-usage).
+
+# Overview
+When application starts procesing an operation e.g. HTTP request or task from queue, it creates an `Activity` to track it through the system as the request is processed. Examples of context stored in `Activity` could be HTTP request path, method, user-agent, or correlation id: all the details important to be logged along with every trace.
+When application calls external dependency to complete an operation, it may need to pass some of the context (e.g. correlation id) along with dependency call to be able to correlate logs from multiple services.
+
+`Activity` provides [Tags](#tags) to represent context which is needed for logging only and [Baggage](#baggage) to represent context which needs to be propagated to external dependencies. It has other properties described in [Activity Reference](#activity-reference).
+
+Every `Activity` has an `Id`, defining particular request in application, which is generated when Actvitity is started.
+
+`Activity` (except root one) has a [Parent](#parent) (in-process or external). E.g. application calls external dependency while processing incoming request, so there is an Activity for dependency call and it has Parent Activity representing incoming call.
+External dependency Activity Id is passed along with the request, so the dependency may use it as ParentId for it's activities and thus allow unique mapping between child and parent calls. Parent is assigned when activity is started.
+
+Activities may be created and started/stopped by platform code or frameworks; applications need to be notified about activities events and need to have access to current activity being processed.
+Therefore code which creates activity also writes corresponding event to `DiagnosticSource`:
+- DO - create new `DiagnosticListener` for specific Activity type to allow filtering by activity. E.g Incoming and Outgoing Http Requests should be different DiagnosticListeners. Follow [DiagnosticSource User Guilde](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.md) to peek a name.
+- DO - Guard Activity creation and start with call to `DiagnosticSource.IsEnabled` to avoid creating activities if no one listens to them and enable event name-based filtering.
+- DO - Use `DiagnosticSource.Start(Actvity)` and `DiagnosticSource.Stop(Actvity)` (extension) methods instead of Activity methods to ensure Activity events are always written to `DiagnosticSource`.
+- DO - pass necessary context to `DiagnosticListener`, so application may enrich Activity. In case of HTTP incoming request, application needs `HttpContext` to add custom tags (method, path, user-agent, etc)
+
+Current Activity is exposed as statis variable and flows with async calls so in every Start/Stop event callback it is accurate and may be used to log these events.
+
+Applications may access `Activity.Current` anywhere in the code to log events along with the context stored in Activity.
+# Activity Usage
+At the moment application writes log record, it can access `Activity.Current` to get all required details from it.
+
+## Creating Activities
+```C#
+ Activity activity = new Activity("Http_In");
+```
+Activity can be created with operation name. This is an coarse name that is useful for grouping and filtering log records.
+
+After Activity is created you can add additional details: [Start time](#starttimeutc), [Tags](#tags) and [Baggage](#baggage)
+```C#
+ activity.WithStartTime(highPrecisionStartTime)
+ .WithTag("Path", request.Path)
+ .WithBaggage("CorrelationId", request.Headers["x-ms-correlation-id"]);
+```
+
+When activity is built, it's time to start it and continue with request processing.
+
+## Starting and Stoping Activity
+
+Activity.Start() and Stop() methods maitain [Activity.Current](current) which flows with async calls and available during request processing.
+When activity is started, it assigned with an [Id](id) and [Parent](parent).
+
+```C#
+ public void OnIncomingRequest(DiagnosticListener httpListener, HttpContext request)
+ {
+ if (httpListener.IsEnabled("Http_In"))
+ {
+ Activity activity = new Activity("Http_In");
+ //add tags, baggage, etc..
+ activity.WithParentId(context.Request.headers["x-ms-request-id"])
+ foreach (var header in context.Request.Headers)
+ if (header.Key.StartsWith("x-baggage-")
+ activity.WithBaggage(header.Key, header.Value);
+
+ httpListener.Start(activity, httpContext);
+ try {
+ //process request ...
+ } finally {
+ //stop activity
+ httpListener.Stop(activity, highPrecisionStopTime);
+ }
+ }
+ }
+```
+Note that instead of Activity.Start() and Stop() methods, in above example we call `DiagnosticSource.Start()` and `Stop()` (extension) methods that fire events to be consumed with DiagnosticListener.
+Note that Activities creation is guarded with `DiagnosticSource.IsEnabled` and will only happen if someone listens to this `DiagnosticSource` thus eliminating any unnecessary performance impact.
+
+## Creating child Activities
+When application calls external web-service, new activity is created to represent external operation. This activity may have Parent (if this request is part of incoming request processing), assigned to it during Start().
+
+```C#
+ public void OnOutgoingRequest(DiagnosticListener httpListener, HttpRequestMessage request)
+ {
+ if (httpListener.IsEnabled(request.RequestUri.ToString()))
+ {
+ var activity = new Activity("Http_Out");
+ httpListener.Start(activity, request);
+
+ request.Headers.Add("x-ms-request-id", activity.Id);
+ foreach (var baggage in activity.Baggage)
+ request.Headers.Add(baggage.Key, baggage.Value);
+ try {
+ //process request ...
+ } finally {
+ //stop activity
+ httpListener.Stop(activity, value.Value, DateTimeStopwatch.GetTime(timestamp));
+ }
+ }
+ }
+```
+
+New Activity will inherit Baggage from parent. Above example demonstrates how baggage could be propagated to downstream web-service in HTTP request headers.
+
+Similarly to incoming request processing, activity creation should be guarded with `DiagnosticSource.IsEnabled()` call, however allowing to filter headers injection based on request Uri.
+Note that different DiagnosticSources should be used for incoming and outgoing HTTP activities allowing to implement separate filtering for events.
+
+## Listening to Activity Events
+Application may listen to activity events and log them. It can access `Activity.Current` to get information about current activity.
+This follows normal [DiagnosticListener conventions](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.md)
+
+Application may also add tags and baggage to current activity when processing Activity start callback.
+Note in the [Incoming Request Sample](#starting-and-stoping-activity), we pass `HttpContext` to DiagnosticSource, so application may access request properties to enrich current activity.
+
+# Reference
+## Activity
+### Tags
+`IEnumerable<KeyValuePair<string, string>> Tags { get; }` - Represents information to be logged along with activity. Good examples of tags are instance/machine name, incoming request HTTP method, path, user/user-agent, etc. Tags are **not passed** to the children of Activity.
+Typical tag usage includes adding a few custom tags and enumeration through them to fill log event payload. Getting tag by it's key is not supported.
+
+### Baggage
+`IEnumerable<KeyValuePair<string, string>> Baggage { get; }` - Represents information to be logged woth activity AND passed to it's children. Examples of baggage include correlation id, sampling and feature flags.
+Baggage is serialized and **passed it along with external dependency request**.
+Typical Baggage usage includes adding a few baggage properties and enumeration through them to fill log event payload.
+
+### OperationName
+`string OperationName { get; }` - Coarset name for an activity
+
+### StartTimeUtc
+`DateTime StartTimeUtc { get; private set; }` - DateTime in UTC (Greenwitch Mean Time) when activity was started. DateTime.UtcNow if not specified
+
+### Duration
+`TimeSpan Duration { get; private set; }` - Represent Activity duration if activity was stopped, TimeSpan.Zero otherwise
+
+### Id
+`string Id { get; private set; }` - Represents particular activity identifier. Filtering to a particular Id insures that you get only log records related to specific request within the operation. It is assigned when activity is started.
+Id is passed to external dependencies and considered as [ParentId](#parentid) for new external activity.
+
+### ParentId
+`string ParentId { get; private set; }` - Activity may have either in-process [Parent](#parent) or Id of external Parent if it was deserialized from request. ParentId together with Id represent parent-child relationship in logs and allows to uniquely map outgoing and incoming requests.
+ParentId is implemented as a tag (see [Tags](#tags)).
+
+### Current
+`static Activity Current { get; }` - Returns current Activity which flows across async calls
+
+### Parent
+`public Activity Parent { get; private set; }` - If activity was created from another activity in the same process, you can get that Activity with the Parent accessor. However this can be null if the Activity is root activity or parent is from outside the process.
+
+### Start()
+`static Activity Start(Activity activity)` - Starts Activity: sets Activity.Current and Parent for the activity.
+
+### Stop()
+`static void Stop(Activity activity, DateTime stopTimeUtc = default(DateTime))` - Stops Activity: sets Activity.Current and Diration for the activity. Uses DateTime.UtcNow by default as stopTimeUtc if not provided.
+
+### WithBaggage()
+`Activity WithBaggage(string key, string value)` - adds baggage item, see [Baggage](#baggage)
+
+### GetBaggageItem()
+`string GetBaggageItem(string key)` - returns value of [Baggage](#baggage) key-value pair with given key, null if key does not exist
+
+### WithTag()
+`Activity WithTag(string key, string value)` - adds tag, see [Tags](#tags)
+
+### WithParentId()
+`Activity WithParentId(string key, string value)` - sets parent Id, see [ParentId](#parentid)
+
+### WithStartTime()
+`Activity WithStartTime(DateTime startTimeUtc)` - sets parent Id, see [StartTimeUtc](#starttimeutc)
+
+##DiagnosticSource
+### Start
+`static Activity Start(this DiagnosticSource self, Activity activity, object args)` - Starts activity and writes DiagnosticSource event
+### Stop
+`static Activity Start(this DiagnosticSource self, string activityName, object args)` - Stops activity and writes DiagnosticSource event
diff --git a/src/System.Diagnostics.DiagnosticSource/src/Configurations.props b/src/System.Diagnostics.DiagnosticSource/src/Configurations.props
index 5efb38bc43..d236034a26 100644
--- a/src/System.Diagnostics.DiagnosticSource/src/Configurations.props
+++ b/src/System.Diagnostics.DiagnosticSource/src/Configurations.props
@@ -2,6 +2,7 @@
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildConfigurations>
+ net45-Windows_NT;
netstandard1.1;
netstandard1.3;
netstandard;
diff --git a/src/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
index bef2163241..655a17a8e8 100644
--- a/src/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
+++ b/src/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj
@@ -13,10 +13,12 @@
to work with older NuGet clients -->
<PackageTargetFramework Condition="'$(TargetGroup)' == 'netstandard1.1'">netstandard1.1;portable-net45+win8+wpa81</PackageTargetFramework>
</PropertyGroup>
- <PropertyGroup Condition="'$(TargetGroup)' == 'netstandard1.1'">
+ <PropertyGroup Condition="'$(TargetGroup)' == 'netstandard1.1' Or '$(TargetGroup)' == 'net45'">
<DefineConstants>;NO_EVENTSOURCE_COMPLEX_TYPE_SUPPORT</DefineConstants>
</PropertyGroup>
<!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='net45-Windows_NT-Debug|AnyCPU'" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='net45-Windows_NT-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Debug|AnyCPU'" />
@@ -31,12 +33,26 @@
<Compile Include="System\Diagnostics\DiagnosticSourceEventSource.cs" />
<None Include="DiagnosticSourceUsersGuide.md" />
</ItemGroup>
+ <ItemGroup Condition=" '$(TargetGroup)' != 'netstandard1.1'">
+ <Compile Include="System\Diagnostics\Activity.cs" />
+ <Compile Include="System\Diagnostics\DiagnosticSourceActivity.cs" />
+ <None Include="ActivityUserGuide.md" />
+ </ItemGroup>
+ <ItemGroup Condition=" '$(TargetGroup)' != 'net45' And '$(TargetGroup)' != 'netstandard1.1'">
+ <Compile Include="System\Diagnostics\Activity.net46.cs" />
+ </ItemGroup>
+ <ItemGroup Condition=" '$(TargetGroup)' == 'net45' ">
+ <Compile Include="System\Diagnostics\Activity.net45.cs" />
+ <TargetingPackReference Include="System" />
+ <TargetingPackReference Include="System.Runtime.Remoting" />
+ </ItemGroup>
<ItemGroup>
<Reference Include="System.Collections" />
<Reference Include="System.Diagnostics.Debug" />
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.Reflection" />
<Reference Include="System.Runtime" />
+ <Reference Include="System.Runtime.Extensions" />
<Reference Include="System.Threading" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
diff --git a/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs
new file mode 100644
index 0000000000..33f008263e
--- /dev/null
+++ b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs
@@ -0,0 +1,309 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Threading;
+
+namespace System.Diagnostics
+{
+ /// <summary>
+ /// Activity represents operation with context to be used for logging.
+ /// Activity has operation name, Id, start time and duration, tags and baggage.
+ ///
+ /// Current activity can be accessed with static AsyncLocal variable Activity.Current.
+ ///
+ /// Activities should be created with constructor, configured as necessarily
+ /// and then started with Activity.Start method which maintaines parent-child
+ /// relationships for the activities and sets Activity.Current.
+ ///
+ /// When activity is finished, it should be stopped with static Activity.Stop method.
+ /// </summary>
+ public partial class Activity
+ {
+ /// <summary>
+ /// An operation name is a COARSEST name that is useful grouping/filtering.
+ /// The name is typically a compile time constant. Names of Rest APIs are
+ /// reasonable, but arguments (e.g. specific accounts etc), should not be in
+ /// the name but rather in the tags.
+ /// </summary>
+ public string OperationName { get; }
+
+ /// <summary>
+ /// This is an ID that is specific to a particular request. Filtering
+ /// to a particular ID insures that you get only one request that matches.
+ /// It is typically assigned the system itself.
+ /// </summary>
+ public string Id { get; private set; }
+
+ /// <summary>
+ /// The time that operation started. Typcially when Start() is called
+ /// (but you can pass a value to Start() if necessary. This use UTC (Greenwitch Mean Time)
+ /// </summary>
+ public DateTime StartTimeUtc { get; private set; }
+
+ /// <summary>
+ /// If the Activity that created this activity is from the same process you can get
+ /// that Activit with Parent. However this can be null if the Activity has no
+ /// parent (a root activity) or if the Parent is from outside the process. (see ParentId for more)
+ /// </summary>
+ public Activity Parent { get; private set; }
+
+ /// <summary>
+ /// If the parent for this activity comes from outside the process, the activity
+ /// does not have a Parent Activity but MAY have a ParentId (which was serialized from
+ /// from the parent) . This accessor fetches the parent ID if it exists at all.
+ /// Note this can be null if this is a root Activity (it has no parent)
+ /// </summary>
+ public string ParentId { get; private set; }
+
+ /// <summary>
+ /// Tags are string-string key-value pairs that represent information that will
+ /// be logged along with the Activity to the logging system. This information
+ /// however is NOT passed on to the children of this activity. (see Baggage)
+ /// </summary>
+ public IEnumerable<KeyValuePair<string, string>> Tags
+ {
+ get
+ {
+ if (ParentId != null)
+ yield return new KeyValuePair<string, string>("ParentId", ParentId);
+ for (var tags = _tags; tags != null; tags = tags.Next)
+ yield return tags.keyValue;
+ }
+ }
+
+ /// <summary>
+ /// Tags are string-string key-value pairs that represent information that will
+ /// be passed along to children of this activity. Baggage is serialized
+ /// when requests leave the process (along with the ID). Typically Baggage is
+ /// used to do fine-grained control over logging of the activty and any children.
+ /// In general, if you are not using the data at runtime, you should be using Tags
+ /// instead.
+ /// </summary>
+ public IEnumerable<KeyValuePair<string, string>> Baggage
+ {
+ get
+ {
+ for (var activity = this; activity != null; activity = activity.Parent)
+ for (var baggage = activity._baggage; baggage != null; baggage = baggage.Next)
+ yield return baggage.keyValue;
+ }
+ }
+
+ /// <summary>
+ /// Returns the value of the key-value pair added to the activity with 'WithBaggage'.
+ /// Returns null if that key does not exist.
+ /// </summary>
+ public string GetBaggageItem(string key)
+ {
+ foreach (var keyValue in Baggage)
+ if (key == keyValue.Key)
+ return keyValue.Value;
+ return null;
+ }
+
+ /* Constructors Builder methods */
+
+ /// <summary>
+ /// Note that Activity has a 'builder' pattern, where you call the constructor, a number of 'With*' APIs and then
+ /// call 'Activity.Start' to build the activity. You MUST call Start before using it
+ /// </summary>
+ /// <param name="operationName">Operations name <see cref="OperationName"/></param>
+ public Activity(string operationName)
+ {
+ OperationName = operationName;
+ }
+
+ /// <summary>
+ /// Update the Activity to have a tag with an additional 'key' and value 'value'.
+ /// This shows up in the 'Tags' eumeration. It is meant for information that
+ /// is useful to log but not needed for runtime control (for the latter, use Baggage)
+ /// </summary>
+ /// <returns>'this' for convinient chaining</returns>
+ public Activity AddTag(string key, string value)
+ {
+ _tags = new KeyValueListNode() { keyValue = new KeyValuePair<string, string>(key, value), Next = _tags };
+ return this;
+ }
+
+ /// <summary>
+ /// Update the Activity to have baggage with an additional 'key' and value 'value'.
+ /// This shows up in the 'Baggage' eumeration as well as the 'GetBaggageItem' API.
+ /// Baggage is mean for information that is needed for runtime control. For information
+ /// that is simply useful to show up in the log with the activity use Tags.
+ /// Returns 'this' for convinient chaining.
+ /// </summary>
+ /// <returns>'this' for convinient chaining</returns>
+ public Activity AddBaggage(string key, string value)
+ {
+ _baggage = new KeyValueListNode() { keyValue = new KeyValuePair<string, string>(key, value), Next = _baggage };
+ return this;
+ }
+
+ /// <summary>
+ /// Updates the Activity To indicate that the activity with ID 'parentID'
+ /// caused this activity. This is only intended to be used at 'boundary'
+ /// scenarios where an activity from another process loggically started
+ /// this activity. The Parent ID shows up the Tags (as well as the ParentID
+ /// property), and can be used to reconstruct the causal tree.
+ /// Returns 'this' for convinient chaining.
+ /// </summary>
+ public Activity SetParentId(string parentId)
+ {
+ ParentId = parentId;
+ return this;
+ }
+
+ /// <summary>
+ /// Update the Activity to set start time
+ /// </summary>
+ /// <param name="startTimeUtc">Activity start time in UTC (Greenwitch Mean Time)</param>
+ /// <returns>'this' for convinient chaining</returns>
+ public Activity SetStartTime(DateTime startTimeUtc)
+ {
+ StartTimeUtc = startTimeUtc;
+ return this;
+ }
+ /// <summary>
+ /// Update the Activity to set <see cref="Duration"/>
+ /// as a difference between <see cref="StartTimeUtc"/>
+ /// and given stop timestamp
+ /// </summary>
+ /// <param name="endTimeUtc">Activity stop time in UTC (Greenwitch Mean Time)</param>
+ /// <returns>'this' for convinient chaining</returns>
+ public Activity SetEndTime(DateTime endTimeUtc)
+ {
+ Duration = endTimeUtc - StartTimeUtc;
+ return this;
+ }
+
+ /// <summary>
+ /// If the Activity has ended (Stop was called) then this is the delta
+ /// between start and end. If the activity is not ended then this is
+ /// TimeSpan.Zero.
+ /// </summary>
+ public TimeSpan Duration { get; private set; }
+
+ /// <summary>
+ /// Starts activity: sets <see cref="Parent"/> to hold <see cref="Current"/> and sets Current to this Activity.
+ /// If <see cref="StartTimeUtc"/> was not set previously, sets it to DateTime.UtcNow.
+ /// Use DiagnosticSource.Start to start activity and write start event.
+ /// </summary>
+ /// <returns>Started activity for convinient chaining</returns>
+ /// <seealso cref="DiagnosticSource.StartActivity(Activity, object)"/>
+ /// <seealso cref="SetStartTime(DateTime)"/>
+ public Activity Start()
+ {
+ if (Id != null)
+ throw new InvalidOperationException("Trying to start an Activity that was already started");
+
+ if (ParentId == null)
+ {
+ var parent = Current;
+ if (parent != null)
+ {
+ ParentId = parent.Id;
+ Parent = parent;
+ }
+ }
+
+ if (StartTimeUtc == default(DateTime))
+ StartTimeUtc = DateTime.UtcNow;
+
+ Id = GenerateId();
+
+ Current = this;
+ return this;
+ }
+
+ /// <summary>
+ /// Stops activity: sets Current to Parent.
+ /// If end time was not set previously, sets <see cref="Duration"/> as a difference between DateTime.UtcNow and <see cref="StartTimeUtc"/>
+ /// Use DiagnosticSource.Stop to stop activity and write stop event.
+ /// </summary>
+ /// <seealso cref="DiagnosticSource.StopActivity(Activity, object)"/>
+ /// <seealso cref="SetEndTime(DateTime)"/>
+ public void Stop()
+ {
+ if (Id == null)
+ throw new InvalidOperationException("Trying to stop an Activity that was not started");
+
+ if (!isFinished)
+ {
+ isFinished = true;
+
+ if (Duration == TimeSpan.Zero)
+ SetEndTime(DateTime.UtcNow);
+
+ Current = Parent;
+ }
+ }
+
+ #region private
+
+ private string GenerateId()
+ {
+ string ret;
+ if (Parent != null)
+ {
+ // Normal start within the process
+ Debug.Assert(!string.IsNullOrEmpty(Parent.Id));
+#if DEBUG
+ ret = Parent.Id + "/" + OperationName + "_" + Interlocked.Increment(ref Parent._currentChildId);
+#else // To keep things short, we drop the operation name
+ ret = Parent.Id + "/" + Interlocked.Increment(ref Parent._currentChildId);
+#endif
+ }
+ else if (ParentId != null)
+ {
+ // Start from outside the process (e.g. incoming HTTP)
+ Debug.Assert(ParentId.Length != 0);
+#if DEBUG
+ ret = ParentId + "/" + OperationName + "_I_" + Interlocked.Increment(ref s_currentRootId);
+#else // To keep things short, we drop the operation name
+ ret = ParentId + "/I_" + Interlocked.Increment(ref s_currentRootId);
+#endif
+ }
+ else
+ {
+ // A Root Activity (no parent).
+ if (s_uniqPrefix == null)
+ {
+ // Here we make an ID to represent the Process/AppDomain. Ideally we use process ID but
+ // it is unclear if we have that ID handy. Currently we use low bits of high freq tick
+ // as a unique random number (which is not bad, but loses randomness for startup scenarios).
+ int uniqNum = (int)Stopwatch.GetTimestamp();
+ string uniqPrefix = $"//{uniqNum:x}_";
+ Interlocked.CompareExchange(ref s_uniqPrefix, uniqPrefix, null);
+ }
+#if DEBUG
+ ret = s_uniqPrefix + OperationName + "_" + Interlocked.Increment(ref s_currentRootId);
+#else // To keep things short, we drop the operation name
+ ret = s_uniqPrefix + Interlocked.Increment(ref s_currentRootId);
+#endif
+ }
+ // Useful place to place a conditional breakpoint.
+ return ret;
+ }
+
+ // Used to generate an ID
+ long _currentChildId; // A unique number for all children of this activity.
+ static long s_currentRootId; // A unique number inside the appdomain.
+ static string s_uniqPrefix; // A unique prefix that represents the machine/process/appdomain
+
+ /// <summary>
+ /// Having our own key-value linked list allows us to be more efficient
+ /// </summary>
+ private class KeyValueListNode
+ {
+ public KeyValuePair<string, string> keyValue;
+ public KeyValueListNode Next;
+ }
+
+ private KeyValueListNode _tags;
+ private KeyValueListNode _baggage;
+ private bool isFinished;
+#endregion // private
+ }
+}
diff --git a/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net45.cs b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net45.cs
new file mode 100644
index 0000000000..a2d81e6b0d
--- /dev/null
+++ b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net45.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.Remoting.Messaging;
+
+namespace System.Diagnostics
+{
+ public partial class Activity
+ {
+ /// <summary>
+ /// Returns the current operation (Activity) for the current thread. This flows
+ /// across async calls.
+ /// </summary>
+ public static Activity Current
+ {
+ get
+ {
+ return (Activity)CallContext.LogicalGetData(FieldKey);
+ }
+ private set
+ {
+ CallContext.LogicalSetData(FieldKey, value);
+ }
+ }
+
+ #region private
+ private static readonly string FieldKey = $"{typeof(Activity).FullName}.Value.{AppDomain.CurrentDomain.Id}";
+ #endregion
+ }
+}
diff --git a/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net46.cs b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net46.cs
new file mode 100644
index 0000000000..58aeef813d
--- /dev/null
+++ b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.net46.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+
+namespace System.Diagnostics
+{
+ public partial class Activity
+ {
+ /// <summary>
+ /// Returns the current operation (Activity) for the current thread. This flows
+ /// across async calls.
+ /// </summary>
+ public static Activity Current
+ {
+ get { return s_current.Value; }
+ private set { s_current.Value = value; }
+ }
+
+#region private
+ private static readonly AsyncLocal<Activity> s_current = new AsyncLocal<Activity>();
+#endregion // private
+ }
+}
diff --git a/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs
index 4e4346bb97..d7e25c628e 100644
--- a/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs
+++ b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSource.cs
@@ -14,7 +14,7 @@ namespace System.Diagnostics
/// https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.md
/// for instructions on its use.
/// </summary>
- public abstract class DiagnosticSource
+ public abstract partial class DiagnosticSource
{
/// <summary>
/// Write is a generic way of logging complex payloads. Each notification
diff --git a/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs
new file mode 100644
index 0000000000..8ad372c81a
--- /dev/null
+++ b/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceActivity.cs
@@ -0,0 +1,45 @@
+namespace System.Diagnostics
+{
+ public abstract partial class DiagnosticSource
+ {
+ /// <summary>
+ /// Starts an Activity and writes start event.
+ ///
+ /// Activity describes logical operation, its context and parent relation;
+ /// Current activity flows through the operation processing.
+ ///
+ /// This method starts given Activity (maintains global Current Activity
+ /// and Parent for the given activity) and notifies consumers that new Activity
+ /// was started. Consumers could access <see cref="Activity.Current"/>
+ /// to add context and/or augument telemetry.
+ ///
+ /// Producers may pass additional details to the consumer in the payload.
+ /// </summary>
+ /// <param name="activity">Activity to be started</param>
+ /// <param name="args">An object that represent the value being passed as a payload for the event.</param>
+ /// <returns>Started Activity for convenient chaining</returns>
+ /// <seealso cref="Activity"/>
+ public Activity StartActivity(Activity activity, object args)
+ {
+ activity.Start();
+ Write(activity.OperationName + ".Start", args);
+ return activity;
+ }
+
+ /// <summary>
+ /// Stops given Activity: maintains global Current Activity and notifies consumers
+ /// that Activity was stopped. Consumers could access <see cref="Activity.Current"/>
+ /// to add context and/or augument telemetry.
+ ///
+ /// Producers may pass additional details to the consumer in the payload.
+ /// </summary>
+ /// <param name="activity">Activity to be stoped</param>
+ /// <param name="args">An object that represent the value being passed as a payload for the event.</param>
+ /// <seealso cref="Activity"/>
+ public void StopActivity(Activity activity, object args)
+ {
+ Write(activity.OperationName + ".Stop", args);
+ activity.Stop();
+ }
+ }
+}
diff --git a/src/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs b/src/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs
new file mode 100644
index 0000000000..ae740cee97
--- /dev/null
+++ b/src/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs
@@ -0,0 +1,259 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Diagnostics.Tests
+{
+ public class ActivityTests
+ {
+ /// <summary>
+ /// Tests Activity constructor
+ /// </summary>
+ [Fact]
+ public void DefaultActivity()
+ {
+ string activityName = "activity";
+ var activity = new Activity(activityName);
+ Assert.Equal(activityName, activity.OperationName);
+ Assert.Null(activity.Id);
+ Assert.Equal(TimeSpan.Zero, activity.Duration);
+ Assert.Null(activity.Parent);
+ Assert.Null(activity.ParentId);
+ Assert.Equal(0, activity.Baggage.ToList().Count);
+ Assert.Equal(0, activity.Tags.ToList().Count);
+ }
+
+ /// <summary>
+ /// Tests baggage operations
+ /// </summary>
+ [Fact]
+ public void Baggage()
+ {
+ var activity = new Activity("activity");
+ Assert.Null(activity.GetBaggageItem("some baggage"));
+
+ Assert.Equal(activity, activity.AddBaggage("some baggage", "value"));
+ Assert.Equal("value", activity.GetBaggageItem("some baggage"));
+
+ var baggage = activity.Baggage.ToList();
+ Assert.Equal(1, baggage.Count);
+ }
+
+ /// <summary>
+ /// Tests Tags operations
+ /// </summary>
+ [Fact]
+ public void Tags()
+ {
+ var activity = new Activity("activity");
+
+ Assert.Equal(activity, activity.AddTag("some tag", "value"));
+
+ var tags = activity.Tags.ToList();
+ Assert.Equal(1, tags.Count);
+ Assert.Equal(tags[0].Key, "some tag");
+ Assert.Equal(tags[0].Value, "value");
+
+ Assert.Equal(activity, activity.SetParentId("1"));
+ Assert.Equal(2, activity.Tags.ToList().Count);
+ Assert.Equal("1", activity.ParentId);
+ }
+
+ /// <summary>
+ /// Tests activity start and stop
+ /// Checks Activity.Current correctness, Id generation
+ /// </summary>
+ [Fact]
+ public void StartStop()
+ {
+ var activity = new Activity("activity");
+ Assert.Equal(null, Activity.Current);
+ activity.Start();
+ Assert.Equal(activity, Activity.Current);
+ Assert.Null(activity.Parent);
+ Assert.NotNull(activity.Id);
+ Assert.NotEqual(default(DateTime), activity.StartTimeUtc);
+
+ activity.Stop();
+ Assert.Equal(null, Activity.Current);
+ }
+
+ /// <summary>
+ /// Tests Activity Start and Stop with timestamp
+ /// </summary>
+ [Fact]
+ public void StartStopWithTimestamp()
+ {
+ var startTime = DateTime.UtcNow.AddSeconds(-1);
+ var activity = new Activity("activity")
+ .SetStartTime(startTime);
+
+ activity.Start();
+ Assert.Equal(startTime, activity.StartTimeUtc);
+
+ var stopTime = DateTime.UtcNow;
+ activity.SetEndTime(stopTime);
+ Assert.Equal(stopTime - startTime, activity.Duration);
+ }
+
+ /// <summary>
+ /// Tests Activity Stop without timestamp
+ /// </summary>
+ [Fact]
+ public void StopWithoutTimestamp()
+ {
+ var startTime = DateTime.UtcNow.AddSeconds(-1);
+ var activity = new Activity("activity")
+ .SetStartTime(startTime);
+
+ activity.Start();
+ Assert.Equal(startTime, activity.StartTimeUtc);
+
+ activity.Stop();
+ Assert.True(activity.Duration.TotalSeconds >= 1);
+ }
+
+ /// <summary>
+ /// Tests Activity stack: creates a parent activity and child activity
+ /// Verifies
+ /// - Activity.Parent and ParentId corectness
+ /// - Baggage propagated from parent
+ /// - Tags are not propagated
+ /// Stops child and checks Activity,Current is set to parent
+ /// </summary>
+ [Fact]
+ public void ParentChild()
+ {
+ var parent = new Activity("parent")
+ .AddBaggage("id1", "baggage from parent")
+ .AddTag("tag1", "tag from parent");
+
+ parent.Start();
+ Assert.Equal(parent, Activity.Current);
+
+ var child = new Activity("child");
+ child.Start();
+ Assert.Equal(parent, child.Parent);
+ Assert.Equal(parent.Id, child.ParentId);
+
+ //baggage from parent
+ Assert.Equal("baggage from parent", child.GetBaggageItem("id1"));
+
+ //no tags from parent
+ var childTags = child.Tags.ToList();
+ Assert.Equal(1, childTags.Count);
+ Assert.Equal(child.ParentId, childTags[0].Value);
+
+ child.Stop();
+ Assert.Equal(parent, Activity.Current);
+
+ parent.Stop();
+ Assert.Equal(null, Activity.Current);
+ }
+
+ /// <summary>
+ /// Tests wrong stop order, when parent is stopped before child
+ /// </summary>
+ [Fact]
+ public void StopParent()
+ {
+ var parent = new Activity("parent");
+ parent.Start();
+ var child = new Activity("child");
+ child.Start();
+
+ parent.Stop();
+ Assert.Equal(null, Activity.Current);
+ }
+
+ /// <summary>
+ /// Tests that activity can not be stated twice
+ /// </summary>
+ [Fact]
+ public void StartTwice()
+ {
+ var activity = new Activity("");
+ activity.Start();
+ Assert.Throws<InvalidOperationException>(() => activity.Start());
+ }
+
+ /// <summary>
+ /// Tests that activity that has not been started can not be stopped
+ /// </summary>
+ [Fact]
+ public void StopNotStarted()
+ {
+ Assert.Throws<InvalidOperationException>(() => new Activity("").Stop());
+ }
+
+ /// <summary>
+ /// Tests that second activity stop does not update Activity.Current
+ /// </summary>
+ [Fact]
+ public void StopTwice()
+ {
+ var parent = new Activity("parent");
+ parent.Start();
+
+ var child1 = new Activity("child1");
+ child1.Start();
+ child1.Stop();
+
+ var child2 = new Activity("child2");
+ child2.Start();
+
+ child1.Stop();
+
+ Assert.Equal(child2, Activity.Current);
+ }
+
+ [Fact]
+ public void DiagnosticSourceStartStop()
+ {
+ using (DiagnosticListener listener = new DiagnosticListener("Testing"))
+ {
+ DiagnosticSource source = listener;
+ var observer = new TestObserver();
+
+ using (listener.Subscribe(observer))
+ {
+ var arguments = new { args = "arguments" };
+
+ var activity = new Activity("activity");
+
+ source.StartActivity(activity, arguments);
+ Assert.Equal(activity.OperationName + ".Start", observer.EventName);
+ Assert.Equal(arguments, observer.EventObject);
+
+ observer.Reset();
+
+ source.StopActivity(activity, arguments);
+ Assert.Equal(activity.OperationName + ".Stop", observer.EventName);
+ Assert.Equal(arguments, observer.EventObject);
+ }
+ }
+ }
+
+ private class TestObserver : IObserver<KeyValuePair<string, object>>
+ {
+ public string EventName { get; private set; }
+ public object EventObject { get; private set; }
+
+ public void OnNext(KeyValuePair<string, object> value)
+ {
+ EventName = value.Key;
+ EventObject = value.Value;
+ }
+
+ public void Reset()
+ {
+ EventName = null;
+ EventObject = null;
+ }
+
+ public void OnCompleted() { }
+
+ public void OnError(Exception error) { }
+ }
+ }
+}
diff --git a/src/System.Diagnostics.DiagnosticSource/tests/Configurations.props b/src/System.Diagnostics.DiagnosticSource/tests/Configurations.props
index c398e42e89..c6602850c6 100644
--- a/src/System.Diagnostics.DiagnosticSource/tests/Configurations.props
+++ b/src/System.Diagnostics.DiagnosticSource/tests/Configurations.props
@@ -2,7 +2,9 @@
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildConfigurations>
+ net45-Windows_NT;
netstandard;
+ netstandard1.1;
</BuildConfigurations>
</PropertyGroup>
</Project> \ No newline at end of file
diff --git a/src/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs b/src/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs
index d0e0c39f2b..a34e197b35 100644
--- a/src/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs
+++ b/src/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs
@@ -2,10 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
using System.Collections.Generic;
using Xunit;
-using System.Threading.Tasks;
using System.Diagnostics.Tracing;
using System.Text;
@@ -16,6 +14,8 @@ using System.Text;
namespace System.Diagnostics.Tests
{
+ //Complex types are not supported on EventSource for .NET 4.5
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework45)]
public class DiagnosticSourceEventSourceBridgeTests
{
/// <summary>
@@ -657,7 +657,7 @@ namespace System.Diagnostics.Tests
// Here just for debugging. Lets you see the last 3 events that were sent.
public DiagnosticSourceEvent SecondLast;
public DiagnosticSourceEvent ThirdLast;
-#endif
+#endif
/// <summary>
/// Sets the EventCount to 0 and LastEvent to null
@@ -666,7 +666,7 @@ namespace System.Diagnostics.Tests
{
EventCount = 0;
LastEvent = null;
-#if DEBUG
+#if DEBUG
SecondLast = null;
ThirdLast = null;
#endif
@@ -677,7 +677,7 @@ namespace System.Diagnostics.Tests
/// </summary>
public Predicate<DiagnosticSourceEvent> Filter;
- #region private
+#region private
private void UpdateLastEvent(DiagnosticSourceEvent anEvent)
{
if (Filter != null && !Filter(anEvent))
@@ -691,7 +691,7 @@ namespace System.Diagnostics.Tests
EventCount++;
LastEvent = anEvent;
}
- #endregion
+#endregion
}
/// <summary>
@@ -826,4 +826,4 @@ namespace System.Diagnostics.Tests
EventSource _diagnosticSourceEventSource;
#endregion
}
-}
+} \ No newline at end of file
diff --git a/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.builds b/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.builds
index 216a65acfa..fa10042e93 100644
--- a/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.builds
+++ b/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.builds
@@ -7,6 +7,10 @@
<OSGroup>Windows_NT</OSGroup>
<TestTFMs>netcore50;net46</TestTFMs>
</Project>
+ <Project Include="System.Diagnostics.DiagnosticSource.Tests.csproj">
+ <OSGroup>Windows_NT</OSGroup>
+ <TestTFMs>net45</TestTFMs>
+ </Project>
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.traversal.targets))\dir.traversal.targets" />
</Project> \ No newline at end of file
diff --git a/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj b/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
index 8b5c9ce20b..a8260e8ad0 100644
--- a/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
+++ b/src/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj
@@ -4,8 +4,13 @@
<PropertyGroup>
<ProjectGuid>{A7922FA3-306A-41B9-B8DC-CC4DBE685A85}</ProjectGuid>
</PropertyGroup>
+<<<<<<< HEAD
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'net45-Windows_NT-Debug|AnyCPU'" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'net45-Windows_NT-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Release|AnyCPU'" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard1.1-Debug|AnyCPU'" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard1.1-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="DiagnosticSourceEventSourceBridgeTests.cs" />
<Compile Include="DiagnosticSourceTests.cs" />