GithubHelp home page GithubHelp logo

kdcllc / cometd.netcore Goto Github PK

View Code? Open in Web Editor NEW
28.0 8.0 16.0 244 KB

CometD for use with Salesforce Platform Events

License: MIT License

C# 99.93% Shell 0.07%
salesforce salesforce-developers salesforce-api cometd bayeux-protocol bayeux platform-events dotnet-core netcore workbench

cometd.netcore's Introduction

CometD for Salesforce Platform events

GitHub license Build status NuGet Nuget feedz.io

Note: Pre-release packages are distributed via feedz.io.

Summary

This repo contains the CometD .NET Core implementation of the Java ported code.

buymeacoffee

Give a Star! โญ

If you like or are using this project to learn or start your solution, please give it a star. Thanks!

Install

    dotnet add package CometD.NetCore2

Projects that utilize this library

Configure Salesforce Developer instance

Watch: Salesforce Platform Events - Video

  1. Sing up for development sandbox with Saleforce: https://developer.salesforce.com/signup.
  2. Create Connected App in Salesforce.
  3. Create a Platform Event.

Create Connected App in Salesforce

  1. Setup -> Quick Find -> manage -> App Manager -> New Connected App.
  2. Basic Info:

info

  1. API (Enable OAuth Settings):

settings

  1. Retrieve Consumer Key and Consumer Secret to be used within the Test App

Create a Platform Event

  1. Setup -> Quick Find -> Events -> Platform Events -> New Platform Event:

event

  1. Add Custom Field

event

(note: use sandbox custom domain for the login to workbench in order to install this app within your production)

Use workbench to test the Event workbench

OAuth Apps

Use login instead of test

Special thanks to our contributors

Related projects

cometd.netcore's People

Contributors

jesbacon avatar kdcllc avatar martin-podlubny avatar r-matias 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cometd.netcore's Issues

On re-handshake, the client should clear the subscribed channels.

When the server asks for a re-handshake, the client should clear its state, including the subscribed channels.

We added a listener for the handshake event, and in OnMessage, we are calling GetChannel('/topic', lastReplyId).Subscribe(). In the event of a re-handshake, GetChannel returns an existing channel from the Channels dictionary with the previously passed replyId and ignores the replyId in the current #call.

After the handshake, it should clear all non-meta channels from the Channels dictionary.

This bug causes this issue:
##27

Missed events on low-traffic topics

Thanks for this great library for those of us that don't live in Java land. I'm using this on a low-traffic Salesforce Platform Event queue, so likely hit a lot of long-polling transport timeouts, and we are seeing issues of infrequently missed messages (maybe 1 per week). We don't have any log messages indicating an issue, just other monitoring that alerts us that we have missed an event. Looking through the code, our current theory is that the ReplayExtension diverges from the Java implementation. The Java implementation maintains a table of received replayIds for each subscribed topic and updates outgoing subscription messages with this last known replayId. The replay extension here doesn't seem to maintain that internal state, and wonder if in some cases a reconnect happens requiring a re-subscription but only new events are requested. We had a little trouble fully tracing the codepath for network disconnects/timeouts.

Resubscribing after advice:"reconnect:handshake"

Hey! I have a problem in a specific situation where i the server i am connecting to (that i dont have control over) sometimes shut off (or just restart). When we lose the connection, we get this error on the longpolling:

{"id":"13641","channel":"/meta/connect","successful":false,"error":"401:lfzlt13a0h2j53oqb9b5mpwhxnn0nov:Unknown client","advice":{"reconnect":"handshake"}}"

And as i see it currently, the handshake is successfully done. But since we are not also "re-" subscribing, then we dont any longer get any messages.

So if someone could please help me get an overview of how i can setup an automatic "Re-Subscribe" feature for when this happens ๐Ÿ˜“ I have sat with this problem for a very long time now

If it is any help, this is the code i am using when starting my client:

