blob: 511b22110449ffd83f9074a1865eb98f337d2504 [file] [log] [blame]
#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
// netstandard has no support for System.Runtime.Remoting
#if NET462_OR_GREATER
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Threading;
using log4net.Appender;
using log4net.Core;
using log4net.Repository.Hierarchy;
using log4net.Tests.Appender.Remoting.Data;
using log4net.Tests.Appender.Remoting.UserInterfaces;
using NUnit.Framework;
namespace log4net.Tests.Appender
{
/// <summary>
/// Used for internal unit testing the <see cref="RemotingAppender"/> class.
/// </summary>
/// <remarks>
/// Used for internal unit testing the <see cref="RemotingAppender"/> class.
/// </remarks>
[TestFixture]
public class RemotingAppenderTest
{
private IChannel? m_remotingChannel;
/// <summary>
/// Test that the Message property is correctly remoted
/// </summary>
[Test]
public void TestRemotedMessage()
{
// Set up the remoting appender
ConfigureRootAppender(FixFlags.Partial);
RemoteLoggingSinkImpl.Instance.Reset();
Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
string testMessage = $"test message [ {(new Random()).Next()} ]";
// Log a message that will be remoted
root.Log(Level.Debug, testMessage, null);
// Wait for the remoted object to be delivered
WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
Assert.AreEqual(testMessage, events[0].RenderedMessage, "Expect Message match after remoting event");
}
/// <summary>
/// Test that the LocationInfo property is not remoted when doing a Fix.Partial
/// </summary>
[Test]
public void TestPartialFix()
{
// Set up the remoting appender
ConfigureRootAppender(FixFlags.Partial);
RemoteLoggingSinkImpl.Instance.Reset();
Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
// Log a message that will be remoted
root.Log(Level.Debug, "test message", null);
// Wait for the remoted object to be delivered
WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
Assert.IsNull(events[0].LocationInfo, "Expect LocationInfo to be null because only doing a partial fix");
}
/// <summary>
/// Test that the LocationInfo property is remoted when doing a Fix.All
/// </summary>
[Test]
public void TestFullFix()
{
// Set up the remoting appender
ConfigureRootAppender(FixFlags.All);
RemoteLoggingSinkImpl.Instance.Reset();
Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
// Log a message that will be remoted
root.Log(Level.Debug, "test message", null);
// Wait for the remoted object to be delivered
WaitFor("Remote instance should have received a remoting event", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
Assert.IsNotNull(events[0].LocationInfo, "Expect LocationInfo to not be null because doing a full fix");
}
private static void WaitFor(
string failMessage,
Func<bool> condition,
int maxWaitMilliseconds = 5000)
{
var start = DateTime.Now;
do
{
if (condition())
{
return;
}
Thread.Sleep(100);
} while ((DateTime.Now - start).TotalMilliseconds < maxWaitMilliseconds);
throw new TimeoutException($"Condition not achieved within {maxWaitMilliseconds}ms: {failMessage}");
}
/// <summary>
/// Test that the Message property is correctly remoted
/// </summary>
[Test]
public void TestRemotedMessageNdcPushPop()
{
// Set up the remoting appender
ConfigureRootAppender(FixFlags.Partial);
RemoteLoggingSinkImpl.Instance.Reset();
Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
string testMessage = $"test message [ {(new Random()).Next()} ]";
using (NDC.Push("value"))
{
}
// Log a message that will be remoted
root.Log(Level.Debug, testMessage, null);
// Wait for the remoted object to be delivered
WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
Assert.AreEqual(testMessage, events[0].RenderedMessage, "Expect Message match after remoting event");
}
[Test]
public void TestNestedNdc()
{
// Set up the remoting appender
ConfigureRootAppender(FixFlags.Partial);
RemoteLoggingSinkImpl.Instance.Reset();
var t = new TestService();
t.Test();
WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length == 5);
LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
// RemotingAppender dispatches events asynchronously, messages could be in any order.
LoggingEvent beingTest = events.First(e => e.RenderedMessage == "begin test");
Assert.IsNull(beingTest.Properties["NDC"], "Verify 'being test' event Properties");
LoggingEvent feature = events.First(e => e.RenderedMessage == "feature");
Assert.AreEqual("test1", feature.Properties["NDC"], "Verify 'feature' event Properties");
LoggingEvent return1 = events.First(e => e.RenderedMessage == "return" && Equals(e.Properties["NDC"], "test1 test2"));
LoggingEvent return2 = events.First(e => e.RenderedMessage == "return" && Equals(e.Properties["NDC"], "test1"));
LoggingEvent endTest = events.First(e => e.RenderedMessage == "end test");
Assert.IsNull(endTest.Properties["NDC"], "Verify 'end test' event Properties");
}
private void RegisterRemotingServerChannel()
{
if (m_remotingChannel is null)
{
BinaryClientFormatterSinkProvider clientSinkProvider = new BinaryClientFormatterSinkProvider();
BinaryServerFormatterSinkProvider serverSinkProvider = new BinaryServerFormatterSinkProvider();
serverSinkProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
var channelProperties = new Hashtable();
channelProperties["port"] = 8085;
m_remotingChannel = new TcpChannel(channelProperties, clientSinkProvider, serverSinkProvider);
// Setup remoting server
try
{
ChannelServices.RegisterChannel(m_remotingChannel, false);
}
catch (Exception ex)
{
Assert.Fail("Failed to set up LoggingSink: {0}", ex);
}
// Marshal the sink object
RemotingServices.Marshal(RemoteLoggingSinkImpl.Instance, "LoggingSink", typeof(RemotingAppender.IRemoteLoggingSink));
}
}
/// <summary>
/// Shuts down any loggers in the hierarchy, along
/// with all appenders.
/// </summary>
private static void ResetRepository()
{
// Regular users should not use the clear method lightly!
LogManager.GetRepository().ResetConfiguration();
LogManager.GetRepository().Shutdown();
((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Clear();
}
/// <summary>
/// Any initialization that happens before each test can
/// go here
/// </summary>
[SetUp]
public void SetUp()
{
ResetRepository();
RegisterRemotingServerChannel();
}
/// <summary>
/// Any steps that happen after each test go here
/// </summary>
[TearDown]
public void TearDown()
{
ResetRepository();
}
/// <summary>
/// Close down remoting infrastructure
/// </summary>
[OneTimeTearDown]
public void UnregisterRemotingServerChannel()
{
if (m_remotingChannel is not null)
{
((TcpChannel)m_remotingChannel).StopListening(null);
try
{
ChannelServices.UnregisterChannel(m_remotingChannel);
}
catch (Exception)
{
}
m_remotingChannel = null;
}
}
/// <summary>
/// Configures the root appender for counting and rolling
/// </summary>
private static void ConfigureRootAppender(FixFlags fixFlags)
{
Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
root.Level = Level.Debug;
root.AddAppender(CreateAppender(fixFlags));
root.Repository!.Configured = true;
}
private static RemotingAppender CreateAppender(FixFlags fixFlags)
{
var appender = new RemotingAppender
{
Sink = "tcp://localhost:8085/LoggingSink",
Lossy = false,
BufferSize = 1,
Fix = fixFlags
};
appender.ActivateOptions();
return appender;
}
public class RemoteLoggingSinkImpl : MarshalByRefObject, RemotingAppender.IRemoteLoggingSink
{
public static readonly RemoteLoggingSinkImpl Instance = new();
private readonly List<LoggingEvent> m_events = new();
private RemoteLoggingSinkImpl()
{
}
/// <summary>
/// Logs the events to an internal buffer.
/// </summary>
/// <param name="events">The events to log.</param>
/// <remarks>
/// The logged events can
/// be retrieved via the <see cref="Events"/> property. To clear
/// the buffer call the <see cref="Reset"/> method.
/// </remarks>
public void LogEvents(LoggingEvent[] events) => m_events.AddRange(events);
/// <summary>
/// Obtains a lifetime service object to control the lifetime
/// policy for this instance.
/// </summary>
/// <returns>
/// <c>null</c> to indicate that this instance should live
/// forever.
/// </returns>
public override object? InitializeLifetimeService()
{
return null;
}
public void Reset() => m_events.Clear();
public LoggingEvent[] Events => m_events.ToArray();
}
}
}
// helper for TestNestedNdc
namespace log4net.Tests.Appender.Remoting.UserInterfaces
{
public class TestService
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod()!.DeclaringType!);
public void Test()
{
log.Info("begin test");
Thread.Sleep(100);
var f = new Feature();
f.Test();
log.Info("end test");
Thread.Sleep(100);
}
}
}
// helper for TestNestedNdc
namespace log4net.Tests.Appender.Remoting
{
public class Feature
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod()!.DeclaringType!);
public void Test()
{
using (NDC.Push("test1"))
{
log.Info("feature");
Thread.Sleep(100);
var d = new Dal();
d.Test();
log.Info("return");
Thread.Sleep(100);
}
}
}
}
// helper for TestNestedNdc
namespace log4net.Tests.Appender.Remoting.Data
{
public class Dal
{
private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod()!.DeclaringType!);
public void Test()
{
using (NDC.Push("test2"))
{
log.Info("return");
Thread.Sleep(100);
}
}
}
}
#endif // NET462_OR_GREATER