NLog callback possible? - callback

I am in the process of converting a home grown logging system to NLog and am wondering if there is a way to add an event to a Logger or otherwise support a mechanism where when I log a message I can get a callback with the final formatted message and the LogLevel. I currently use something like this to send server messages back to a connected client.
Thx

This is an MCVE of what I was talking about in the comments. Create a target that accepts some callback functions:
[Target("MyFirst")]
public sealed class MyFirstTarget : TargetWithLayout
{
private readonly Action<string>[] _callbacks;
public MyFirstTarget(params Action<string>[] callbacks)
{
_callbacks = callbacks;
}
protected override void Write(LogEventInfo logEvent)
{
foreach (var callback in _callbacks)
{
callback(logEvent.FormattedMessage);
}
}
}
Configure NLog to use the target. I do this programmatically since the callbacks are passed in the constructor. You can also configure the target in the NLog.config, but your target will need to be a singleton then so you can register the callbacks in code.
class Program
{
public static void Main()
{
LogManager.Configuration.AddTarget("MyFirst", new MyFirstTarget(s => Debug.WriteLine(s)));
var logger = LogManager.GetCurrentClassLogger();
logger.Debug("test");
}
}
With no other NLog configuration (copy this code into an empty project and add the NLog nuget package), this will emit a message to your debug window.

Related

How Do I Create a DbContextFactory Within a Blazor Background Service?

