How to best deploy Sentry in cross-assembly environment? - c#

So we built this library/framework thing full of code related to business processes and common elements that are shared across multiple applications (C#, .Net 4.7.1,WPF, MVVM). Our Logging stuff is all set up through this framework so naturally it felt like the best place for Sentry. All the references in our individual applications are manually pointed to the dlls the folder where our shared library thingy installs itself. So far so good.
When we set up Sentry initially everything seemed to work great. We do some updates and errors seem to be going way down. That's cause we are awesome and Sentry helped us be more awesome, right? Nope! Well I mean kind of.
The scope is being disposed of so we are no longer getting Unhandled exceptions. We didn't notice at first because we are still getting sentry logs when we are handling errors through our Logging.Log() method. This logging method calls SentrySdk.Init() which I suspect is disposing the client in the executing assembly.
We also started using Sentry for some simple Usage tracking by spinning up a separate project in Sentry called Usage-Tracker and passing a simple "DoThingApplication has been launched" with an ApplicationName.UsageTracker Enum as a parameter to our Logging method.
Question: What is a good way to handle this where my setup can have a Sentry instance that wraps my using(sentryClientStuff){ ComposeObjects(); } and still have my logging method look for the existing client and use it if it exists?
Caveats:
I believe before any of this happens we still need to make a call to send a Sentry log to our UsageTracker.
I would like to pass in as few options as possible if I'm setting up the Sentry Client/Scope in our shared library. Maybe Release and Environment. Maybe check tags for Fingerprint and set it in the Log method.
I'm open to new approaches to any of this.
Some related thoughts
Maybe there is a better way to handle references that could solve both this and some other pains of when they have become mismatched between client and shared framework/library thing
Maybe the answer can be found through adding some Unit Tests but I could use a Sentry specific example or a nudge there because I don't know a muc about that.
Maybe there is a way to use my shared library to return a Sentry Client or Scope that I could use in my client assembly that would not be so fragile and the library could somehow also use it.
Maybe there is a better solution I can't conceive because I'm just kind of an OK programmer and it escapes me. I'm open to any advice/correction/ridicule.
Maybe there is a smarter way to handle "Usage-Tracker" type signals in Sentry
Really I want a cross-assembly singleton kind of thing in practice.

There are really many things going on here. Also without looking at any code it's hard to picture how things are laid out. There's a better chance you can get the answer your are looking for if you share some (dummy even) example of the structure of your project.
I'll try to break it down and address what I can anyway:
With regards to:
Usage-Tracker:
You can create a new client and bind to a scope. That way any use of the SentrySdk static class (which I assume your Logger.Log routes to) will pick up.
In other words, call SentrySdk.Init as you currently do, with the options that are shared across any application using your shared library, and after that create a client using the DSN of your Usage-Tracker project in sentry. Push a scope, bind the client and you can use SentrySdk with it.
There's an example in the GitHub repo of the SDK:
using (SentrySdk.PushScope())
{
SentrySdk.AddBreadcrumb(request.Path, "request-path");
// Change the SentryClient in case the request is to the admin part:
if (request.Path.StartsWith("/admin"))
{
// Within this scope, the _adminClient will be used instead of whatever
// client was defined before this point:
SentrySdk.BindClient(_adminClient);
}
SentrySdk.CaptureException(new Exception("Error at the admin section"));
// Else it uses the default client
_middleware?.Invoke(request);
} // Scope is disposed.
The SDK only has to be initialized once but you can always create a new client with new SentryClient, push a new scope (SentrySdk.PushScope()) and bind it to that new scope (SentrySdk.BindClient). Once you pop the scope the client is no longer accessdible via SentrySdk.CaptureException or any other method on the static class SentrySdk.
You can also use the client directly, without binding it to the scope at all.
using (var c = new SentryClient(new SentryOptions { Dsn = new Dsn("...") })) {
c.CaptureMessage("hello world!");
}
The using block is there to make sure the background thread flushes the event.
Central place to initialize the SDK:
There will be configuration which you want to have fixed in your shared framework/library but surely each application (composition root) will have its own setting. Release is auto-discovered.
From docs.sentry.io:
The SDK will firstly look at the entry assembly’s AssemblyInformationalVersionAttribute, which accepts a string as value and is often used to set a GIT commit hash.
If that returns null, it’ll look at the default AssemblyVersionAttribute which accepts the numeric version number.
If you patch your assemblies in your build server, the correct Release should be reported automatically. If not, you could define it per application by taking a delegate that passes the SentryOptions as argument.
Something like:
Framework code:
public class MyLogging
{
void Init(Action<SentryOptions> configuration)
{
var o = new SentryOptions();
// Add things that should run for all users of this library:
o.AddInAppExclude("SomePrefixTrueForAllApplications");
o.AddEventProcessor(new GeneralEventProessor());
// Give the application a chance to reconfigure anything it needs:
configuration?.Invoke(o);
}
}
App code:
void Main()
{
MyLogging.Init(o => o.Environment = "my env");
}
The scope is being disposed of so we are no longer getting Unhandled exceptions."
Not sure I understand what's going on here. Pushing and popping (disposing) scopes don't affect the ability of the SDK to capture unhandled exceptions. Could you please share a repro?
This logging method calls SentrySdk.Init() which I suspect is disposing the client in the executing assembly.:
Unless you create a client "by hand" with new SentryClient, there's only 1 client in the running process. Please note I said running process and not assembly. Instances are not held within an assembly. The assembly only contains the code that can be executed. If you call SentrySdk.CaptureException it will dispatch the call to the SentryClient bound to the current scope. If you didn't PushScope, there's always an implicit scope, the root scope. In this case it's all transparent enough you shouldn't care there's a scope in there. You also can't dispose of that scope since you never got a handle to do so (you didn't call PushScope so you didn't get what it returns to call Dispose on).
All the references in our individual applications are manually pointed to the dlls the folder where our shared library thingy installs itself.:
One thing to consider, depending on your environment is to distribute packages via NuGet. I'm unsure whether you expect to use these libraries in non .NET Framework applications (like .NET Core). But considering .NET Core 3.0 is bringing Windows Desktop framework support like WPF and WinForm, it's possible that eventually you will. If that's the case, consider targeting .NET Standard instead of .NET Framework for your code libraries.

Related

.NET Maui how to make use of IOptionsSnapshot

Basically I'm trying to work around the fact that you can't really use IOptionsSnapshot in Maui since the appsettings.json file is set in stone once it's bundled in with the app.
Manually updating the IConfiguration with Configuration["key"] = myValue
require then to notify all scoped services or singletons to retrieve new istances of their IOptionsSnapshot properties.
Yep I need to update those options at runtime. (Even autofac moved from this)
So I either use ApiControllers which are transient that are locally to the app and I don't know if Maui supports them, so the requests always have the updated options.
Or I make use of transient services and resolve them manually every time I need them with
using var scope = scopeFactory.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<MyTransientService>()
Ok, you need to do few things.
First, make a settings service, that stores and reads small key-value pairs:
https://stackoverflow.com/a/74402836/6643940
Now you have to make sure, that everyone is notified about changes.
In my case it is easy:
Using CommunityToolkit.Mvvm, I implement Messaging.
Setting a property sends a message, for whoever cares about those changes. If there is something running, and has subscribed for that message, it will receive it.
Otherwise I fire something, that no one listens to (and this is not bad thing).
The good thing for me is that, I don't even have this Service in the places that I want to detect a change. Everything is de-coupled.
The stuff that DOES use this service, it gets the new values anyway, and since this is singleton, you can add other properties that will be updated for everyone.
The interesting part here, is that custom code you have to write. At one place you may have BaseAddress setting of HttpClient. Good luck remembering that you have to re-construct it when changed.
People are not doing this during runtime for a reason. You will infest your code with bugs.

Compile-time / Post-Build Dependency Injection IoC?

I currently use NInject to bind interfaces to concrete types and inject those into my classes. However, it is my understanding that this is a run-time affair. To me, it seems like a point of attack if someone wanted to change the behavior of my application.
Is there anything that would allow me to migrate my dependency injection IoC to compile time (Read: post-build IL weaving/replacement)?
To Elaborate
In my code I setup a binding
Bind<IFoo>().To<Foo>()
Bind<Bar>().ToSelf().InSingletonScope();
with ctor Foo(Bar dependency)
At the root of my application (on start-up) I resolve the graph
var foo = kernel.Get<IFoo>();
Assume I have no service locators (anti-pattern anyway right?). So I have no use for kernel anymore.
Now I want to have a "post-build release-compile" that replaces the kernel's resolution engine with instanciators, or references to constant/singleton, etc. Such that while my code looks like this;
var foo = kernel.Get<IFoo>();
In reality, after IL replacement in my final build stage, it looks like this:
var bar = new Bar();
var foo = new Foo(bar);
And there is no reference to NInject anymore.
My rational for this question is that I'm using Fody to IL Weave all my PropertyChanged raisers and I'm wondering whether it would be possible to do something similar for Dependency Injection.
From a security perspective in general, the use of a DI container does not pose any extra threats to your application.
When you write a service (such as web service or web site) application, the attacker could only change the DI configured behavior of the application when that application or server has already been compromized. When this happens, the server should be be considered lost (you will have to reformat that server or throw it away completely). DI doesn't make this worse, since a DI container does typically not allow the behavior to be changed from the outside. You will have to do something very weird to make this happen.
For an application that runs on the user's machine on the other hand, you should always consider that application to be compromised, since an attacker can decompile your code, change the behavior at runtime etc. Again, DI doesn't make this worse, since you can only protect yourself against attacks on the service boundary. That client app must communicate with the server and the place to store your valuable assets is within the service boundaries. For instance, you should never store a accounts password inside a DLL on the client. No matter whether it is encrypted or not.
The use of DI however, can make it somewhat easier for an attacker to change the behavior of a client application, especially when you configure everything in XML. But that holds for everything you store in the configuration file. And if that's your only line of defense (either with or without DI) you're screwed anyway.
it seems like a point of attack if someone wanted to change the
behavior of my application
Please note that any application can be decompiled, changed, and recompiled. It doesn't matter whether it's managed (.NET, Java) or not (C++), or obfuscated or not. So again, from a security perspective it doesn't matter whether you do runtime DI or compile-time DI. If this is an issue, don't deploy that code on machines that you have no control over.
As discussed, your cited reasons for doing this don't add up. However, Philip Laureano (Linfu author) did a Hiro project some time back which does pre-deployment DI. No idea if it went anywhere...
I am working on a compile time IOC container for .Net utilizing source generators:
https://github.com/YairHalberstadt/stronginject
https://www.nuget.org/packages/StrongInject/
With it you can do the following:
using StrongInject;
[Registration(typeof(Foo), typeof(IFoo))]
[Registration(typeof(Bar), Scope.SingleInstance)]
public class Container : IContainer<IFoo> {}
public static class Program
{
public static void Main()
{
new Container().Resolve(foo => //use foo here);
}
}
This will give you compile time errors and warnings if it can't resolve IFoo and avoids use of reflection.

Interface change between versions - how to manage?

Here's a rather unpleasant pickle that we got into on a client site. The client has about 100 workstations, on which we deployed version 1.0.0 of our product "MyApp".
Now, one of the things the product does is it loads up an add-in (call it "MyPlugIn", which it first looks for on a central server to see if there's a newer version, and if it is then it copies that file locally, then it loads up the add-in using Assembly.Load and invokes a certain known interface. This has been working well for several months.
Then the client wanted to install v1.0.1 of our product on some machines (but not all). That came with a new and updated version of MyPlugIn.
But then came the problem. There's a shared DLL, which is referenced by both MyApp and MyPlugIn, called MyDLL, which has a method MyClass.MyMethod. Between v1.0.0 and v1.0.1, the signature of MyClass.MyMethod changed (a parameter was added). And now the new version of MyPlugIn causes the v1.0.0 client apps to crash:
Method not found: MyClass.MyMethod(System.String)
The client pointedly does not want to deploy v1.0.1 on all client stations, being that the fix that was included in v1.0.1 was necessary only for a few workstations, and there is no need to roll it out to all clients. Sadly, we are not (yet) using ClickOnce or other mass-deployment utilities, so rolling out v1.0.1 will be a painful and otherwise unnecessary exercise.
Is there some way of writing the code in MyPlugin so that it will work equally well, irrespective of whether it's dealing with MyDLL v1.0.0 or v1.0.1? Perhaps there's some way of probing for an expected interface using reflection to see if it exists, before actually calling it?
EDIT: I should also mention - we have some pretty tight QA procedures. Since v1.0.1 has been officially released by QA, we are not allowed to make any changes to MyApp or MyDLL. The only freedom of movement we have is to change MyPlugin, which is custom code written specifically for this customer.
The thing is that the changes you made have to be basically in addition and not the change. So if you want to be back compatible in your deployment (as much as I understood in current deployment strategy you have this is an only option) you should never change the interface but add a new methods to it and avoid tight linking of your plugin with shared DLL, but load it dynamically. In this case
you will add a new funcionality without disturbing a old one
you will be able to choose which version of dll to load at runtime.
I have extracted this code from an application I wrote some time ago and removed some parts.
Many things are assumed here:
Location of MyDll.dll is the current directory
The Namespace to get reflection info is "MyDll.MyClass"
The class has a constructor without parameters.
You don't expect a return value
using System.Reflection;
private void CallPluginMethod(string param)
{
// Is MyDLL.Dll in current directory ???
// Probably it's better to call Assembly.GetExecutingAssembly().Location but....
string libToCheck = Path.Combine(Environment.CurrentDirectory, "MyDLL.dll");
Assembly a = Assembly.LoadFile(libToCheck);
string typeAssembly = "MyDll.MyClass"; // Is this namespace correct ???
Type c = a.GetType(typeAssembly);
// Get all method infos for public non static methods
MethodInfo[] miList = c.GetMethods(BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly);
// Search the one required (could be optimized with Linq?)
foreach(MethodInfo mi in miList)
{
if(mi.Name == "MyMethod")
{
// Create a MyClass object supposing it has an empty constructor
ConstructorInfo clsConstructor = c.GetConstructor(Type.EmptyTypes);
object myClass = clsConstructor.Invoke(new object[]{});
// check how many parameters are required
if(mi.GetParameters().Length == 1)
// call the new interface
mi.Invoke(myClass, new object[]{param});
else
// call the old interface or give out an exception
mi.Invoke(myClass, null);
break;
}
}
}
What we do here:
Load dynamically the library and extract the type of MyClass.
Using the type, ask to the reflection subsystem the list of MethodInfo present in that type.
Check every method name to find the required one.
When the method is found build an instance of the type.
Get the number of parameters expected by the method.
Depending on the number of parameters call the right version using Invoke.
My team has made the same mistake you have more than once. We have a similar plugin architecture and the best advice I can give you in the long run is to change this architecture as soon as possible. This is a maintainability nightmare. The backwards compatibility matrix grows non-linearly with each release. Strict code reviews can provide some relief, but the problem is you always need to know when methods were added or changed to call them in the appropriate way. Unless both the developer and reviewer know exactly when a method was last changed you run the risk of there being a runtime exception when the method is not found. You can NEVER call a new method in MyDLL in the plugin safely, because you may run on a older client that does not have the newest MyDLL version with the methods.
For the time being, you can do something like this in MyPlugin:
static class MyClassWrapper
{
internal static void MyMethodWrapper(string name)
{
try
{
MyMethodWrapperImpl(name);
}
catch (MissingMethodException)
{
// do whatever you need to to make it work without the method.
// this may go as far as re-implementing my method.
}
}
private static void MyMethodWrapperImpl(string name)
{
MyClass.MyMethod(name);
}
}
If MyMethod is not static you can make a similar non-static wrapper.
As for long term changes, one thing you can do on your end is to give your plugins interfaces to communicate through. You cannot change the interfaces after release, but you can define new interfaces that the later versions of the plugin will use. Also, you cannot call static methods in MyDLL from MyPlugIn. If you can change things at the server level (I realize this may be outside your control), another option is to provide some sort of versioning support so that a new plugin can declare it doesn't work with an old client. Then the old client will only download the old version from the server, while newer clients download the new version.
Actually, it sounds like a bad idea to change the contract between releases. Being in an object-oriented environment, you should rather create a new contract, possibly inheriting from the old one.
public interface MyServiceV1 { }
public interface MyServiceV2 { }
Internally you make your engine to use the new interface and you provide an adapter to translate old objects to the new interface.
public class V1ToV2Adapter : MyServiceV2 {
public V1ToV2Adapter( MyServiceV1 ) { ... }
}
Upon loading an assembly, you scan it and:
when you find a class implementing the new interface, you use it directly
when you find a class implementing the old interface, you use the adapter over it
Using hacks (like testing the interface) will sooner or later bite you or anyone else using the contract - details of the hack have to be known to anyone relying on the interface which sounds terrible from the object-oriented perspective.
In MyDLL 1.0.1, deprecate the old MyClass.MyMethod(System.String)and overload it with the new version.
Could you overload MyMethod to accept MyMethod(string) ( version 1.0.0 compatible) and MyMethod(string, string) (v1.0.1 version)?
Given the circumstances, I think the only thing you can do really is have two versions of MyDLL running 'side by side',
and that means something like what Tigran suggested, loading the MyDLL dynamically - e.g. as an a side example not related but might help you, take a look at the the RedemptionLoader http://www.dimastr.com/redemption/security.htm#redemptionloader (that's for an Outlook plugins which often have problems crashing to each other referencing different versions of a helper dll, just as a background story - that's a bit more complex cause of the COM involved but doesn't change much here) -
it's what you can do, something similar. Load dynamically the dll by it's location, name - you can specify that location internally, hard-code, or even set it up from config or something (or check and do that if you see that MyDll is not of the right version),
and then 'wrap' the objects, calls form the dynamically loaded dll to match what you normally have - or do some trick like that (you'd have to wrap something or 'fork' on the implementation) to make everything work in both cases.
Also to add on the 'no-nos' and your QA sorrows :),
they should not break the backward compatibility from 1.0.0 to 1.0.1 - those are (usually) the minor changes, fixes - not breaking changes, major version # is needed for that.

Need help avoiding the use of a Singleton

I'm not a hater of singletons, but I know they get abused and for that reason I want to learn to avoid using them when not needed.
I'm developing an application to be cross platform (Windows XP/Vista/7, Windows Mobile 6.x, Windows CE5, Windows CE6). As part of the process I am re-factoring out code into separate projects, to reduce code duplication, and hence a chance to fix the mistakes of the inital system.
One such part of the application that is being made separate is quite simple, its a profile manager. This project is responsible for storing Profiles. It has a Profile class that contains some configuration data that is used by all parts of the application. It has a ProfileManager class which contains Profiles. The ProfileManager will read/save Profiles as separate XML files on the harddrive, and allow the application to retrieve and set the "active" Profile. Simple.
On the first internal build, the GUI was the anti-pattern SmartGUI. It was a WinForms implementation without MVC/MVP done because we wanted it working sooner rather than being well engineered. This lead to ProfileManager being a singleton. This was so from anywhere in the application, the GUI could access the active Profile.
This meant I could just go ProfileManager.Instance.ActiveProfile to retrieve the configuration for different parts of the system as needed. Each GUI could also make changes to the profile, so each GUI had a save button, so they all had access to ProfileManager.Instance.SaveActiveProfile() method as well.
I see nothing wrong in using the singleton here, and because I see nothing wrong in it yet know singletons aren't ideal. Is there a better way this should be handled? Should an instance of ProfileManager be passed into every Controller/Presenter? When the ProfileManager is created, should other core components be made and register to events when profiles are changed. The example is quite simple, and probably a common feature in many systems so think this is a great place to learn how to avoid singletons.
P.s. I'm having to build the application against Compact Framework 3.5, which does limit alot of the normal .Net Framework classes which can be used.
One of the reasons singletons are maligned is that they often act as a container for global, shared, and sometimes mutable, state. Singletons are a great abstraction when your application really does need access to global, shared state: your mobile app that needs to access the microphone or audio playback needs to coordinate this, as there's only one set of speakers, for instance.
In the case of your application, you have a single, "active" profile, that different parts of the application need to be able to modify. I think you need to decide whether or not the user's profile truly fits into this abstraction. Given that the manifestation of a profile is a single XML file on disk, I think it's fine to have as a singleton.
I do think you should either use dependency injection or a factory pattern to get a hold of a profile manager, though. You only need to write a unit test for a class that requires the use of a profile to understand the need for this; you want to be able to pass in a programatically created profile at runtime, otherwise your code will have a tightly coupled dependency to some XML file on disk somewhere.
One thing to consider is to have an interface for your ProfileManager, and pass an instance of that to the constructor of each view (or anything) that uses it. This way, you can easily have a singleton, or an instance per thread / user / etc, or have an implementation that goes to a database / web service / etc.
Another option would be to have all the things that use the ProfileManager call a factory instead of accessing it directly. Then that factory could return an instance, again it could be a singleton or not (go to database or file or web service, etc, etc) and most of your code doesn't need to know.
Doesn't answer your direct question, but it does make the impact of a change in the future close to zero.
"Singletons" are really only bad if they're essentially used to replace "global" variables. In this case, and if that's what it's being used for, it's not necessarily Singleton anyway.
In the case you describe, it's fine, and in fact ideal so that your application can be sure that the Profile Manager is available to everyone that needs it, and that no other part of the application can instantiate an extra one that will conflict with the existing one. This reduces ugly extra parameters/fields everywhere too, where you're attempting to pass around the one instance, and then maintaining extra unnecessary references to it. As long as it's forced into one and only one instantiation, I see nothing wrong with it.
Singleton was designed to avoid multiple instantiations and single point of "entry". If that's what you want, then that's the way to go. Just make sure it's well documented.

How to explicitly and precisely control composition scoping?

I'm interested in ways to control composition scoping with MEF.
The most obvious example - web applications, where you have to create certain subset of components per request and dispose of them when the request is finished.
However, a general implementation of scoping may be useful in other contexts as well.
I'm looking at MEF2 preview and trying to make sense of it, but don't see a complete solution for some reason.
On one hand, there is this MVC integration module, where MEF is nice enough to take care of request scope for me, but that is not very usable outside of MVC (and outside of web for that matter), is it?
On the other hand, in the first preview-related post "What's new in MEF2", I've seen this thing called CompositionScopeDefinition that looks like an explicit specification for scopes, but with that one, I don't see a way to "close" the scope. To put it in other words: how does MEF determine when to dispose of components that were created within a scope?
And on third hand (yep :-), with MEF v1, I used to deal with scoping by creating nested CompositionContainers, but that doesn't work very well with custom ExportProviders.
What would really like to see is something like:
using( var scope = compositionContainer.OpenScope( /* some scope definition here */ ) )
{
var rootComponent = scope.GetExport<MyRootComponent>(); // The component graph gets composed at this point
rootComponent.DoYourScopedThing();
} // The component graph gets disposed at this point
If I had that thing, I could easily build MVC integration on top of it, but I could also use it in other contexts.
So, the question again: what do you use to deal with scoping problems like that? Or do you say MEF is not yet mature enough for serious use?
Good question- we are working on more documentation that should answer your question about CompositionScopeDefinition. Short version; CSD is used via an ExportFactory<T>, where CreateExport() returns a handle that is used to control the lifetime of the scope.
However, CSD is intended and optimized for desktop application scenarios; as you have no doubt seen, the MVC integration uses filtered catalogs and nested containers to control lifetime. This is still the recommended approach for 'transactional'-type lifetime in web and other work-processing scenarios.
it would be good to know more about the problems you face using custom ExportProviders wih this approach.
A stronger 'custom' lifetime story is something we're very much working towards; letting us know about where MEF 2 falls short for your scenarios, especially via the CodePlex discussion forum, is a great help.
I've found this post searching for details about CSD.
I want to use MEF to create extensible WPF application which has screen navigation that allow the client to open screen after screen inside a single window.
Each screen should have access to parts setup by previous screens and also have the ability to override some parts.
For example, when the user open a ProcessView it should have a ProcessProvider part which may be imported by screen navigated from the ProcessView, let's say ActivityView. The ActivityView should have access to the ProcessProvider so it will have context on which to operate.
Another example is that the root screen may have a ProcessListProvider which by default return all processes in the database. A screen that want to open the ProcessListView will need to somehow override the root ProcessListProvider with a customized ProcessListProvider so the ProcessListView will still work but with the customized process list provider.
I hope I was able to communicate my requirements.
Ido.

Categories

Resources