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 /// <mapping> 54 /// <level value="ERROR" /> 55 /// <eventLogEntryType value="Error" /> 56 /// </mapping> 57 /// <mapping> 58 /// <level value="DEBUG" /> 59 /// <eventLogEntryType value="Information" /> 60 /// </mapping> 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