Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OperationDetails object bag to DependencyTelemtry for Initializers #788

Merged
merged 20 commits into from
May 9, 2018

Conversation

michaelwstark
Copy link
Contributor

@michaelwstark michaelwstark commented Apr 26, 2018

Fix Issue # .
[Consolidated]
microsoft/ApplicationInsights-dotnet-server#900

[Original]
microsoft/ApplicationInsights-dotnet-server#820
microsoft/ApplicationInsights-dotnet-server#747
microsoft/ApplicationInsights-dotnet-server#587

For significant contributions please make sure you have completed the following items:

@msftclas
Copy link

msftclas commented Apr 26, 2018

CLA assistant check
All CLA requirements met.

@michaelwstark
Copy link
Contributor Author

FWIW. I am not in love with the name of the property -- "Data" was already taken, and putting this on a "Data" field on OperationContext would have made this a much more significant change. Open to suggestions (obviously).

@pharring
Copy link
Member

Consider making the Dictionary instantiation lazy to minimize the allocation penalty for code that doesn't need/use it. Also, it should probably be a ConcurrentDictionary (which I'm reluctant to point out because it is an allocation hog)

/// <summary>
/// Gets the dependency operation details, if any.
/// </summary>
public IDictionary<string, object> OperationDetails
Copy link
Member

@lmolkova lmolkova Apr 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned with two things here

  1. confusion - we have too many properties already. I imagine users would not understand what info should go here and is it sent to AI backend.
  2. abuse - users should not use this dict to pass something unrelated

One possible way to address at least first could be to

  1. Make operation details private field
  2. Add getter and setter functions
  3. mark setter function as not browsable (like TelemetryClient.Track)
  4. keep getter public and browsable and give a detailed documentation summary of what it is.

Another way

  1. keep OperationDetails public, but not browsable
  2. Add extension method in the DependencyCollector package like DependencyTelemtery.TryGetValue(string key)

Even more strict way could be make an enum with possible keys in the DependenycCollector package and allow to get by enum.

@@ -19,6 +22,7 @@ public sealed class DependencyTelemetry : OperationTelemetry, ITelemetry, ISuppo

internal readonly RemoteDependencyData InternalData;
private readonly TelemetryContext context;
private readonly Lazy<IDictionary<string, object>> operationDetails;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For compactness (fewer object allocations), I prefer not to use Lazy<T>.
Instead, make the field non-readonly IDictionary<string, object> and use System.Threading.LazyInitializer.EnsureInitialized.

The pattern is this:

using static System.Threading.LazyInitializer;

private IDictionary<string, object> operationDetails;

public IDictionary<string, object> OperationDetails => EnsureInitialized(ref operationDetails, () => new ConcurrentDictionary<string, object>());

