XmlLayout.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 System.Text;
  21. using System.Xml;
  22. using log4net.Core;
  23. using log4net.Util;
  24. namespace log4net.Layout
  25. {
  26. /// <summary>
  27. /// Layout that formats the log events as XML elements.
  28. /// </summary>
  29. /// <remarks>
  30. /// <para>
  31. /// The output of the <see cref="XmlLayout" /> consists of a series of
  32. /// log4net:event elements. It does not output a complete well-formed XML
  33. /// file. The output is designed to be included as an <em>external entity</em>
  34. /// in a separate file to form a correct XML file.
  35. /// </para>
  36. /// <para>
  37. /// For example, if <c>abc</c> is the name of the file where
  38. /// the <see cref="XmlLayout" /> output goes, then a well-formed XML file would
  39. /// be:
  40. /// </para>
  41. /// <code lang="XML">
  42. /// &lt;?xml version="1.0" ?&gt;
  43. ///
  44. /// &lt;!DOCTYPE log4net:events SYSTEM "log4net-events.dtd" [&lt;!ENTITY data SYSTEM "abc"&gt;]&gt;
  45. ///
  46. /// &lt;log4net:events version="1.2" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2&gt;
  47. /// &amp;data;
  48. /// &lt;/log4net:events&gt;
  49. /// </code>
  50. /// <para>
  51. /// This approach enforces the independence of the <see cref="XmlLayout" />
  52. /// and the appender where it is embedded.
  53. /// </para>
  54. /// <para>
  55. /// The <c>version</c> attribute helps components to correctly
  56. /// interpret output generated by <see cref="XmlLayout" />. The value of
  57. /// this attribute should be "1.2" for release 1.2 and later.
  58. /// </para>
  59. /// <para>
  60. /// Alternatively the <c>Header</c> and <c>Footer</c> properties can be
  61. /// configured to output the correct XML header, open tag and close tag.
  62. /// When setting the <c>Header</c> and <c>Footer</c> properties it is essential
  63. /// that the underlying data store not be appendable otherwise the data
  64. /// will become invalid XML.
  65. /// </para>
  66. /// </remarks>
  67. /// <author>Nicko Cadell</author>
  68. /// <author>Gert Driesen</author>
  69. public class XmlLayout : XmlLayoutBase
  70. {
  71. #region Public Instance Constructors
  72. /// <summary>
  73. /// Constructs an XmlLayout
  74. /// </summary>
  75. public XmlLayout() : base()
  76. {
  77. }
  78. /// <summary>
  79. /// Constructs an XmlLayout.
  80. /// </summary>
  81. /// <remarks>
  82. /// <para>
  83. /// The <b>LocationInfo</b> option takes a boolean value. By
  84. /// default, it is set to false which means there will be no location
  85. /// information output by this layout. If the the option is set to
  86. /// true, then the file name and line number of the statement
  87. /// at the origin of the log statement will be output.
  88. /// </para>
  89. /// <para>
  90. /// If you are embedding this layout within an SmtpAppender
  91. /// then make sure to set the <b>LocationInfo</b> option of that
  92. /// appender as well.
  93. /// </para>
  94. /// </remarks>
  95. public XmlLayout(bool locationInfo) : base(locationInfo)
  96. {
  97. }
  98. #endregion Public Instance Constructors
  99. #region Public Instance Properties
  100. /// <summary>
  101. /// The prefix to use for all element names
  102. /// </summary>
  103. /// <remarks>
  104. /// <para>
  105. /// The default prefix is <b>log4net</b>. Set this property
  106. /// to change the prefix. If the prefix is set to an empty string
  107. /// then no prefix will be written.
  108. /// </para>
  109. /// </remarks>
  110. public string Prefix
  111. {
  112. get { return m_prefix; }
  113. set { m_prefix = value; }
  114. }
  115. /// <summary>
  116. /// Set whether or not to base64 encode the message.
  117. /// </summary>
  118. /// <remarks>
  119. /// <para>
  120. /// By default the log message will be written as text to the xml
  121. /// output. This can cause problems when the message contains binary
  122. /// data. By setting this to true the contents of the message will be
  123. /// base64 encoded. If this is set then invalid character replacement
  124. /// (see <see cref="XmlLayoutBase.InvalidCharReplacement"/>) will not be performed
  125. /// on the log message.
  126. /// </para>
  127. /// </remarks>
  128. public bool Base64EncodeMessage
  129. {
  130. get {return m_base64Message;}
  131. set {m_base64Message=value;}
  132. }
  133. /// <summary>
  134. /// Set whether or not to base64 encode the property values.
  135. /// </summary>
  136. /// <remarks>
  137. /// <para>
  138. /// By default the properties will be written as text to the xml
  139. /// output. This can cause problems when one or more properties contain
  140. /// binary data. By setting this to true the values of the properties
  141. /// will be base64 encoded. If this is set then invalid character replacement
  142. /// (see <see cref="XmlLayoutBase.InvalidCharReplacement"/>) will not be performed
  143. /// on the property values.
  144. /// </para>
  145. /// </remarks>
  146. public bool Base64EncodeProperties
  147. {
  148. get {return m_base64Properties;}
  149. set {m_base64Properties=value;}
  150. }
  151. #endregion Public Instance Properties
  152. #region Implementation of IOptionHandler
  153. /// <summary>
  154. /// Initialize layout options
  155. /// </summary>
  156. /// <remarks>
  157. /// <para>
  158. /// This is part of the <see cref="IOptionHandler"/> delayed object
  159. /// activation scheme. The <see cref="ActivateOptions"/> method must
  160. /// be called on this object after the configuration properties have
  161. /// been set. Until <see cref="ActivateOptions"/> is called this
  162. /// object is in an undefined state and must not be used.
  163. /// </para>
  164. /// <para>
  165. /// If any of the configuration properties are modified then
  166. /// <see cref="ActivateOptions"/> must be called again.
  167. /// </para>
  168. /// <para>
  169. /// Builds a cache of the element names
  170. /// </para>
  171. /// </remarks>
  172. override public void ActivateOptions()
  173. {
  174. base.ActivateOptions();
  175. // Cache the full element names including the prefix
  176. if (m_prefix != null && m_prefix.Length > 0)
  177. {
  178. m_elmEvent = m_prefix + ":" + ELM_EVENT;
  179. m_elmMessage = m_prefix + ":" + ELM_MESSAGE;
  180. m_elmProperties = m_prefix + ":" + ELM_PROPERTIES;
  181. m_elmData = m_prefix + ":" + ELM_DATA;
  182. m_elmException = m_prefix + ":" + ELM_EXCEPTION;
  183. m_elmLocation = m_prefix + ":" + ELM_LOCATION;
  184. }
  185. }
  186. #endregion Implementation of IOptionHandler
  187. #region Override implementation of XMLLayoutBase
  188. /// <summary>
  189. /// Does the actual writing of the XML.
  190. /// </summary>
  191. /// <param name="writer">The writer to use to output the event to.</param>
  192. /// <param name="loggingEvent">The event to write.</param>
  193. /// <remarks>
  194. /// <para>
  195. /// Override the base class <see cref="XmlLayoutBase.FormatXml"/> method
  196. /// to write the <see cref="LoggingEvent"/> to the <see cref="XmlWriter"/>.
  197. /// </para>
  198. /// </remarks>
  199. override protected void FormatXml(XmlWriter writer, LoggingEvent loggingEvent)
  200. {
  201. writer.WriteStartElement(m_elmEvent);
  202. writer.WriteAttributeString(ATTR_LOGGER, loggingEvent.LoggerName);
  203. #if NET_2_0 || NETCF_2_0 || MONO_2_0 || NETSTANDARD1_3
  204. writer.WriteAttributeString(ATTR_TIMESTAMP, XmlConvert.ToString(loggingEvent.TimeStamp, XmlDateTimeSerializationMode.Local));
  205. #else
  206. writer.WriteAttributeString(ATTR_TIMESTAMP, XmlConvert.ToString(loggingEvent.TimeStamp));
  207. #endif
  208. writer.WriteAttributeString(ATTR_LEVEL, loggingEvent.Level.DisplayName);
  209. writer.WriteAttributeString(ATTR_THREAD, loggingEvent.ThreadName);
  210. if (loggingEvent.Domain != null && loggingEvent.Domain.Length > 0)
  211. {
  212. writer.WriteAttributeString(ATTR_DOMAIN, loggingEvent.Domain);
  213. }
  214. if (loggingEvent.Identity != null && loggingEvent.Identity.Length > 0)
  215. {
  216. writer.WriteAttributeString(ATTR_IDENTITY, loggingEvent.Identity);
  217. }
  218. if (loggingEvent.UserName != null && loggingEvent.UserName.Length > 0)
  219. {
  220. writer.WriteAttributeString(ATTR_USERNAME, loggingEvent.UserName);
  221. }
  222. // Append the message text
  223. writer.WriteStartElement(m_elmMessage);
  224. if (!this.Base64EncodeMessage)
  225. {
  226. Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, this.InvalidCharReplacement);
  227. }
  228. else
  229. {
  230. byte[] messageBytes = Encoding.UTF8.GetBytes(loggingEvent.RenderedMessage);
  231. string base64Message = Convert.ToBase64String(messageBytes, 0, messageBytes.Length);
  232. Transform.WriteEscapedXmlString(writer, base64Message,this.InvalidCharReplacement);
  233. }
  234. writer.WriteEndElement();
  235. PropertiesDictionary properties = loggingEvent.GetProperties();
  236. // Append the properties text
  237. if (properties.Count > 0)
  238. {
  239. writer.WriteStartElement(m_elmProperties);
  240. foreach(System.Collections.DictionaryEntry entry in properties)
  241. {
  242. writer.WriteStartElement(m_elmData);
  243. writer.WriteAttributeString(ATTR_NAME, Transform.MaskXmlInvalidCharacters((string)entry.Key,this.InvalidCharReplacement));
  244. // Use an ObjectRenderer to convert the object to a string
  245. string valueStr =null;
  246. if (!this.Base64EncodeProperties)
  247. {
  248. valueStr = Transform.MaskXmlInvalidCharacters(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value),this.InvalidCharReplacement);
  249. }
  250. else
  251. {
  252. byte[] propertyValueBytes = Encoding.UTF8.GetBytes(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value));
  253. valueStr = Convert.ToBase64String(propertyValueBytes, 0, propertyValueBytes.Length);
  254. }
  255. writer.WriteAttributeString(ATTR_VALUE, valueStr);
  256. writer.WriteEndElement();
  257. }
  258. writer.WriteEndElement();
  259. }
  260. string exceptionStr = loggingEvent.GetExceptionString();
  261. if (exceptionStr != null && exceptionStr.Length > 0)
  262. {
  263. // Append the stack trace line
  264. writer.WriteStartElement(m_elmException);
  265. Transform.WriteEscapedXmlString(writer, exceptionStr,this.InvalidCharReplacement);
  266. writer.WriteEndElement();
  267. }
  268. if (LocationInfo)
  269. {
  270. LocationInfo locationInfo = loggingEvent.LocationInformation;
  271. writer.WriteStartElement(m_elmLocation);
  272. writer.WriteAttributeString(ATTR_CLASS, locationInfo.ClassName);
  273. writer.WriteAttributeString(ATTR_METHOD, locationInfo.MethodName);
  274. writer.WriteAttributeString(ATTR_FILE, locationInfo.FileName);
  275. writer.WriteAttributeString(ATTR_LINE, locationInfo.LineNumber);
  276. writer.WriteEndElement();
  277. }
  278. writer.WriteEndElement();
  279. }
  280. #endregion Override implementation of XMLLayoutBase
  281. #region Private Instance Fields
  282. /// <summary>
  283. /// The prefix to use for all generated element names
  284. /// </summary>
  285. private string m_prefix = PREFIX;
  286. private string m_elmEvent = ELM_EVENT;
  287. private string m_elmMessage = ELM_MESSAGE;
  288. private string m_elmData = ELM_DATA;
  289. private string m_elmProperties = ELM_PROPERTIES;
  290. private string m_elmException = ELM_EXCEPTION;
  291. private string m_elmLocation = ELM_LOCATION;
  292. private bool m_base64Message=false;
  293. private bool m_base64Properties=false;
  294. #endregion Private Instance Fields
  295. #region Private Static Fields
  296. private const string PREFIX = "log4net";
  297. private const string ELM_EVENT = "event";
  298. private const string ELM_MESSAGE = "message";
  299. private const string ELM_PROPERTIES = "properties";
  300. private const string ELM_GLOBAL_PROPERTIES = "global-properties";
  301. private const string ELM_DATA = "data";
  302. private const string ELM_EXCEPTION = "exception";
  303. private const string ELM_LOCATION = "locationInfo";
  304. private const string ATTR_LOGGER = "logger";
  305. private const string ATTR_TIMESTAMP = "timestamp";
  306. private const string ATTR_LEVEL = "level";
  307. private const string ATTR_THREAD = "thread";
  308. private const string ATTR_DOMAIN = "domain";
  309. private const string ATTR_IDENTITY = "identity";
  310. private const string ATTR_USERNAME = "username";
  311. private const string ATTR_CLASS = "class";
  312. private const string ATTR_METHOD = "method";
  313. private const string ATTR_FILE = "file";
  314. private const string ATTR_LINE = "line";
  315. private const string ATTR_NAME = "name";
  316. private const string ATTR_VALUE = "value";
  317. #endregion Private Static Fields
  318. }
  319. }