NServiceBus 4 with Azure Storage getting NullReferenceException in TimeoutPersister - azure

I am trying to use NServiceBus (version 4.6.0.0) with an ASP.NET MVC application hosted in a Windows Azure Website and Azure Storage for persistence but am having problems with the TimeoutManager.
Before trying to use Azure I got my app working with NServiceBus using the default MSMQ and RavenDB settings and then changed over to using Azure Storage. With that setup however I am getting multiple NullReferenceException errors right after startup in NServiceBus.Azure.TimoutManagerPersister.TryGetLastSuccessfulRead().
I have tried running this local using the Storage Emulator and with everything deployed to Azure and have the same error on both.
Here is how I have NServiceBus setup in my app:
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
public static IBus Bus { get; private set; }
protected void Application_Start()
{
...
Configure.ScaleOut(s => s.UseSingleBrokerQueue());
Feature.Enable<Sagas>();
IStartableBus startableBus = Configure.With()
.DefaultBuilder()
.DefineEndpointName("MyApp.Web")
.AzureConfigurationSource()
.UseTransport<AzureStorageQueue>()
.AzureMessageQueue()
.AzureSubscriptionStorage()
.UseAzureTimeoutPersister()
.AzureSagaPersister()
.PurgeOnStartup(false)
.UnicastBus()
.LoadMessageHandlers()
.RunHandlersUnderIncomingPrincipal(false)
.Log4Net(new DebugAppender { Threshold = Level.Warn })
.RijndaelEncryptionService()
.CreateBus();
Configure.Instance.ForInstallationOn<Windows>().Install();
Bus = startableBus.Start();
}
}
Web.config
<configSections>
<section name="MessageForwardingInCaseOfFaultConfig" type="NServiceBus.Config.MessageForwardingInCaseOfFaultConfig, NServiceBus.Core" />
<section name="UnicastBusConfig" type="NServiceBus.Config.UnicastBusConfig, NServiceBus.Core" />
<section name="AuditConfig" type="NServiceBus.Config.AuditConfig, NServiceBus.Core" />
<section name="AzureProfileConfig" type="NServiceBus.Config.AzureProfileConfig, NServiceBus.Hosting.Azure" />
<section name="AzureSubscriptionStorageConfig" type="NServiceBus.Config.AzureSubscriptionStorageConfig, NServiceBus.Azure" />
<section name="AzureSagaPersisterConfig" type="NServiceBus.Config.AzureSagaPersisterConfig, NserviceBus.Azure" />
<section name="AzureTimeoutPersisterConfig" type="NServiceBus.Config.AzureTimeoutPersisterConfig, NserviceBus.Azure" />
</configSections>
<connectionStrings>
<add name="NServiceBus/Transport" connectionString="DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey" />
</connectionStrings>
<AzureSagaPersisterConfig ConnectionString="DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey" CreateSchema="true" />
<AzureTimeoutPersisterConfig ConnectionString="DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey" TimeoutManagerDataTableName="TimeoutManagerData" TimeoutDataTableName="TimeoutData" />
<AzureSubscriptionStorageConfig ConnectionString="DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey" />
And here is the exception I am getting:
System.NullReferenceException was unhandled by user code
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=Microsoft.Data.Services.Client
StackTrace:
at System.Data.Services.Client.UriWriter.VisitResourceSetExpression(ResourceSetExpression rse)
at System.Data.Services.Client.DataServiceALinqExpressionVisitor.Visit(Expression exp)
at System.Data.Services.Client.UriWriter.Translate(DataServiceContext context, Boolean addTrailingParens, Expression e, Uri& uri, Version& version)
at System.Data.Services.Client.DataServiceQueryProvider.Translate(Expression e)
at System.Data.Services.Client.DataServiceQuery`1.Translate()
at System.Data.Services.Client.DataServiceQuery`1.Execute()
at System.Data.Services.Client.DataServiceQuery`1.GetEnumerator()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at NServiceBus.Azure.SafeLinqExtensions.SafeFirstOrDefault[TSource](IEnumerable`1 source) in y:\BuildAgent\work\ba77a0c29cee2af1\src\NServiceBus.Azure\SafeLinqExtensions.cs:line 13
at NServiceBus.Azure.TimeoutPersister.TryGetLastSuccessfulRead(ServiceContext context, TimeoutManagerDataEntity& lastSuccessfulReadEntity) in
y:\BuildAgent\work\ba77a0c29cee2af1\src\NServiceBus.Azure\Timeout\TimeoutLogic\TimeoutPersister.cs:line 338
at NServiceBus.Azure.TimeoutPersister.GetNextChunk(DateTime startSlice, DateTime& nextTimeToRunQuery) in y:\BuildAgent\work\ba77a0c29cee2af1\src\NServiceBus.Azure\Timeout\TimeoutLogic\TimeoutPersister.cs:line 27
at NServiceBus.Timeout.Hosting.Windows.TimeoutPersisterReceiver.Poll(Object obj) in y:\BuildAgent\work\31f8c64a6e8a2d7c\src\NServiceBus.Core\Timeout\Hosting\Windows\TimeoutPersisterReceiver.cs:line 80
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
Even with the error messages are getting added to the Azure queue and are being processed but only for a couple of minutes than the TimeoutManager stops processing and messages are not dequeued.

