RemoteSyslogAppender.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. #region Apache License
  2. //
  3. // Licensed to the Apache Software Foundation (ASF) under one or more
  4. // contributor license agreements. See the NOTICE file distributed with
  5. // this work for additional information regarding copyright ownership.
  6. // The ASF licenses this file to you under the Apache License, Version 2.0
  7. // (the "License"); you may not use this file except in compliance with
  8. // the License. You may obtain a copy of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS,
  14. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. // See the License for the specific language governing permissions and
  16. // limitations under the License.
  17. //
  18. #endregion
  19. using System;
  20. using log4net.Core;
  21. using log4net.Appender;
  22. using log4net.Util;
  23. using log4net.Layout;
  24. using System.Text;
  25. namespace log4net.Appender
  26. {
  27. /// <summary>
  28. /// Logs events to a remote syslog daemon.
  29. /// </summary>
  30. /// <remarks>
  31. /// <para>
  32. /// The BSD syslog protocol is used to remotely log to
  33. /// a syslog daemon. The syslogd listens for for messages
  34. /// on UDP port 514.
  35. /// </para>
  36. /// <para>
  37. /// The syslog UDP protocol is not authenticated. Most syslog daemons
  38. /// do not accept remote log messages because of the security implications.
  39. /// You may be able to use the LocalSyslogAppender to talk to a local
  40. /// syslog service.
  41. /// </para>
  42. /// <para>
  43. /// There is an RFC 3164 that claims to document the BSD Syslog Protocol.
  44. /// This RFC can be seen here: http://www.faqs.org/rfcs/rfc3164.html.
  45. /// This appender generates what the RFC calls an "Original Device Message",
  46. /// i.e. does not include the TIMESTAMP or HOSTNAME fields. By observation
  47. /// this format of message will be accepted by all current syslog daemon
  48. /// implementations. The daemon will attach the current time and the source
  49. /// hostname or IP address to any messages received.
  50. /// </para>
  51. /// <para>
  52. /// Syslog messages must have a facility and and a severity. The severity
  53. /// is derived from the Level of the logging event.
  54. /// The facility must be chosen from the set of defined syslog
  55. /// <see cref="SyslogFacility"/> values. The facilities list is predefined
  56. /// and cannot be extended.
  57. /// </para>
  58. /// <para>
  59. /// An identifier is specified with each log message. This can be specified
  60. /// by setting the <see cref="Identity"/> property. The identity (also know
  61. /// as the tag) must not contain white space. The default value for the
  62. /// identity is the application name (from <see cref="LoggingEvent.Domain"/>).
  63. /// </para>
  64. /// </remarks>
  65. /// <author>Rob Lyon</author>
  66. /// <author>Nicko Cadell</author>
  67. public class RemoteSyslogAppender : UdpAppender
  68. {
  69. /// <summary>
  70. /// Syslog port 514
  71. /// </summary>
  72. private const int DefaultSyslogPort = 514;
  73. #region Enumerations
  74. /// <summary>
  75. /// syslog severities
  76. /// </summary>
  77. /// <remarks>
  78. /// <para>
  79. /// The syslog severities.
  80. /// </para>
  81. /// </remarks>
  82. public enum SyslogSeverity
  83. {
  84. /// <summary>
  85. /// system is unusable
  86. /// </summary>
  87. Emergency = 0,
  88. /// <summary>
  89. /// action must be taken immediately
  90. /// </summary>
  91. Alert = 1,
  92. /// <summary>
  93. /// critical conditions
  94. /// </summary>
  95. Critical = 2,
  96. /// <summary>
  97. /// error conditions
  98. /// </summary>
  99. Error = 3,
  100. /// <summary>
  101. /// warning conditions
  102. /// </summary>
  103. Warning = 4,
  104. /// <summary>
  105. /// normal but significant condition
  106. /// </summary>
  107. Notice = 5,
  108. /// <summary>
  109. /// informational
  110. /// </summary>
  111. Informational = 6,
  112. /// <summary>
  113. /// debug-level messages
  114. /// </summary>
  115. Debug = 7
  116. };
  117. /// <summary>
  118. /// syslog facilities
  119. /// </summary>
  120. /// <remarks>
  121. /// <para>
  122. /// The syslog facilities
  123. /// </para>
  124. /// </remarks>
  125. public enum SyslogFacility
  126. {
  127. /// <summary>
  128. /// kernel messages
  129. /// </summary>
  130. Kernel = 0,
  131. /// <summary>
  132. /// random user-level messages
  133. /// </summary>
  134. User = 1,
  135. /// <summary>
  136. /// mail system
  137. /// </summary>
  138. Mail = 2,
  139. /// <summary>
  140. /// system daemons
  141. /// </summary>
  142. Daemons = 3,
  143. /// <summary>
  144. /// security/authorization messages
  145. /// </summary>
  146. Authorization = 4,
  147. /// <summary>
  148. /// messages generated internally by syslogd
  149. /// </summary>
  150. Syslog = 5,
  151. /// <summary>
  152. /// line printer subsystem
  153. /// </summary>
  154. Printer = 6,
  155. /// <summary>
  156. /// network news subsystem
  157. /// </summary>
  158. News = 7,
  159. /// <summary>
  160. /// UUCP subsystem
  161. /// </summary>
  162. Uucp = 8,
  163. /// <summary>
  164. /// clock (cron/at) daemon
  165. /// </summary>
  166. Clock = 9,
  167. /// <summary>
  168. /// security/authorization messages (private)
  169. /// </summary>
  170. Authorization2 = 10,
  171. /// <summary>
  172. /// ftp daemon
  173. /// </summary>
  174. Ftp = 11,
  175. /// <summary>
  176. /// NTP subsystem
  177. /// </summary>
  178. Ntp = 12,
  179. /// <summary>
  180. /// log audit
  181. /// </summary>
  182. Audit = 13,
  183. /// <summary>
  184. /// log alert
  185. /// </summary>
  186. Alert = 14,
  187. /// <summary>
  188. /// clock daemon
  189. /// </summary>
  190. Clock2 = 15,
  191. /// <summary>
  192. /// reserved for local use
  193. /// </summary>
  194. Local0 = 16,
  195. /// <summary>
  196. /// reserved for local use
  197. /// </summary>
  198. Local1 = 17,
  199. /// <summary>
  200. /// reserved for local use
  201. /// </summary>
  202. Local2 = 18,
  203. /// <summary>
  204. /// reserved for local use
  205. /// </summary>
  206. Local3 = 19,
  207. /// <summary>
  208. /// reserved for local use
  209. /// </summary>
  210. Local4 = 20,
  211. /// <summary>
  212. /// reserved for local use
  213. /// </summary>
  214. Local5 = 21,
  215. /// <summary>
  216. /// reserved for local use
  217. /// </summary>
  218. Local6 = 22,
  219. /// <summary>
  220. /// reserved for local use
  221. /// </summary>
  222. Local7 = 23
  223. }
  224. #endregion Enumerations
  225. #region Public Instance Constructors
  226. /// <summary>
  227. /// Initializes a new instance of the <see cref="RemoteSyslogAppender" /> class.
  228. /// </summary>
  229. /// <remarks>
  230. /// This instance of the <see cref="RemoteSyslogAppender" /> class is set up to write
  231. /// to a remote syslog daemon.
  232. /// </remarks>
  233. public RemoteSyslogAppender()
  234. {
  235. // syslog udp defaults
  236. this.RemotePort = DefaultSyslogPort;
  237. this.RemoteAddress = System.Net.IPAddress.Parse("127.0.0.1");
  238. this.Encoding = System.Text.Encoding.ASCII;
  239. }
  240. #endregion Public Instance Constructors
  241. #region Public Instance Properties
  242. /// <summary>
  243. /// Message identity
  244. /// </summary>
  245. /// <remarks>
  246. /// <para>
  247. /// An identifier is specified with each log message. This can be specified
  248. /// by setting the <see cref="Identity"/> property. The identity (also know
  249. /// as the tag) must not contain white space. The default value for the
  250. /// identity is the application name (from <see cref="LoggingEvent.Domain"/>).
  251. /// </para>
  252. /// </remarks>
  253. public PatternLayout Identity
  254. {
  255. get { return m_identity; }
  256. set { m_identity = value; }
  257. }
  258. /// <summary>
  259. /// Syslog facility
  260. /// </summary>
  261. /// <remarks>
  262. /// Set to one of the <see cref="SyslogFacility"/> values. The list of
  263. /// facilities is predefined and cannot be extended. The default value
  264. /// is <see cref="SyslogFacility.User"/>.
  265. /// </remarks>
  266. public SyslogFacility Facility
  267. {
  268. get { return m_facility; }
  269. set { m_facility = value; }
  270. }
  271. #endregion Public Instance Properties
  272. /// <summary>
  273. /// Add a mapping of level to severity
  274. /// </summary>
  275. /// <param name="mapping">The mapping to add</param>
  276. /// <remarks>
  277. /// <para>
  278. /// Add a <see cref="LevelSeverity"/> mapping to this appender.
  279. /// </para>
  280. /// </remarks>
  281. public void AddMapping(LevelSeverity mapping)
  282. {
  283. m_levelMapping.Add(mapping);
  284. }
  285. #region AppenderSkeleton Implementation
  286. /// <summary>
  287. /// This method is called by the <see cref="M:AppenderSkeleton.DoAppend(LoggingEvent)"/> method.
  288. /// </summary>
  289. /// <param name="loggingEvent">The event to log.</param>
  290. /// <remarks>
  291. /// <para>
  292. /// Writes the event to a remote syslog daemon.
  293. /// </para>
  294. /// <para>
  295. /// The format of the output will depend on the appender's layout.
  296. /// </para>
  297. /// </remarks>
  298. protected override void Append(LoggingEvent loggingEvent)
  299. {
  300. try
  301. {
  302. // Priority
  303. int priority = GeneratePriority(m_facility, GetSeverity(loggingEvent.Level));
  304. // Identity
  305. string identity;
  306. if (m_identity != null)
  307. {
  308. identity = m_identity.Format(loggingEvent);
  309. }
  310. else
  311. {
  312. identity = loggingEvent.Domain;
  313. }
  314. // Message. The message goes after the tag/identity
  315. string message = RenderLoggingEvent(loggingEvent);
  316. Byte[] buffer;
  317. int i = 0;
  318. char c;
  319. StringBuilder builder = new StringBuilder();
  320. while (i < message.Length)
  321. {
  322. // Clear StringBuilder
  323. builder.Length = 0;
  324. // Write priority
  325. builder.Append('<');
  326. builder.Append(priority);
  327. builder.Append('>');
  328. // Write identity
  329. builder.Append(identity);
  330. builder.Append(": ");
  331. for (; i < message.Length; i++)
  332. {
  333. c = message[i];
  334. // Accept only visible ASCII characters and space. See RFC 3164 section 4.1.3
  335. if (((int)c >= 32) && ((int)c <= 126))
  336. {
  337. builder.Append(c);
  338. }
  339. // If character is newline, break and send the current line
  340. else if ((c == '\r') || (c == '\n'))
  341. {
  342. // Check the next character to handle \r\n or \n\r
  343. if ((message.Length > i + 1) && ((message[i + 1] == '\r') || (message[i + 1] == '\n')))
  344. {
  345. i++;
  346. }
  347. i++;
  348. break;
  349. }
  350. }
  351. // Grab as a byte array
  352. buffer = this.Encoding.GetBytes(builder.ToString());
  353. #if NETSTANDARD1_3
  354. Client.SendAsync(buffer, buffer.Length, RemoteEndPoint).Wait();
  355. #else
  356. this.Client.Send(buffer, buffer.Length, this.RemoteEndPoint);
  357. #endif
  358. }
  359. }
  360. catch (Exception e)
  361. {
  362. ErrorHandler.Error(
  363. "Unable to send logging event to remote syslog " +
  364. this.RemoteAddress.ToString() +
  365. " on port " +
  366. this.RemotePort + ".",
  367. e,
  368. ErrorCode.WriteFailure);
  369. }
  370. }
  371. /// <summary>
  372. /// Initialize the options for this appender
  373. /// </summary>
  374. /// <remarks>
  375. /// <para>
  376. /// Initialize the level to syslog severity mappings set on this appender.
  377. /// </para>
  378. /// </remarks>
  379. public override void ActivateOptions()
  380. {
  381. base.ActivateOptions();
  382. m_levelMapping.ActivateOptions();
  383. }
  384. #endregion AppenderSkeleton Implementation
  385. #region Protected Members
  386. /// <summary>
  387. /// Translates a log4net level to a syslog severity.
  388. /// </summary>
  389. /// <param name="level">A log4net level.</param>
  390. /// <returns>A syslog severity.</returns>
  391. /// <remarks>
  392. /// <para>
  393. /// Translates a log4net level to a syslog severity.
  394. /// </para>
  395. /// </remarks>
  396. virtual protected SyslogSeverity GetSeverity(Level level)
  397. {
  398. LevelSeverity levelSeverity = m_levelMapping.Lookup(level) as LevelSeverity;
  399. if (levelSeverity != null)
  400. {
  401. return levelSeverity.Severity;
  402. }
  403. //
  404. // Fallback to sensible default values
  405. //
  406. if (level >= Level.Alert)
  407. {
  408. return SyslogSeverity.Alert;
  409. }
  410. else if (level >= Level.Critical)
  411. {
  412. return SyslogSeverity.Critical;
  413. }
  414. else if (level >= Level.Error)
  415. {
  416. return SyslogSeverity.Error;
  417. }
  418. else if (level >= Level.Warn)
  419. {
  420. return SyslogSeverity.Warning;
  421. }
  422. else if (level >= Level.Notice)
  423. {
  424. return SyslogSeverity.Notice;
  425. }
  426. else if (level >= Level.Info)
  427. {
  428. return SyslogSeverity.Informational;
  429. }
  430. // Default setting
  431. return SyslogSeverity.Debug;
  432. }
  433. #endregion Protected Members
  434. #region Public Static Members
  435. /// <summary>
  436. /// Generate a syslog priority.
  437. /// </summary>
  438. /// <param name="facility">The syslog facility.</param>
  439. /// <param name="severity">The syslog severity.</param>
  440. /// <returns>A syslog priority.</returns>
  441. /// <remarks>
  442. /// <para>
  443. /// Generate a syslog priority.
  444. /// </para>
  445. /// </remarks>
  446. public static int GeneratePriority(SyslogFacility facility, SyslogSeverity severity)
  447. {
  448. if (facility < SyslogFacility.Kernel || facility > SyslogFacility.Local7)
  449. {
  450. throw new ArgumentException("SyslogFacility out of range", "facility");
  451. }
  452. if (severity < SyslogSeverity.Emergency || severity > SyslogSeverity.Debug)
  453. {
  454. throw new ArgumentException("SyslogSeverity out of range", "severity");
  455. }
  456. unchecked
  457. {
  458. return ((int)facility * 8) + (int)severity;
  459. }
  460. }
  461. #endregion Public Static Members
  462. #region Private Instances Fields
  463. /// <summary>
  464. /// The facility. The default facility is <see cref="SyslogFacility.User"/>.
  465. /// </summary>
  466. private SyslogFacility m_facility = SyslogFacility.User;
  467. /// <summary>
  468. /// The message identity
  469. /// </summary>
  470. private PatternLayout m_identity;
  471. /// <summary>
  472. /// Mapping from level object to syslog severity
  473. /// </summary>
  474. private LevelMapping m_levelMapping = new LevelMapping();
  475. /// <summary>
  476. /// Initial buffer size
  477. /// </summary>
  478. private const int c_renderBufferSize = 256;
  479. /// <summary>
  480. /// Maximum buffer size before it is recycled
  481. /// </summary>
  482. private const int c_renderBufferMaxCapacity = 1024;
  483. #endregion Private Instances Fields
  484. #region LevelSeverity LevelMapping Entry
  485. /// <summary>
  486. /// A class to act as a mapping between the level that a logging call is made at and
  487. /// the syslog severity that is should be logged at.
  488. /// </summary>
  489. /// <remarks>
  490. /// <para>
  491. /// A class to act as a mapping between the level that a logging call is made at and
  492. /// the syslog severity that is should be logged at.
  493. /// </para>
  494. /// </remarks>
  495. public class LevelSeverity : LevelMappingEntry
  496. {
  497. private SyslogSeverity m_severity;
  498. /// <summary>
  499. /// The mapped syslog severity for the specified level
  500. /// </summary>
  501. /// <remarks>
  502. /// <para>
  503. /// Required property.
  504. /// The mapped syslog severity for the specified level
  505. /// </para>
  506. /// </remarks>
  507. public SyslogSeverity Severity
  508. {
  509. get { return m_severity; }
  510. set { m_severity = value; }
  511. }
  512. }
  513. #endregion // LevelSeverity LevelMapping Entry
  514. }
  515. }