#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
///
///
public override 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 .
///
///
protected override 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 || NETSTANDARD
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
}
}