Sounds like something is wrong in the storage sdk that you are using, which version are you on?

Related

How to get the full path of current target file using NLog at runtime in .NET Core 7?

I have an application in .NET Core Console that is working fine in .NET Core 6. I am testing the conversion of this app in the newly released .NET Core 7 and all works fine except the part where I dynamically get the path of the NLog target.
My NLog.config is like this:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="internal-CimplDataLoader.log">
<extensions>
<add assembly="NLog.Extensions.Logging" />
</extensions>
<targets>
<target xsi:type="File" name="allfile" fileName="${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message} ${exception}"
keepFileOpen="false"/>
</targets>
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
</rules>
</nlog>
The code that works in .NET Core 6:
if (LogManager.Configuration != null)
{
Target target = LogManager.Configuration.FindTargetByName("allfile");
var logEventInfo = new LogEventInfo { TimeStamp = DateTime.Now };
FileTarget? fileTarget;
// Unwrap the target if necessary.
if (target is not WrapperTargetBase wrapperTarget)
fileTarget = target as FileTarget;
else
fileTarget = wrapperTarget.WrappedTarget as FileTarget;
if (fileTarget != null)
{
string fileName = fileTarget.FileName.Render(logEventInfo);
string LogPath = Path.GetDirectoryName(fileName)!;
/* Work with LogPath */
}
else
{
_logger.LogError("Unable to get NLog \"allfile\" base directory.");
}
}
else
{
_logger.LogError("Unable to read NLog configuration.");
}
This same code in .NET Core 7 fails because LogManager.Configuration is always null. Is there any way I can still dynamically get the path in .NET Core 7?
NLog.config was not present, hence the failure. This was due to the switch from .NET 6 to .NET 7 and nlog.config property "Copy To Output Directory" set to "Do not copy".

Spring Integration - Scheduling Job from configuration file

