GithubHelp home page GithubHelp logo

aspnet / eventnotification Goto Github PK

View Code? Open in Web Editor NEW
45.0 40.0 27.0 390 KB

[Archived] Event notification system for broadcasting application state and configuration. Project moved to https://github.com/aspnet/Extensions

License: Apache License 2.0

Shell 7.53% C# 86.65% Batchfile 0.41% PowerShell 5.41%
aspnet-product

eventnotification's Introduction

EventNotification [Archived]

This GitHub project has been archived. Ongoing development on this project can be found in https://github.com/aspnet/Extensions.

Notice

The infrastructure for publishing notifications has moved to the .NET Framework. See the new DiagnosticSource and DiagnosticListener APIs in the System.Diagnostics.DiagnosticSource package. The infrastructure provided here is for subscribing to events using runtime-generated proxies.

This project is part of .NET Extensions. You can find samples, documentation and getting started instructions at the Extensions repo.

eventnotification's People

Contributors

ajaybhargavb avatar aspnetci avatar avanderhoorn avatar brennanconroy avatar bricelam avatar davidfowl avatar dougbu avatar eilon avatar flcdrg avatar hishamco avatar javiercn avatar jbagga avatar jkotalik avatar juntaoluo avatar kichalla avatar natemcmaster avatar ntaylormullen avatar pakrym avatar pranavkm avatar ryanbrandenburg avatar rynowak avatar troydai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eventnotification's Issues

Support new DiagnosticSource.IsEnabled(string, object, object) API

There were couple of API recently introduced in dotnet/corefx#15984 and dotnet/corefx#15985:

  • DiagnosticSource.IsEnabled(string, object,object)
  • DiagnosticListener.Subscribe(IObserver<KeyValuePair<string, object>> observer, Func<string, object, object, bool> isEnabled)

DiagnosticListenerExtensions.SubscribeWithAdapter and DiagnosticSourceAdapter constructors should provide an overloads to subscribe with Func<string, object, object, bool>

Cross node notification

Add the ability (or at least a sample) to trigger notifications between nodes. This could be useful for cross-node configuration synchronization in a large distributed application. Perhaps using something like a SignalR hub or redis client.

Clean up package metadata and pre-Core references

NuGet package metadata

The project.json file of every shipping NuGet package has two properties that must be non-empty:

  1. description
  2. tags

Description guidance

Description should be at least something like this:
"description": "ASP.NET Core MVC view rendering features. Contains common types used in most MVC applications as well as view rendering features such as view engines, views, view components, and HTML helpers.",

And if there are types in the package that are frequently referenced in source code, add this:
"description": "%description%\r\nCommonly used types:\r\nNS1.Type1\r\nNS1.Type2\r\nNS2.Type1\r\nNS3.Type1",
Note: Sort the types by the full namespace+type name

Tags guidance

The tags should generally include a "product" tag indicating the main product this is a part of (e.g. aspnetcore), and also "subject" or "feature" tags to associate this package with other related packages (e.g. aspnetcoremvc, logging, json).

Examples of tags:

  "tags": [
    "aspnetcore"
  ],
  "tags": [
    "aspnetcore",
    "aspnetcoremvc",
    "XYZ"
  ],

Note: Certain packages are not part of ASP.NET Core or Entity Framework Core, so be careful not to add those tags to the metadata of these packages.

Other stuff to clean up related to the "Core" product rename

Need to do a search in all files in the repo for strings such as asp.net, aspnet, entity framework, and entityframework. This obviously has a lot of false positives because these strings appear in namespace names all over the place. However, it's not too hard to weed through those (despite thousands of hits) because when scrolling in the output of the commands it's easy to see breaks in the pattern that need to be investigated.

Some useful commands to run:

findstr /snip /c:"entityframework" *
findstr /snip /c:"entity framework" *
findstr /snip /c:"asp.net" *
findstr /snip /c:"aspnet" *

Update readme.md in the root of the repo

This is a fairly simple task of ensuring the content in the readme.md file also doesn't have references to pre-Core naming.

Problem with walking inheritance chain

Currently we have a BeforeAction event listener. In this listener, we have our own IActionDescriptor which currently it has a few properties like DisplayName and Id. This is all as you would expect.

