nicko 2004/11/19 13:50:00
Added: src/Appender TelnetAppender.cs Log: Added TelnetAppender ported from log4j by Keith Long Revision Changes Path 1.1 logging-log4net/src/Appender/TelnetAppender.cs Index: TelnetAppender.cs =================================================================== #region Copyright & License // // Copyright 2004 The Apache Software Foundation // // Licensed 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.Collections; using System.Globalization; using System.Net; using System.Net.Sockets; using System.Text; using System.IO; using System.Threading; using log4net.Layout; using log4net.Core; using log4net.Util; namespace log4net.Appender { /// <summary> /// Appender that allows clients to connect via Telnet to receive log messages /// </summary> /// <remarks> /// <para> /// The TelnetAppender accepts socket connections and streams logging messages /// back to the client. /// The output is provided in a telnet-friendly way so that a log can be monitored /// over a TCP/IP socket. /// This allows simple remote monitoring of application logging. /// </para> /// <para> /// The default <see cref="Port"/> is 23 (the telnet port). /// </para> /// </remarks> /// <author>Keith Long</author> /// <author>Nicko Cadell</author> public class TelnetAppender : AppenderSkeleton { private SocketHandler m_handler; private int m_listeningPort = 23; #region Constructor /// <summary> /// Default constructor /// </summary> public TelnetAppender() { } #endregion /// <summary> /// Gets or sets the TCP port number on which this <see cref="TelnetAppender"/> will listen for connections. /// </summary> /// <value> /// An integer value in the range <see cref="IPEndPoint.MinPort" /> to <see cref="IPEndPoint.MaxPort" /> /// indicating the TCP port number on which this <see cref="TelnetAppender"/> will listen for connections. /// </value> /// <remarks> /// <para> /// The default value is 23 (the telnet 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 Port { get { return m_listeningPort; } set { if (value < IPEndPoint.MinPort || value > IPEndPoint.MaxPort) { throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("value", (object)value, "The value specified for Port is less than " + IPEndPoint.MinPort.ToString(NumberFormatInfo.InvariantInfo) + " or greater than " + IPEndPoint.MaxPort.ToString(NumberFormatInfo.InvariantInfo) + "."); } else { m_listeningPort = value; } } } #region Override implementation of AppenderSkeleton /// <summary> /// Overrides the parent method to close the socket handler /// </summary> protected override void OnClose() { base.OnClose(); if (m_handler != null) { m_handler.Dispose(); m_handler = null; } } /// <summary> /// This appender requires a <see cref="Layout"/> to be set. /// </summary> /// <value><c>true</c></value> protected override bool RequiresLayout { get { return true; } } /// <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> /// Create the socket handler and wait for connections /// </para> /// </remarks> public override void ActivateOptions() { base.ActivateOptions(); try { LogLog.Debug("TelnetAppender: Creating SocketHandler to listen on port ["+m_listeningPort+"]"); m_handler = new SocketHandler(m_listeningPort); } catch(Exception ex) { LogLog.Error("TelnetAppender: Failed to create SocketHandler", ex); throw; } } /// <summary> /// Writes the logging event to each connected client. /// </summary> /// <param name="loggingEvent">The event to log.</param> protected override void Append(LoggingEvent loggingEvent) { if (m_handler != null) { m_handler.Send(RenderLoggingEvent(loggingEvent)); } } #endregion #region SocketHandler helper class /// <summary> /// Helper class to manage connected clients /// </summary> /// <remarks> /// <para> /// The SocketHandler class is used to accept connections from /// clients. It is threaded so that clients can connect/disconnect /// asynchronously. /// </para> /// </remarks> protected class SocketHandler : IDisposable { private const int MAX_CONNECTIONS = 20; private Socket m_serverSocket; private ArrayList m_clients = new ArrayList(); /// <summary> /// Class that represents a client connected to this handler /// </summary> protected class SocketClient : IDisposable { private Socket m_socket; private StreamWriter m_writer; /// <summary> /// Create this <see cref="SocketClient"/> for the specified <see cref="Socket"/> /// </summary> /// <param name="socket">the client's socket</param> public SocketClient(Socket socket) { m_socket = socket; try { m_writer = new StreamWriter(new NetworkStream(socket)); } catch { Dispose(); throw; } } /// <summary> /// Write a string to the client /// </summary> /// <param name="message">string to send</param> public void Send(String message) { m_writer.Write(message); m_writer.Flush(); } #region IDisposable Members /// <summary> /// Cleanup the clients connection /// </summary> public void Dispose() { try { if (m_writer != null) { m_writer.Close(); m_writer = null; } } catch { } if (m_socket != null) { try { m_socket.Shutdown(SocketShutdown.Both); } catch { } try { m_socket.Close(); } catch { } m_socket = null; } } #endregion } /// <summary> /// Opens a new server port on <paramref ref="port"/> /// </summary> /// <param name="port">the local port to listen on for connections</param> public SocketHandler(int port) { m_serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); m_serverSocket.Bind(new IPEndPoint(IPAddress.Any, port)); m_serverSocket.Listen(5); m_serverSocket.BeginAccept(new AsyncCallback(OnConnect), null); } /// <summary> /// Sends a string message to each of the connected clients /// </summary> /// <param name="message">the text to send</param> public void Send(String message) { ArrayList localClients = m_clients; foreach (SocketClient client in localClients) { try { client.Send(message); } catch (Exception) { // The client has closed the connection, remove it from our list client.Dispose(); RemoveClient(client); } } } /// <summary> /// Add a client to the internal clients list /// </summary> /// <param name="client">client to add</param> private void AddClient(SocketClient client) { lock(this) { ArrayList clientsCopy = (ArrayList)m_clients.Clone(); clientsCopy.Add(client); m_clients = clientsCopy; } } /// <summary> /// Remove a client from the internal clients list /// </summary> /// <param name="client">client to remove</param> private void RemoveClient(SocketClient client) { lock(this) { ArrayList clientsCopy = (ArrayList)m_clients.Clone(); clientsCopy.Remove(client); m_clients = clientsCopy; } } /// <summary> /// Callback used to accept a connection on the server socket /// </summary> /// <param name="asyncResult">The result of the asynchronous operation</param> /// <remarks> /// <para> /// On connection adds to the list of connections /// if there are two many open connections you will be disconnected /// </para> /// </remarks> private void OnConnect(IAsyncResult asyncResult) { try { // Block until a client connects Socket socket = m_serverSocket.EndAccept(asyncResult); LogLog.Debug("TelnetAppender: Accepting connection from ["+socket.RemoteEndPoint.ToString()+"]"); SocketClient client = new SocketClient(socket); int currentActiveConnectionsCount = m_clients.Count; if (currentActiveConnectionsCount < MAX_CONNECTIONS) { try { client.Send("TelnetAppender v1.0 (" + (currentActiveConnectionsCount + 1) + " active connections)\r\n\r\n"); AddClient(client); } catch { client.Dispose(); } } else { client.Send("Sorry - Too many connections.\r\n"); client.Dispose(); } } catch { } finally { if (m_serverSocket != null) { m_serverSocket.BeginAccept(new AsyncCallback(OnConnect), null); } } } #region IDisposable Members /// <summary> /// make sure we close all network connections when this handler is destroyed /// </summary> public void Dispose() { ArrayList localClients = m_clients; foreach (SocketClient client in localClients) { client.Dispose(); } m_clients.Clear(); Socket localSocket = m_serverSocket; m_serverSocket = null; try { localSocket.Shutdown(SocketShutdown.Both); } catch { } try { localSocket.Close(); } catch { } } #endregion } #endregion } }