I'm using Spring Integration to parse XML file and i will need to create a thread (and each one have a different rate) for each tag.
Right now (with the help of many users here :)) i'm able to split XML by tag and then route it to the appropiate service-activator.
This works great but i'm not able to redirect to a channel that create "a thread" and then execute the operations. Right now i have the following configuration and in my mind (that i dont know if it is correct...)
Split tag -> Route to the appropiate channel -> Start a thread(from tag configuration) -> Execute the operation
This is my actual configuration that split tag and redirect to the channel.
The router should redirect not toward a channel directly, but schedule them.
In first instance will be enought to redirect it in a pool with fixed rate and later i will use XPATH to get the attribute and then replace this "fixed" rate with the correct value.
I've tried many solutions to create this flow but each one fails or do not compile :(
<context:component-scan base-package="it.mypkg" />
<si:channel id="rootChannel" />
<si-xml:xpath-splitter id="mySplitter" input-channel="rootChannel" output-channel="routerChannel" create-documents="true">
<si-xml:xpath-expression expression="//service" />
</si-xml:xpath-splitter>
<si-xml:xpath-router id="router" input-channel="routerChannel" evaluate-as-string="true">
<si-xml:xpath-expression expression="concat(name(./node()), 'Channel')" />
</si-xml:xpath-router>
<si:service-activator input-channel="serviceChannel" output-channel="endChannel">
<bean class="it.mypkg.Service" />
</si:service-activator>
UPDATE:
Using this configuration for the service this should run a task every 10 seconds (the id=service1) and every 5 seconds the other (the id=service2). In the same way i can have another tag that is handle by another class (because this will have another behaviour)
<root>
<service id="service1" interval="10000" />
<service id="service2" interval="5000" />
<activity id="activity1" interval="50000" />
<root>
I will have a classe (Service) that is general to handle Service tag and this complete some operation and then "return me" the value so i can redirect to another channel.
public class Service {
public int execute() {
// Execute the task and return the value to continue the "chain"
}
}
It's not at all clear what you mean; you split a tag; route it but want to "schedule" it at a rate in the XML. It's not clear what you mean by "schedule" here - normally each message is processed once not multiple times on a schedule.
As I said, I don't understand what you need to do, but a smart poller might be suitable.
Another possibility is the delayer where the amount of the delay can be derived from the message.
EDIT
Since your "services" don't seem to take any input data, it looks like you simply need to configure/start an <inbound-channel-adapter/> for each service, and then start it, based on the arguments in the XML.
<int:inbound-channel-adapter id="service1" channel="foo"
auto-startup="false"
ref="service1Bean" method="execute">
<poller fixed-delay="1000" />
</int:inbound-channel-adapter/>
Note auto-startup="false".
Now, in the code that receives the split
#Autowired
SourcePollingChannelAdapter service1;
...
public void startService1(Node node) {
...
service1.setTrigger(new PeridicTrigger(...));
service1.start();
...
}
I dont know if this is the right way to implement the flow, but i've write the follow code:
applicationContext.xml
<context:component-scan base-package="it.mypkg" />
<!-- Expression to extract interval from XML tag -->
<si-xml:xpath-expression id="selectIntervalXpath" expression="//*/#interval" />
<si:channel id="rootChannel" />
<!-- Split each tag to redirect on router -->
<si-xml:xpath-splitter id="mySplitter" input-channel="rootChannel" output-channel="routerChannel" create-documents="true">
<si-xml:xpath-expression expression="//service|//activity" />
</si-xml:xpath-splitter>
<!-- Route each tag to the appropiate channel -->
<si-xml:xpath-router id="router" input-channel="routerChannel" evaluate-as-string="true">
<si-xml:xpath-expression expression="concat(name(./node()), 'Channel')" />
</si-xml:xpath-router>
<!-- Activator for Service Tag -->
<si:service-activator input-channel="serviceChannel" method="schedule">
<bean class="it.mypkg.Service" />
</si:service-activator>
<!-- Activator for Activity Tag -->
<si:service-activator input-channel="activityChannel" method="schedule">
<bean class="it.mypkg.Activity" />
</si:service-activator>
<!-- Task scheduler -->
<task:scheduler id="taskScheduler" pool-size="10"/>
Each tag will extend an Operation class (to avoid code duplication on bean injection)
Operation.java
public abstract class Operation {
protected TaskScheduler taskScheduler;
protected XPathExpression selectIntervalXpath;
abstract public void schedule(Node document);
#Autowired
public void setTaskScheduler(TaskScheduler taskScheduler) {
this.taskScheduler= taskScheduler;
}
public TaskScheduler getTaskScheduler() {
return this.taskScheduler;
}
#Autowired
public void setSelectIntervalXpath(XPathExpression selectIntervalXpath) {
this.selectIntervalXpath = selectIntervalXpath;
}
public XPathExpression getSelectIntervalXPath() {
return this.selectIntervalXpath;
}
}
And an example of Service class (that handle all tags service provided on .xml)
public class Service extends Operation {
private static final Logger log = Logger.getLogger(Service.class);
#Override
public void schedule(Node document) {
log.debug("Scheduling Service");
long interval = Long.parseLong(this.selectIntervalXpath.evaluateAsString(document));
this.taskScheduler.scheduleAtFixedRate(new ServiceRunner(), interval);
}
private class ServiceRunner implements Runnable {
public void run() {
log.debug("Running...");
}
}
}
Now to continue my flow i will need to find a way to redirect the output of each job to Spring Integration (applicationContext.xml).

