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 	/// &lt;?xml version="1.0" ?&gt;
45 	///
46 	/// &lt;!DOCTYPE log4net:events SYSTEM "log4net-events.dtd" [&lt;!ENTITY data SYSTEM "abc"&gt;]&gt;
47 	///
48 	/// &lt;log4net:events version="1.2" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2&gt;
49 	///     &amp;data;
50 	/// &lt;/log4net:events&gt;
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