I am working on my first Blazor Server application, which is also my first Entity Framework Core application. I am wanting to set up a background service which, once a day in the early morning, checks the database to see if any of a certain record type has been added with yesterday's date. If so, the relevant records are pulled, formatted, and then emailed to a stakeholder.
I have the EF, formatting, and emailing code working just fine when I trigger the report by manually visiting the page. The problem that I have is how to provide the background service with a DbContextFactory so that the EF and related code can execute.
Up to this point I've always used Razor-based dependency injection to inject the IDbContextFactory via an inject IDbContextFactory<OurAppContext> DbFactory at the top of the page, and then accessed the DbFactory via the DbFactory variable.
However, background services are (according to this Microsoft tutorial) set up through Program.cs, so I don't have access to Razor-based dependency injection there.
I have set up my background service (what I call the PhaseChangeReportService) as indicated in the above link, and it dutifully outputs to the console every 10 seconds that it is running with an updated execution count. I don't fully understand what's going on with the various layers of indirection, but it appears to be working as Microsoft intended.
I noted that the constructor for the background service takes in an ILogger as a parameter, specifically:
namespace miniDARTS.ScopedService
{
public sealed class PhaseChangeReportService : IScopedProcessingService
{
private int _executionCount;
private readonly ILogger<PhaseChangeReportService> _logger;
public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger)
{
_logger = logger;
}
public async Task DoWorkAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
++_executionCount;
_logger.LogInformation("{ServiceName} working, execution count: {Count}", nameof(PhaseChangeReportService), _executionCount);
await Task.Delay(10_000, stoppingToken);
}
}
}
}
I was (and am) confused that the constructor is never referenced within Visual Studio, but when I drop a breakpoint on its one line of code it is hit. I tried modifying this constructor's signature so that it took in an IDbFactory<OurAppContext> as well, so that whatever dark magic is allowing an ILogger<BackgroundServiceType> to come in for assignment to _logger might bring in a DbFactory<OurAppContext> as well, like so:
private readonly ILogger<PhaseChangeReportService> _logger;
private readonly IDbContextFactory<miniDARTSContext> _dbContextFactory;
public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger, IDbContextFactory<miniDARTSContext> dbContextFactory)
{
_logger = logger;
_dbContextFactory = dbContextFactory;
}
However, doing so just led to the constructor breakpoint being skipped over and not breaking, with no exception being thrown or any console output of any kind (i.e. the prior execution count console output no longer showed up). So, I gave up on that approach.
Here is the relevant section of Program.cs:
// Configure the database connection.
string connectionString = builder.Configuration.GetConnectionString("miniDARTSContext");
var serverVersion = new MySqlServerVersion(new Version(8, 0, 28));
builder.Services.AddDbContextFactory<miniDARTSContext>(options => options.UseMySql(connectionString, serverVersion), ServiceLifetime.Scoped);
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<ScopedBackgroundService>();
services.AddScoped<IScopedProcessingService, PhaseChangeReportService>();
})
.Build();
host.RunAsync();
Here's IScopedProcessingService.cs:
namespace miniDARTS.ScopedService
{
public interface IScopedProcessingService
{
Task DoWorkAsync(CancellationToken stoppingToken);
}
}
And here's ScopedBackgroundService.cs:
namespace miniDARTS.ScopedService;
public sealed class ScopedBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ScopedBackgroundService> _logger;
public ScopedBackgroundService(IServiceProvider serviceProvider, ILogger<ScopedBackgroundService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"{nameof(ScopedBackgroundService)} is running.");
await DoWorkAsync(stoppingToken);
}
private async Task DoWorkAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"{nameof(ScopedBackgroundService)} is working.");
using (IServiceScope scope = _serviceProvider.CreateScope())
{
IScopedProcessingService scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWorkAsync(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"{nameof(ScopedBackgroundService)} is stopping.");
await base.StopAsync(stoppingToken);
}
}
I'm confident I'm misunderstanding something relatively fundamental here when it comes to services / dependency injection, but my Googling and review of past StackOverflow answers has not turned up anything I can run with.
The IDbContextFactory is an interface that is used for creating instances of a DbContext. When you add it to your services on program.cs for Blazor (services.AddDbContextFactory(parameters)), it implements the IDbContextFactory for you. This allows you to use the #inject IDbContextFactory<YourDbContext> DbFactory at the top of your razor components and then within your code you can call the CreateDbContext method when you need to create an instance of the DbContext (ex. using var context = DbFactory.CreateDbContext()).
You can pass an injected DbContextFactory as a parameter from a razor component to a class, and then use that DbContextFactory in a method to create an instance of the DbContext (see constructor injection), but that still relies on the razor component to inject the DbContextFactory to begin with.
To create an instance of a DbContext independent of a razor component, you need to use the constructor for your DbContext. Your DbContext will have a public constructor with a DbContextOptions parameter (this is required to be able to use AddDbContextFactory when registering the factory service in program.cs). You can use this constructor to implement your own factory. If you aren't sure which options to use, you can check your program.cs to see what options you used there.
public class YourDbFactory : IDbContextFactory<YourDbContext>
{
public YourDbContext CreateDbContext()
{
var optionsBuilder = new DbContextOptionsBuilder<YourDbContext>();
optionsBuilder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test"));
return new YourDbContext(optionsBuilder);
}
}
Once you've created your own implementation of the IDbContextFactory interface, you can then use it in your code independent of razor components - for example in the background service class.
YourDbFactory DbFactory = new YourDbFactory();
using var context = DbFactory.CreateDbContext();

AEM | Why doesn't a Sling ContentDistribution Event provide the deepPaths event property?

I have created an OSGi EventHandler to know about the Sling CD events, here is the sample class.
#Component(immediate = true,
property = {
"event.topics=org/apache/sling/distribution/agent/package/distributed",
"event.filter=(|(distribution.type=ADD)(distribution.type=DELETE))"
})
public class MyEventHandler implements EventHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#Override
public void handleEvent(Event event) {
// consume event.
}
}
When a content is distributed from AEM using the Add Tree option, the deepPaths field in org.apache.sling.distribution.SimpleDistributionRequest is initialised but it is not sent to the event handler.
This way we never get to know that it was a tree activation. So, is there a way to know about the deepPaths using this approach?
I found out that if we subscribe to this topic org/apache/sling/event/notification/job/FINISHED and filter on (|(distribution.request.type=ADD)(distribution.request.type=DELETE)) we can get the deepPaths as well as several other event properties but I am not sure if that would be right approach.
Please suggest.