How to create a declarative HTML helper in MVC 5

my scenario:
I am finally getting around to creating my own blog, and I am trying to learn as much as possible with regards to MVC while doing so. I am trying to display my tags as a custom declarative helper in my "PostView.cshtml" file but my problem is that it isn't in the current context and I don't know how to make it so.
I have had a look at the following 2 questions on SO:
this one is for previous version of MVC (<= 4) and
this one was answered by the guy who asked the question and isn't very explanatory.
I tried the above advice but with no success, hopefully someone can help me out. Here is my code:
Tags.cshtml (in ~/Views/Helpers/):
#helper Tags(System.Web.Mvc.HtmlHelper htmlHelper,
ICollection<MyNamespace.Objects.Tag> tags)
{
foreach (var tag in tags)
{
<div class="tags-div">
#MyNamespace.Extensions.ActionLinkExtensions.TagLink(htmlHelper, tag):
</div>
}
}
ActionLinkExtensions.cs (in ~/Extensions/ActionLinkExtensions/)
namespace MyNamespace.Extensions
{
public static class ActionLinkExtensions
{
public static MvcHtmlString TagLink(this HtmlHelper helper, Tag tag)
{
return helper.ActionLink("", ""); //logic removed for simplicity
}
}
}
PostView.cshtml (in ~/Views/Shared/) //where i want to use my custom helper:
#model MyNamespace.Objects.Post
<!--extra html removed for simplicity-->
<div>
<span>Tags:</span>#Tags(Html, Model.Tags) // '#Tags' doesn't exist in current context
</div>
I also tried adding namespaces to '~/Views/web.config':
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="MyNamespace" />
<add namespace="MyNamespace.Extensions" />
</namespaces>
</pages>
My "full name" for my "Tag.cs" class is MyNamespace.Objects.Tag and "Post".cs" is MyNamespace.Objects.Post.
Any explanations and advice with an answer would be greatly appreciated too, thank you very much in advance.
I decided to try use MVC3 way, I added the App_Code folder manually and followed steps from this great article.
And it worked, I needed to restart Visual Studio for my Intellisense to work (which prolonged finding my solution).
I deleted the folder '~/Views/Shared/'
I Added a file MyHelpers.cshtml into the App_Code folder, inside the file I added my helper method:
#helper Tags(System.Web.Mvc.HtmlHelper htmlHelper,
ICollection<MyNamespace.Objects.Tag> tags)
{
foreach (var tag in tags)
{
<div class="tags-div">
#MyNamespace.Extensions.ActionLinkExtensions.TagLink(htmlHelper, tag)
</div>
}
}
And called it in my PostView.cshtml like so:
#MyHelpers.Tags(Html, Model.Tags)
And Viola works as expected... hopefully this helps someone else who ends up in this situation...
I believe the better and simpler way would be to define a display template for you Tags collection that would be placed in ~Views/Shared/DisplayTemplates:
#model ICollection<MyNamespace.Objects.Tag>
foreach (var tag in Model)
{
<div class="tags-div">
#MyNamespace.Extensions.ActionLinkExtensions.TagLink(htmlHelper, tag)
</div>
}
In your PostView.cshtml you would then just write:
#Html.DisplayFor(model => model.Tags)

WCF MaxItemsInObjectGraph setting not working