` public RelatelHostedService(IMessageListener messageListener, ILoggerService loggerService)
{
_messageListener = messageListener;
_loggerService = loggerService;

}

public Task StartAsync(CancellationToken cancellationToken)
{
    StartRelatelConnection();
    return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
    RelatelChannel.Unsubscribe();
    RelatelBayeuxClient.Disconnect();
    
    return Task.CompletedTask;
}

private void StartRelatelConnection()
    {
        string relatelServer = Config.RelatelServerEndpoint;
        string companyId = Config.KeyholesCompanyIdAtRelatel;
        string accessToken = Config.RelatelAccessToken;
        
        
        //"Options" for the longpolling transport (such as timout, interval, etc)
        Dictionary<string, object> options = new Dictionary<string, object>();
        
        
        //Optional HTTP headers to be sent with the longpollingtransport
        NameValueCollection httpHeaders = new NameValueCollection();
        
        LongPollingTransport clientTransport = new LongPollingTransport(options, httpHeaders);
        RelatelBayeuxClient = new BayeuxClient(relatelServer, clientTransport);
       
        //Adds access_token to outgoing meta messages, (so far only used on Subscribing)
        RelatelBayeuxClient.AddExtension(new AuthTokenExtension(accessToken, _loggerService));
        
        RelatelBayeuxClient.Handshake();
        RelatelBayeuxClient.WaitFor(10000, new List<BayeuxClient.State> { BayeuxClient.State.CONNECTED });
    }

}`

Listener Unsubscribes from the Channel after 24 hours

First off, thank you for the great library! There are many CometD NuGets, but this is the only one I was able to find that supports the concept of "Replays" and is also dotnet core.

While testing this library I ran across an interesting behavior where there would be a loss of receiving events if there were no Platform Events added within a 24 hour period. I have a hypothesis that this is caused by chain reaction where this client is forced to reconnect to the channel. During this re-connection process the Bayeux client unsubscribes, but fails to resubscribe.

Here is where the ResetSubscription is called:

I was wondering if one of the original authors can shed light on how the ResetSubscription process is supposed to work?

Timeout value in LongPollingTransport.GetRequestStreamCallback

This project is awesome but one thing is that I'm getting timeouts followed by re-subscribe (ReceiveMeta/SendMeta) logged to the console. Timeout exception is:

System.Net.WebException (HResult=0x80131509)
Message=The operation has timed out.
Source=System.Net.Requests
StackTrace:
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at CometD.NetCore.Client.Transport.LongPollingTransport.GetResponseCallback(IAsyncResult asynchronousResult)

Looking at the call stack, this appears to be called from LongPollingTransport.GetRequestStreamCallback(IAsyncResult asynchronousResult), where there is a constant timeout of 2 minutes.

What is the thought behind choosing 2 minutes? Any thought to allowing passing a timeout value as a parameter, perhaps as part of SalesforceConfiguration?

Thanks.

In case it helps, here is what my log looks like...