How to use an mvvmcross plugin such as the file plugin

I'm using mvvmcross version 6.4.1 to develop an app for IOS, Android, and WPF.
I've searched all over for my to use plugins. There seems to be no code examples. The documentation said to install the nuget in both my core and ui application projects. Which I did. Is there any special IOC registration/setup/or loading that needs to be done before I can use the plugin and how do I go about using the plugin? Do they get injected in the constructor or Do I have to manually pull them from the IOC container or new () them up.
I've installed nuget for the File plugin into my WPF UI and Core project. I added the IMvxFileStore to one of my core project's service constructor thinking it automagically gets added to the DI container, but it doesn't seem to get injected.
namespace My.Core.Project.Services
{
public class SomeService : ISomeService
{
private IMvxFileStore mvxFileStore;
public SomeService(IMvxFileStore mvxFileStore)
{
this.mvxFileStore = mvxFileStore;
}
public string SomeMethod(string somePath)
{
mvxFileStore.TryReadTextFile(somePath, out string content);
return content;
}
}
}
App.xaml.cs
using MvvmCross.Core;
using MvvmCross.Platforms.Wpf.Views;
...
public partial class App : MvxApplicatin
{
protected override void RegisterSetup()
{
this.RegisterSetupType<Setup<Core.App>>();
}
}
App.cs
using MvvmCross;
using MvvmCross.ViewModels;
using My.Core.Project.Services;
public class App: MvxApplication
{
public override void Initialize()
{
Mvx.IocProvider.RegisterType<ISomeService, SomeService>();
RegisterCustomAppStart<AppStart>();
}
}
AppStart.cs
using MvvmCross.Exceptions;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using My.Core.Project.ViewModels;
using System;
using System.Threading.Tasks;
....
public class AppStart : MvxAppStart
{
public AppStart(IMvxApplication application, IMvxNavigationService navigationService) : base(application, navigationService)
{}
public override Task NavigateToFirstViewModel(object hint = null)
{
try {
return NavigationService.Navigate<FirstPageViewModel>();
} catch {
throw e.MvxWrap("Some error message {0}", typeof(FirstPageViewModel).Name);
}
}
}
Setup.cs in WPF project
using MvvmCross;
using MvvmCross.Base;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Plugin.File;
using MvvmCross.Plugin.Json;
using MvvmCross.ViewModels;
using My.Wpf.Project.Services;
...
public class Setup<T> : MvxWpfSetup
{
public Setup() : base() {}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override void InitializeFirstChange()
{
base.InitializeFirstChange();
Mvx.IocProvider.RegisterType<ISomeWpfSpecificService>(() => new SomeWpfSpecificService());
}
protected override void InitializeLastChange()
{
base.InitializeLastChange();
}
}
I'm expecting my service to load but instead, I get the error message
MvxIoCResolveException: Failed to resolve parameter for parameter mvxJsonConverter of type IMvxJsonConverter
NOTE: I get the same error message for both File and Json plugin, The plugin that gets listed first in the constructor gets the error message when the app trys to load.
Am I properly using or loading the plugin?
UPDATE: I manually registered the Plugins in the UI Setup.cs and it is working but I am not sure if this is the proper way to do it.
WPF UI project Setup.cs
using MvvmCross;
using MvvmCross.Base;
using MvvmCross.Platforms.Wpf.Core;
using MvvmCross.Plugin.File;
using MvvmCross.Plugin.Json;
using MvvmCross.ViewModels;
using My.Wpf.Project.Services;
...
public class Setup<T> : MvxWpfSetup
{
public Setup() : base() {}
protected override IMvxApplication CreateApp()
{
return new Core.App();
}
protected override void InitializeFirstChange()
{
base.InitializeFirstChange();
Mvx.IocProvider.RegisterType<ISomeWpfSpecificService>(() => new SomeWpfSpecificService());
Mvx.IoCProvider.RegisterType<IMvxFileStore, MvxFileStoreBase>();
Mvx.IoCProvider.RegisterType<IMvxJsonConverter, MvxJsonConverter>();
}
protected override void InitializeLastChange()
{
base.InitializeLastChange();
}
}
Yes you are using the plugin properly and I think that for now your solution to manually register your plugin is viable.
The root of the problem is located in the MvxSetup class. This class contains the method LoadPlugins which is responsible for loading the MvvmCross plugins which are referenced by your UI project. This is how LoadPlugins determines what plugins to load:
Get all assemblies that have been loaded into the execution context of the application domain.
Find types within these assemblies which contain the MvxPluginAttribute.
Now the problem occurs in step 1. In a .NET framework project, by default, your referenced assemblies won't be loaded into the execution context until you actually use them in your code. So if you don't use something from your MvvmCross.Plugin.File reference in your UI project it won't be loaded into your execution context and it won't be found in step 1 and thus it won't be registered by LoadPlugins. (good read: when does a .NET assembly Dependency get loaded)
One way I have tested this is by doing this:
protected override void InitializeFirstChance()
{
// Because a type of the MvvmCross.Plugin.File.Platforms.Wpf reference is
// used here the assembly will now get loaded in the execution context
var throwaway = typeof(Plugin);
base.InitializeFirstChance();
}
With the above code you don't have to manually register the Plugin.
There has been a pull request to fix this in the MvvmCross framework but this has been reverted later since it caused problems on other platforms.
In other platforms the plugin assemblies will get loaded into the execution context without any tricks so I would say updating the MvvmCross documentation stating you have to register your plugin manually for WPF would be useful for other developers in the future.