I was looking what other data I could get and started to poke around at underlying data. When doing this, I noticed that the actual type in most cases is ControllerActionDescriptor. So as a test, I added ControllerName to my IActionDescriptor. I was expecting that if this property exists somewhere in the inheritance chain of the underlying type it would bring it across and if it doesn’t (i.e. the object is FooActionDescriptor that inherits from ActionDescriptor and hence doesn’t have ControllerName) it would leave it alone. Currently it doesn’t bring across ControllerName, even though its on the underlying type.

Design Notes on Correlation and Timings in DiagnosticSource events

I met with @avanderhoorn yesterday to discuss some of the issues raised by aspnet/Hosting#678 as the general desire to be able to correlate events across multiple DiagnosticSource listeners post-mortem.

Foreword

DiagnosticSource and the DiagnosticSourceAdapter are intended to provide a degree of separation between product code (producer) and diagnostics code like Glimpse (consumer). Any strategy that requires boilerplate on the part of the producer or requires repeated asks to "enhance" events to handle cross-cutting concerns is a non-solution.

Additionally, dictating that producers place non-fundamental data such as a timestamp or a correlationId into DiagnosticSource transforms it from a notification system into a protocol. Currently DiagnosticSource produces send objects that are specific to the business domain (HTTP, SQL) or state being signaled. It is right that the framework (producer) own, specify, and evolve the shape of the objects sent in events on their own terms. If HttpContext's definition must be changed, it will be changed because ASP.Net requires that it should change, not because the consumers of events require a change.

Data related to cross-cutting concerns would form a similar contract, but is "owned" by the consumer. If ASP.Net and HttpClient decide to include timestamps that aren't exactly compatible then this cross-cutting concern can't be handled in a cross-cutting way.

Issues

1). Where ASP.Net shows a duration in logs, the exact timing information captured by the framework is not made available via DiagnosticSource. In the case of showing logs and DiagnosticSource data side-by-side, small differences in timings would be visible.

2). The problem described in 1.) above can also manifest when multiple listeners are attached to DiagnosticSource assuming that each listener derives timings independently. Each listener will be called in sequence and so will generate a slightly different timestamp that includes the processing time of previous listeners.

3). Correlating events across multiple diagnostic source listeners can be challenging. This is for scenarios where data streams produced by multiple listeners are combined. Filtering redundant information would require some complex logic as there's no way to unify multiple 'views' of the same event. As highlighted in 2). simply adding timestamps to correlation doesn't work unless they are using the same source as each other.

Proposals

It seems like the best way to address 2). and 3). would be to add this information by convention to the diagnostic source adapter. The adapter already uses code generation (IL emit) to operate on event streams by convention, it makes sense to also address cross-cutting concerns in a conventional way in the adapter.

For instance, suppose the following listener code as a starting point:

public class MyListener
{
    [DiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]
    public void OnBeginRequest(HttpContext context)
    {
    }

    [DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
    public void OnEndRequest(HttpContext context)
    {
    }
}

Adding manual tracking of a duration for the request would looks something like this:

public class MyListener
{
    private readonly AsyncLocal<long> _beginRequestTimestamp = new AsyncLocal<long>();

    [DiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]
    public void OnBeginRequest(HttpContext context)
    {
        _beginRequestTimestamp.Value = StopWatch.GetCurrentTimestamp();
    }

    [DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
    public void OnEndRequest(HttpContext context)
    {
        var start = _beginRequestTimestamp.Value;
        var end = StopWatch.GetCurrentTimestamp();

        var duration = TimeSpan.FromTicks(end - start);
        ...
    }
}

Whereas listening to a duration sent in the payload for "EndRequest" would looks something like this:

public class MyListener
{
    [DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
    public void OnEndRequest(HttpContext context, TimeSpan duration)
    {
    }
}

Therefore listening to a duration added to a "virtual payload" would look something like this:

public class MyListener
{
    [BeginDiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]
    [DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
    public void OnEndRequest(HttpContext context, TimeSpan duration)
    {
    }
}

In this case code inside the diagnostic source adapter can perform the the same task performed by the "manual" state tracking example. Multiple listeners need to agree on the same definition of [BeginDiagnosticName(...)] to see the same timestamp.

We could extend this further and have the listener generate a correlationId:

public class MyListener
{
    [BeginDiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]
    [DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
    public void OnEndRequest(HttpContext context, TimeSpan duration, Guid correlationId)
    {
    }
}

To address 1). we can allow an event payload containing a duration or correlationId to supercede these values as-generated by the adapter.

Remaining Concerns

The remaining concern then is to standardize on conventions then since this is all by convention.

Proposals:

  • correlationId - Guid identifying both events of a begin/end pair
  • duration - TimeSpan identifying the elapsed time between begin/end pair as observed by the adapter (before calling into listeners)

In either case these 'enhanced' payload items can be overridden by the provided payload.

Notification with Observables question

I see event notifications and I can't help but think Rx :). Is there a plan / thought to leverage the work that's already being done there with Observables and Subjects ?

Null checking needed for generated code

Null checking needs to be added to the generated code so that when a parameter is null it passes that through to the target method rather than trying to create a ProxyBase<T>, passing in null and hence throwing an exception.

Support scenario when IsEnabled is called to control flow but there is no event for the same diagnosticName

We have recently introduced a new DiagnosticSource usage pattern in the HttpClient and AspNetCore.Hosting:

E.g. in AspNetCore.Hosting:

  1. producer calls IsEnabled("Microsoft.AspNetCore.Hosting.HttpRequestIn", httpContext) just to determine that listener wants instrumentation to happen. Producer optionally adds context that could help consumer to check if he wants instrumentation for this particular request.
  2. if 1 is true, producer calls IsEnabled("Microsoft.AspNetCore.Hosting.HttpRequestIn.Start") to check if consumer wants start event and if true, writes event with the same name
  3. producers just writes IsEnabled("Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop") if instrumentation was on in step 1.

The problem that even if there is no event for the first IsEnabled, user still has to write a method with corresponding DiagnosticName attribute.

    class HttpHandlerListener
    {
        [DiagnosticName("System.Net.Http.HttpRequestOut")]
        public void IsEnabled()
        {
        }

        [DiagnosticName("System.Net.Http.HttpRequestOut.Start")]
        public void OnStart(HttpRequestMessage request)
        {
        }

        [DiagnosticName("System.Net.Http.HttpRequestOut.Stop")]
        public void Stop(HttpResponseMessage response)
        {
        }
    }

I have tried to work it around in the other way with SubscribeWithAdapter(object, Predicate isEnabled),
but isEnabled never gets called if there is no attribute with this diagnosticName.

As a minimum solution, I would expect isEnabled predicate (provided to SubscribeWithAdapter) at least be called and it's result should be taken into account.

But may be IsEnabled should be done in the same way as Write?

[DiagnosticName("System.Net.Http.HttpRequestOut")] //or another attribute?
public bool IsEnabled(HttpContext context)
{
   return context.Request.Method != "OPTIONS";
}

And if there is no IsEnabled, DiagnosticName on the Write callback could be used as an indicator that event is enabled

Provide context in error messages

As mentionaed in #53 there isn't enough context in error messages like this to make diagnosing issues easy. Let's see about piping through some information like what method was trying to be called.

Problem with walking object graph

I have the following which aims to mimic the graph of the object being passed in. These types live in my codebase and should be mapped to the data from the origin object being passed in:

    public interface IRouteData
    {
        IReadOnlyList<IRouter> Routers { get; }
        IDictionary<string, object> DataTokens { get; }
        IDictionary<string, object> Values { get; }
    }

    public interface IRouter
    {
        string Name { get; }
        string RouteTemplate { get; }
        IDictionary<string, object> Values { get; }
    }

Currently, IRouteData gets populated and IRouteData.Routers even gets populated with multiple items, but the details of those items are all null. Its not mapping the details of the items in the collection.

I suspect that this problem is related to #22 as the publishers type for IRouteData.Routers is List<Microsoft.AspNet.Routing.IRouter> and Microsoft.AspNet.Routing.IRouter doesn't directly implement any of the properties I'm interested in... they are implemented by types that inherit from Microsoft.AspNet.Routing.IRouter.

Working with polymorphic object

Currently working with objects that are polymorphic in nature is painful. For the most part, the adapter only knows about the compile time object and can't process these objects. Due to technical/performance reasons, it will probably never be able to do this blindly, but some helps could be added to make working with polymorphic objects much much easier.

Currently the code we need to write to process these cases looks something like the following:

var message = (BeforeActionResultMessage)null;
switch (result.GetType().FullName)
{
    case "Microsoft.AspNet.Mvc.ViewResult":
        var viewResult = _proxyAdapter.Process<ActionResultTypes.IViewResult>("Microsoft.AspNet.Mvc.ViewResult", result);

        message = new BeforeActionViewResultMessage
        {
            ViewName = viewResult.ViewName,
            StatusCode = viewResult.StatusCode,
            ContentType = viewResult.ContentType?.ToString()
        };

        break;
    case "Microsoft.AspNet.Mvc.ContentResult":
        var contentResult = _proxyAdapter.Process<ActionResultTypes.IContentResult>("Microsoft.AspNet.Mvc.ContentResult", result);

        message = new BeforeActionContentResultMessage
        {
            StatusCode = contentResult.StatusCode,
            Content = contentResult.Content,
            ContentType = contentResult.ContentType?.ToString()
        };

        break;
    case "Microsoft.AspNet.Mvc.ObjectResult":
        var objectResult = _proxyAdapter.Process<ActionResultTypes.IObjectResult>("Microsoft.AspNet.Mvc.ContentResult", result);

        message = new BeforeActionObjectResultMessage
        {
            StatusCode = objectResult.StatusCode,
            Value = objectResult.Value,
            Formatters = objectResult.Formatters?.Select(x => x.GetType()).ToList(),
            ContentTypes = objectResult.ContentTypes?.Select(x => x.ToString()).ToList()
        };

        break;
    default:
        message = new BeforeActionResultMessage();

        break;
}

Initial conversations about this problem has suggested that a visitor pattern could easy this problem. In that case, the above would look more like the following:

public class MyActionResultVisitor : BaseVisitor
{
    public T DoTheNeedful(IActionResult/object result)
    {
        //...
    }

