#region Copyright & License
//
// Copyright 2001-2005 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
// SSCLI 1.0 has no support for ADO.NET
#if !SSCLI
using System;
using System.Collections;
using System.Data;
using System.IO;
using System.Reflection;
using log4net.Util;
using log4net.Layout;
using log4net.Core;
namespace log4net.Appender
{
///
/// Appender that logs to a database.
///
///
///
/// appends logging events to a table within a
/// database. The appender can be configured to specify the connection
/// string by setting the property.
/// The connection type (provider) can be specified by setting the
/// property. For more information on database connection strings for
/// your specific database see http://www.connectionstrings.com/.
///
///
/// Records are written into the database either using a prepared
/// statement or a stored procedure. The property
/// is set to (System.Data.CommandType.Text) to specify a prepared statement
/// or to (System.Data.CommandType.StoredProcedure) to specify a stored
/// procedure.
///
///
/// The prepared statement text or the name of the stored procedure
/// must be set in the property.
///
///
/// The prepared statement or stored procedure can take a number
/// of parameters. Parameters are added using the
/// method. This adds a single to the
/// ordered list of parameters. The
/// type may be subclassed if required to provide database specific
/// functionality. The specifies
/// the parameter name, database type, size, and how the value should
/// be generated using a .
///
///
///
/// An example of a SQL Server table that could be logged to:
///
/// CREATE TABLE [dbo].[Log] (
/// [ID] [int] IDENTITY (1, 1) NOT NULL ,
/// [Date] [datetime] NOT NULL ,
/// [Thread] [varchar] (255) NOT NULL ,
/// [Level] [varchar] (20) NOT NULL ,
/// [Logger] [varchar] (255) NOT NULL ,
/// [Message] [varchar] (4000) NOT NULL
/// ) ON [PRIMARY]
///
///
///
/// An example configuration to log to the above table:
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// Julian Biddle
/// Nicko Cadell
/// Gert Driesen
/// Lance Nehring
public class AdoNetAppender : BufferingAppenderSkeleton
{
#region Public Instance Constructors
///
/// Initializes a new instance of the class.
///
///
/// Public default constructor to initialize a new instance of this class.
///
public AdoNetAppender()
{
m_connectionType = "System.Data.OleDb.OleDbConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
m_useTransactions = true;
m_commandType = System.Data.CommandType.Text;
m_parameters = new ArrayList();
m_reconnectOnError = false;
}
#endregion // Public Instance Constructors
#region Public Instance Properties
///
/// Gets or sets the database connection string that is used to connect to
/// the database.
///
///
/// The database connection string used to connect to the database.
///
///
///
/// The connections string is specific to the connection type.
/// See for more information.
///
///
/// Connection string for MS Access via ODBC:
/// "DSN=MS Access Database;UID=admin;PWD=;SystemDB=C:\data\System.mdw;SafeTransactions = 0;FIL=MS Access;DriverID = 25;DBQ=C:\data\train33.mdb"
///
/// Another connection string for MS Access via ODBC:
/// "Driver={Microsoft Access Driver (*.mdb)};DBQ=C:\Work\cvs_root\log4net-1.2\access.mdb;UID=;PWD=;"
///
/// Connection string for MS Access via OLE DB:
/// "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Work\cvs_root\log4net-1.2\access.mdb;User Id=;Password=;"
///
public string ConnectionString
{
get { return m_connectionString; }
set { m_connectionString = value; }
}
///
/// Gets or sets the type name of the connection
/// that should be created.
///
///
/// The type name of the connection.
///
///
///
/// The type name of the ADO.NET provider to use.
///
///
/// The default is to use the OLE DB provider.
///
///
/// Use the OLE DB Provider. This is the default value.
/// System.Data.OleDb.OleDbConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
///
/// Use the MS SQL Server Provider.
/// System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
///
/// Use the ODBC Provider.
/// Microsoft.Data.Odbc.OdbcConnection,Microsoft.Data.Odbc,version=1.0.3300.0,publicKeyToken=b77a5c561934e089,culture=neutral
/// This is an optional package that you can download from
/// http://msdn.microsoft.com/downloads
/// search for ODBC .NET Data Provider.
///
/// Use the Oracle Provider.
/// System.Data.OracleClient.OracleConnection, System.Data.OracleClient, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
/// This is an optional package that you can download from
/// http://msdn.microsoft.com/downloads
/// search for .NET Managed Provider for Oracle.
///
public string ConnectionType
{
get { return m_connectionType; }
set { m_connectionType = value; }
}
///
/// Gets or sets the command text that is used to insert logging events
/// into the database.
///
///
/// The command text used to insert logging events into the database.
///
///
///
/// Either the text of the prepared statement or the
/// name of the stored procedure to execute to write into
/// the database.
///
///
/// The property determines if
/// this text is a prepared statement or a stored procedure.
///
///
public string CommandText
{
get { return m_commandText; }
set { m_commandText = value; }
}
///
/// Gets or sets the command type to execute.
///
///
/// The command type to execute.
///
///
///
/// This value may be either (System.Data.CommandType.Text) to specify
/// that the is a prepared statement to execute,
/// or (System.Data.CommandType.StoredProcedure) to specify that the
/// property is the name of a stored procedure
/// to execute.
///
///
/// The default value is (System.Data.CommandType.Text).
///
///
public CommandType CommandType
{
get { return m_commandType; }
set { m_commandType = value; }
}
///
/// Should transactions be used to insert logging events in the database.
///
///
/// true if transactions should be used to insert logging events in
/// the database, otherwise false. The default value is true.
///
///
///
/// Gets or sets a value that indicates whether transactions should be used
/// to insert logging events in the database.
///
///
/// When set a single transaction will be used to insert the buffered events
/// into the database. Otherwise each event will be inserted without using
/// an explicit transaction.
///
///
public bool UseTransactions
{
get { return m_useTransactions; }
set { m_useTransactions = value; }
}
///
/// Gets or sets the used to call the NetSend method.
///
///
/// The used to call the NetSend method.
///
///
///
/// Unless a specified here for this appender
/// the is queried for the
/// security context to use. The default behavior is to use the security context
/// of the current thread.
///
///
public SecurityContext SecurityContext
{
get { return m_securityContext; }
set { m_securityContext = value; }
}
///
/// Should this appender try to reconnect to the database on error.
///
///
/// true if the appender should try to reconnect to the database after an
/// error has occurred, otherwise false. The default value is false,
/// i.e. not to try to reconnect.
///
///
///
/// The default behaviour is for the appender not to try to reconnect to the
/// database if an error occurs. Subsequent logging events are discarded.
///
///
/// To force the appender to attempt to reconnect to the database set this
/// property to true.
///
///
/// When the appender attempts to connect to the database there may be a
/// delay of up to the connection timeout specified in the connection string.
/// This delay will block the calling application's thread.
/// Until the connection can be reestablished this potential delay may occur multiple times.
///
///
public bool ReconnectOnError
{
get { return m_reconnectOnError; }
set { m_reconnectOnError = value; }
}
#endregion // Public Instance Properties
#region Protected Instance Properties
///
/// Gets or sets the underlying .
///
///
/// The underlying .
///
///
/// creates a to insert
/// logging events into a database. Classes deriving from
/// can use this property to get or set this . Use the
/// underlying returned from if
/// you require access beyond that which provides.
///
protected IDbConnection Connection
{
get { return this.m_dbConnection; }
set { this.m_dbConnection = value; }
}
#endregion // Protected Instance Properties
#region Implementation of IOptionHandler
///
/// Initialize the appender based on the options set
///
///
///
/// This is part of the delayed object
/// activation scheme. The method must
/// be called on this object after the configuration properties have
/// been set. Until is called this
/// object is in an undefined state and must not be used.
///
///
/// If any of the configuration properties are modified then
/// must be called again.
///
///
override public void ActivateOptions()
{
base.ActivateOptions();
// Are we using a command object
m_usePreparedCommand = (m_commandText != null && m_commandText.Length > 0);
if (m_securityContext == null)
{
m_securityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
}
InitializeDatabaseConnection();
InitializeDatabaseCommand();
}
#endregion
#region Override implementation of AppenderSkeleton
///
/// Override the parent method to close the database
///
///
///
/// Closes the database command and database connection.
///
///
override protected void OnClose()
{
base.OnClose();
// Close the cached command and connection objects
if (m_dbCommand != null)
{
try
{
m_dbCommand.Dispose();
}
catch (Exception ex)
{
LogLog.Warn("AdoNetAppender: Exception while disposing cached command object", ex);
}
m_dbCommand = null;
}
if (m_dbConnection != null)
{
try
{
m_dbConnection.Close();
}
catch (Exception ex)
{
LogLog.Warn("AdoNetAppender: Exception while disposing cached connection object", ex);
}
m_dbConnection = null;
}
}
#endregion
#region Override implementation of BufferingAppenderSkeleton
///
/// Inserts the events into the database.
///
/// The events to insert into the database.
///
///
/// Insert all the events specified in the
/// array into the database.
///
///
override protected void SendBuffer(LoggingEvent[] events)
{
if (m_reconnectOnError && (m_dbConnection == null || m_dbConnection.State != ConnectionState.Open))
{
LogLog.Debug("AdoNetAppender: Attempting to reconnect to database. Current Connection State: " + ((m_dbConnection==null)?"":m_dbConnection.State.ToString()) );
InitializeDatabaseConnection();
InitializeDatabaseCommand();
}
// Check that the connection exists and is open
if (m_dbConnection != null && m_dbConnection.State == ConnectionState.Open)
{
if (m_useTransactions)
{
// Create transaction
// NJC - Do this on 2 lines because it can confuse the debugger
IDbTransaction dbTran = null;
try
{
dbTran = m_dbConnection.BeginTransaction();
SendBuffer(dbTran, events);
// commit transaction
dbTran.Commit();
}
catch(Exception ex)
{
// rollback the transaction
if (dbTran != null)
{
try
{
dbTran.Rollback();
}
catch(Exception)
{
// Ignore exception
}
}
// Can't insert into the database. That's a bad thing
ErrorHandler.Error("Exception while writing to database", ex);
}
}
else
{
// Send without transaction
SendBuffer(null, events);
}
}
}
#endregion // Override implementation of BufferingAppenderSkeleton
#region Public Instance Methods
///
/// Adds a parameter to the command.
///
/// The parameter to add to the command.
///
///
/// Adds a parameter to the ordered list of command parameters.
///
///
public void AddParameter(AdoNetAppenderParameter parameter)
{
m_parameters.Add(parameter);
}
#endregion // Public Instance Methods
#region Protected Instance Methods
///
/// Writes the events to the database using the transaction specified.
///
/// The transaction that the events will be executed under.
/// The array of events to insert into the database.
///
///
/// The transaction argument can be null if the appender has been
/// configured not to use transactions. See
/// property for more information.
///
///
virtual protected void SendBuffer(IDbTransaction dbTran, LoggingEvent[] events)
{
if (m_usePreparedCommand)
{
// Send buffer using the prepared command object
if (m_dbCommand != null)
{
if (dbTran != null)
{
m_dbCommand.Transaction = dbTran;
}
// run for all events
foreach(LoggingEvent e in events)
{
// Set the parameter values
foreach(AdoNetAppenderParameter param in m_parameters)
{
param.FormatValue(m_dbCommand, e);
}
// Execute the query
m_dbCommand.ExecuteNonQuery();
}
}
}
else
{
// create a new command
using(IDbCommand dbCmd = m_dbConnection.CreateCommand())
{
if (dbTran != null)
{
dbCmd.Transaction = dbTran;
}
// run for all events
foreach(LoggingEvent e in events)
{
// Get the command text from the Layout
string logStatement = GetLogStatement(e);
LogLog.Debug("AdoNetAppender: LogStatement ["+logStatement+"]");
dbCmd.CommandText = logStatement;
dbCmd.ExecuteNonQuery();
}
}
}
}
///
/// Formats the log message into database statement text.
///
/// The event being logged.
///
/// This method can be overridden by subclasses to provide
/// more control over the format of the database statement.
///
///
/// Text that can be passed to a .
///
virtual protected string GetLogStatement(LoggingEvent logEvent)
{
if (Layout == null)
{
ErrorHandler.Error("ADOAppender: No Layout specified.");
return "";
}
else
{
StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
Layout.Format(writer, logEvent);
return writer.ToString();
}
}
///
/// Connects to the database.
///
private void InitializeDatabaseConnection()
{
try
{
// Cleanup any existing command or connection
if (m_dbCommand != null)
{
try
{
m_dbCommand.Dispose();
}
catch (Exception ex)
{
LogLog.Warn("AdoNetAppender: Exception while disposing cached command object", ex);
}
m_dbCommand = null;
}
if (m_dbConnection != null)
{
try
{
m_dbConnection.Close();
}
catch (Exception ex)
{
LogLog.Warn("AdoNetAppender: Exception while disposing cached connection object", ex);
}
m_dbConnection = null;
}
// Create the connection object
m_dbConnection = (IDbConnection)Activator.CreateInstance(ResolveConnectionType());
// Set the connection string
m_dbConnection.ConnectionString = m_connectionString;
using(SecurityContext.Impersonate(this))
{
// Open the database connection
m_dbConnection.Open();
}
}
catch (System.Exception e)
{
// Sadly, your connection string is bad.
ErrorHandler.Error("Could not open database connection [" + m_connectionString + "]", e);
m_dbConnection = null;
}
}
///
/// Retrieves the class type of the ADO.NET provider.
///
///
///
/// Gets the Type of the ADO.NET provider to use to connect to the
/// database. This method resolves the type specified in the
/// property.
///
///
/// Subclasses can override this method to return a different type
/// if necessary.
///
///
/// The of the ADO.NET provider
virtual protected Type ResolveConnectionType()
{
try
{
return SystemInfo.GetTypeFromString(m_connectionType, true, false);
}
catch(Exception ex)
{
ErrorHandler.Error("Failed to load connection type ["+m_connectionType+"]", ex);
throw;
}
}
///
/// Prepares the database command and initialize the parameters.
///
private void InitializeDatabaseCommand()
{
if (m_dbConnection != null && m_usePreparedCommand)
{
try
{
// Cleanup any existing command or connection
if (m_dbCommand != null)
{
try
{
m_dbCommand.Dispose();
}
catch (Exception ex)
{
LogLog.Warn("AdoNetAppender: Exception while disposing cached command object", ex);
}
m_dbCommand = null;
}
// Create the command object
m_dbCommand = m_dbConnection.CreateCommand();
// Set the command string
m_dbCommand.CommandText = m_commandText;
// Set the command type
m_dbCommand.CommandType = m_commandType;
}
catch(System.Exception e)
{
ErrorHandler.Error("Could not create database command ["+m_commandText+"]", e);
if (m_dbCommand != null)
{
try
{
m_dbCommand.Dispose();
}
catch
{
// Ignore exception
}
m_dbCommand = null;
}
}
if (m_dbCommand != null)
{
try
{
foreach(AdoNetAppenderParameter param in m_parameters)
{
try
{
param.Prepare(m_dbCommand);
}
catch(System.Exception e)
{
ErrorHandler.Error("Could not add database command parameter ["+param.ParameterName+"]", e);
throw;
}
}
}
catch
{
try
{
m_dbCommand.Dispose();
}
catch
{
// Ignore exception
}
m_dbCommand = null;
}
}
if (m_dbCommand != null)
{
try
{
// Prepare the command statement.
m_dbCommand.Prepare();
}
catch (System.Exception e)
{
ErrorHandler.Error("Could not prepare database command ["+m_commandText+"]", e);
try
{
m_dbCommand.Dispose();
}
catch
{
// Ignore exception
}
m_dbCommand = null;
}
}
}
}
#endregion // Protected Instance Methods
#region Protected Instance Fields
///
/// Flag to indicate if we are using a command object
///
///
///
/// Set to true when the appender is to use a prepared
/// statement or stored procedure to insert into the database.
///
///
protected bool m_usePreparedCommand;
///
/// The list of objects.
///
///
///
/// The list of objects.
///
///
protected ArrayList m_parameters;
#endregion // Protected Instance Fields
#region Private Instance Fields
///
/// The security context to use for privileged calls
///
private SecurityContext m_securityContext;
///
/// The that will be used
/// to insert logging events into a database.
///
private IDbConnection m_dbConnection;
///
/// The database command.
///
private IDbCommand m_dbCommand;
///
/// Database connection string.
///
private string m_connectionString;
///
/// String type name of the type name.
///
private string m_connectionType;
///
/// The text of the command.
///
private string m_commandText;
///
/// The command type.
///
private CommandType m_commandType;
///
/// Indicates whether to use transactions when writing to the database.
///
private bool m_useTransactions;
///
/// Indicates whether to use transactions when writing to the database.
///
private bool m_reconnectOnError;
#endregion // Private Instance Fields
}
///
/// Parameter type used by the .
///
///
///
/// This class provides the basic database parameter properties
/// as defined by the interface.
///
/// This type can be subclassed to provide database specific
/// functionality. The two methods that are called externally are
/// and .
///
///
public class AdoNetAppenderParameter
{
#region Public Instance Constructors
///
/// Initializes a new instance of the class.
///
///
/// Default constructor for the AdoNetAppenderParameter class.
///
public AdoNetAppenderParameter()
{
m_precision = 0;
m_scale = 0;
m_size = 0;
}
#endregion // Public Instance Constructors
#region Public Instance Properties
///
/// Gets or sets the name of this parameter.
///
///
/// The name of this parameter.
///
///
///
/// The name of this parameter. The parameter name
/// must match up to a named parameter to the SQL stored procedure
/// or prepared statement.
///
///
public string ParameterName
{
get { return m_parameterName; }
set { m_parameterName = value; }
}
///
/// Gets or sets the database type for this parameter.
///
///
/// The database type for this parameter.
///
///
///
/// The database type for this parameter. This property should
/// be set to the database type from the
/// enumeration. See .
///
///
/// This property is optional. If not specified the ADO.NET provider
/// will attempt to infer the type from the value.
///
///
///
public DbType DbType
{
get { return m_dbType; }
set
{
m_dbType = value;
m_inferType = false;
}
}
///
/// Gets or sets the precision for this parameter.
///
///
/// The precision for this parameter.
///
///
///
/// The maximum number of digits used to represent the Value.
///
///
/// This property is optional. If not specified the ADO.NET provider
/// will attempt to infer the precision from the value.
///
///
///
public byte Precision
{
get { return m_precision; }
set { m_precision = value; }
}
///
/// Gets or sets the scale for this parameter.
///
///
/// The scale for this parameter.
///
///
///
/// The number of decimal places to which Value is resolved.
///
///
/// This property is optional. If not specified the ADO.NET provider
/// will attempt to infer the scale from the value.
///
///
///
public byte Scale
{
get { return m_scale; }
set { m_scale = value; }
}
///
/// Gets or sets the size for this parameter.
///
///
/// The size for this parameter.
///
///
///
/// The maximum size, in bytes, of the data within the column.
///
///
/// This property is optional. If not specified the ADO.NET provider
/// will attempt to infer the size from the value.
///
///
///
public int Size
{
get { return m_size; }
set { m_size = value; }
}
///
/// Gets or sets the to use to
/// render the logging event into an object for this
/// parameter.
///
///
/// The used to render the
/// logging event into an object for this parameter.
///
///
///
/// The that renders the value for this
/// parameter.
///
///
/// The can be used to adapt
/// any into a
/// for use in the property.
///
///
public IRawLayout Layout
{
get { return m_layout; }
set { m_layout = value; }
}
#endregion // Public Instance Properties
#region Public Instance Methods
///
/// Prepare the specified database command object.
///
/// The command to prepare.
///
///
/// Prepares the database command object by adding
/// this parameter to its collection of parameters.
///
///
virtual public void Prepare(IDbCommand command)
{
// Create a new parameter
IDbDataParameter param = command.CreateParameter();
// Set the parameter properties
param.ParameterName = m_parameterName;
if (!m_inferType)
{
param.DbType = m_dbType;
}
if (m_precision != 0)
{
param.Precision = m_precision;
}
if (m_scale != 0)
{
param.Scale = m_scale;
}
if (m_size != 0)
{
param.Size = m_size;
}
// Add the parameter to the collection of params
command.Parameters.Add(param);
}
///
/// Renders the logging event and set the parameter value in the command.
///
/// The command containing the parameter.
/// The event to be rendered.
///
///
/// Renders the logging event using this parameters layout
/// object. Sets the value of the parameter on the command object.
///
///
virtual public void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
{
// Lookup the parameter
IDbDataParameter param = (IDbDataParameter)command.Parameters[m_parameterName];
// Format the value
object formattedValue = Layout.Format(loggingEvent);
// If the value is null then convert to a DBNull
if (formattedValue == null)
{
formattedValue = DBNull.Value;
}
param.Value = formattedValue;
}
#endregion // Public Instance Methods
#region Private Instance Fields
///
/// The name of this parameter.
///
private string m_parameterName;
///
/// The database type for this parameter.
///
private DbType m_dbType;
///
/// Flag to infer type rather than use the DbType
///
private bool m_inferType = true;
///
/// The precision for this parameter.
///
private byte m_precision;
///
/// The scale for this parameter.
///
private byte m_scale;
///
/// The size for this parameter.
///
private int m_size;
///
/// The to use to render the
/// logging event into an object for this parameter.
///
private IRawLayout m_layout;
#endregion // Private Instance Fields
}
}
#endif // !SSCLI