How to write integration tests for spring-batch-integration?

I'm using spring-integration bundled with spring-batch and got stuck trying to write integration tests to test the whole flow, not just single config.
I've created Embedded Sftp Server for this tests and trying to send message to sftpInboundChannel - the message is sent, but nothing happens, but when i send this message to the next channel (after sftpInboundChannel) it goes ok. Also i'm not able to load test source properties, even though i'm using #TestPropertySource annotation.
This are my class annotations
#TestPropertySource(properties = {
//here goes all the properties
})
#EnableConfigurationProperties
#RunWith(SpringRunner.class)
#Import({TestConfig.class, SessionConfig.class})
#ActiveProfiles("it")
#SpringIntegrationTest
#EnableIntegration
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
This is my class body
#Autowired
private PollableChannel sftpInboundChannel;
#Autowired
private SessionFactory<ChannelSftp.LsEntry> defaultSftpSessionFactory;
#Autowired
private EmbeddedSftpServer server;
#Test
public void shouldDoSmth() {
RemoteFileTemplate<ChannelSftp.LsEntry> template;
try {
template = new RemoteFileTemplate<>(defaultSftpSessionFactory);
SftpTestUtils.moveToRemoteFolder(template);
final List<ChannelSftp.LsEntry> movedFiles = SftpTestUtils.listFilesFromDirectory("folder/subfolder", template);
log.info("Moved file {}", movedFiles.size());
final MessageBuilder<String> messageBuilder = MessageBuilder.withPayload("Sample.txt") // path to file
.setHeader("file_Path", "Sample.txt")
boolean wasSent = this.sftpInboundChannel.send(messageBuilder.build());
log.info("Was sent to sftpInboundChannel channel {}", wasSent);
log.info("message {}", messageBuilder.build());
} finally {
SftpTestUtils.cleanUp();
}
}
To the case of not read the property file one solution is add in your Test class something like this:
#BeforeClass
public static void beforeClass() {
System.setProperty("propertyfile", "nameOfFile.properties");
}
A second way is to create a xml (or class) config where you add the tag:
<context:property-placeholder
location="nameOfFile.properties"
ignore-resource-not-found="true" system-properties-mode="OVERRIDE" />
and your file will be localized.
The property file should be inside of resources folder.