    [*TypeName("Microsoft.AspNet.Mvc.ViewResult")]
    public T Visit(IViewResult result)
    {
        //...
    }

    [*TypeName("Microsoft.AspNet.Mvc.ObjectResult")]
    public T Visit(IObjectResult result)
    {
        //...
    }

    [Default]
    public T Visit(IActionResult result)
    {
        //...
    }
}

@rynowak @Eilon

Pascal casing on source object properties

When Pascal casing is used for the casing on source object properties, it means that the parameters on the listening method also need to be Pascal casing. Normally this wouldn't be a problem, but when you adjust the casing to be Pascal case on the listening method, the adapter throws an exception.

Here is the test case - https://gist.github.com/avanderhoorn/f0106bca8be1a33acd37 - and here is the exception that is raised:

  • ProxyMethodEmitterTest.TargetClassUpperCase does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type ProxyMethodEmitterTest.TargetClassUpperCase could be found (are you missing a using directive or an assembly reference?)
  • <anonymous type: string Name, int Age> does not contain a definition for 'name' and no extension method 'name' accepting a first argument of type <anonymous type: string Name, int Age> could be found (are you missing a using directive or an assembly reference?)
  • ProxyMethodEmitterTest.TargetClassUpperCase does not contain a definition for 'Age' and no extension method 'Age' accepting a first argument of type ProxyMethodEmitterTest.TargetClassUpperCase could be found (are you missing a using directive or an assembly reference?)
  • <anonymous type: string Name, int Age> does not contain a definition for 'age' and no extension method 'age' accepting a first argument of type <anonymous type: string Name, int Age> could be found (are you missing a using directive or an assembly reference?)

Add support for IDictionary<string, object> to Adapt

At the moment you can only pass in an anon type to the notifier in-order for parameter resolution to work, however, you don't always have an anon type. So it would be nice to support something a little bit more generic. i.e. an example:

var param1 = new Dictionary<string, object>() { { "value", "Foo" } };
var adapter1 = _methodAdapter.Adapt(entry.MethodInfo, param1.GetType());
succeeded = adapter1(entry.Target, param1);

var param2 = new { value = "Foo" };
var adapter2 = _methodAdapter.Adapt(entry.MethodInfo, param2.GetType());
succeeded = adapter2(entry.Target, param2);

ExecutionEngineException in diagnostic source listener

You can cause the CLR to crash by using an invalid combination of types.

new { bar = new Guid() }

...

[DiagnosticName(...)]
public void OnFoo (string bar)
{
    ...
}

This is not valid, but the failure mode should be throwing an exception explaining the mistake, not shutting down the CLR 😆


The issue here is that the generated thunk for splatting the type isn't doing the right thing when the result of accessing the anonymoustype<>.bar property is a value type.

Adding the following at ProxyMethodEmitter@L160 fixing the issue

if (mapping.PropertyType.GetTypeInfo().IsValueType)
{
    il.Emit(OpCodes.Box, mapping.PropertyType);
}

Additionally, we should see if we can do anything to surface errors about this with context as it is today when you fix the bug, the exception message you get doesn't mention the method on the adapter which caused the problem.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.