#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.Collections; using System.Globalization; using System.Net; using System.Net.Sockets; using System.Text; using System.IO; using System.Threading; #if NETSTANDARD1_3 using System.Threading.Tasks; #endif using log4net.Layout; using log4net.Core; using log4net.Util; namespace log4net.Appender { /// /// Appender that allows clients to connect via Telnet to receive log messages /// /// /// /// 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. /// /// /// The default is 23 (the telnet port). /// /// /// Keith Long /// Nicko Cadell public class TelnetAppender : AppenderSkeleton { private SocketHandler m_handler; private int m_listeningPort = 23; #region Constructor /// /// Default constructor /// /// /// /// Default constructor /// /// public TelnetAppender() { } #endregion #region Private Static Fields /// /// The fully qualified type of the TelnetAppender class. /// /// /// Used by the internal logger to record the Type of the /// log message. /// private readonly static Type declaringType = typeof(TelnetAppender); #endregion Private Static Fields /// /// Gets or sets the TCP port number on which this will listen for connections. /// /// /// An integer value in the range to /// indicating the TCP port number on which this will listen for connections. /// /// /// /// The default value is 23 (the telnet port). /// /// /// The value specified is less than /// or greater than . 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 /// /// Overrides the parent method to close the socket handler /// /// /// /// Closes all the outstanding connections. /// /// protected override void OnClose() { base.OnClose(); if (m_handler != null) { m_handler.Dispose(); m_handler = null; } } /// /// This appender requires a to be set. /// /// true /// /// /// This appender requires a to be set. /// /// protected override bool RequiresLayout { get { return true; } } /// /// Initialize the appender based on the options set. /// /// /// /// This is part of the delayed object /// activation scheme. The method must /// be called on this object after the configuration properties have /// been set. Until is called this /// object is in an undefined state and must not be used. /// /// /// If any of the configuration properties are modified then /// must be called again. /// /// /// Create the socket handler and wait for connections /// /// public override void ActivateOptions() { base.ActivateOptions(); try { LogLog.Debug(declaringType, "Creating SocketHandler to listen on port ["+m_listeningPort+"]"); m_handler = new SocketHandler(m_listeningPort); } catch(Exception ex) { LogLog.Error(declaringType, "Failed to create SocketHandler", ex); throw; } } /// /// Writes the logging event to each connected client. /// /// The event to log. /// /// /// Writes the logging event to each connected client. /// /// protected override void Append(LoggingEvent loggingEvent) { if (m_handler != null && m_handler.HasConnections) { m_handler.Send(RenderLoggingEvent(loggingEvent)); } } #endregion #region SocketHandler helper class /// /// Helper class to manage connected clients /// /// /// /// The SocketHandler class is used to accept connections from /// clients. It is threaded so that clients can connect/disconnect /// asynchronously. /// /// protected class SocketHandler : IDisposable { private const int MAX_CONNECTIONS = 20; private Socket m_serverSocket; private ArrayList m_clients = new ArrayList(); /// /// Class that represents a client connected to this handler /// /// /// /// Class that represents a client connected to this handler /// /// protected class SocketClient : IDisposable { private Socket m_socket; private StreamWriter m_writer; /// /// Create this for the specified /// /// the client's socket /// /// /// Opens a stream writer on the socket. /// /// public SocketClient(Socket socket) { m_socket = socket; try { m_writer = new StreamWriter(new NetworkStream(socket)); } catch { Dispose(); throw; } } /// /// Write a string to the client /// /// string to send /// /// /// Write a string to the client /// /// public void Send(String message) { m_writer.Write(message); m_writer.Flush(); } #region IDisposable Members /// /// Cleanup the clients connection /// /// /// /// Close the socket connection. /// /// 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 } /// /// Opens a new server port on /// /// the local port to listen on for connections /// /// /// Creates a socket handler on the specified local server port. /// /// 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); AcceptConnection(); } private void AcceptConnection() { #if NETSTANDARD1_3 m_serverSocket.AcceptAsync().ContinueWith(OnConnect, TaskScheduler.Default); #else m_serverSocket.BeginAccept(new AsyncCallback(OnConnect), null); #endif } /// /// Sends a string message to each of the connected clients /// /// the text to send /// /// /// Sends a string message to each of the connected clients /// /// 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); } } } /// /// Add a client to the internal clients list /// /// client to add private void AddClient(SocketClient client) { lock(this) { ArrayList clientsCopy = (ArrayList)m_clients.Clone(); clientsCopy.Add(client); m_clients = clientsCopy; } } /// /// Remove a client from the internal clients list /// /// client to remove private void RemoveClient(SocketClient client) { lock(this) { ArrayList clientsCopy = (ArrayList)m_clients.Clone(); clientsCopy.Remove(client); m_clients = clientsCopy; } } /// /// Test if this handler has active connections /// /// /// true if this handler has active connections /// /// /// /// This property will be true while this handler has /// active connections, that is at least one connection that /// the handler will attempt to send a message to. /// /// public bool HasConnections { get { ArrayList localClients = m_clients; return (localClients != null && localClients.Count > 0); } } #if NETSTANDARD1_3 private void OnConnect(Task acceptTask) #else /// /// Callback used to accept a connection on the server socket /// /// The result of the asynchronous operation /// /// /// On connection adds to the list of connections /// if there are two many open connections you will be disconnected /// /// private void OnConnect(IAsyncResult asyncResult) #endif { try { #if NETSTANDARD1_3 Socket socket = acceptTask.GetAwaiter().GetResult(); #else // Block until a client connects Socket socket = m_serverSocket.EndAccept(asyncResult); #endif LogLog.Debug(declaringType, "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) { AcceptConnection(); } } } #region IDisposable Members /// /// Close all network connections /// /// /// /// Make sure we close all network connections /// /// 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 } }