How to hide the output of a console view?

I'm writing an Eclipse plug-in in which the user can interact with another process via the Console view (in this case, an interpreter), for example, evaluate expressions and so on.
Sometimes the program needs to ask the interpreter for certain values. These interactions however, shouldn't be shown in the console view to the user.
I have following instances:
private IProcess process;
private ILaunch launch;
private IStreamsProxy proxy;
the queries my program do are made via adding an IStreamListener to the proxy:
proxy.getOutputStreamMonitor().addListener(new IStreamListener(){
#Override
public void streamAppended(String response, IStreamMonitor arg1) {
doSomeStuffWiththeRepsonse(response);
}
});
while the listener is listening to the OutputStreamMonitor of the proxy, I don't want the response to pop up in the console view of the plugin.
How can I do that?
Okay, here is how I did it.
The launch system of Eclipse works as follows:
1. Implement a ILaunchConfigurationDelegate, the only method in this interface is launch, which recieves an ILaunchConfiguration, a mode, an ILaunch and an IProgressMonitor.
In my program, launch starts an inferiorProcess using DebugPlugin.exec() using a commandline argument. Then a new Process is created by calling DebugPlugin.newProcess() with the ILaunch, the inferiorProcess, the name for the interpreter and some attributes.
This method creates a new RuntimeProcess and adds it to the ILaunch and vice versa.
2. Define a LaunchConfigurationType by using the extension point org.eclipse.debug.core.launchConfigurationTypes and add it to the plugin.xml:
<extension
point="org.eclipse.debug.core.launchConfigurationTypes">
<launchConfigurationType
delegate="myplugin.MyLaunchConfigurationDelegate" (1)
id="myplugin.myExternalProgram" (2)
modes="run" (3)
name="MyExternalProgram" (4)
public="false"> (5)
</launchConfigurationType>
</extension>
The extension point gives the exact path to the ILaunchConfigurationDelegate class created as above (1) and an unqiue identifier (2) to retrieve the instance of ILaunchConfigurationType from the LaunchManager used to launch the program. (3) defines the modes it can run as, run and debug. The name (4) is later shown in the top bar of the console view. If you only want to access and launch your external program programmatically in your plug-in (and not via the Run drop-down menu) (5) must be set to false.
3. Create a class that stores the Instances of IProcess, ILaunch and IStreamsProxy and which calls apropiate methods to start the process and to write onto the streamsproxy. A method for starting the process could look like this:
// is the process already running?
public boolean isRunning() {
boolean result = false;
try {
if (this.process != null) {
result = true;
this.process.getExitValue();
result = false;
}
}
catch (DebugException exception) {
}
return result;
}
// start the process
public void start() {
try {
if (!isRunning()) {
// get the ILaunchConfigurationType from the platform
ILaunchConfigurationType configType = DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurationType(myplugin.myExternalProgram);
// the ILaunchConfigurationType can't be changed or worked with, so get a WorkingCopy
ILaunchConfigurationWorkingCopy copy = configType.newInstance(null, "myExternalProgram");
this.launch = copy.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor());
IProcess[] processes = this.launch.getProcesses();
if (processes.length > 0) {
// get the IProcess instance from the launch
this.process = this.launch.getProcesses()[0];
// get the streamsproxy from the process
this.proxy = this.process.getStreamsProxy();
}
}
}
catch (CoreException exception) {
}
if (isRunning())
// bring up the console and show it in the workbench
showConsole();
}
public void showConsole() {
if (this.process != null && this.process.getLaunch() != null) {
IConsole console = DebugUITools.getConsole(this.process);
ConsolePlugin.getDefault().getConsoleManager().showConsoleView(console);
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
IViewPart view = page.findView("org.eclipse.ui.console.ConsoleView");
if (view != null)
view.setFocus();
}
}
Now to the initial problem of the question
The IStreamsListener of the console view, which listens to the OutputStreamMonitor of the IStreamsProxy could not be retrieved and thus not being stopped of listening. Prints to the console could not be prevented. OutputStreamMonitor doesn't provide methods to get the current listeners. It is not possible to just subclass it and override/add some methods, because the important fields and methods are private.
http://www.java2s.com/Open-Source/Java-Document/IDE-Eclipse/debug/org/eclipse/debug/internal/core/OutputStreamMonitor.java.htm
Just copy the code and add a get-method for the fListeners field and change some method modifiers to public.
In order to get your own OutputStreamMonitor into the system, you need to create your own IStreamsProxy. Again only subclassing wont work, you need to copy the code again and make some changes.
http://www.java2s.com/Open-Source/Java-Document/IDE-Eclipse/debug/org/eclipse/debug/internal/core/StreamsProxy.java.htm
Important:
public class MyStreamsProxy implements IStreamsProxy, IStreamsProxy2 {
/**
* The monitor for the output stream (connected to standard out of the process)
*/
private MyOutputStreamMonitor fOutputMonitor;
/**
* The monitor for the error stream (connected to standard error of the process)
*/
private MyOutputStreamMonitor fErrorMonitor;
(...)
public MyStreamsProxy(Process process) {
if (process == null) {
return;
}
fOutputMonitor = new MyOutputStreamMonitor(process
.getInputStream());
fErrorMonitor = new MyOutputStreamMonitor(process
.getErrorStream());
fInputMonitor = new InputStreamMonitor(process
.getOutputStream());
fOutputMonitor.startMonitoring();
fErrorMonitor.startMonitoring();
fInputMonitor.startMonitoring();
}
The only thing remaining is providing your own IProcess that uses your IStreamsProxy. This time subclassing RuntimeProcess and overriding the method createStreamsProxy() is enough:
public class MyProcess extends RuntimeProcess {
public MyProcess(ILaunch launch, Process process, String name,
Map attributes) {
super(launch, process, name, attributes);
}
#Override
protected IStreamsProxy createStreamsProxy() {
String encoding = getLaunch().getAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING);
return new MyStreamsProxy(getSystemProcess());
}
}
MyProcess is integrated by creating a new instance of it in the launch method in the ILaunchConfigurationDelegate instead of using DebugPlugin.newProcess().
Now it is possible to hide and expose the output of the console view.
/**
* Storage field for the console listener
*/
private IStreamListener oldListener;
/**
* Hides the output coming from the process so the user doesn't see it.
*/
protected void hideConsoleOutput() {
MyOutputStreamMonitor out
= (MyOutputStreamMonitor) this.process.getStreamsProxy().getOutputStreamMonitor();
List<IStreamListener> listeners = out.getListeners();
// the console listener
this.oldListener = listeners.get(0);
out.removeListener(this.oldListener);
}
/**
* Reverts the changes made by hideConsoleOutput() so the user sees the response from the process again.
*/
protected void exposeConsoleOutput() {
MyOutputStreamMonitor out
= (MyOutputStreamMonitor) this.process.getStreamsProxy().getOutputStreamMonitor();
out.addListener(oldListener);
this.oldListener = null;
}
The hide and expose methods have to be called before any other listeners are added. There might be a better solution, however, this works.
Previous answer does the trick and I was going with something similar first after hours of trying to solve this. Finally I ended up doing something a bit simpler, but also somewhat nastier...basically:
...
ILaunch launch = launcconf.launch(
ILaunchManager.RUN_MODE, monitor);
DebugUIPlugin.getDefault().
getProcessConsoleManager().launchRemoved(launch);
...
So, I'm basically telling the console manager listener methods that this lauch has already been removed and it removes the console. Seems to do the trick for me atleast.
i don't want the response to pop up in the console view of the plugin. how can i do that?
Well since that is your actual concern, then just toggle the button on the console view called "Show console when standard output changes". Way more of a simpler approach than all of this, and it can be turned back on/off.