AdoNetAppender.cs 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151
  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. // SSCLI 1.0 has no support for ADO.NET
  20. #if !SSCLI
  21. using System;
  22. using System.Collections;
  23. using System.Configuration;
  24. using System.Data;
  25. using System.IO;
  26. using log4net.Util;
  27. using log4net.Layout;
  28. using log4net.Core;
  29. namespace log4net.Appender
  30. {
  31. /// <summary>
  32. /// Appender that logs to a database.
  33. /// </summary>
  34. /// <remarks>
  35. /// <para>
  36. /// <see cref="AdoNetAppender"/> appends logging events to a table within a
  37. /// database. The appender can be configured to specify the connection
  38. /// string by setting the <see cref="ConnectionString"/> property.
  39. /// The connection type (provider) can be specified by setting the <see cref="ConnectionType"/>
  40. /// property. For more information on database connection strings for
  41. /// your specific database see <a href="http://www.connectionstrings.com/">http://www.connectionstrings.com/</a>.
  42. /// </para>
  43. /// <para>
  44. /// Records are written into the database either using a prepared
  45. /// statement or a stored procedure. The <see cref="CommandType"/> property
  46. /// is set to <see cref="System.Data.CommandType.Text"/> (<c>System.Data.CommandType.Text</c>) to specify a prepared statement
  47. /// or to <see cref="System.Data.CommandType.StoredProcedure"/> (<c>System.Data.CommandType.StoredProcedure</c>) to specify a stored
  48. /// procedure.
  49. /// </para>
  50. /// <para>
  51. /// The prepared statement text or the name of the stored procedure
  52. /// must be set in the <see cref="CommandText"/> property.
  53. /// </para>
  54. /// <para>
  55. /// The prepared statement or stored procedure can take a number
  56. /// of parameters. Parameters are added using the <see cref="AddParameter"/>
  57. /// method. This adds a single <see cref="AdoNetAppenderParameter"/> to the
  58. /// ordered list of parameters. The <see cref="AdoNetAppenderParameter"/>
  59. /// type may be subclassed if required to provide database specific
  60. /// functionality. The <see cref="AdoNetAppenderParameter"/> specifies
  61. /// the parameter name, database type, size, and how the value should
  62. /// be generated using a <see cref="ILayout"/>.
  63. /// </para>
  64. /// </remarks>
  65. /// <example>
  66. /// An example of a SQL Server table that could be logged to:
  67. /// <code lang="SQL">
  68. /// CREATE TABLE [dbo].[Log] (
  69. /// [ID] [int] IDENTITY (1, 1) NOT NULL ,
  70. /// [Date] [datetime] NOT NULL ,
  71. /// [Thread] [varchar] (255) NOT NULL ,
  72. /// [Level] [varchar] (20) NOT NULL ,
  73. /// [Logger] [varchar] (255) NOT NULL ,
  74. /// [Message] [varchar] (4000) NOT NULL
  75. /// ) ON [PRIMARY]
  76. /// </code>
  77. /// </example>
  78. /// <example>
  79. /// An example configuration to log to the above table:
  80. /// <code lang="XML" escaped="true">
  81. /// <appender name="AdoNetAppender_SqlServer" type="log4net.Appender.AdoNetAppender" >
  82. /// <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  83. /// <connectionString value="data source=SQLSVR;initial catalog=test_log4net;integrated security=false;persist security info=True;User ID=sa;Password=sa" />
  84. /// <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message]) VALUES (@log_date, @thread, @log_level, @logger, @message)" />
  85. /// <parameter>
  86. /// <parameterName value="@log_date" />
  87. /// <dbType value="DateTime" />
  88. /// <layout type="log4net.Layout.PatternLayout" value="%date{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}" />
  89. /// </parameter>
  90. /// <parameter>
  91. /// <parameterName value="@thread" />
  92. /// <dbType value="String" />
  93. /// <size value="255" />
  94. /// <layout type="log4net.Layout.PatternLayout" value="%thread" />
  95. /// </parameter>
  96. /// <parameter>
  97. /// <parameterName value="@log_level" />
  98. /// <dbType value="String" />
  99. /// <size value="50" />
  100. /// <layout type="log4net.Layout.PatternLayout" value="%level" />
  101. /// </parameter>
  102. /// <parameter>
  103. /// <parameterName value="@logger" />
  104. /// <dbType value="String" />
  105. /// <size value="255" />
  106. /// <layout type="log4net.Layout.PatternLayout" value="%logger" />
  107. /// </parameter>
  108. /// <parameter>
  109. /// <parameterName value="@message" />
  110. /// <dbType value="String" />
  111. /// <size value="4000" />
  112. /// <layout type="log4net.Layout.PatternLayout" value="%message" />
  113. /// </parameter>
  114. /// </appender>
  115. /// </code>
  116. /// </example>
  117. /// <author>Julian Biddle</author>
  118. /// <author>Nicko Cadell</author>
  119. /// <author>Gert Driesen</author>
  120. /// <author>Lance Nehring</author>
  121. public class AdoNetAppender : BufferingAppenderSkeleton
  122. {
  123. #region Public Instance Constructors
  124. /// <summary>
  125. /// Initializes a new instance of the <see cref="AdoNetAppender" /> class.
  126. /// </summary>
  127. /// <remarks>
  128. /// Public default constructor to initialize a new instance of this class.
  129. /// </remarks>
  130. public AdoNetAppender()
  131. {
  132. ConnectionType = "System.Data.OleDb.OleDbConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
  133. UseTransactions = true;
  134. CommandType = System.Data.CommandType.Text;
  135. m_parameters = new ArrayList();
  136. ReconnectOnError = false;
  137. }
  138. #endregion // Public Instance Constructors
  139. #region Public Instance Properties
  140. /// <summary>
  141. /// Gets or sets the database connection string that is used to connect to
  142. /// the database.
  143. /// </summary>
  144. /// <value>
  145. /// The database connection string used to connect to the database.
  146. /// </value>
  147. /// <remarks>
  148. /// <para>
  149. /// The connections string is specific to the connection type.
  150. /// See <see cref="ConnectionType"/> for more information.
  151. /// </para>
  152. /// </remarks>
  153. /// <example>Connection string for MS Access via ODBC:
  154. /// <code>"DSN=MS Access Database;UID=admin;PWD=;SystemDB=C:\data\System.mdw;SafeTransactions = 0;FIL=MS Access;DriverID = 25;DBQ=C:\data\train33.mdb"</code>
  155. /// </example>
  156. /// <example>Another connection string for MS Access via ODBC:
  157. /// <code>"Driver={Microsoft Access Driver (*.mdb)};DBQ=C:\Work\cvs_root\log4net-1.2\access.mdb;UID=;PWD=;"</code>
  158. /// </example>
  159. /// <example>Connection string for MS Access via OLE DB:
  160. /// <code>"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Work\cvs_root\log4net-1.2\access.mdb;User Id=;Password=;"</code>
  161. /// </example>
  162. public string ConnectionString
  163. {
  164. get { return m_connectionString; }
  165. set { m_connectionString = value; }
  166. }
  167. /// <summary>
  168. /// The appSettings key from App.Config that contains the connection string.
  169. /// </summary>
  170. public string AppSettingsKey
  171. {
  172. get { return m_appSettingsKey; }
  173. set { m_appSettingsKey = value; }
  174. }
  175. #if NET_2_0
  176. /// <summary>
  177. /// The connectionStrings key from App.Config that contains the connection string.
  178. /// </summary>
  179. /// <remarks>
  180. /// This property requires at least .NET 2.0.
  181. /// </remarks>
  182. public string ConnectionStringName
  183. {
  184. get { return m_connectionStringName; }
  185. set { m_connectionStringName = value; }
  186. }
  187. #endif
  188. /// <summary>
  189. /// Gets or sets the type name of the <see cref="IDbConnection"/> connection
  190. /// that should be created.
  191. /// </summary>
  192. /// <value>
  193. /// The type name of the <see cref="IDbConnection"/> connection.
  194. /// </value>
  195. /// <remarks>
  196. /// <para>
  197. /// The type name of the ADO.NET provider to use.
  198. /// </para>
  199. /// <para>
  200. /// The default is to use the OLE DB provider.
  201. /// </para>
  202. /// </remarks>
  203. /// <example>Use the OLE DB Provider. This is the default value.
  204. /// <code>System.Data.OleDb.OleDbConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</code>
  205. /// </example>
  206. /// <example>Use the MS SQL Server Provider.
  207. /// <code>System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</code>
  208. /// </example>
  209. /// <example>Use the ODBC Provider.
  210. /// <code>Microsoft.Data.Odbc.OdbcConnection,Microsoft.Data.Odbc,version=1.0.3300.0,publicKeyToken=b77a5c561934e089,culture=neutral</code>
  211. /// This is an optional package that you can download from
  212. /// <a href="http://msdn.microsoft.com/downloads">http://msdn.microsoft.com/downloads</a>
  213. /// search for <b>ODBC .NET Data Provider</b>.
  214. /// </example>
  215. /// <example>Use the Oracle Provider.
  216. /// <code>System.Data.OracleClient.OracleConnection, System.Data.OracleClient, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</code>
  217. /// This is an optional package that you can download from
  218. /// <a href="http://msdn.microsoft.com/downloads">http://msdn.microsoft.com/downloads</a>
  219. /// search for <b>.NET Managed Provider for Oracle</b>.
  220. /// </example>
  221. public string ConnectionType
  222. {
  223. get { return m_connectionType; }
  224. set { m_connectionType = value; }
  225. }
  226. /// <summary>
  227. /// Gets or sets the command text that is used to insert logging events
  228. /// into the database.
  229. /// </summary>
  230. /// <value>
  231. /// The command text used to insert logging events into the database.
  232. /// </value>
  233. /// <remarks>
  234. /// <para>
  235. /// Either the text of the prepared statement or the
  236. /// name of the stored procedure to execute to write into
  237. /// the database.
  238. /// </para>
  239. /// <para>
  240. /// The <see cref="CommandType"/> property determines if
  241. /// this text is a prepared statement or a stored procedure.
  242. /// </para>
  243. /// <para>
  244. /// If this property is not set, the command text is retrieved by invoking
  245. /// <see cref="GetLogStatement(LoggingEvent)"/>.
  246. /// </para>
  247. /// </remarks>
  248. public string CommandText
  249. {
  250. get { return m_commandText; }
  251. set { m_commandText = value; }
  252. }
  253. /// <summary>
  254. /// Gets or sets the command type to execute.
  255. /// </summary>
  256. /// <value>
  257. /// The command type to execute.
  258. /// </value>
  259. /// <remarks>
  260. /// <para>
  261. /// This value may be either <see cref="System.Data.CommandType.Text"/> (<c>System.Data.CommandType.Text</c>) to specify
  262. /// that the <see cref="CommandText"/> is a prepared statement to execute,
  263. /// or <see cref="System.Data.CommandType.StoredProcedure"/> (<c>System.Data.CommandType.StoredProcedure</c>) to specify that the
  264. /// <see cref="CommandText"/> property is the name of a stored procedure
  265. /// to execute.
  266. /// </para>
  267. /// <para>
  268. /// The default value is <see cref="System.Data.CommandType.Text"/> (<c>System.Data.CommandType.Text</c>).
  269. /// </para>
  270. /// </remarks>
  271. public CommandType CommandType
  272. {
  273. get { return m_commandType; }
  274. set { m_commandType = value; }
  275. }
  276. /// <summary>
  277. /// Should transactions be used to insert logging events in the database.
  278. /// </summary>
  279. /// <value>
  280. /// <c>true</c> if transactions should be used to insert logging events in
  281. /// the database, otherwise <c>false</c>. The default value is <c>true</c>.
  282. /// </value>
  283. /// <remarks>
  284. /// <para>
  285. /// Gets or sets a value that indicates whether transactions should be used
  286. /// to insert logging events in the database.
  287. /// </para>
  288. /// <para>
  289. /// When set a single transaction will be used to insert the buffered events
  290. /// into the database. Otherwise each event will be inserted without using
  291. /// an explicit transaction.
  292. /// </para>
  293. /// </remarks>
  294. public bool UseTransactions
  295. {
  296. get { return m_useTransactions; }
  297. set { m_useTransactions = value; }
  298. }
  299. /// <summary>
  300. /// Gets or sets the <see cref="SecurityContext"/> used to call the NetSend method.
  301. /// </summary>
  302. /// <value>
  303. /// The <see cref="SecurityContext"/> used to call the NetSend method.
  304. /// </value>
  305. /// <remarks>
  306. /// <para>
  307. /// Unless a <see cref="SecurityContext"/> specified here for this appender
  308. /// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the
  309. /// security context to use. The default behavior is to use the security context
  310. /// of the current thread.
  311. /// </para>
  312. /// </remarks>
  313. public SecurityContext SecurityContext
  314. {
  315. get { return m_securityContext; }
  316. set { m_securityContext = value; }
  317. }
  318. /// <summary>
  319. /// Should this appender try to reconnect to the database on error.
  320. /// </summary>
  321. /// <value>
  322. /// <c>true</c> if the appender should try to reconnect to the database after an
  323. /// error has occurred, otherwise <c>false</c>. The default value is <c>false</c>,
  324. /// i.e. not to try to reconnect.
  325. /// </value>
  326. /// <remarks>
  327. /// <para>
  328. /// The default behaviour is for the appender not to try to reconnect to the
  329. /// database if an error occurs. Subsequent logging events are discarded.
  330. /// </para>
  331. /// <para>
  332. /// To force the appender to attempt to reconnect to the database set this
  333. /// property to <c>true</c>.
  334. /// </para>
  335. /// <note>
  336. /// When the appender attempts to connect to the database there may be a
  337. /// delay of up to the connection timeout specified in the connection string.
  338. /// This delay will block the calling application's thread.
  339. /// Until the connection can be reestablished this potential delay may occur multiple times.
  340. /// </note>
  341. /// </remarks>
  342. public bool ReconnectOnError
  343. {
  344. get { return m_reconnectOnError; }
  345. set { m_reconnectOnError = value; }
  346. }
  347. #endregion // Public Instance Properties
  348. #region Protected Instance Properties
  349. /// <summary>
  350. /// Gets or sets the underlying <see cref="IDbConnection" />.
  351. /// </summary>
  352. /// <value>
  353. /// The underlying <see cref="IDbConnection" />.
  354. /// </value>
  355. /// <remarks>
  356. /// <see cref="AdoNetAppender" /> creates a <see cref="IDbConnection" /> to insert
  357. /// logging events into a database. Classes deriving from <see cref="AdoNetAppender" />
  358. /// can use this property to get or set this <see cref="IDbConnection" />. Use the
  359. /// underlying <see cref="IDbConnection" /> returned from <see cref="Connection" /> if
  360. /// you require access beyond that which <see cref="AdoNetAppender" /> provides.
  361. /// </remarks>
  362. protected IDbConnection Connection
  363. {
  364. get { return m_dbConnection; }
  365. set { m_dbConnection = value; }
  366. }
  367. #endregion // Protected Instance Properties
  368. #region Implementation of IOptionHandler
  369. /// <summary>
  370. /// Initialize the appender based on the options set
  371. /// </summary>
  372. /// <remarks>
  373. /// <para>
  374. /// This is part of the <see cref="IOptionHandler"/> delayed object
  375. /// activation scheme. The <see cref="ActivateOptions"/> method must
  376. /// be called on this object after the configuration properties have
  377. /// been set. Until <see cref="ActivateOptions"/> is called this
  378. /// object is in an undefined state and must not be used.
  379. /// </para>
  380. /// <para>
  381. /// If any of the configuration properties are modified then
  382. /// <see cref="ActivateOptions"/> must be called again.
  383. /// </para>
  384. /// </remarks>
  385. override public void ActivateOptions()
  386. {
  387. base.ActivateOptions();
  388. if (SecurityContext == null)
  389. {
  390. SecurityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
  391. }
  392. InitializeDatabaseConnection();
  393. }
  394. #endregion
  395. #region Override implementation of AppenderSkeleton
  396. /// <summary>
  397. /// Override the parent method to close the database
  398. /// </summary>
  399. /// <remarks>
  400. /// <para>
  401. /// Closes the database command and database connection.
  402. /// </para>
  403. /// </remarks>
  404. override protected void OnClose()
  405. {
  406. base.OnClose();
  407. DiposeConnection();
  408. }
  409. #endregion
  410. #region Override implementation of BufferingAppenderSkeleton
  411. /// <summary>
  412. /// Inserts the events into the database.
  413. /// </summary>
  414. /// <param name="events">The events to insert into the database.</param>
  415. /// <remarks>
  416. /// <para>
  417. /// Insert all the events specified in the <paramref name="events"/>
  418. /// array into the database.
  419. /// </para>
  420. /// </remarks>
  421. override protected void SendBuffer(LoggingEvent[] events)
  422. {
  423. if (ReconnectOnError && (Connection == null || Connection.State != ConnectionState.Open))
  424. {
  425. LogLog.Debug(declaringType, "Attempting to reconnect to database. Current Connection State: " + ((Connection == null) ? SystemInfo.NullText : Connection.State.ToString()));
  426. InitializeDatabaseConnection();
  427. }
  428. // Check that the connection exists and is open
  429. if (Connection != null && Connection.State == ConnectionState.Open)
  430. {
  431. if (UseTransactions)
  432. {
  433. // Create transaction
  434. // NJC - Do this on 2 lines because it can confuse the debugger
  435. using (IDbTransaction dbTran = Connection.BeginTransaction())
  436. {
  437. try
  438. {
  439. SendBuffer(dbTran, events);
  440. // commit transaction
  441. dbTran.Commit();
  442. }
  443. catch (Exception ex)
  444. {
  445. // rollback the transaction
  446. try
  447. {
  448. dbTran.Rollback();
  449. }
  450. catch (Exception)
  451. {
  452. // Ignore exception
  453. }
  454. // Can't insert into the database. That's a bad thing
  455. ErrorHandler.Error("Exception while writing to database", ex);
  456. }
  457. }
  458. }
  459. else
  460. {
  461. // Send without transaction
  462. SendBuffer(null, events);
  463. }
  464. }
  465. }
  466. #endregion // Override implementation of BufferingAppenderSkeleton
  467. #region Public Instance Methods
  468. /// <summary>
  469. /// Adds a parameter to the command.
  470. /// </summary>
  471. /// <param name="parameter">The parameter to add to the command.</param>
  472. /// <remarks>
  473. /// <para>
  474. /// Adds a parameter to the ordered list of command parameters.
  475. /// </para>
  476. /// </remarks>
  477. public void AddParameter(AdoNetAppenderParameter parameter)
  478. {
  479. m_parameters.Add(parameter);
  480. }
  481. #endregion // Public Instance Methods
  482. #region Protected Instance Methods
  483. /// <summary>
  484. /// Writes the events to the database using the transaction specified.
  485. /// </summary>
  486. /// <param name="dbTran">The transaction that the events will be executed under.</param>
  487. /// <param name="events">The array of events to insert into the database.</param>
  488. /// <remarks>
  489. /// <para>
  490. /// The transaction argument can be <c>null</c> if the appender has been
  491. /// configured not to use transactions. See <see cref="UseTransactions"/>
  492. /// property for more information.
  493. /// </para>
  494. /// </remarks>
  495. virtual protected void SendBuffer(IDbTransaction dbTran, LoggingEvent[] events)
  496. {
  497. // string.IsNotNullOrWhiteSpace() does not exist in ancient .NET frameworks
  498. if (CommandText != null && CommandText.Trim() != "")
  499. {
  500. using (IDbCommand dbCmd = Connection.CreateCommand())
  501. {
  502. // Set the command string
  503. dbCmd.CommandText = CommandText;
  504. // Set the command type
  505. dbCmd.CommandType = CommandType;
  506. // Send buffer using the prepared command object
  507. if (dbTran != null)
  508. {
  509. dbCmd.Transaction = dbTran;
  510. }
  511. // prepare the command, which is significantly faster
  512. dbCmd.Prepare();
  513. // run for all events
  514. foreach (LoggingEvent e in events)
  515. {
  516. // clear parameters that have been set
  517. dbCmd.Parameters.Clear();
  518. // Set the parameter values
  519. foreach (AdoNetAppenderParameter param in m_parameters)
  520. {
  521. param.Prepare(dbCmd);
  522. param.FormatValue(dbCmd, e);
  523. }
  524. // Execute the query
  525. dbCmd.ExecuteNonQuery();
  526. }
  527. }
  528. }
  529. else
  530. {
  531. // create a new command
  532. using (IDbCommand dbCmd = Connection.CreateCommand())
  533. {
  534. if (dbTran != null)
  535. {
  536. dbCmd.Transaction = dbTran;
  537. }
  538. // run for all events
  539. foreach (LoggingEvent e in events)
  540. {
  541. // Get the command text from the Layout
  542. string logStatement = GetLogStatement(e);
  543. LogLog.Debug(declaringType, "LogStatement [" + logStatement + "]");
  544. dbCmd.CommandText = logStatement;
  545. dbCmd.ExecuteNonQuery();
  546. }
  547. }
  548. }
  549. }
  550. /// <summary>
  551. /// Formats the log message into database statement text.
  552. /// </summary>
  553. /// <param name="logEvent">The event being logged.</param>
  554. /// <remarks>
  555. /// This method can be overridden by subclasses to provide
  556. /// more control over the format of the database statement.
  557. /// </remarks>
  558. /// <returns>
  559. /// Text that can be passed to a <see cref="System.Data.IDbCommand"/>.
  560. /// </returns>
  561. virtual protected string GetLogStatement(LoggingEvent logEvent)
  562. {
  563. if (Layout == null)
  564. {
  565. ErrorHandler.Error("AdoNetAppender: No Layout specified.");
  566. return "";
  567. }
  568. else
  569. {
  570. StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
  571. Layout.Format(writer, logEvent);
  572. return writer.ToString();
  573. }
  574. }
  575. /// <summary>
  576. /// Creates an <see cref="IDbConnection"/> instance used to connect to the database.
  577. /// </summary>
  578. /// <remarks>
  579. /// This method is called whenever a new IDbConnection is needed (i.e. when a reconnect is necessary).
  580. /// </remarks>
  581. /// <param name="connectionType">The <see cref="Type"/> of the <see cref="IDbConnection"/> object.</param>
  582. /// <param name="connectionString">The connectionString output from the ResolveConnectionString method.</param>
  583. /// <returns>An <see cref="IDbConnection"/> instance with a valid connection string.</returns>
  584. virtual protected IDbConnection CreateConnection(Type connectionType, string connectionString)
  585. {
  586. IDbConnection connection = (IDbConnection)Activator.CreateInstance(connectionType);
  587. connection.ConnectionString = connectionString;
  588. return connection;
  589. }
  590. /// <summary>
  591. /// Resolves the connection string from the ConnectionString, ConnectionStringName, or AppSettingsKey
  592. /// property.
  593. /// </summary>
  594. /// <remarks>
  595. /// ConnectiongStringName is only supported on .NET 2.0 and higher.
  596. /// </remarks>
  597. /// <param name="connectionStringContext">Additional information describing the connection string.</param>
  598. /// <returns>A connection string used to connect to the database.</returns>
  599. virtual protected string ResolveConnectionString(out string connectionStringContext)
  600. {
  601. if (ConnectionString != null && ConnectionString.Length > 0)
  602. {
  603. connectionStringContext = "ConnectionString";
  604. return ConnectionString;
  605. }
  606. #if NET_2_0
  607. if (!String.IsNullOrEmpty(ConnectionStringName))
  608. {
  609. ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[ConnectionStringName];
  610. if (settings != null)
  611. {
  612. connectionStringContext = "ConnectionStringName";
  613. return settings.ConnectionString;
  614. }
  615. else
  616. {
  617. throw new LogException("Unable to find [" + ConnectionStringName + "] ConfigurationManager.ConnectionStrings item");
  618. }
  619. }
  620. #endif
  621. if (AppSettingsKey != null && AppSettingsKey.Length > 0)
  622. {
  623. connectionStringContext = "AppSettingsKey";
  624. string appSettingsConnectionString = SystemInfo.GetAppSetting(AppSettingsKey);
  625. if (appSettingsConnectionString == null || appSettingsConnectionString.Length == 0)
  626. {
  627. throw new LogException("Unable to find [" + AppSettingsKey + "] AppSettings key.");
  628. }
  629. return appSettingsConnectionString;
  630. }
  631. connectionStringContext = "Unable to resolve connection string from ConnectionString, ConnectionStrings, or AppSettings.";
  632. return string.Empty;
  633. }
  634. /// <summary>
  635. /// Retrieves the class type of the ADO.NET provider.
  636. /// </summary>
  637. /// <remarks>
  638. /// <para>
  639. /// Gets the Type of the ADO.NET provider to use to connect to the
  640. /// database. This method resolves the type specified in the
  641. /// <see cref="ConnectionType"/> property.
  642. /// </para>
  643. /// <para>
  644. /// Subclasses can override this method to return a different type
  645. /// if necessary.
  646. /// </para>
  647. /// </remarks>
  648. /// <returns>The <see cref="Type"/> of the ADO.NET provider</returns>
  649. virtual protected Type ResolveConnectionType()
  650. {
  651. try
  652. {
  653. return SystemInfo.GetTypeFromString(ConnectionType, true, false);
  654. }
  655. catch (Exception ex)
  656. {
  657. ErrorHandler.Error("Failed to load connection type [" + ConnectionType + "]", ex);
  658. throw;
  659. }
  660. }
  661. #endregion // Protected Instance Methods
  662. #region Private Instance Methods
  663. /// <summary>
  664. /// Connects to the database.
  665. /// </summary>
  666. private void InitializeDatabaseConnection()
  667. {
  668. string connectionStringContext = "Unable to determine connection string context.";
  669. string resolvedConnectionString = string.Empty;
  670. try
  671. {
  672. DiposeConnection();
  673. // Set the connection string
  674. resolvedConnectionString = ResolveConnectionString(out connectionStringContext);
  675. Connection = CreateConnection(ResolveConnectionType(), resolvedConnectionString);
  676. using (SecurityContext.Impersonate(this))
  677. {
  678. // Open the database connection
  679. Connection.Open();
  680. }
  681. }
  682. catch (Exception e)
  683. {
  684. // Sadly, your connection string is bad.
  685. ErrorHandler.Error("Could not open database connection [" + resolvedConnectionString + "]. Connection string context [" + connectionStringContext + "].", e);
  686. Connection = null;
  687. }
  688. }
  689. /// <summary>
  690. /// Cleanup the existing connection.
  691. /// </summary>
  692. /// <remarks>
  693. /// Calls the IDbConnection's <see cref="IDbConnection.Close"/> method.
  694. /// </remarks>
  695. private void DiposeConnection()
  696. {
  697. if (Connection != null)
  698. {
  699. try
  700. {
  701. Connection.Close();
  702. }
  703. catch (Exception ex)
  704. {
  705. LogLog.Warn(declaringType, "Exception while disposing cached connection object", ex);
  706. }
  707. Connection = null;
  708. }
  709. }
  710. #endregion // Private Instance Methods
  711. #region Protected Instance Fields
  712. /// <summary>
  713. /// The list of <see cref="AdoNetAppenderParameter"/> objects.
  714. /// </summary>
  715. /// <remarks>
  716. /// <para>
  717. /// The list of <see cref="AdoNetAppenderParameter"/> objects.
  718. /// </para>
  719. /// </remarks>
  720. protected ArrayList m_parameters;
  721. #endregion // Protected Instance Fields
  722. #region Private Instance Fields
  723. /// <summary>
  724. /// The security context to use for privileged calls
  725. /// </summary>
  726. private SecurityContext m_securityContext;
  727. /// <summary>
  728. /// The <see cref="IDbConnection" /> that will be used
  729. /// to insert logging events into a database.
  730. /// </summary>
  731. private IDbConnection m_dbConnection;
  732. /// <summary>
  733. /// Database connection string.
  734. /// </summary>
  735. private string m_connectionString;
  736. /// <summary>
  737. /// The appSettings key from App.Config that contains the connection string.
  738. /// </summary>
  739. private string m_appSettingsKey;
  740. #if NET_2_0
  741. /// <summary>
  742. /// The connectionStrings key from App.Config that contains the connection string.
  743. /// </summary>
  744. private string m_connectionStringName;
  745. #endif
  746. /// <summary>
  747. /// String type name of the <see cref="IDbConnection"/> type name.
  748. /// </summary>
  749. private string m_connectionType;
  750. /// <summary>
  751. /// The text of the command.
  752. /// </summary>
  753. private string m_commandText;
  754. /// <summary>
  755. /// The command type.
  756. /// </summary>
  757. private CommandType m_commandType;
  758. /// <summary>
  759. /// Indicates whether to use transactions when writing to the database.
  760. /// </summary>
  761. private bool m_useTransactions;
  762. /// <summary>
  763. /// Indicates whether to reconnect when a connection is lost.
  764. /// </summary>
  765. private bool m_reconnectOnError;
  766. #endregion // Private Instance Fields
  767. #region Private Static Fields
  768. /// <summary>
  769. /// The fully qualified type of the AdoNetAppender class.
  770. /// </summary>
  771. /// <remarks>
  772. /// Used by the internal logger to record the Type of the
  773. /// log message.
  774. /// </remarks>
  775. private readonly static Type declaringType = typeof(AdoNetAppender);
  776. #endregion Private Static Fields
  777. }
  778. /// <summary>
  779. /// Parameter type used by the <see cref="AdoNetAppender"/>.
  780. /// </summary>
  781. /// <remarks>
  782. /// <para>
  783. /// This class provides the basic database parameter properties
  784. /// as defined by the <see cref="System.Data.IDbDataParameter"/> interface.
  785. /// </para>
  786. /// <para>This type can be subclassed to provide database specific
  787. /// functionality. The two methods that are called externally are
  788. /// <see cref="Prepare"/> and <see cref="FormatValue"/>.
  789. /// </para>
  790. /// </remarks>
  791. public class AdoNetAppenderParameter
  792. {
  793. #region Public Instance Constructors
  794. /// <summary>
  795. /// Initializes a new instance of the <see cref="AdoNetAppenderParameter" /> class.
  796. /// </summary>
  797. /// <remarks>
  798. /// Default constructor for the AdoNetAppenderParameter class.
  799. /// </remarks>
  800. public AdoNetAppenderParameter()
  801. {
  802. Precision = 0;
  803. Scale = 0;
  804. Size = 0;
  805. }
  806. #endregion // Public Instance Constructors
  807. #region Public Instance Properties
  808. /// <summary>
  809. /// Gets or sets the name of this parameter.
  810. /// </summary>
  811. /// <value>
  812. /// The name of this parameter.
  813. /// </value>
  814. /// <remarks>
  815. /// <para>
  816. /// The name of this parameter. The parameter name
  817. /// must match up to a named parameter to the SQL stored procedure
  818. /// or prepared statement.
  819. /// </para>
  820. /// </remarks>
  821. public string ParameterName
  822. {
  823. get { return m_parameterName; }
  824. set { m_parameterName = value; }
  825. }
  826. /// <summary>
  827. /// Gets or sets the database type for this parameter.
  828. /// </summary>
  829. /// <value>
  830. /// The database type for this parameter.
  831. /// </value>
  832. /// <remarks>
  833. /// <para>
  834. /// The database type for this parameter. This property should
  835. /// be set to the database type from the <see cref="DbType"/>
  836. /// enumeration. See <see cref="IDataParameter.DbType"/>.
  837. /// </para>
  838. /// <para>
  839. /// This property is optional. If not specified the ADO.NET provider
  840. /// will attempt to infer the type from the value.
  841. /// </para>
  842. /// </remarks>
  843. /// <seealso cref="IDataParameter.DbType" />
  844. public DbType DbType
  845. {
  846. get { return m_dbType; }
  847. set
  848. {
  849. m_dbType = value;
  850. m_inferType = false;
  851. }
  852. }
  853. /// <summary>
  854. /// Gets or sets the precision for this parameter.
  855. /// </summary>
  856. /// <value>
  857. /// The precision for this parameter.
  858. /// </value>
  859. /// <remarks>
  860. /// <para>
  861. /// The maximum number of digits used to represent the Value.
  862. /// </para>
  863. /// <para>
  864. /// This property is optional. If not specified the ADO.NET provider
  865. /// will attempt to infer the precision from the value.
  866. /// </para>
  867. /// </remarks>
  868. /// <seealso cref="IDbDataParameter.Precision" />
  869. public byte Precision
  870. {
  871. get { return m_precision; }
  872. set { m_precision = value; }
  873. }
  874. /// <summary>
  875. /// Gets or sets the scale for this parameter.
  876. /// </summary>
  877. /// <value>
  878. /// The scale for this parameter.
  879. /// </value>
  880. /// <remarks>
  881. /// <para>
  882. /// The number of decimal places to which Value is resolved.
  883. /// </para>
  884. /// <para>
  885. /// This property is optional. If not specified the ADO.NET provider
  886. /// will attempt to infer the scale from the value.
  887. /// </para>
  888. /// </remarks>
  889. /// <seealso cref="IDbDataParameter.Scale" />
  890. public byte Scale
  891. {
  892. get { return m_scale; }
  893. set { m_scale = value; }
  894. }
  895. /// <summary>
  896. /// Gets or sets the size for this parameter.
  897. /// </summary>
  898. /// <value>
  899. /// The size for this parameter.
  900. /// </value>
  901. /// <remarks>
  902. /// <para>
  903. /// The maximum size, in bytes, of the data within the column.
  904. /// </para>
  905. /// <para>
  906. /// This property is optional. If not specified the ADO.NET provider
  907. /// will attempt to infer the size from the value.
  908. /// </para>
  909. /// <para>
  910. /// For BLOB data types like VARCHAR(max) it may be impossible to infer the value automatically, use -1 as the size in this case.
  911. /// </para>
  912. /// </remarks>
  913. /// <seealso cref="IDbDataParameter.Size" />
  914. public int Size
  915. {
  916. get { return m_size; }
  917. set { m_size = value; }
  918. }
  919. /// <summary>
  920. /// Gets or sets the <see cref="IRawLayout"/> to use to
  921. /// render the logging event into an object for this
  922. /// parameter.
  923. /// </summary>
  924. /// <value>
  925. /// The <see cref="IRawLayout"/> used to render the
  926. /// logging event into an object for this parameter.
  927. /// </value>
  928. /// <remarks>
  929. /// <para>
  930. /// The <see cref="IRawLayout"/> that renders the value for this
  931. /// parameter.
  932. /// </para>
  933. /// <para>
  934. /// The <see cref="RawLayoutConverter"/> can be used to adapt
  935. /// any <see cref="ILayout"/> into a <see cref="IRawLayout"/>
  936. /// for use in the property.
  937. /// </para>
  938. /// </remarks>
  939. public IRawLayout Layout
  940. {
  941. get { return m_layout; }
  942. set { m_layout = value; }
  943. }
  944. #endregion // Public Instance Properties
  945. #region Public Instance Methods
  946. /// <summary>
  947. /// Prepare the specified database command object.
  948. /// </summary>
  949. /// <param name="command">The command to prepare.</param>
  950. /// <remarks>
  951. /// <para>
  952. /// Prepares the database command object by adding
  953. /// this parameter to its collection of parameters.
  954. /// </para>
  955. /// </remarks>
  956. virtual public void Prepare(IDbCommand command)
  957. {
  958. // Create a new parameter
  959. IDbDataParameter param = command.CreateParameter();
  960. // Set the parameter properties
  961. param.ParameterName = ParameterName;
  962. if (!m_inferType)
  963. {
  964. param.DbType = DbType;
  965. }
  966. if (Precision != 0)
  967. {
  968. param.Precision = Precision;
  969. }
  970. if (Scale != 0)
  971. {
  972. param.Scale = Scale;
  973. }
  974. if (Size != 0)
  975. {
  976. param.Size = Size;
  977. }
  978. // Add the parameter to the collection of params
  979. command.Parameters.Add(param);
  980. }
  981. /// <summary>
  982. /// Renders the logging event and set the parameter value in the command.
  983. /// </summary>
  984. /// <param name="command">The command containing the parameter.</param>
  985. /// <param name="loggingEvent">The event to be rendered.</param>
  986. /// <remarks>
  987. /// <para>
  988. /// Renders the logging event using this parameters layout
  989. /// object. Sets the value of the parameter on the command object.
  990. /// </para>
  991. /// </remarks>
  992. virtual public void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
  993. {
  994. // Lookup the parameter
  995. IDbDataParameter param = (IDbDataParameter)command.Parameters[ParameterName];
  996. // Format the value
  997. object formattedValue = Layout.Format(loggingEvent);
  998. // If the value is null then convert to a DBNull
  999. if (formattedValue == null)
  1000. {
  1001. formattedValue = DBNull.Value;
  1002. }
  1003. param.Value = formattedValue;
  1004. }
  1005. #endregion // Public Instance Methods
  1006. #region Private Instance Fields
  1007. /// <summary>
  1008. /// The name of this parameter.
  1009. /// </summary>
  1010. private string m_parameterName;
  1011. /// <summary>
  1012. /// The database type for this parameter.
  1013. /// </summary>
  1014. private DbType m_dbType;
  1015. /// <summary>
  1016. /// Flag to infer type rather than use the DbType
  1017. /// </summary>
  1018. private bool m_inferType = true;
  1019. /// <summary>
  1020. /// The precision for this parameter.
  1021. /// </summary>
  1022. private byte m_precision;
  1023. /// <summary>
  1024. /// The scale for this parameter.
  1025. /// </summary>
  1026. private byte m_scale;
  1027. /// <summary>
  1028. /// The size for this parameter.
  1029. /// </summary>
  1030. private int m_size;
  1031. /// <summary>
  1032. /// The <see cref="IRawLayout"/> to use to render the
  1033. /// logging event into an object for this parameter.
  1034. /// </summary>
  1035. private IRawLayout m_layout;
  1036. #endregion // Private Instance Fields
  1037. }
  1038. }
  1039. #endif // !SSCLI