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