XmlHierarchyConfigurator.cs 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163
  1. #region Apache License
  2. //
  3. // Licensed to the Apache Software Foundation (ASF) under one or more
  4. // contributor license agreements. See the NOTICE file distributed with
  5. // this work for additional information regarding copyright ownership.
  6. // The ASF licenses this file to you under the Apache License, Version 2.0
  7. // (the "License"); you may not use this file except in compliance with
  8. // the License. You may obtain a copy of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS,
  14. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. // See the License for the specific language governing permissions and
  16. // limitations under the License.
  17. //
  18. #endregion
  19. using System;
  20. using System.Collections;
  21. using System.Globalization;
  22. using System.Reflection;
  23. using System.Xml;
  24. using log4net.Appender;
  25. using log4net.Util;
  26. using log4net.Core;
  27. using log4net.ObjectRenderer;
  28. namespace log4net.Repository.Hierarchy
  29. {
  30. /// <summary>
  31. /// Initializes the log4net environment using an XML DOM.
  32. /// </summary>
  33. /// <remarks>
  34. /// <para>
  35. /// Configures a <see cref="Hierarchy"/> using an XML DOM.
  36. /// </para>
  37. /// </remarks>
  38. /// <author>Nicko Cadell</author>
  39. /// <author>Gert Driesen</author>
  40. public class XmlHierarchyConfigurator
  41. {
  42. private enum ConfigUpdateMode
  43. {
  44. Merge,
  45. Overwrite
  46. }
  47. #region Public Instance Constructors
  48. /// <summary>
  49. /// Construct the configurator for a hierarchy
  50. /// </summary>
  51. /// <param name="hierarchy">The hierarchy to build.</param>
  52. /// <remarks>
  53. /// <para>
  54. /// Initializes a new instance of the <see cref="XmlHierarchyConfigurator" /> class
  55. /// with the specified <see cref="Hierarchy" />.
  56. /// </para>
  57. /// </remarks>
  58. public XmlHierarchyConfigurator(Hierarchy hierarchy)
  59. {
  60. m_hierarchy = hierarchy;
  61. m_appenderBag = new Hashtable();
  62. }
  63. #endregion Public Instance Constructors
  64. #region Public Instance Methods
  65. /// <summary>
  66. /// Configure the hierarchy by parsing a DOM tree of XML elements.
  67. /// </summary>
  68. /// <param name="element">The root element to parse.</param>
  69. /// <remarks>
  70. /// <para>
  71. /// Configure the hierarchy by parsing a DOM tree of XML elements.
  72. /// </para>
  73. /// </remarks>
  74. public void Configure(XmlElement element)
  75. {
  76. if (element == null || m_hierarchy == null)
  77. {
  78. return;
  79. }
  80. string rootElementName = element.LocalName;
  81. if (rootElementName != CONFIGURATION_TAG)
  82. {
  83. LogLog.Error(declaringType, "Xml element is - not a <" + CONFIGURATION_TAG + "> element.");
  84. return;
  85. }
  86. if (!LogLog.EmitInternalMessages)
  87. {
  88. // Look for a emitDebug attribute to enable internal debug
  89. string emitDebugAttribute = element.GetAttribute(EMIT_INTERNAL_DEBUG_ATTR);
  90. LogLog.Debug(declaringType, EMIT_INTERNAL_DEBUG_ATTR + " attribute [" + emitDebugAttribute + "].");
  91. if (emitDebugAttribute.Length > 0 && emitDebugAttribute != "null")
  92. {
  93. LogLog.EmitInternalMessages = OptionConverter.ToBoolean(emitDebugAttribute, true);
  94. }
  95. else
  96. {
  97. LogLog.Debug(declaringType, "Ignoring " + EMIT_INTERNAL_DEBUG_ATTR + " attribute.");
  98. }
  99. }
  100. if (!LogLog.InternalDebugging)
  101. {
  102. // Look for a debug attribute to enable internal debug
  103. string debugAttribute = element.GetAttribute(INTERNAL_DEBUG_ATTR);
  104. LogLog.Debug(declaringType, INTERNAL_DEBUG_ATTR+" attribute [" + debugAttribute + "].");
  105. if (debugAttribute.Length>0 && debugAttribute != "null")
  106. {
  107. LogLog.InternalDebugging = OptionConverter.ToBoolean(debugAttribute, true);
  108. }
  109. else
  110. {
  111. LogLog.Debug(declaringType, "Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
  112. }
  113. string confDebug = element.GetAttribute(CONFIG_DEBUG_ATTR);
  114. if (confDebug.Length>0 && confDebug != "null")
  115. {
  116. LogLog.Warn(declaringType, "The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
  117. LogLog.Warn(declaringType, "Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
  118. LogLog.InternalDebugging = OptionConverter.ToBoolean(confDebug, true);
  119. }
  120. }
  121. // Default mode is merge
  122. ConfigUpdateMode configUpdateMode = ConfigUpdateMode.Merge;
  123. // Look for the config update attribute
  124. string configUpdateModeAttribute = element.GetAttribute(CONFIG_UPDATE_MODE_ATTR);
  125. if (configUpdateModeAttribute != null && configUpdateModeAttribute.Length > 0)
  126. {
  127. // Parse the attribute
  128. try
  129. {
  130. configUpdateMode = (ConfigUpdateMode)OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute);
  131. }
  132. catch
  133. {
  134. LogLog.Error(declaringType, "Invalid " + CONFIG_UPDATE_MODE_ATTR + " attribute value [" + configUpdateModeAttribute + "]");
  135. }
  136. }
  137. // IMPL: The IFormatProvider argument to Enum.ToString() is deprecated in .NET 2.0
  138. LogLog.Debug(declaringType, "Configuration update mode [" + configUpdateMode.ToString() + "].");
  139. // Only reset configuration if overwrite flag specified
  140. if (configUpdateMode == ConfigUpdateMode.Overwrite)
  141. {
  142. // Reset to original unset configuration
  143. m_hierarchy.ResetConfiguration();
  144. LogLog.Debug(declaringType, "Configuration reset before reading config.");
  145. }
  146. /* Building Appender objects, placing them in a local namespace
  147. for future reference */
  148. /* Process all the top level elements */
  149. foreach (XmlNode currentNode in element.ChildNodes)
  150. {
  151. if (currentNode.NodeType == XmlNodeType.Element)
  152. {
  153. XmlElement currentElement = (XmlElement)currentNode;
  154. if (currentElement.LocalName == LOGGER_TAG)
  155. {
  156. ParseLogger(currentElement);
  157. }
  158. else if (currentElement.LocalName == CATEGORY_TAG)
  159. {
  160. // TODO: deprecated use of category
  161. ParseLogger(currentElement);
  162. }
  163. else if (currentElement.LocalName == ROOT_TAG)
  164. {
  165. ParseRoot(currentElement);
  166. }
  167. else if (currentElement.LocalName == RENDERER_TAG)
  168. {
  169. ParseRenderer(currentElement);
  170. }
  171. else if (currentElement.LocalName == APPENDER_TAG)
  172. {
  173. // We ignore appenders in this pass. They will
  174. // be found and loaded if they are referenced.
  175. }
  176. else
  177. {
  178. // Read the param tags and set properties on the hierarchy
  179. SetParameter(currentElement, m_hierarchy);
  180. }
  181. }
  182. }
  183. // Lastly set the hierarchy threshold
  184. string thresholdStr = element.GetAttribute(THRESHOLD_ATTR);
  185. LogLog.Debug(declaringType, "Hierarchy Threshold [" + thresholdStr + "]");
  186. if (thresholdStr.Length > 0 && thresholdStr != "null")
  187. {
  188. Level thresholdLevel = (Level) ConvertStringTo(typeof(Level), thresholdStr);
  189. if (thresholdLevel != null)
  190. {
  191. m_hierarchy.Threshold = thresholdLevel;
  192. }
  193. else
  194. {
  195. LogLog.Warn(declaringType, "Unable to set hierarchy threshold using value [" + thresholdStr + "] (with acceptable conversion types)");
  196. }
  197. }
  198. // Done reading config
  199. }
  200. #endregion Public Instance Methods
  201. #region Protected Instance Methods
  202. /// <summary>
  203. /// Parse appenders by IDREF.
  204. /// </summary>
  205. /// <param name="appenderRef">The appender ref element.</param>
  206. /// <returns>The instance of the appender that the ref refers to.</returns>
  207. /// <remarks>
  208. /// <para>
  209. /// Parse an XML element that represents an appender and return
  210. /// the appender.
  211. /// </para>
  212. /// </remarks>
  213. protected IAppender FindAppenderByReference(XmlElement appenderRef)
  214. {
  215. string appenderName = appenderRef.GetAttribute(REF_ATTR);
  216. IAppender appender = (IAppender)m_appenderBag[appenderName];
  217. if (appender != null)
  218. {
  219. return appender;
  220. }
  221. else
  222. {
  223. // Find the element with that id
  224. XmlElement element = null;
  225. if (appenderName != null && appenderName.Length > 0)
  226. {
  227. foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(APPENDER_TAG))
  228. {
  229. if (curAppenderElement.GetAttribute("name") == appenderName)
  230. {
  231. element = curAppenderElement;
  232. break;
  233. }
  234. }
  235. }
  236. if (element == null)
  237. {
  238. LogLog.Error(declaringType, "XmlHierarchyConfigurator: No appender named [" + appenderName + "] could be found.");
  239. return null;
  240. }
  241. else
  242. {
  243. appender = ParseAppender(element);
  244. if (appender != null)
  245. {
  246. m_appenderBag[appenderName] = appender;
  247. }
  248. return appender;
  249. }
  250. }
  251. }
  252. /// <summary>
  253. /// Parses an appender element.
  254. /// </summary>
  255. /// <param name="appenderElement">The appender element.</param>
  256. /// <returns>The appender instance or <c>null</c> when parsing failed.</returns>
  257. /// <remarks>
  258. /// <para>
  259. /// Parse an XML element that represents an appender and return
  260. /// the appender instance.
  261. /// </para>
  262. /// </remarks>
  263. protected IAppender ParseAppender(XmlElement appenderElement)
  264. {
  265. string appenderName = appenderElement.GetAttribute(NAME_ATTR);
  266. string typeName = appenderElement.GetAttribute(TYPE_ATTR);
  267. LogLog.Debug(declaringType, "Loading Appender [" + appenderName + "] type: [" + typeName + "]");
  268. try
  269. {
  270. #if NETSTANDARD1_3
  271. IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, typeName, true, true));
  272. #else
  273. IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true));
  274. #endif
  275. appender.Name = appenderName;
  276. foreach (XmlNode currentNode in appenderElement.ChildNodes)
  277. {
  278. /* We're only interested in Elements */
  279. if (currentNode.NodeType == XmlNodeType.Element)
  280. {
  281. XmlElement currentElement = (XmlElement)currentNode;
  282. // Look for the appender ref tag
  283. if (currentElement.LocalName == APPENDER_REF_TAG)
  284. {
  285. string refName = currentElement.GetAttribute(REF_ATTR);
  286. IAppenderAttachable appenderContainer = appender as IAppenderAttachable;
  287. if (appenderContainer != null)
  288. {
  289. LogLog.Debug(declaringType, "Attaching appender named [" + refName + "] to appender named [" + appender.Name + "].");
  290. IAppender referencedAppender = FindAppenderByReference(currentElement);
  291. if (referencedAppender != null)
  292. {
  293. appenderContainer.AddAppender(referencedAppender);
  294. }
  295. }
  296. else
  297. {
  298. LogLog.Error(declaringType, "Requesting attachment of appender named ["+refName+ "] to appender named [" + appender.Name + "] which does not implement log4net.Core.IAppenderAttachable.");
  299. }
  300. }
  301. else
  302. {
  303. // For all other tags we use standard set param method
  304. SetParameter(currentElement, appender);
  305. }
  306. }
  307. }
  308. IOptionHandler optionHandler = appender as IOptionHandler;
  309. if (optionHandler != null)
  310. {
  311. optionHandler.ActivateOptions();
  312. }
  313. LogLog.Debug(declaringType, "Created Appender [" + appenderName + "]");
  314. return appender;
  315. }
  316. catch (Exception ex)
  317. {
  318. // Yes, it's ugly. But all exceptions point to the same problem: we can't create an Appender
  319. LogLog.Error(declaringType, "Could not create Appender [" + appenderName + "] of type [" + typeName + "]. Reported error follows.", ex);
  320. return null;
  321. }
  322. }
  323. /// <summary>
  324. /// Parses a logger element.
  325. /// </summary>
  326. /// <param name="loggerElement">The logger element.</param>
  327. /// <remarks>
  328. /// <para>
  329. /// Parse an XML element that represents a logger.
  330. /// </para>
  331. /// </remarks>
  332. protected void ParseLogger(XmlElement loggerElement)
  333. {
  334. // Create a new log4net.Logger object from the <logger> element.
  335. string loggerName = loggerElement.GetAttribute(NAME_ATTR);
  336. LogLog.Debug(declaringType, "Retrieving an instance of log4net.Repository.Logger for logger [" + loggerName + "].");
  337. Logger log = m_hierarchy.GetLogger(loggerName) as Logger;
  338. // Setting up a logger needs to be an atomic operation, in order
  339. // to protect potential log operations while logger
  340. // configuration is in progress.
  341. lock(log)
  342. {
  343. bool additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(ADDITIVITY_ATTR), true);
  344. LogLog.Debug(declaringType, "Setting [" + log.Name + "] additivity to [" + additivity + "].");
  345. log.Additivity = additivity;
  346. ParseChildrenOfLoggerElement(loggerElement, log, false);
  347. }
  348. }
  349. /// <summary>
  350. /// Parses the root logger element.
  351. /// </summary>
  352. /// <param name="rootElement">The root element.</param>
  353. /// <remarks>
  354. /// <para>
  355. /// Parse an XML element that represents the root logger.
  356. /// </para>
  357. /// </remarks>
  358. protected void ParseRoot(XmlElement rootElement)
  359. {
  360. Logger root = m_hierarchy.Root;
  361. // logger configuration needs to be atomic
  362. lock(root)
  363. {
  364. ParseChildrenOfLoggerElement(rootElement, root, true);
  365. }
  366. }
  367. /// <summary>
  368. /// Parses the children of a logger element.
  369. /// </summary>
  370. /// <param name="catElement">The category element.</param>
  371. /// <param name="log">The logger instance.</param>
  372. /// <param name="isRoot">Flag to indicate if the logger is the root logger.</param>
  373. /// <remarks>
  374. /// <para>
  375. /// Parse the child elements of a &lt;logger&gt; element.
  376. /// </para>
  377. /// </remarks>
  378. protected void ParseChildrenOfLoggerElement(XmlElement catElement, Logger log, bool isRoot)
  379. {
  380. // Remove all existing appenders from log. They will be
  381. // reconstructed if need be.
  382. log.RemoveAllAppenders();
  383. foreach (XmlNode currentNode in catElement.ChildNodes)
  384. {
  385. if (currentNode.NodeType == XmlNodeType.Element)
  386. {
  387. XmlElement currentElement = (XmlElement) currentNode;
  388. if (currentElement.LocalName == APPENDER_REF_TAG)
  389. {
  390. IAppender appender = FindAppenderByReference(currentElement);
  391. string refName = currentElement.GetAttribute(REF_ATTR);
  392. if (appender != null)
  393. {
  394. LogLog.Debug(declaringType, "Adding appender named [" + refName + "] to logger [" + log.Name + "].");
  395. log.AddAppender(appender);
  396. }
  397. else
  398. {
  399. LogLog.Error(declaringType, "Appender named [" + refName + "] not found.");
  400. }
  401. }
  402. else if (currentElement.LocalName == LEVEL_TAG || currentElement.LocalName == PRIORITY_TAG)
  403. {
  404. ParseLevel(currentElement, log, isRoot);
  405. }
  406. else
  407. {
  408. SetParameter(currentElement, log);
  409. }
  410. }
  411. }
  412. IOptionHandler optionHandler = log as IOptionHandler;
  413. if (optionHandler != null)
  414. {
  415. optionHandler.ActivateOptions();
  416. }
  417. }
  418. /// <summary>
  419. /// Parses an object renderer.
  420. /// </summary>
  421. /// <param name="element">The renderer element.</param>
  422. /// <remarks>
  423. /// <para>
  424. /// Parse an XML element that represents a renderer.
  425. /// </para>
  426. /// </remarks>
  427. protected void ParseRenderer(XmlElement element)
  428. {
  429. string renderingClassName = element.GetAttribute(RENDERING_TYPE_ATTR);
  430. string renderedClassName = element.GetAttribute(RENDERED_TYPE_ATTR);
  431. LogLog.Debug(declaringType, "Rendering class [" + renderingClassName + "], Rendered class [" + renderedClassName + "].");
  432. IObjectRenderer renderer = (IObjectRenderer)OptionConverter.InstantiateByClassName(renderingClassName, typeof(IObjectRenderer), null);
  433. if (renderer == null)
  434. {
  435. LogLog.Error(declaringType, "Could not instantiate renderer [" + renderingClassName + "].");
  436. return;
  437. }
  438. else
  439. {
  440. try
  441. {
  442. #if NETSTANDARD1_3
  443. m_hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, renderedClassName, true, true), renderer);
  444. #else
  445. m_hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true), renderer);
  446. #endif
  447. }
  448. catch(Exception e)
  449. {
  450. LogLog.Error(declaringType, "Could not find class [" + renderedClassName + "].", e);
  451. }
  452. }
  453. }
  454. /// <summary>
  455. /// Parses a level element.
  456. /// </summary>
  457. /// <param name="element">The level element.</param>
  458. /// <param name="log">The logger object to set the level on.</param>
  459. /// <param name="isRoot">Flag to indicate if the logger is the root logger.</param>
  460. /// <remarks>
  461. /// <para>
  462. /// Parse an XML element that represents a level.
  463. /// </para>
  464. /// </remarks>
  465. protected void ParseLevel(XmlElement element, Logger log, bool isRoot)
  466. {
  467. string loggerName = log.Name;
  468. if (isRoot)
  469. {
  470. loggerName = "root";
  471. }
  472. string levelStr = element.GetAttribute(VALUE_ATTR);
  473. LogLog.Debug(declaringType, "Logger [" + loggerName + "] Level string is [" + levelStr + "].");
  474. if (INHERITED == levelStr)
  475. {
  476. if (isRoot)
  477. {
  478. LogLog.Error(declaringType, "Root level cannot be inherited. Ignoring directive.");
  479. }
  480. else
  481. {
  482. LogLog.Debug(declaringType, "Logger [" + loggerName + "] level set to inherit from parent.");
  483. log.Level = null;
  484. }
  485. }
  486. else
  487. {
  488. log.Level = log.Hierarchy.LevelMap[levelStr];
  489. if (log.Level == null)
  490. {
  491. LogLog.Error(declaringType, "Undefined level [" + levelStr + "] on Logger [" + loggerName + "].");
  492. }
  493. else
  494. {
  495. LogLog.Debug(declaringType, "Logger [" + loggerName + "] level set to [name=\"" + log.Level.Name + "\",value=" + log.Level.Value + "].");
  496. }
  497. }
  498. }
  499. /// <summary>
  500. /// Sets a parameter on an object.
  501. /// </summary>
  502. /// <param name="element">The parameter element.</param>
  503. /// <param name="target">The object to set the parameter on.</param>
  504. /// <remarks>
  505. /// The parameter name must correspond to a writable property
  506. /// on the object. The value of the parameter is a string,
  507. /// therefore this function will attempt to set a string
  508. /// property first. If unable to set a string property it
  509. /// will inspect the property and its argument type. It will
  510. /// attempt to call a static method called <c>Parse</c> on the
  511. /// type of the property. This method will take a single
  512. /// string argument and return a value that can be used to
  513. /// set the property.
  514. /// </remarks>
  515. protected void SetParameter(XmlElement element, object target)
  516. {
  517. // Get the property name
  518. string name = element.GetAttribute(NAME_ATTR);
  519. // If the name attribute does not exist then use the name of the element
  520. if (element.LocalName != PARAM_TAG || name == null || name.Length == 0)
  521. {
  522. name = element.LocalName;
  523. }
  524. // Look for the property on the target object
  525. Type targetType = target.GetType();
  526. Type propertyType = null;
  527. PropertyInfo propInfo = null;
  528. MethodInfo methInfo = null;
  529. // Try to find a writable property
  530. propInfo = targetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
  531. if (propInfo != null && propInfo.CanWrite)
  532. {
  533. // found a property
  534. propertyType = propInfo.PropertyType;
  535. }
  536. else
  537. {
  538. propInfo = null;
  539. // look for a method with the signature Add<property>(type)
  540. methInfo = FindMethodInfo(targetType, name);
  541. if (methInfo != null)
  542. {
  543. propertyType = methInfo.GetParameters()[0].ParameterType;
  544. }
  545. }
  546. if (propertyType == null)
  547. {
  548. LogLog.Error(declaringType, "XmlHierarchyConfigurator: Cannot find Property [" + name + "] to set object on [" + target.ToString() + "]");
  549. }
  550. else
  551. {
  552. string propertyValue = null;
  553. if (element.GetAttributeNode(VALUE_ATTR) != null)
  554. {
  555. propertyValue = element.GetAttribute(VALUE_ATTR);
  556. }
  557. else if (element.HasChildNodes)
  558. {
  559. // Concatenate the CDATA and Text nodes together
  560. foreach(XmlNode childNode in element.ChildNodes)
  561. {
  562. if (childNode.NodeType == XmlNodeType.CDATA || childNode.NodeType == XmlNodeType.Text)
  563. {
  564. if (propertyValue == null)
  565. {
  566. propertyValue = childNode.InnerText;
  567. }
  568. else
  569. {
  570. propertyValue += childNode.InnerText;
  571. }
  572. }
  573. }
  574. }
  575. if(propertyValue != null)
  576. {
  577. #if !(NETCF || NETSTANDARD1_3) // NETSTANDARD1_3: System.Runtime.InteropServices.RuntimeInformation not available on desktop 4.6
  578. try
  579. {
  580. // Expand environment variables in the string.
  581. IDictionary environmentVariables = Environment.GetEnvironmentVariables();
  582. if (HasCaseInsensitiveEnvironment) {
  583. environmentVariables = CreateCaseInsensitiveWrapper(environmentVariables);
  584. }
  585. propertyValue = OptionConverter.SubstituteVariables(propertyValue, environmentVariables);
  586. }
  587. catch(System.Security.SecurityException)
  588. {
  589. // This security exception will occur if the caller does not have
  590. // unrestricted environment permission. If this occurs the expansion
  591. // will be skipped with the following warning message.
  592. LogLog.Debug(declaringType, "Security exception while trying to expand environment variables. Error Ignored. No Expansion.");
  593. }
  594. #endif
  595. Type parsedObjectConversionTargetType = null;
  596. // Check if a specific subtype is specified on the element using the 'type' attribute
  597. string subTypeString = element.GetAttribute(TYPE_ATTR);
  598. if (subTypeString != null && subTypeString.Length > 0)
  599. {
  600. // Read the explicit subtype
  601. try
  602. {
  603. #if NETSTANDARD1_3
  604. Type subType = SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, subTypeString, true, true);
  605. #else
  606. Type subType = SystemInfo.GetTypeFromString(subTypeString, true, true);
  607. #endif
  608. LogLog.Debug(declaringType, "Parameter ["+name+"] specified subtype ["+subType.FullName+"]");
  609. if (!propertyType.IsAssignableFrom(subType))
  610. {
  611. // Check if there is an appropriate type converter
  612. if (OptionConverter.CanConvertTypeTo(subType, propertyType))
  613. {
  614. // Must re-convert to the real property type
  615. parsedObjectConversionTargetType = propertyType;
  616. // Use sub type as intermediary type
  617. propertyType = subType;
  618. }
  619. else
  620. {
  621. LogLog.Error(declaringType, "subtype ["+subType.FullName+"] set on ["+name+"] is not a subclass of property type ["+propertyType.FullName+"] and there are no acceptable type conversions.");
  622. }
  623. }
  624. else
  625. {
  626. // The subtype specified is found and is actually a subtype of the property
  627. // type, therefore we can switch to using this type.
  628. propertyType = subType;
  629. }
  630. }
  631. catch(Exception ex)
  632. {
  633. LogLog.Error(declaringType, "Failed to find type ["+subTypeString+"] set on ["+name+"]", ex);
  634. }
  635. }
  636. // Now try to convert the string value to an acceptable type
  637. // to pass to this property.
  638. object convertedValue = ConvertStringTo(propertyType, propertyValue);
  639. // Check if we need to do an additional conversion
  640. if (convertedValue != null && parsedObjectConversionTargetType != null)
  641. {
  642. LogLog.Debug(declaringType, "Performing additional conversion of value from [" + convertedValue.GetType().Name + "] to [" + parsedObjectConversionTargetType.Name + "]");
  643. convertedValue = OptionConverter.ConvertTypeTo(convertedValue, parsedObjectConversionTargetType);
  644. }
  645. if (convertedValue != null)
  646. {
  647. if (propInfo != null)
  648. {
  649. // Got a converted result
  650. LogLog.Debug(declaringType, "Setting Property [" + propInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue.ToString() + "]");
  651. try
  652. {
  653. // Pass to the property
  654. #if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
  655. propInfo.SetValue(target, convertedValue, null);
  656. #else
  657. propInfo.SetValue(target, convertedValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
  658. #endif
  659. }
  660. catch(TargetInvocationException targetInvocationEx)
  661. {
  662. LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
  663. }
  664. }
  665. else if (methInfo != null)
  666. {
  667. // Got a converted result
  668. LogLog.Debug(declaringType, "Setting Collection Property [" + methInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue.ToString() + "]");
  669. try
  670. {
  671. // Pass to the property
  672. #if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
  673. methInfo.Invoke(target, new[] { convertedValue });
  674. #else
  675. methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] {convertedValue}, CultureInfo.InvariantCulture);
  676. #endif
  677. }
  678. catch(TargetInvocationException targetInvocationEx)
  679. {
  680. LogLog.Error(declaringType, "Failed to set parameter [" + name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
  681. }
  682. }
  683. }
  684. else
  685. {
  686. LogLog.Warn(declaringType, "Unable to set property [" + name + "] on object [" + target + "] using value [" + propertyValue + "] (with acceptable conversion types)");
  687. }
  688. }
  689. else
  690. {
  691. object createdObject = null;
  692. if (propertyType == typeof(string) && !HasAttributesOrElements(element))
  693. {
  694. // If the property is a string and the element is empty (no attributes
  695. // or child elements) then we special case the object value to an empty string.
  696. // This is necessary because while the String is a class it does not have
  697. // a default constructor that creates an empty string, which is the behavior
  698. // we are trying to simulate and would be expected from CreateObjectFromXml
  699. createdObject = "";
  700. }
  701. else
  702. {
  703. // No value specified
  704. Type defaultObjectType = null;
  705. if (IsTypeConstructible(propertyType))
  706. {
  707. defaultObjectType = propertyType;
  708. }
  709. createdObject = CreateObjectFromXml(element, defaultObjectType, propertyType);
  710. }
  711. if (createdObject == null)
  712. {
  713. LogLog.Error(declaringType, "Failed to create object to set param: "+name);
  714. }
  715. else
  716. {
  717. if (propInfo != null)
  718. {
  719. // Got a converted result
  720. LogLog.Debug(declaringType, "Setting Property ["+ propInfo.Name +"] to object ["+ createdObject +"]");
  721. try
  722. {
  723. // Pass to the property
  724. #if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
  725. propInfo.SetValue(target, createdObject, null);
  726. #else
  727. propInfo.SetValue(target, createdObject, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
  728. #endif
  729. }
  730. catch(TargetInvocationException targetInvocationEx)
  731. {
  732. LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
  733. }
  734. }
  735. else if (methInfo != null)
  736. {
  737. // Got a converted result
  738. LogLog.Debug(declaringType, "Setting Collection Property ["+ methInfo.Name +"] to object ["+ createdObject +"]");
  739. try
  740. {
  741. // Pass to the property
  742. #if NETSTANDARD1_3 // TODO BindingFlags is available for netstandard1.5
  743. methInfo.Invoke(target, new[] { createdObject });
  744. #else
  745. methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] {createdObject}, CultureInfo.InvariantCulture);
  746. #endif
  747. }
  748. catch(TargetInvocationException targetInvocationEx)
  749. {
  750. LogLog.Error(declaringType, "Failed to set parameter [" + methInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
  751. }
  752. }
  753. }
  754. }
  755. }
  756. }
  757. /// <summary>
  758. /// Test if an element has no attributes or child elements
  759. /// </summary>
  760. /// <param name="element">the element to inspect</param>
  761. /// <returns><c>true</c> if the element has any attributes or child elements, <c>false</c> otherwise</returns>
  762. private bool HasAttributesOrElements(XmlElement element)
  763. {
  764. foreach(XmlNode node in element.ChildNodes)
  765. {
  766. if (node.NodeType == XmlNodeType.Attribute || node.NodeType == XmlNodeType.Element)
  767. {
  768. return true;
  769. }
  770. }
  771. return false;
  772. }
  773. /// <summary>
  774. /// Test if a <see cref="Type"/> is constructible with <c>Activator.CreateInstance</c>.
  775. /// </summary>
  776. /// <param name="type">the type to inspect</param>
  777. /// <returns><c>true</c> if the type is creatable using a default constructor, <c>false</c> otherwise</returns>
  778. private static bool IsTypeConstructible(Type type)
  779. {
  780. #if NETSTANDARD1_3
  781. TypeInfo typeInfo = type.GetTypeInfo();
  782. if (typeInfo.IsClass && !typeInfo.IsAbstract)
  783. #else
  784. if (type.IsClass && !type.IsAbstract)
  785. #endif
  786. {
  787. ConstructorInfo defaultConstructor = type.GetConstructor(new Type[0]);
  788. if (defaultConstructor != null && !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate)
  789. {
  790. return true;
  791. }
  792. }
  793. return false;
  794. }
  795. /// <summary>
  796. /// Look for a method on the <paramref name="targetType"/> that matches the <paramref name="name"/> supplied
  797. /// </summary>
  798. /// <param name="targetType">the type that has the method</param>
  799. /// <param name="name">the name of the method</param>
  800. /// <returns>the method info found</returns>
  801. /// <remarks>
  802. /// <para>
  803. /// The method must be a public instance method on the <paramref name="targetType"/>.
  804. /// The method must be named <paramref name="name"/> or "Add" followed by <paramref name="name"/>.
  805. /// The method must take a single parameter.
  806. /// </para>
  807. /// </remarks>
  808. private MethodInfo FindMethodInfo(Type targetType, string name)
  809. {
  810. string requiredMethodNameA = name;
  811. string requiredMethodNameB = "Add" + name;
  812. MethodInfo[] methods = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  813. foreach(MethodInfo methInfo in methods)
  814. {
  815. if (!methInfo.IsStatic)
  816. {
  817. string methodInfoName = methInfo.Name;
  818. if (SystemInfo.EqualsIgnoringCase(methodInfoName, requiredMethodNameA) ||
  819. SystemInfo.EqualsIgnoringCase(methodInfoName, requiredMethodNameB))
  820. {
  821. // Found matching method name
  822. // Look for version with one arg only
  823. System.Reflection.ParameterInfo[] methParams = methInfo.GetParameters();
  824. if (methParams.Length == 1)
  825. {
  826. return methInfo;
  827. }
  828. }
  829. }
  830. }
  831. return null;
  832. }
  833. /// <summary>
  834. /// Converts a string value to a target type.
  835. /// </summary>
  836. /// <param name="type">The type of object to convert the string to.</param>
  837. /// <param name="value">The string value to use as the value of the object.</param>
  838. /// <returns>
  839. /// <para>
  840. /// An object of type <paramref name="type"/> with value <paramref name="value"/> or
  841. /// <c>null</c> when the conversion could not be performed.
  842. /// </para>
  843. /// </returns>
  844. protected object ConvertStringTo(Type type, string value)
  845. {
  846. // Hack to allow use of Level in property
  847. if (typeof(Level) == type)
  848. {
  849. // Property wants a level
  850. Level levelValue = m_hierarchy.LevelMap[value];
  851. if (levelValue == null)
  852. {
  853. LogLog.Error(declaringType, "XmlHierarchyConfigurator: Unknown Level Specified ["+ value +"]");
  854. }
  855. return levelValue;
  856. }
  857. return OptionConverter.ConvertStringTo(type, value);
  858. }
  859. /// <summary>
  860. /// Creates an object as specified in XML.
  861. /// </summary>
  862. /// <param name="element">The XML element that contains the definition of the object.</param>
  863. /// <param name="defaultTargetType">The object type to use if not explicitly specified.</param>
  864. /// <param name="typeConstraint">The type that the returned object must be or must inherit from.</param>
  865. /// <returns>The object or <c>null</c></returns>
  866. /// <remarks>
  867. /// <para>
  868. /// Parse an XML element and create an object instance based on the configuration
  869. /// data.
  870. /// </para>
  871. /// <para>
  872. /// The type of the instance may be specified in the XML. If not
  873. /// specified then the <paramref name="defaultTargetType"/> is used
  874. /// as the type. However the type is specified it must support the
  875. /// <paramref name="typeConstraint"/> type.
  876. /// </para>
  877. /// </remarks>
  878. protected object CreateObjectFromXml(XmlElement element, Type defaultTargetType, Type typeConstraint)
  879. {
  880. Type objectType = null;
  881. // Get the object type
  882. string objectTypeString = element.GetAttribute(TYPE_ATTR);
  883. if (objectTypeString == null || objectTypeString.Length == 0)
  884. {
  885. if (defaultTargetType == null)
  886. {
  887. LogLog.Error(declaringType, "Object type not specified. Cannot create object of type ["+typeConstraint.FullName+"]. Missing Value or Type.");
  888. return null;
  889. }
  890. else
  891. {
  892. // Use the default object type
  893. objectType = defaultTargetType;
  894. }
  895. }
  896. else
  897. {
  898. // Read the explicit object type
  899. try
  900. {
  901. #if NETSTANDARD1_3
  902. objectType = SystemInfo.GetTypeFromString(this.GetType().GetTypeInfo().Assembly, objectTypeString, true, true);
  903. #else
  904. objectType = SystemInfo.GetTypeFromString(objectTypeString, true, true);
  905. #endif
  906. }
  907. catch(Exception ex)
  908. {
  909. LogLog.Error(declaringType, "Failed to find type ["+objectTypeString+"]", ex);
  910. return null;
  911. }
  912. }
  913. bool requiresConversion = false;
  914. // Got the object type. Check that it meets the typeConstraint
  915. if (typeConstraint != null)
  916. {
  917. if (!typeConstraint.IsAssignableFrom(objectType))
  918. {
  919. // Check if there is an appropriate type converter
  920. if (OptionConverter.CanConvertTypeTo(objectType, typeConstraint))
  921. {
  922. requiresConversion = true;
  923. }
  924. else
  925. {
  926. LogLog.Error(declaringType, "Object type ["+objectType.FullName+"] is not assignable to type ["+typeConstraint.FullName+"]. There are no acceptable type conversions.");
  927. return null;
  928. }
  929. }
  930. }
  931. // Create using the default constructor
  932. object createdObject = null;
  933. try
  934. {
  935. createdObject = Activator.CreateInstance(objectType);
  936. }
  937. catch(Exception createInstanceEx)
  938. {
  939. LogLog.Error(declaringType, "XmlHierarchyConfigurator: Failed to construct object of type [" + objectType.FullName + "] Exception: "+createInstanceEx.ToString());
  940. }
  941. // Set any params on object
  942. foreach (XmlNode currentNode in element.ChildNodes)
  943. {
  944. if (currentNode.NodeType == XmlNodeType.Element)
  945. {
  946. SetParameter((XmlElement)currentNode, createdObject);
  947. }
  948. }
  949. // Check if we need to call ActivateOptions
  950. IOptionHandler optionHandler = createdObject as IOptionHandler;
  951. if (optionHandler != null)
  952. {
  953. optionHandler.ActivateOptions();
  954. }
  955. // Ok object should be initialized
  956. if (requiresConversion)
  957. {
  958. // Convert the object type
  959. return OptionConverter.ConvertTypeTo(createdObject, typeConstraint);
  960. }
  961. else
  962. {
  963. // The object is of the correct type
  964. return createdObject;
  965. }
  966. }
  967. #endregion Protected Instance Methods
  968. #if !(NETCF || NETSTANDARD1_3) // NETSTANDARD1_3: System.Runtime.InteropServices.RuntimeInformation not available on desktop 4.6
  969. private bool HasCaseInsensitiveEnvironment
  970. {
  971. get
  972. {
  973. #if NET_1_0 || NET_1_1 || CLI_1_0
  974. // actually there is no guarantee, but we don't know better
  975. return true;
  976. #elif MONO_1_0
  977. // see above
  978. return false;
  979. #else
  980. PlatformID platform = Environment.OSVersion.Platform;
  981. return platform != PlatformID.Unix && platform != PlatformID.MacOSX;
  982. #endif
  983. }
  984. }
  985. private IDictionary CreateCaseInsensitiveWrapper(IDictionary dict)
  986. {
  987. if (dict == null)
  988. {
  989. return dict;
  990. }
  991. Hashtable hash = SystemInfo.CreateCaseInsensitiveHashtable();
  992. foreach (DictionaryEntry entry in dict) {
  993. hash[entry.Key] = entry.Value;
  994. }
  995. return hash;
  996. }
  997. #endif
  998. #region Private Constants
  999. // String constants used while parsing the XML data
  1000. private const string CONFIGURATION_TAG = "log4net";
  1001. private const string RENDERER_TAG = "renderer";
  1002. private const string APPENDER_TAG = "appender";
  1003. private const string APPENDER_REF_TAG = "appender-ref";
  1004. private const string PARAM_TAG = "param";
  1005. // TODO: Deprecate use of category tags
  1006. private const string CATEGORY_TAG = "category";
  1007. // TODO: Deprecate use of priority tag
  1008. private const string PRIORITY_TAG = "priority";
  1009. private const string LOGGER_TAG = "logger";
  1010. private const string NAME_ATTR = "name";
  1011. private const string TYPE_ATTR = "type";
  1012. private const string VALUE_ATTR = "value";
  1013. private const string ROOT_TAG = "root";
  1014. private const string LEVEL_TAG = "level";
  1015. private const string REF_ATTR = "ref";
  1016. private const string ADDITIVITY_ATTR = "additivity";
  1017. private const string THRESHOLD_ATTR = "threshold";
  1018. private const string CONFIG_DEBUG_ATTR = "configDebug";
  1019. private const string INTERNAL_DEBUG_ATTR = "debug";
  1020. private const string EMIT_INTERNAL_DEBUG_ATTR = "emitDebug";
  1021. private const string CONFIG_UPDATE_MODE_ATTR = "update";
  1022. private const string RENDERING_TYPE_ATTR = "renderingClass";
  1023. private const string RENDERED_TYPE_ATTR = "renderedClass";
  1024. // flag used on the level element
  1025. private const string INHERITED = "inherited";
  1026. #endregion Private Constants
  1027. #region Private Instance Fields
  1028. /// <summary>
  1029. /// key: appenderName, value: appender.
  1030. /// </summary>
  1031. private Hashtable m_appenderBag;
  1032. /// <summary>
  1033. /// The Hierarchy being configured.
  1034. /// </summary>
  1035. private readonly Hierarchy m_hierarchy;
  1036. #endregion Private Instance Fields
  1037. #region Private Static Fields
  1038. /// <summary>
  1039. /// The fully qualified type of the XmlHierarchyConfigurator class.
  1040. /// </summary>
  1041. /// <remarks>
  1042. /// Used by the internal logger to record the Type of the
  1043. /// log message.
  1044. /// </remarks>
  1045. private readonly static Type declaringType = typeof(XmlHierarchyConfigurator);
  1046. #endregion Private Static Fields
  1047. }
  1048. }