I have been getting the following error trying to access my WCF service.
'Maximum number of items that can be serialized or deserialized in an object graph is '65536'. Change the object graph or increase the MaxItemsInObjectGraph quota
Doing some research, it looks like all I need to do is update this setting to be a higher value. This is what I am trying to do, but the setting does not seem to be getting read from the configuration. I keep getting the same exception with the 65536 value in it.
I followed the instructions found at this Link, but am having no luck.
Here is what I have configured on the WCF Service's Web.Config.
<behaviors>
<serviceBehaviors>
<behavior name="metadataBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl="" />
<serviceDebug includeExceptionDetailInFaults="false" />
<dataContractSerializer maxItemsInObjectGraph="2147483646"/>
</behavior>
</serviceBehaviors>
</behaviors>
This is what is in the Client's app.config:
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior >
<dataContractSerializer maxItemsInObjectGraph="2147483646"/>
</behavior>
</endpointBehaviors>
</behaviors>
And lastly, I have the following attribute on the WCF service itself:
[ServiceBehavior(MaxItemsInObjectGraph = 2147483646, IncludeExceptionDetailInFaults = true)]
Despite the configurations above, I still get an Exception complaining about the 65536 value. Why aren't any of these settings being used by the applications? Is there something else that needs to be set somewhere?
You were on the right track!
All you had to do was add a name to the behavior
<behavior name="MyBehavior">
<dataContractSerializer maxItemsInObjectGraph="2147483646"/>
</behavior>
And then on the end point add
<endpoint .... behaviorConfiguration="MyBehavior"/>
Had to go nuclear and update that machine.config;
Directions Here
The gist of it is to add the following to the "system.serviceModel" section.
<commonBehaviors>
<endpointBehaviors>
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</endpointBehaviors>
<serviceBehaviors>
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</serviceBehaviors>
</commonBehaviors>
I wrote a program to modify the machine configs for this, because support. It works for me, but I haven't done tons of testing.
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace FixMachineConfigBehavior
{
class Program
{
public static XElement IfNotExistsAdd(XDocument xd, XElement rootElement, string childName, XElement newChild)
{
if (rootElement.Elements(childName).Count() == 0)
{
Console.WriteLine(" adding " + childName + " node...");
rootElement.Add(newChild);
}
return rootElement.Element(childName);
}
static void Main(string[] args)
{
foreach (var file in Directory.EnumerateFiles(Environment.GetEnvironmentVariable("windir") + #"\Microsoft.NET\","machine.config",SearchOption.AllDirectories))
{
Console.WriteLine("fixing: " + file);
TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1);
double ms = t.TotalMilliseconds;
File.Copy(file, file + "." + ms + ".bak", true);
var xd = XDocument.Load(file);
XElement i = xd.Root;
i = IfNotExistsAdd(xd, i, "system.serviceModel", new XElement("system.serviceModel"));
i = IfNotExistsAdd(xd, i, "commonBehaviors", new XElement("commonBehaviors"));
i = IfNotExistsAdd(xd, i, "endpointBehaviors", new XElement("endpointBehaviors"));
i = IfNotExistsAdd(xd, i, "dataContractSerializer", new XElement("dataContractSerializer", new XAttribute("maxItemsInObjectGraph", Int32.MaxValue)));
xd.Save(file);
}
Console.ReadLine();
}
}
}
I had the same problem and tried several options but I found the solution here:
https://msdn.microsoft.com/en-us/library/ms732038.aspx
In "Controlling the serialization process".
Adding ...
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
class My Service ...
good luck
I had the same issue , There was some enums in returning class. What found out they cannot be null. Check whether you have any Enums that are to be returned.

log4net MemoryAppender not working

I'm using log4net to log in my app. My FileAppender is working fine, but I'm having problems with MemoryAppender.
Here is my config file.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="LogFileAppender" type="log4net.Appender.FileAppender">
<param name="File" value="Envision.log" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="" />
<param name="Footer" value="" />
<param name="ConversionPattern" value="%d [%t] %-5p %m%n" />
</layout>
</appender>
<appender name="MemoryAppender" type="log4net.Appender.MemoryAppender">
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="MemoryAppender" />
</root>
</log4net>
</configuration>
I use this code to setup the config file.
FileInfo file = new FileInfo(configPath);
log4net.Config.XmlConfigurator.Configure(file);
file = null;
Like I said, the FileAppender works great. But I can't seem to get any events.
I've tried using something like this to get the MemoryAppender.
Hierarchy hierarchy = LogManager.GetRepository() as Hierarchy;
MemoryAppender mappender = hierarchy.Root.GetAppender("MemoryAppender") as MemoryAppender;
I've tried using:
var events = mappender.GetEvents()
after logging something, and events is always empty. I've tried setting up the FileAppender and MemoryAppender in code instead of using the config file, and I get the same, the FileAppender works fine, but can't seem to get any events from MemoryAppender. Curious if I'm understanding MemoryAppender right? I also tried setting up a thread that loops checking for the GetEvents to not be empty, and while logging away it always comes back empty. I've tried setting the Threshold to Core.Level.All on the MemoryAppender but that did not change anything.
Thanks for any direction. I've looked around, and from the sites I've seen, I can't tell what I'm doing different.
Even something as simple as this does not work. events length is always zero;
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
MemoryAppender appender = new MemoryAppender();
ILog logger = LogManager.GetLogger("foo");
BasicConfigurator.Configure(appender);
logger.Error("Should work");
var events = appender.GetEvents();
}
}
For those that need it, here's how to do it programmatically in C#:
var memoryAppender = new MemoryAppender();
var repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
repository.Root.AddAppender(memoryAppender);
var events = memoryAppender.GetEvents();
I used Ralph's code above in my unit testing:
using log4net;
using log4net.Appender;
// ...
internal static MemoryAppender GetMemoLog<T>() where T: class
{
var memoLog = new MemoryAppender();
ILog appendableLog = LogManager.GetLogger(typeof(T).Assembly, typeof(T));
var repository = (log4net.Repository.Hierarchy.Hierarchy)appendableLog.Logger.Repository;
repository.Root.AddAppender(memoLog);
var logField = typeof(T).GetField("Log", BindingFlags.Static | BindingFlags.NonPublic);
if (logField != null) logField.SetValue(null, appendableLog);
return memoLog;
}
This assumes you have a private static Log field on your class:
private static readonly ILog Log = LogManager.GetLogger(typeof(MyClass));
So, in the test, it's just:
var memoLog = GetMemoLog<MyClass>();
// followed by test logic, and then...
var events = memoLog.GetEvents();
The simple sample code you posted works fine for me using log4net 1.2.10.0.
I would recommend downloading the source and stepping through it in a debugger. It may seem a little daunting at first, but you get used to their code pretty quickly and it's not hard to follow. I've done this many times when I had problems with custom constraints and appenders. It really helps solve problems quickly and gives you a much better understanding of how log4net works.
I figured it out. I was using the Compact Framework .dll by mistake. Once I realized that I switched to the .net 2.0 version, which caused a problem with log4net namespace not being found, so I did a search on that and realized I needed to change my .net Framework 4 client Profile to .net Framework 4. I'm now getting the events as expected.
I adapted CZahrobsky's answer. Had to tweak slightly, since my class cannot have static logger by design.
Class under test has the log field declared like:
private ILog Logger = Log4netFactory.GetLogger(typeof(MyClass));
In the GetMemLog logic I have to first create an instance of MyClass and change the logField look up to getField by name 'Logger' and BindingFlags.Instance instead of BindingFlags.Static
//create an instance of the class
var myObject = new MyClass(context);
var memoryLog = new MemoryAppender();
ILog appendableLog = LogManager.GetLogger(typeof(JobQueue).Assembly, typeof(MyClass));
var repository = (log4net.Repository.Hierarchy.Hierarchy)appendableLog.Logger.Repository;
repository.Root.AddAppender(memoryLog);
var logField = typeof(MyClass).GetField("Logger", BindingFlags.NonPublic | BindingFlags.Instance);
if (logField != null)
{
//set logfield property value for the instance
logField.SetValue(myObject, appendableLog);
}
Examples on SetValue() for PropertyInfo is here

Resources