blob: 2e0dc9578c06a16807155c509a1073ec70421612 [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
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using log4net.Appender.Internal;
using log4net.Core;
using log4net.Util;
namespace log4net.Appender;
/// <summary>
/// Sends logging events as connectionless UDP datagrams to a remote host or a
/// multicast group using an <see cref="UdpClient" />.
/// </summary>
/// <remarks>
/// <para>
/// UDP guarantees neither that messages arrive, nor that they arrive in the correct order.
/// </para>
/// <para>
/// To view the logging results, a custom application can be developed that listens for logging
/// events.
/// </para>
/// <para>
/// When decoding events send via this appender remember to use the same encoding
/// to decode the events as was used to send the events. See the <see cref="Encoding"/>
/// property to specify the encoding to use.
/// </para>
/// </remarks>
/// <example>
/// This example shows how to log receive logging events that are sent
/// on IP address 244.0.0.1 and port 8080 to the console. The event is
/// encoded in the packet as a unicode string and it is decoded as such.
/// <code lang="C#">
/// IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
/// UdpClient udpClient;
/// byte[] buffer;
/// string loggingEvent;
///
/// try
/// {
/// udpClient = new UdpClient(8080);
///
/// while(true)
/// {
/// buffer = udpClient.Receive(ref remoteEndPoint);
/// loggingEvent = System.Text.Encoding.Unicode.GetString(buffer);
/// Console.WriteLine(loggingEvent);
/// }
/// }
/// catch(Exception e)
/// {
/// Console.WriteLine(e.ToString());
/// }
/// </code>
/// <code lang="Visual Basic">
/// Dim remoteEndPoint as IPEndPoint
/// Dim udpClient as UdpClient
/// Dim buffer as Byte()
/// Dim loggingEvent as String
///
/// Try
/// remoteEndPoint = new IPEndPoint(IPAddress.Any, 0)
/// udpClient = new UdpClient(8080)
///
/// While True
/// buffer = udpClient.Receive(ByRef remoteEndPoint)
/// loggingEvent = System.Text.Encoding.Unicode.GetString(buffer)
/// Console.WriteLine(loggingEvent)
/// Wend
/// Catch e As Exception
/// Console.WriteLine(e.ToString())
/// End Try
/// </code>
/// <para>
/// An example configuration section to log information using this appender to the
/// IP 224.0.0.1 on port 8080:
/// </para>
/// <code lang="XML" escaped="true">
/// <appender name="UdpAppender" type="log4net.Appender.UdpAppender">
/// <remoteAddress value="224.0.0.1" />
/// <remotePort value="8080" />
/// <layout type="log4net.Layout.PatternLayout" value="%-5level %logger [%ndc] - %message%newline" />
/// </appender>
/// </code>
/// </example>
/// <author>Gert Driesen</author>
/// <author>Nicko Cadell</author>
public class UdpAppender : AppenderSkeleton
{
/// <summary>
/// Initializes a new instance of the <see cref="UdpAppender" /> class.
/// </summary>
/// <remarks>
/// The default constructor initializes all fields to their default values.
/// </remarks>
public UdpAppender()
{
}
/// <summary>
/// Gets or sets the IP address of the remote host or multicast group to which
/// the underlying <see cref="UdpClient" /> should sent the logging event.
/// </summary>
/// <value>
/// The IP address of the remote host or multicast group to which the logging event
/// will be sent.
/// </value>
/// <remarks>
/// <para>
/// Multicast addresses are identified by IP class <b>D</b> addresses (in the range 224.0.0.0 to
/// 239.255.255.255). Multicast packets can pass across different networks through routers, so
/// it is possible to use multicasts in an Internet scenario as long as your network provider
/// supports multicasting.
/// </para>
/// <para>
/// Hosts that want to receive particular multicast messages must register their interest by joining
/// the multicast group. Multicast messages are not sent to networks where no host has joined
/// the multicast group. Class <b>D</b> IP addresses are used for multicast groups, to differentiate
/// them from normal host addresses, allowing nodes to easily detect if a message is of interest.
/// </para>
/// <para>
/// Static multicast addresses that are needed globally are assigned by IANA. A few examples are listed in the table below:
/// </para>
/// <para>
/// <list type="table">
/// <listheader>
/// <term>IP Address</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>224.0.0.1</term>
/// <description>
/// <para>
/// Sends a message to all system on the subnet.
/// </para>
/// </description>
/// </item>
/// <item>
/// <term>224.0.0.2</term>
/// <description>
/// <para>
/// Sends a message to all routers on the subnet.
/// </para>
/// </description>
/// </item>
/// <item>
/// <term>224.0.0.12</term>
/// <description>
/// <para>
/// The DHCP server answers messages on the IP address 224.0.0.12, but only on a subnet.
/// </para>
/// </description>
/// </item>
/// </list>
/// </para>
/// <para>
/// A complete list of actually reserved multicast addresses and their owners in the ranges
/// defined by RFC 3171 can be found at the <A href="http://www.iana.org/assignments/multicast-addresses">IANA web site</A>.
/// </para>
/// <para>
/// The address range 239.0.0.0 to 239.255.255.255 is reserved for administrative scope-relative
/// addresses. These addresses can be reused with other local groups. Routers are typically
/// configured with filters to prevent multicast traffic in this range from flowing outside
/// of the local network.
/// </para>
/// </remarks>
public IPAddress? RemoteAddress { get; set; }
/// <summary>
/// Gets or sets the TCP port number of the remote host or multicast group to which
/// the underlying <see cref="UdpClient" /> should sent the logging event.
/// </summary>
/// <value>
/// An integer value in the range <see cref="IPEndPoint.MinPort" /> to <see cref="IPEndPoint.MaxPort" />
/// indicating the TCP port number of the remote host or multicast group to which the logging event
/// will be sent.
/// </value>
/// <remarks>
/// The underlying <see cref="UdpClient" /> will send messages to this TCP port number
/// on the remote host or multicast group.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">The value specified is less than <see cref="IPEndPoint.MinPort" /> or greater than <see cref="IPEndPoint.MaxPort" />.</exception>
public int RemotePort
{
get => _remotePort;
set
{
if (value is < IPEndPoint.MinPort or > IPEndPoint.MaxPort)
{
throw SystemInfo.CreateArgumentOutOfRangeException(nameof(value), value,
$"The value specified is less than {IPEndPoint.MinPort} or greater than {IPEndPoint.MaxPort}.");
}
_remotePort = value;
}
}
/// <summary>
/// Gets or sets the TCP port number from which the underlying <see cref="UdpClient" /> will communicate.
/// </summary>
/// <value>
/// An integer value in the range <see cref="IPEndPoint.MinPort" /> to <see cref="IPEndPoint.MaxPort" />
/// indicating the TCP port number from which the underlying <see cref="UdpClient" /> will communicate.
/// </value>
/// <remarks>
/// <para>
/// The underlying <see cref="UdpClient" /> will bind to this port for sending messages.
/// </para>
/// <para>
/// Setting the value to 0 (the default) will cause the udp client not to bind to
/// a local port.
/// </para>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">The value specified is less than <see cref="IPEndPoint.MinPort" /> or greater than <see cref="IPEndPoint.MaxPort" />.</exception>
public int LocalPort
{
get => _localPort;
set
{
if (value is not 0 and (< IPEndPoint.MinPort or > IPEndPoint.MaxPort))
{
throw SystemInfo.CreateArgumentOutOfRangeException(nameof(value), value,
$"The value specified is less than {IPEndPoint.MinPort} or greater than {IPEndPoint.MaxPort}.");
}
_localPort = value;
}
}
/// <summary>
/// Gets or sets <see cref="Encoding"/> used to write the packets.
/// </summary>
/// <value>
/// The <see cref="Encoding"/> used to write the packets.
/// </value>
/// <remarks>
/// <para>
/// The <see cref="Encoding"/> used to write the packets.
/// </para>
/// </remarks>
public Encoding Encoding { get; set; } = Encoding.Default;
/// <summary>
/// Gets or sets the underlying <see cref="UdpClient" />.
/// </summary>
/// <value>
/// The underlying <see cref="UdpClient" />.
/// </value>
/// <remarks>
/// <see cref="UdpAppender" /> creates a <see cref="UdpClient" /> to send logging events
/// over a network. Classes deriving from <see cref="UdpAppender" /> can use this
/// property to get or set this <see cref="UdpClient" />. Use the underlying <see cref="UdpClient" />
/// returned from <see cref="Client" /> if you require access beyond that which
/// <see cref="UdpAppender" /> provides.
/// </remarks>
protected UdpClient? Client { get; set; }
/// <summary>
/// Gets or sets the cached remote endpoint to which the logging events should be sent.
/// </summary>
/// <remarks>
/// The <see cref="ActivateOptions" /> method will initialize the remote endpoint
/// with the values of the <see cref="RemoteAddress" /> and <see cref="RemotePort"/>
/// properties.
/// </remarks>
protected IPEndPoint? RemoteEndPoint { get; set; }
/// <summary>
/// Initialize the appender based on the options set.
/// </summary>
/// <remarks>
/// <para>
/// This is part of the <see cref="IOptionHandler"/> delayed object
/// activation scheme. The <see cref="ActivateOptions"/> method must
/// be called on this object after the configuration properties have
/// been set. Until <see cref="ActivateOptions"/> is called this
/// object is in an undefined state and must not be used.
/// </para>
/// <para>
/// If any of the configuration properties are modified then
/// <see cref="ActivateOptions"/> must be called again.
/// </para>
/// <para>
/// The appender will be ignored if no <see cref="RemoteAddress" /> was specified or
/// an invalid remote or local TCP port number was specified.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">The required property <see cref="RemoteAddress" /> was not specified.</exception>
/// <exception cref="ArgumentOutOfRangeException">The TCP port number assigned to <see cref="LocalPort" /> or <see cref="RemotePort" /> is less than <see cref="IPEndPoint.MinPort" /> or greater than <see cref="IPEndPoint.MaxPort" />.</exception>
public override void ActivateOptions()
{
base.ActivateOptions();
RemoteAddress.EnsureNotNull();
if (RemotePort is < IPEndPoint.MinPort or > IPEndPoint.MaxPort)
{
throw SystemInfo.CreateArgumentOutOfRangeException(nameof(RemotePort), RemotePort,
$"The RemotePort is less than {IPEndPoint.MinPort} or greater than {IPEndPoint.MaxPort}.");
}
if (LocalPort is not 0 and (< IPEndPoint.MinPort or > IPEndPoint.MaxPort))
{
throw SystemInfo.CreateArgumentOutOfRangeException(nameof(LocalPort), LocalPort,
$"The LocalPort is less than {IPEndPoint.MinPort} or greater than {IPEndPoint.MaxPort}.");
}
RemoteEndPoint = new(RemoteAddress, RemotePort);
InitializeClientConnection();
}
/// <summary>
/// This method is called by the <see cref="AppenderSkeleton.DoAppend(LoggingEvent)"/> method.
/// </summary>
/// <param name="loggingEvent">The event to log.</param>
/// <remarks>
/// <para>
/// Sends the event using an UDP datagram.
/// </para>
/// <para>
/// Exceptions are passed to the <see cref="AppenderSkeleton.ErrorHandler"/>.
/// </para>
/// </remarks>
protected override void Append(LoggingEvent loggingEvent)
{
if (Client is null)
{
ErrorHandler.Error(
$"Unable to send logging event to remote host {RemoteAddress} on port {RemotePort}, no client created",
e: null,
ErrorCode.WriteFailure);
return;
}
try
{
byte[] buffer = Encoding.GetBytes(RenderLoggingEvent(loggingEvent).ToCharArray());
Client.Send(buffer, buffer.Length, RemoteEndPoint);
}
catch (Exception e) when (!e.IsFatal())
{
ErrorHandler.Error(
$"Unable to send logging event to remote host {RemoteAddress} on port {RemotePort}.",
e, ErrorCode.WriteFailure);
}
}
/// <summary>
/// This appender requires a <see cref="Layout"/> to be set.
/// </summary>
protected override bool RequiresLayout => true;
/// <summary>
/// Closes the UDP connection and releases all resources associated with
/// this <see cref="UdpAppender" /> instance.
/// </summary>
/// <remarks>
/// <para>
/// Disables the underlying <see cref="UdpClient" /> and releases all managed
/// and unmanaged resources associated with the <see cref="UdpAppender" />.
/// </para>
/// </remarks>
protected override void OnClose()
{
base.OnClose();
Client?.Close();
}
/// <summary>
/// Initializes the underlying <see cref="UdpClient" /> connection.
/// </summary>
/// <remarks>
/// <para>
/// The underlying <see cref="UdpClient"/> is initialized and binds to the
/// port number from which you intend to communicate.
/// </para>
/// <para>
/// Exceptions are passed to the <see cref="AppenderSkeleton.ErrorHandler"/>.
/// </para>
/// </remarks>
protected virtual void InitializeClientConnection()
{
try
{
Client = UdpConnection.CreateClient(LocalPort, RemoteAddress.EnsureNotNull());
}
catch (Exception e) when (!e.IsFatal())
{
ErrorHandler.Error(
$"Could not initialize the UdpClient connection on port {LocalPort}.",
e, ErrorCode.GenericFailure);
Client = null;
}
}
/// <summary>
/// The TCP port number of the remote host or multicast group to
/// which the logging event will be sent.
/// </summary>
private int _remotePort;
/// <summary>
/// The TCP port number from which the <see cref="UdpClient" /> will communicate.
/// </summary>
private int _localPort;
}