ReceiveMeta: {"clientId":"","channel":"/meta/connect","id":"3","successful":true}
SendMeta: {"channel":"/meta/connect","connectionType":"long-polling","id":"4","clientId":"
"}
dbug: LongPollingTransport[0]
Send: [{"channel":"/meta/connect","connectionType":"long-polling","id":"4","clientId":""}]
System.Net.WebException: The operation has timed out.
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at CometD.NetCore.Client.Transport.LongPollingTransport.GetResponseCallback(IAsyncResult asynchronousResult)
ReceiveMeta: {"id":"4","successful":false,"channel":"/meta/connect","message":[{"channel":"/meta/connect","connectionType":"long-polling","id":"4","clientId":"
"}],"exception":{"ClassName":"System.Net.WebException","Message":"The operation has timed out.","Data":null,"InnerException":null,"HelpURL":null,"StackTraceString":" at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)\r\n at CometD.NetCore.Client.Transport.LongPollingTransport.GetResponseCallback(IAsyncResult asynchronousResult)","RemoteStackTraceString":null,"RemoteStackIndex":0,"ExceptionMethod":null,"HResult":-2146233079,"Source":"System.Net.Requests","WatsonBuckets":null}}
SendMeta: {"channel":"/meta/connect","connectionType":"long-polling","advice":{"timeout":0},"id":"5","clientId":""}
dbug: LongPollingTransport[0]
Send: [{"channel":"/meta/connect","connectionType":"long-polling","advice":{"timeout":0},"id":"5","clientId":"
"}]
ReceiveMeta: {"clientId":"","advice":{"interval":0,"timeout":110000,"reconnect":"retry"},"channel":"/meta/connect","id":"5","successful":true}
SendMeta: {"channel":"/meta/connect","connectionType":"long-polling","id":"6","clientId":"
"}
dbug: LongPollingTransport[0]
Send: [{"channel":"/meta/connect","connectionType":"long-polling","id":"6","clientId":"********"}]

Subscribing with the last replay id makes the app receive the older events

The following issue occurs when a session is closed after 3 hours of inactivity. We are subscribing with the last replay id but the app receives older (already received) events.

code in main()

bayeuxClient.AddExtension(new ReplayExtension());
ClientSessionChannelListener clientSessionChannel = new ClientSessionChannelListener();
bayeuxClient.GetChannel(ChannelFields.META_HANDSHAKE).AddListener(clientSessionChannel);

code in ClientSessionChannelListener

public void OnMessage(IClientSessionChannel channel, IMessage message)
        {
            long lastProcessedReplayId = GetLastProcessedReplayId();
            _bayeuxClient.GetChannel('/topic', lastProcessedReplayId).Subscribe(new Listener(_platformEventsMessage, channelInfo));                        
        }

If we do not pass the last id, the app does not receive the already processed events (the issue does not occur).
_bayeuxClient.GetChannel('/topic').Subscribe(new Listener(_platformEventsMessage, channelInfo));

Similar issue:
#5

403::Unknown Client error due to missing cookie

When using this library to connect to some instances of Salesforce (not all instances of Salesforce have this problem), it would successfully do the handshake but then fail on the connect request and subscribe request with the error 403::Unknown client.

The reason is due to Salesforce returning a cookie on the response to the /cometd/42.0/handshake request without a path attribute.
E.g. HTTP header

set-cookie: sfproxy-id="9c13437298e48434"; Max-Age=3600; HttpOnly

If these libraries are compiled on .NET Core 3.1 or below then the .NET libraries does not correctly follow RFC 6265 for cookie path resolution (uses the older RFC 2109 spec) and so the cookie's default path gets set to /cometd/42.0/handshake and therefore this cookie is not sent in /cometd/42.0/connect and /cometd/42.0/subscribe requests. In .NET 5.0 and above RFC 6265 is correctly followed setting the path to /cometd/42.0 and so this cookie is sent.

In my case I am not able to use .NET 5.0 or above as I am using other libraries that are not compatible with .NET 5.0.

I am currently working around this issue by using reflection to access the private field _cookieCollection in the BayeuxClient class after the handshaking CONECTING state, then modifying all cookies that have a path of /cometd/{version}/handshake to /cometd/ and this solves the problem. However this is very much a hack and it would be good if this could be implemented within the libraries or have an option to do this.

Channel resubscribe after error

Hello ,
It seems that once you get a connection error for example 403::UnknowClient , the bayeux client reconnect to and still do some handshaking after but doesn't resubscribe to channels
Is there a way to automate this resubscribe after failure ?

It would be great to have some documentation on the nuget to have a clear steps to how to integrate with your app
Thanks in advance,

Getting LockRecursionException lately

Hello.

In the last month I got this error two times. It never happened before and not sure what is the root cause. The coded hasn't change (just lib upgrades in may).

I'm using CometD.NetCore2 2.0.8 w/ dotnet 7.

Any clue?

at System.Threading.ReaderWriterLockSlim.TryEnterReadLockCore(TimeoutTracker timeout)
at System.Collections.Generic.ThreadSafeList`1.GetEnumerator()+MoveNext() in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Internal\\ThreadSafeList.cs:line 214
at CometD.NetCore.Common.AbstractClientSession.ExtendSend(IMutableMessage message) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Common\\AbstractClientSession.cs:line 234
at CometD.NetCore.Client.BayeuxClient.BayeuxClientState.Send(ITransportListener listener, IList`1 messages, Int32 clientTimeout) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\BayeuxClient.cs:line 1018
at CometD.NetCore.Client.BayeuxClient.DisconnectingState.Execute() in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\BayeuxClient.cs:line 1249
at CometD.NetCore.Client.BayeuxClient.UpdateBayeuxClientState(BayeuxClientStateUpdater_createDelegate create, BayeuxClientStateUpdater_postCreateDelegate postCreate) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\BayeuxClient.cs:line 741
at SalesForceEventBridge.DataServices.Services.ResilientStreamingClient.Disconnect(Int32 timeout) in /builds/SQA3gNho/0/crm/salesforce-event-bridge/SalesForceEventBridge.DataServices/Services/ResilientStreamingClient.cs:line 88
at SalesForceEventBridge.DataServices.Services.ResilientStreamingClient.ErrorExtension_ConnectionError(Object sender, String e) in /builds/SQA3gNho/0/crm/salesforce-event-bridge/SalesForceEventBridge.DataServices/Services/ResilientStreamingClient.cs:line 254
at CometD.NetCore.Client.Extension.ErrorExtension.ReceiveMeta(IClientSession session, IMutableMessage message) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\Extension\\ErrorExtension.cs:line 59
at CometD.NetCore.Common.AbstractClientSession.ExtendReceive(IMutableMessage message) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Common\\AbstractClientSession.cs:line 208
at CometD.NetCore.Common.AbstractClientSession.Receive(IMutableMessage message) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Common\\AbstractClientSession.cs:line 174
at CometD.NetCore.Client.BayeuxClient.PublishTransportListener.OnMessages(IList`1 messages) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\BayeuxClient.cs:line 776
at CometD.NetCore.Client.Transport.LongPollingTransport.GetResponseCallback(IAsyncResult asynchronousResult) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\Transport\\LongPollingTransport.cs:line 318```

Upgrade nuget Packages

Upgrade

Nuget Packages

  • DotNetCore 3.1
  • Microsoft.SourceLink.GitHub

Other

  • Updating Debugging info include
  • Upgrade icon nuget package include
  • Add netcoreapp3.1
  • Add Bet.CodeAnalyzers and formatted accordly

Handshake issue when getting replayid from async call to database

I am trying to get the replayid from the cosmos database before creating a Bayeux client object. I am not able to handshake when after bayeuxclient.Handshake(). The same code is working fine when I am not using the cosmos db call. Please find below code snippet.

var authResponse = await _authenticateConnectedApp.Authenticate().ConfigureAwait(false);
            if (authResponse.AccessToken is not null)
            {
                var (channel, apiVersion) = Config.GetSaleForceAccountChannelDetails();
                //DB call
                var lastUsedReplayId = await _operatingParametersRepository.GetSaleForceLastUsedReplayId(channel).ConfigureAwait(false);
               
                var endpointUrl = $"{authResponse.InstanceUrl}/cometd/{apiVersion}";
                var readTimeOut = 120 * 1000;

                var transportOptions = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
                {
                    {ClientTransport.TIMEOUT_OPTION, readTimeOut},
                    {ClientTransport.MAX_NETWORK_DELAY_OPTION, readTimeOut}
                };
                var headers = new NameValueCollection
                    {{HttpRequestHeader.Authorization.ToString(), $"Bearer {authResponse.AccessToken}"}};

                var transport = new LongPollingTransport(transportOptions, headers);
                var transports = new ClientTransport[]
                {
                    transport
                };

                // Create a CometD client instance
                var bayeuxClient = new BayeuxClient(endpointUrl, transports);
                bayeuxClient.Handshake();
                bayeuxClient.WaitFor(100000, new List<BayeuxClient.State> {BayeuxClient.State.CONNECTED});
                log.LogInformation("Handshake Status : {Status}", bayeuxClient.Handshook);
                bayeuxClient.GetChannel(channel, lastUsedReplayId).Subscribe(new SalesForceMessageListener());
                log.LogInformation("Connection Status : {Status}. Now, Waiting for the event", bayeuxClient.Connected);
            }

// CosmosHelperRepository Code Called from above function

public async Task<long> GetSaleForceLastUsedReplayId(string channel)
    {
        try
        {
            var dbExistingRecordList = await _cosmosHelper.GetQuery(x =>
                x.Pk == nameof(SalesForceReplayIdRunDetail)
                && x.ChannelName == channel);
            var lastUsedReplayId = dbExistingRecordList.FirstOrDefault()?.LastUsedReplayId ?? -1;
            return lastUsedReplayId;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
        
    }

// Implementation of Cosmos helper GetQuery method called in above repository function

public async Task<IList<T>> GetQuery(Expression<Func<T, bool>> predicate)
    {
        var container = GetContainer();
        var query = container.GetItemLinqQueryable<T>()
            .Where(predicate);
        var list = new List<T>();
        using var feedIterator = _feedIteratorProvider.GetFeedIterator(query);
        while (feedIterator.HasMoreResults) list.AddRange(await feedIterator.ReadNextAsync());

        return list;
    }

Just an FYI, I am able to get the replayId from cosmos db using the above code but the handshake is not happening.

Occasional ConnectionException because of ObjectDisposedException

I am successfully running an integration with this library, however a few times a day I log a ConnectionException with the following stack trace:

System.ObjectDisposedException: Safe handle has been closed
   at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
   at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success)
   at Microsoft.Win32.Win32Native.SetEvent(SafeWaitHandle handle)
   at System.Threading.EventWaitHandle.Set()
   at CometD.NetCore.Client.BayeuxClient.UpdateBayeuxClientState(BayeuxClientStateUpdater_createDelegate create, BayeuxClientStateUpdater_postCreateDelegate postCreate) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\BayeuxClient.cs:line 733
   at CometD.NetCore.Client.BayeuxClient.PublishTransportListener.OnMessages(IList`1 messages) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\BayeuxClient.cs:line 767
   at CometD.NetCore.Client.Transport.LongPollingTransport.GetResponseCallback(IAsyncResult asynchronousResult) in C:\\projects\\cometd-netcore\\src\\CometD.NetCore\\Client\\Transport\\LongPollingTransport.cs:line 311

I have been looking through the code and trying to debug this issue but I am unsure what causes this. Has somebody else seen this, or know more about this exception occurring?

How to re-subscribe to a channel with a different replay id?

In this scenario we see a message on the meta/connect channel along the lines: 400::The replayId you provided was invalid..

When trying to resubscribe with a different replay id, the method GetChannel in AbstractClientSession always gets the originally created channel with the invalid replay id (code).

public IClientSessionChannel GetChannel(string channelId, long replayId)
{
        Channels.TryGetValue(channelId, out var channel);

What's the best way to recover from this error? We're subscribing to various channels so would prefer not to create a whole new client. Is there anyway to dispose of the original channel and get a new one?

LockRecursionException in ThreadSafeList when disconnecting

Hi there - first of all thanks for these brilliant libraries.

I'm getting the following exception when disconnecting as part of handling a connection error (a 403:denied_by_security_policy:create_denied in my case since the channel doesn't exist):

System.Threading.LockRecursionException

"Recursive read lock acquisitions not allowed in this mode"

at System.Threading.ReaderWriterLockSlim.TryEnterReadLockCore(TimeoutTracker timeout)
at System.Collections.Generic.ThreadSafeList`1.<GetEnumerator>d__18.MoveNext() in C:\projects\cometd-netcore\src\CometD.NetCore\Internal\ThreadSafeList.cs:line 201
at CometD.NetCore.Common.AbstractClientSession.ExtendSend(IMutableMessage message) in C:\projects\cometd-netcore\src\CometD.NetCore\Common\AbstractClientSession.cs:line 214
at CometD.NetCore.Client.BayeuxClient.BayeuxClientState.Send(ITransportListener listener, IList`1 messages, Int32 clientTimeout) in C:\projects\cometd-netcore\src\CometD.NetCore\Client\BayeuxClient.cs:line 1003
at CometD.NetCore.Client.BayeuxClient.BayeuxClientState.Send(ITransportListener listener, IMutableMessage message, Int32 clientTimeout) in C:\projects\cometd-netcore\src\CometD.NetCore\Client\BayeuxClient.cs:line 987
at CometD.NetCore.Client.BayeuxClient.DisconnectingState.Execute() in C:\projects\cometd-netcore\src\CometD.NetCore\Client\BayeuxClient.cs:line 1245
at CometD.NetCore.Client.BayeuxClient.UpdateBayeuxClientState(BayeuxClientStateUpdater_createDelegate create, BayeuxClientStateUpdater_postCreateDelegate postCreate) in C:\projects\cometd-netcore\src\CometD.NetCore\Client\BayeuxClient.cs:line 727
at CometD.NetCore.Client.BayeuxClient.UpdateBayeuxClientState(BayeuxClientStateUpdater_createDelegate create) in C:\projects\cometd-netcore\src\CometD.NetCore\Client\BayeuxClient.cs:line 690
at CometD.NetCore.Client.BayeuxClient.Disconnect() in C:\projects\cometd-netcore\src\CometD.NetCore\Client\BayeuxClient.cs:line 143

What I'm using:

  • a slightly modified version of ResilientStreamingClient from your CometD.NetCore.Salesforce repo
  • version 2.0.6 of this nuget (v2.0.7 looks like no code changes)
  • .net core 3.1 on Windows 10

My Disconnect() method in my ResilientStreamingClient is unchanged from your repo version.

Any help appreciated. I'll continue troubleshooting

Getting started with the library

Your examples to setup salesforce are top notch. However, I've not seen any examples with the library itself. How do I create the object and register it to watch for streams/events/etc. (I'm new at this).

I'll gladly help write documentation if you can point me in the right direction on this ticket...

AbstractClientSession Thread-Safe

Hi guys,
Just to double-check the class AbstractClientSession, line 17, the list _extensions, any concern about using ArrayList.Synchronized?

Honor the timeout advice given by the server

I'm having the exact same error as was presented in #3 .

I have tracked the problem to be that the Salesforce server indicates that we need to connect again within 110 seconds; this is from the advice field provided and can be seen in the logs provided in the above issue.
When we send our long polling message there is a watchdog set to fire in 120 seconds.
What isn't done however is to set the timeout for the actual HTTP request. In .Net core, that timeout is 100 seconds by default.
Hence it occurs before any of these other events. We get the exception observed.

In the resolution to #3 there was a timeout value plumbed partially in; I don't see where it is ever actually specified to be other than the default 120 seconds, and it doesn't appear to be reachable to anyone using this package. (Nor should it be, this is an internal implementation detail.)

I looked through the CometD JavaScript implementation and it uses the advice values returned by the server plus a configurable network jitter value (maxNetworkDelay) to intelligently set the timeout of the HTTP request.

Is there a plan or appetite for this functionality?

Token expiration?

When spinning up a listener with http long polling, do we have manage token expirations? Salesforce JWT's have 15 minute lifespan. Does that not apply in the change-data-capture integration?

Async IMessageListener

Hello,
Is it possible to update the package to support async/await patttern to end up with Async method for IMessageListener

Task OnMessage(IClientSessionChannel channel, IMessage message);

Several logging nuget packages are referenced, but are unused

There appears to be no use for the following nugets:

Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Configuration
Microsoft.Extensions.Logging.Console
Microsoft.Extensions.Logging.Debug

(The only one that actually is used is Microsoft.Extensions.Logging.Abstractions)

Can these be removed?

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.