/// <returns>true if the key was found; otherwise, false.</returns>
public bool TryGetOperationDetail(string key, out object detail)
{
return this.OperationDetails.TryGetValue(key, out detail);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short-circuit if operationDetails (the backing field) has not been initialized (avoid creating an empty ConcurrentDictionary here).

CHANGELOG.md Outdated
-[Fix: changed namespace SamplingPercentageEstimatorSettings](https://github.com/Microsoft/ApplicationInsights-dotnet/issues/727)

- [Fix: changed namespace SamplingPercentageEstimatorSettings](https://github.com/Microsoft/ApplicationInsights-dotnet/issues/727)
- [NEW: Added TryGetOperationDetail to DependencyTelemetry to facilitate advanced ITelemetryInitializer scenarios] (https://github.com/Microsoft/ApplicationInsights-dotnet-server/issues/587)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was meant for 2.6.0 stable or 2.7.0-beta1? @MS-TimothyMothra can help.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be great to explicitly mention that these new fields are NOT meant to be serialized and sent to AI backend, they only exist for facilitate TelemetryInitializers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if the release the note is under needs to change -- I just put it under the latest one assuming that all pending changes should be put there. If you'd rather not release something in a stable release that hasn't been in a beta release I understand. I'll update the release note and the comment on the set method to explicitly state that the data is not serialized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this changes would go into 2.7.0-beta1.
We had already shipped the last beta of 2.6.0 so only critical bugs would be released into 2.6.0-stable. So this change should be in beta1 of the next release, which is 2.7.0-beta1

…elds are not automatically serialized to the backend.
/// Initializes a new instance of the <see cref="DependencyTelemetry"/> class with the given <paramref name="dependencyName"/>, <paramref name="data"/>,
/// <paramref name="startTime"/>, <paramref name="duration"/> and <paramref name="success"/> property values.
/// </summary>
[Obsolete("Use other constructors which allows to define dependency call with all the properties.")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lost some indenting here (lines 43-47). Please fix.

@lmolkova
Copy link
Member

lmolkova commented May 1, 2018

See comment from the Web SDK here:

microsoft/ApplicationInsights-dotnet-server#897 (comment)


The concern is that the feature would postpone GC for the requests/responses since reference would be kept in dependency telemetry until it is sent to the backend (that happens asynchronously).

I wonder, can we clean up the OperationDetails in TrackDependency call after initialize is called to address this concern?

Here

        public void TrackDependency(DependencyTelemetry telemetry)
        {
            if (telemetry == null)
            {
                telemetry = new DependencyTelemetry();
            }

            this.Track(telemetry);

!!!         telemetry.OperationDetails.Clear();
        }

It totally makes sense to me, as Track happens syncronously with dependency call and request/response should not be accessed after that.

@michaelwstark
Copy link
Contributor Author

Seems reasonable. I'll have to add some additional work to the test verification in the other repo, but shouldn't be to hard to grab these items prior to them being cleared out.

@michaelwstark
Copy link
Contributor Author

michaelwstark commented May 2, 2018

I expected my tests for the dependency collector package to start failing, and when they did not, I investigated and found TrackDependency() is never called -- Track() is called directly. Is there a reason TrackDependency is not being called?

@lmolkova
Copy link
Member

lmolkova commented May 2, 2018

I expected my tests for the dependency collector package to start failing, and when they did not, I investigated and found TrackDependency() is never called -- Track() is called directly. Is there a reason TrackDependency is not being called?

You are right, we don't actually call TrackDependency, and I don't think there is any reason behind it as it is equivalent to Track.

Ok so the alternative could be to clean OperationDetail in the Web SDK (in dependency collector) where we assign them after the track call...

@michaelwstark
Copy link
Contributor Author

Ok so the alternative could be to clean OperationDetail in the Web SDK (in dependency collector) where we assign them after the track call...

@lmolkova - I actually prefer the original suggestion (seems like less maintenance over time), and had already updated the web sdk repo to call TrackDependency(), I was just verifying that there was not a reason for this. I noticed that Track() itself is not browsable.

@lmolkova
Copy link
Member

lmolkova commented May 2, 2018

I actually prefer the original suggestion (seems like less maintenance over time), and had already updated the web sdk repo to call TrackDependency(), I was just verifying that there was not a reason for this. I noticed that Track() itself is not browsable.

Sure, calling TrackDependency instead of Track is fine

@cijothomas
Copy link
Contributor

Manually triggered CI build/tests - all good and ready to merge

@cijothomas
Copy link
Contributor

@MS-TimothyMothra - Please merge this when branch is open for 2.7.0-beta1

@TimothyMothra TimothyMothra added this to the 2.7-Beta1 milestone May 8, 2018
@MikeEvansLarah
Copy link

Is there any way to retrieve the request body for HTTP dependency calls using the OperationDetails?

Following the links posted earlier, I find that I can retrieve the HttpWebRequest in a telemetry initializer like so:

if (!(telemetry is DependencyTelemetry dependency))
{
    return;
}

if (dependency.TryGetOperationDetail("HttpRequest", out var request)
    && request is HttpWebRequest httpWebRequest)
{
    if (httpWebRequest.Method == "POST" || httpWebRequest.Method == "PUT")
    {
        // get request body somehow?
        dependency.Properties.Add("RequestBody", body);
    }
}

But the stream on the request is closed at this point, so unable to be read.

Alternatively, is there any other way to capture the request body such that it is attached to the dependency telemetry?

@lmolkova
Copy link
Member

@MikeLarah no, there is no way to read closed stream.
You may log the request body at the place where it is created and written to the request stream, you may log it with TelemetryClient.TrackTrace(). It is problematic to make it a child of the dependency, by default, it will be the child of the request

TimothyMothra pushed a commit that referenced this pull request Oct 25, 2019
Do not prevent correlation headers injection on localhost
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants