#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.Text; using System.Xml; using log4net.Core; using log4net.Util; namespace log4net.Layout { /// /// Layout that formats the log events as XML elements. /// /// /// /// The output of the consists of a series of /// log4net:event elements. It does not output a complete well-formed XML /// file. The output is designed to be included as an external entity /// in a separate file to form a correct XML file. /// /// /// For example, if abc is the name of the file where /// the output goes, then a well-formed XML file would /// be: /// /// /// <?xml version="1.0" ?> /// /// <!DOCTYPE log4net:events SYSTEM "log4net-events.dtd" [<!ENTITY data SYSTEM "abc">]> /// /// <log4net:events version="1.2" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2> /// &data; /// </log4net:events> /// /// /// This approach enforces the independence of the /// and the appender where it is embedded. /// /// /// The version attribute helps components to correctly /// interpret output generated by . The value of /// this attribute should be "1.2" for release 1.2 and later. /// /// /// Alternatively the Header and Footer properties can be /// configured to output the correct XML header, open tag and close tag. /// When setting the Header and Footer properties it is essential /// that the underlying data store not be appendable otherwise the data /// will become invalid XML. /// /// /// Nicko Cadell /// Gert Driesen public class XmlLayout : XmlLayoutBase { #region Public Instance Constructors /// /// Constructs an XmlLayout /// public XmlLayout() : base() { } /// /// Constructs an XmlLayout. /// /// /// /// The LocationInfo option takes a boolean value. By /// default, it is set to false which means there will be no location /// information output by this layout. If the the option is set to /// true, then the file name and line number of the statement /// at the origin of the log statement will be output. /// /// /// If you are embedding this layout within an SmtpAppender /// then make sure to set the LocationInfo option of that /// appender as well. /// /// public XmlLayout(bool locationInfo) : base(locationInfo) { } #endregion Public Instance Constructors #region Public Instance Properties /// /// The prefix to use for all element names /// /// /// /// The default prefix is log4net. Set this property /// to change the prefix. If the prefix is set to an empty string /// then no prefix will be written. /// /// public string Prefix { get { return m_prefix; } set { m_prefix = value; } } /// /// Set whether or not to base64 encode the message. /// /// /// /// By default the log message will be written as text to the xml /// output. This can cause problems when the message contains binary /// data. By setting this to true the contents of the message will be /// base64 encoded. If this is set then invalid character replacement /// (see ) will not be performed /// on the log message. /// /// public bool Base64EncodeMessage { get {return m_base64Message;} set {m_base64Message=value;} } /// /// Set whether or not to base64 encode the property values. /// /// /// /// By default the properties will be written as text to the xml /// output. This can cause problems when one or more properties contain /// binary data. By setting this to true the values of the properties /// will be base64 encoded. If this is set then invalid character replacement /// (see ) will not be performed /// on the property values. /// /// public bool Base64EncodeProperties { get {return m_base64Properties;} set {m_base64Properties=value;} } #endregion Public Instance Properties #region Implementation of IOptionHandler /// /// Initialize layout options /// /// /// /// 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. /// /// /// Builds a cache of the element names /// /// override public void ActivateOptions() { base.ActivateOptions(); // Cache the full element names including the prefix if (m_prefix != null && m_prefix.Length > 0) { m_elmEvent = m_prefix + ":" + ELM_EVENT; m_elmMessage = m_prefix + ":" + ELM_MESSAGE; m_elmProperties = m_prefix + ":" + ELM_PROPERTIES; m_elmData = m_prefix + ":" + ELM_DATA; m_elmException = m_prefix + ":" + ELM_EXCEPTION; m_elmLocation = m_prefix + ":" + ELM_LOCATION; } } #endregion Implementation of IOptionHandler #region Override implementation of XMLLayoutBase /// /// Does the actual writing of the XML. /// /// The writer to use to output the event to. /// The event to write. /// /// /// Override the base class method /// to write the to the . /// /// override protected void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) { writer.WriteStartElement(m_elmEvent); writer.WriteAttributeString(ATTR_LOGGER, loggingEvent.LoggerName); #if NET_2_0 || NETCF_2_0 || MONO_2_0 || NETSTANDARD1_3 writer.WriteAttributeString(ATTR_TIMESTAMP, XmlConvert.ToString(loggingEvent.TimeStamp, XmlDateTimeSerializationMode.Local)); #else writer.WriteAttributeString(ATTR_TIMESTAMP, XmlConvert.ToString(loggingEvent.TimeStamp)); #endif writer.WriteAttributeString(ATTR_LEVEL, loggingEvent.Level.DisplayName); writer.WriteAttributeString(ATTR_THREAD, loggingEvent.ThreadName); if (loggingEvent.Domain != null && loggingEvent.Domain.Length > 0) { writer.WriteAttributeString(ATTR_DOMAIN, loggingEvent.Domain); } if (loggingEvent.Identity != null && loggingEvent.Identity.Length > 0) { writer.WriteAttributeString(ATTR_IDENTITY, loggingEvent.Identity); } if (loggingEvent.UserName != null && loggingEvent.UserName.Length > 0) { writer.WriteAttributeString(ATTR_USERNAME, loggingEvent.UserName); } // Append the message text writer.WriteStartElement(m_elmMessage); if (!this.Base64EncodeMessage) { Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, this.InvalidCharReplacement); } else { byte[] messageBytes = Encoding.UTF8.GetBytes(loggingEvent.RenderedMessage); string base64Message = Convert.ToBase64String(messageBytes, 0, messageBytes.Length); Transform.WriteEscapedXmlString(writer, base64Message,this.InvalidCharReplacement); } writer.WriteEndElement(); PropertiesDictionary properties = loggingEvent.GetProperties(); // Append the properties text if (properties.Count > 0) { writer.WriteStartElement(m_elmProperties); foreach(System.Collections.DictionaryEntry entry in properties) { writer.WriteStartElement(m_elmData); writer.WriteAttributeString(ATTR_NAME, Transform.MaskXmlInvalidCharacters((string)entry.Key,this.InvalidCharReplacement)); // Use an ObjectRenderer to convert the object to a string string valueStr =null; if (!this.Base64EncodeProperties) { valueStr = Transform.MaskXmlInvalidCharacters(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value),this.InvalidCharReplacement); } else { byte[] propertyValueBytes = Encoding.UTF8.GetBytes(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value)); valueStr = Convert.ToBase64String(propertyValueBytes, 0, propertyValueBytes.Length); } writer.WriteAttributeString(ATTR_VALUE, valueStr); writer.WriteEndElement(); } writer.WriteEndElement(); } string exceptionStr = loggingEvent.GetExceptionString(); if (exceptionStr != null && exceptionStr.Length > 0) { // Append the stack trace line writer.WriteStartElement(m_elmException); Transform.WriteEscapedXmlString(writer, exceptionStr,this.InvalidCharReplacement); writer.WriteEndElement(); } if (LocationInfo) { LocationInfo locationInfo = loggingEvent.LocationInformation; writer.WriteStartElement(m_elmLocation); writer.WriteAttributeString(ATTR_CLASS, locationInfo.ClassName); writer.WriteAttributeString(ATTR_METHOD, locationInfo.MethodName); writer.WriteAttributeString(ATTR_FILE, locationInfo.FileName); writer.WriteAttributeString(ATTR_LINE, locationInfo.LineNumber); writer.WriteEndElement(); } writer.WriteEndElement(); } #endregion Override implementation of XMLLayoutBase #region Private Instance Fields /// /// The prefix to use for all generated element names /// private string m_prefix = PREFIX; private string m_elmEvent = ELM_EVENT; private string m_elmMessage = ELM_MESSAGE; private string m_elmData = ELM_DATA; private string m_elmProperties = ELM_PROPERTIES; private string m_elmException = ELM_EXCEPTION; private string m_elmLocation = ELM_LOCATION; private bool m_base64Message=false; private bool m_base64Properties=false; #endregion Private Instance Fields #region Private Static Fields private const string PREFIX = "log4net"; private const string ELM_EVENT = "event"; private const string ELM_MESSAGE = "message"; private const string ELM_PROPERTIES = "properties"; private const string ELM_GLOBAL_PROPERTIES = "global-properties"; private const string ELM_DATA = "data"; private const string ELM_EXCEPTION = "exception"; private const string ELM_LOCATION = "locationInfo"; private const string ATTR_LOGGER = "logger"; private const string ATTR_TIMESTAMP = "timestamp"; private const string ATTR_LEVEL = "level"; private const string ATTR_THREAD = "thread"; private const string ATTR_DOMAIN = "domain"; private const string ATTR_IDENTITY = "identity"; private const string ATTR_USERNAME = "username"; private const string ATTR_CLASS = "class"; private const string ATTR_METHOD = "method"; private const string ATTR_FILE = "file"; private const string ATTR_LINE = "line"; private const string ATTR_NAME = "name"; private const string ATTR_VALUE = "value"; #endregion Private Static Fields } }