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 // MONO 1.0 Beta mcs does not like #if !A && !B && !C syntax
20 
21 // .NET Compact Framework 1.0 has no support for EventLog
22 #if !NETCF
23 // SSCLI 1.0 has no support for EventLog
24 #if !SSCLI
25 
26 using System;
27 using System.Diagnostics;
28 using System.Globalization;
29 
30 using log4net.Util;
31 using log4net.Layout;
32 using log4net.Core;
33 
34 namespace log4net.Appender
35 {
36 	/// <summary>
37 	/// Writes events to the system event log.
38 	/// </summary>
39 	/// <remarks>
40 	/// <para>
41 	/// The <c>EventID</c> of the event log entry can be
42 	/// set using the <c>EventLogEventID</c> property (<see cref="LoggingEvent.Properties"/>)
43 	/// on the <see cref="LoggingEvent"/>.
44 	/// </para>
45 	/// <para>
46 	/// There is a limit of 32K characters for an event log message
47 	/// </para>
48 	/// <para>
49 	/// When configuring the EventLogAppender a mapping can be
50 	/// specified to map a logging level to an event log entry type. For example:
51 	/// </para>
52 	/// <code lang="XML">
53 	/// &lt;mapping&gt;
54 	/// 	&lt;level value="ERROR" /&gt;
55 	/// 	&lt;eventLogEntryType value="Error" /&gt;
56 	/// &lt;/mapping&gt;
57 	/// &lt;mapping&gt;
58 	/// 	&lt;level value="DEBUG" /&gt;
59 	/// 	&lt;eventLogEntryType value="Information" /&gt;
60 	/// &lt;/mapping&gt;
61 	/// </code>
62 	/// <para>
63 	/// The Level is the standard log4net logging level and eventLogEntryType can be any value
64 	/// from the <see cref="EventLogEntryType"/> enum, i.e.:
65 	/// <list type="bullet">
66 	/// <item><term>Error</term><description>an error event</description></item>
67 	/// <item><term>Warning</term><description>a warning event</description></item>
68 	/// <item><term>Information</term><description>an informational event</description></item>
69 	/// </list>
70 	/// </para>
71 	/// </remarks>
72 	/// <author>Aspi Havewala</author>
73 	/// <author>Douglas de la Torre</author>
74 	/// <author>Nicko Cadell</author>
75 	/// <author>Gert Driesen</author>
76 	/// <author>Thomas Voss</author>
77 	public class EventLogAppender : AppenderSkeleton
78 	{
79 		#region Public Instance Constructors
80 
81 		/// <summary>
82 		/// Initializes a new instance of the <see cref="EventLogAppender" /> class.
83 		/// </summary>
84 		/// <remarks>
85 		/// <para>
86 		/// Default constructor.
87 		/// </para>
88 		/// </remarks>
EventLogAppender()89 		public EventLogAppender()
90 		{
91 			m_applicationName	= System.Threading.Thread.GetDomain().FriendlyName;
92 			m_logName			= "Application";	// Defaults to application log
93 			m_machineName		= ".";	// Only log on the local machine
94 		}
95 
96 		/// <summary>
97 		/// Initializes a new instance of the <see cref="EventLogAppender" /> class
98 		/// with the specified <see cref="ILayout" />.
99 		/// </summary>
100 		/// <param name="layout">The <see cref="ILayout" /> to use with this appender.</param>
101 		/// <remarks>
102 		/// <para>
103 		/// Obsolete constructor.
104 		/// </para>
105 		/// </remarks>
106 		[Obsolete("Instead use the default constructor and set the Layout property")]
EventLogAppender(ILayout layout)107 		public EventLogAppender(ILayout layout) : this()
108 		{
109 			Layout = layout;
110 		}
111 
112 		#endregion // Public Instance Constructors
113 
114 		#region Public Instance Properties
115 
116 		/// <summary>
117 		/// The name of the log where messages will be stored.
118 		/// </summary>
119 		/// <value>
120 		/// The string name of the log where messages will be stored.
121 		/// </value>
122 		/// <remarks>
123 		/// <para>This is the name of the log as it appears in the Event Viewer
124 		/// tree. The default value is to log into the <c>Application</c>
125 		/// log, this is where most applications write their events. However
126 		/// if you need a separate log for your application (or applications)
127 		/// then you should set the <see cref="LogName"/> appropriately.</para>
128 		/// <para>This should not be used to distinguish your event log messages
129 		/// from those of other applications, the <see cref="ApplicationName"/>
130 		/// property should be used to distinguish events. This property should be
131 		/// used to group together events into a single log.
132 		/// </para>
133 		/// </remarks>
134 		public string LogName
135 		{
136 			get { return m_logName; }
137 			set { m_logName = value; }
138 		}
139 
140 		/// <summary>
141 		/// Property used to set the Application name.  This appears in the
142 		/// event logs when logging.
143 		/// </summary>
144 		/// <value>
145 		/// The string used to distinguish events from different sources.
146 		/// </value>
147 		/// <remarks>
148 		/// Sets the event log source property.
149 		/// </remarks>
150 		public string ApplicationName
151 		{
152 			get { return m_applicationName; }
153 			set { m_applicationName = value; }
154 		}
155 
156 		/// <summary>
157 		/// This property is used to return the name of the computer to use
158 		/// when accessing the event logs.  Currently, this is the current
159 		/// computer, denoted by a dot "."
160 		/// </summary>
161 		/// <value>
162 		/// The string name of the machine holding the event log that
163 		/// will be logged into.
164 		/// </value>
165 		/// <remarks>
166 		/// This property cannot be changed. It is currently set to '.'
167 		/// i.e. the local machine. This may be changed in future.
168 		/// </remarks>
169 		public string MachineName
170 		{
171 			get { return m_machineName; }
172 			set { /* Currently we do not allow the machine name to be changed */; }
173 		}
174 
175 		/// <summary>
176 		/// Add a mapping of level to <see cref="EventLogEntryType"/> - done by the config file
177 		/// </summary>
178 		/// <param name="mapping">The mapping to add</param>
179 		/// <remarks>
180 		/// <para>
181 		/// Add a <see cref="Level2EventLogEntryType"/> mapping to this appender.
182 		/// Each mapping defines the event log entry type for a level.
183 		/// </para>
184 		/// </remarks>
AddMapping(Level2EventLogEntryType mapping)185 		public void AddMapping(Level2EventLogEntryType mapping)
186 		{
187 			m_levelMapping.Add(mapping);
188 		}
189 
190 		/// <summary>
191 		/// Gets or sets the <see cref="SecurityContext"/> used to write to the EventLog.
192 		/// </summary>
193 		/// <value>
194 		/// The <see cref="SecurityContext"/> used to write to the EventLog.
195 		/// </value>
196 		/// <remarks>
197 		/// <para>
198 		/// The system security context used to write to the EventLog.
199 		/// </para>
200 		/// <para>
201 		/// Unless a <see cref="SecurityContext"/> specified here for this appender
202 		/// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the
203 		/// security context to use. The default behavior is to use the security context
204 		/// of the current thread.
205 		/// </para>
206 		/// </remarks>
207 		public SecurityContext SecurityContext
208 		{
209 			get { return m_securityContext; }
210 			set { m_securityContext = value; }
211 		}
212 
213 		#endregion // Public Instance Properties
214 
215 		#region Implementation of IOptionHandler
216 
217 		/// <summary>
218 		/// Initialize the appender based on the options set
219 		/// </summary>
220 		/// <remarks>
221 		/// <para>
222 		/// This is part of the <see cref="IOptionHandler"/> delayed object
223 		/// activation scheme. The <see cref="ActivateOptions"/> method must
224 		/// be called on this object after the configuration properties have
225 		/// been set. Until <see cref="ActivateOptions"/> is called this
226 		/// object is in an undefined state and must not be used.
227 		/// </para>
228 		/// <para>
229 		/// If any of the configuration properties are modified then
230 		/// <see cref="ActivateOptions"/> must be called again.
231 		/// </para>
232 		/// </remarks>
ActivateOptions()233 		override public void ActivateOptions()
234 		{
235 			base.ActivateOptions();
236 
237 			if (m_securityContext == null)
238 			{
239 				m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
240 			}
241 
242 			bool sourceAlreadyExists = false;
243 			string currentLogName = null;
244 
245 			using(SecurityContext.Impersonate(this))
246 			{
247 				sourceAlreadyExists = EventLog.SourceExists(m_applicationName);
248 				if (sourceAlreadyExists)
249 				{
250 					currentLogName = EventLog.LogNameFromSourceName(m_applicationName, m_machineName);
251 				}
252 			}
253 
254 			if (sourceAlreadyExists && currentLogName != m_logName)
255 			{
256 				LogLog.Debug("EventLogAppender: Changing event source [" + m_applicationName + "] from log [" + currentLogName + "] to log [" + m_logName + "]");
257 			}
258 			else if (!sourceAlreadyExists)
259 			{
260 				LogLog.Debug("EventLogAppender: Creating event source Source [" + m_applicationName + "] in log " + m_logName + "]");
261 			}
262 
263 			string registeredLogName = null;
264 
265 			using(SecurityContext.Impersonate(this))
266 			{
267 				if (sourceAlreadyExists && currentLogName != m_logName)
268 				{
269 					//
270 					// Re-register this to the current application if the user has changed
271 					// the application / logfile association
272 					//
273 					EventLog.DeleteEventSource(m_applicationName, m_machineName);
274 					CreateEventSource(m_applicationName, m_logName, m_machineName);
275 
276 					registeredLogName = EventLog.LogNameFromSourceName(m_applicationName, m_machineName);
277 				}
278 				else if (!sourceAlreadyExists)
279 				{
280 					CreateEventSource(m_applicationName, m_logName, m_machineName);
281 
282 					registeredLogName = EventLog.LogNameFromSourceName(m_applicationName, m_machineName);
283 				}
284 			}
285 
286 			m_levelMapping.ActivateOptions();
287 
288 			LogLog.Debug("EventLogAppender: Source [" + m_applicationName + "] is registered to log [" + registeredLogName + "]");
289 		}
290 
291 		#endregion // Implementation of IOptionHandler
292 
293 		/// <summary>
294 		/// Create an event log source
295 		/// </summary>
296 		/// <remarks>
297 		/// Uses different API calls under NET_2_0
298 		/// </remarks>
CreateEventSource(string source, string logName, string machineName)299 		private static void CreateEventSource(string source, string logName, string machineName)
300 		{
301 #if NET_2_0
302 			EventSourceCreationData eventSourceCreationData = new EventSourceCreationData(source, logName);
303 			eventSourceCreationData.MachineName = machineName;
304 			EventLog.CreateEventSource(eventSourceCreationData);
305 #else
306 			EventLog.CreateEventSource(source, logName, machineName);
307 #endif
308 		}
309 
310 
311 		#region Override implementation of AppenderSkeleton
312 
313 		/// <summary>
314 		/// This method is called by the <see cref="AppenderSkeleton.DoAppend(LoggingEvent)"/>
315 		/// method.
316 		/// </summary>
317 		/// <param name="loggingEvent">the event to log</param>
318 		/// <remarks>
319 		/// <para>Writes the event to the system event log using the
320 		/// <see cref="ApplicationName"/>.</para>
321 		///
322 		/// <para>If the event has an <c>EventID</c> property (see <see cref="LoggingEvent.Properties"/>)
323 		/// set then this integer will be used as the event log event id.</para>
324 		///
325 		/// <para>
326 		/// There is a limit of 32K characters for an event log message
327 		/// </para>
328 		/// </remarks>
Append(LoggingEvent loggingEvent)329 		override protected void Append(LoggingEvent loggingEvent)
330 		{
331 			//
332 			// Write the resulting string to the event log system
333 			//
334 			int eventID = 0;
335 
336 			// Look for the EventLogEventID property
337 			object eventIDPropertyObj = loggingEvent.LookupProperty("EventID");
338 			if (eventIDPropertyObj != null)
339 			{
340 				if (eventIDPropertyObj is int)
341 				{
342 					eventID = (int)eventIDPropertyObj;
343 				}
344 				else
345 				{
346 					string eventIDPropertyString = eventIDPropertyObj as string;
347 					if (eventIDPropertyString != null && eventIDPropertyString.Length > 0)
348 					{
349 						// Read the string property into a number
350 						int intVal;
351 						if (SystemInfo.TryParse(eventIDPropertyString, out intVal))
352 						{
353 							eventID = intVal;
354 						}
355 						else
356 						{
357 							ErrorHandler.Error("Unable to parse event ID property [" + eventIDPropertyString + "].");
358 						}
359 					}
360 				}
361 			}
362 
363 			// Write to the event log
364 			try
365 			{
366 				string eventTxt = RenderLoggingEvent(loggingEvent);
367 
368 				// There is a limit of 32K characters for an event log message
369 				if (eventTxt.Length > 32000)
370 				{
371 					eventTxt = eventTxt.Substring(0, 32000);
372 				}
373 
374 				EventLogEntryType entryType = GetEntryType(loggingEvent.Level);
375 
376 				using(SecurityContext.Impersonate(this))
377 				{
378 					EventLog.WriteEntry(m_applicationName, eventTxt, entryType, eventID);
379 				}
380 			}
381 			catch(Exception ex)
382 			{
383 				ErrorHandler.Error("Unable to write to event log [" + m_logName + "] using source [" + m_applicationName + "]", ex);
384 			}
385 		}
386 
387 		/// <summary>
388 		/// This appender requires a <see cref="Layout"/> to be set.
389 		/// </summary>
390 		/// <value><c>true</c></value>
391 		/// <remarks>
392 		/// <para>
393 		/// This appender requires a <see cref="Layout"/> to be set.
394 		/// </para>
395 		/// </remarks>
396 		override protected bool RequiresLayout
397 		{
398 			get { return true; }
399 		}
400 
401 		#endregion // Override implementation of AppenderSkeleton
402 
403 		#region Protected Instance Methods
404 
405 		/// <summary>
406 		/// Get the equivalent <see cref="EventLogEntryType"/> for a <see cref="Level"/> <paramref name="p"/>
407 		/// </summary>
408 		/// <param name="level">the Level to convert to an EventLogEntryType</param>
409 		/// <returns>The equivalent <see cref="EventLogEntryType"/> for a <see cref="Level"/> <paramref name="p"/></returns>
410 		/// <remarks>
411 		/// Because there are fewer applicable <see cref="EventLogEntryType"/>
412 		/// values to use in logging levels than there are in the
413 		/// <see cref="Level"/> this is a one way mapping. There is
414 		/// a loss of information during the conversion.
415 		/// </remarks>
GetEntryType(Level level)416 		virtual protected EventLogEntryType GetEntryType(Level level)
417 		{
418 			// see if there is a specified lookup.
419 			Level2EventLogEntryType entryType = m_levelMapping.Lookup(level) as Level2EventLogEntryType;
420 			if (entryType != null)
421 			{
422 				return entryType.EventLogEntryType;
423 			}
424 
425 			// Use default behavior
426 
427 			if (level >= Level.Error)
428 			{
429 				return EventLogEntryType.Error;
430 			}
431 			else if (level == Level.Warn)
432 			{
433 				return EventLogEntryType.Warning;
434 			}
435 
436 			// Default setting
437 			return EventLogEntryType.Information;
438 		}
439 
440 		#endregion // Protected Instance Methods
441 
442 		#region Private Instance Fields
443 
444 		/// <summary>
445 		/// The log name is the section in the event logs where the messages
446 		/// are stored.
447 		/// </summary>
448 		private string m_logName;
449 
450 		/// <summary>
451 		/// Name of the application to use when logging.  This appears in the
452 		/// application column of the event log named by <see cref="m_logName"/>.
453 		/// </summary>
454 		private string m_applicationName;
455 
456 		/// <summary>
457 		/// The name of the machine which holds the event log. This is
458 		/// currently only allowed to be '.' i.e. the current machine.
459 		/// </summary>
460 		private string m_machineName;
461 
462 		/// <summary>
463 		/// Mapping from level object to EventLogEntryType
464 		/// </summary>
465 		private LevelMapping m_levelMapping = new LevelMapping();
466 
467 		/// <summary>
468 		/// The security context to use for privileged calls
469 		/// </summary>
470 		private SecurityContext m_securityContext;
471 
472 		#endregion // Private Instance Fields
473 
474 		#region Level2EventLogEntryType LevelMapping Entry
475 
476 		/// <summary>
477 		/// A class to act as a mapping between the level that a logging call is made at and
478 		/// the color it should be displayed as.
479 		/// </summary>
480 		/// <remarks>
481 		/// <para>
482 		/// Defines the mapping between a level and its event log entry type.
483 		/// </para>
484 		/// </remarks>
485 		public class Level2EventLogEntryType : LevelMappingEntry
486 		{
487 			private EventLogEntryType m_entryType;
488 
489 			/// <summary>
490 			/// The <see cref="EventLogEntryType"/> for this entry
491 			/// </summary>
492 			/// <remarks>
493 			/// <para>
494 			/// Required property.
495 			/// The <see cref="EventLogEntryType"/> for this entry
496 			/// </para>
497 			/// </remarks>
498 			public EventLogEntryType EventLogEntryType
499 			{
500 				get { return m_entryType; }
501 				set { m_entryType = value; }
502 			}
503 		}
504 
505 		#endregion // LevelColors LevelMapping Entry
506 	}
507 }
508 
509 #endif // !SSCLI
510 #endif // !NETCF
511