AdoNetAppender and Clob field - log4net

I have a asp.net 3.5 app that is using log4Net and AdoNetAppender. Currently the app uses a message field just like the log4net documentation http://logging.apache.org/log4net/release/config-examples.html. I would like to convert the field from varchar2 to Clob. I was trying to find some documentation on how to do this. All I could find is:
http://old.nabble.com/DbType-for-CLOB-column-using-AdoNetAppender-td1214036.html#a1214036
which wasn't too useful. Does anyone know a link or some samples on how to use a Clob file with a AdoNetAppender?
Thanks, Bill N

Did you try this:
http://marc.info/?l=log4net-user&m=110874200319166
basically you need to set the DbType to string and remove the Size parameter. Apparently this does not work correctly for nvarchar(max) (see here) but that does not mean it will not work for Clob.

I know this is an old question, but I recently needed to pass a CLOB parameter to a package procedure, using log4net. I was not able to do that using the suggestions I found online, including the one with setting the DbType to String and removing Size.
I am using an Oracle package procedure that takes a parameter of type CLOB. Using a custom AdoNetAppenderParameter I am able to pass long strings (270k+ characters) to the procedure and store them in the DB (Oracle 9i).
First of all, I had to use Oracle's Data Access Provider (after all, Microsoft's System.Data.OracleClient has been deprecated). Your project must reference Oracle.DataAccess.dll. I got the NuGet package by searching for "oracle.dataaccess" in NuGet package manager.
The library has an implementation of DbParameter, OracleParameter, that has a OracleDbType property. The type of the property is OracleDbType which is an enumeration that has the Clob value.
After adding the reference, I changed the appender's connection type to:
<connectionType value="Oracle.DataAccess.Client.OracleConnection, Oracle.DataAccess, Version=2.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
I then created a custom AdoNetAppenderParameter that creates a new OracleParameter and sets its type to Clob:
public class OracleAdoNetAppenderParameter : AdoNetAppenderParameter
{
public OracleDbType OracleDbType { get; set; }
public override void Prepare(System.Data.IDbCommand command)
{
if (!(command is OracleCommand))
{
string message = string.Format("The log4net parameter of type {0} can only be used with an appender connection of type {1}. The expected command type, {2}, cannot be supplied. Please check the parent appender's connectionType property.",
this.GetType(), typeof(OracleConnection), typeof(OracleCommand));
throw new System.ArgumentException(message, "command");
}
var parameter = command.CreateParameter() as OracleParameter;
parameter.ParameterName = base.ParameterName;
parameter.OracleDbType = this.OracleDbType;
command.Parameters.Add(parameter);
}
}
I exposed a property, OracleDbType, so I would be able to specify it through configuration.
I initially did not expose the property, named the class OracleClobAdoNetAppenderParameter and set the OracleDbType property to Clob inside the Prepare method.
After I created the class, I added the parameter to the appender's configuration, like this:
<parameter type="YourNamespace.OracleAdoNetAppenderParameter, YourAssembly">
<OracleDbType value="Clob" />
<parameterName value=":yourProcedureClobParam"/>
<layout type="..."></layout>
</parameter>
You can use your own layout. I am passing my large string through log4net's context as a custom parameter.
Here's the final configuration I'm using:
<appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
<connectionType value="Oracle.DataAccess.Client.OracleConnection, Oracle.DataAccess, Version=2.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
<connectionString value="data source=xxx;User ID=xxx;Password=xxx"/>
<commandText value="MY_PKG.LogMessage"/>
<commandType value="StoredProcedure" />
<!-- SERVICE_MESSAGE -->
<parameter type="MyNamespace.OracleAdoNetAppenderParameter, MyAssembly">
<OracleDbType value="Clob" />
<parameterName value=":service_message"/>
<layout type="log4net.Layout.RawPropertyLayout">
<key value="service_message"/>
</layout>
</parameter>
<!-- LOG_LEVEL -->
<parameter>
<parameterName value=":type"/>
<dbType value="String"/>
<size value="20"/>
<layout type="log4net.Layout.PatternLayout" value="%level"/>
</parameter>
<!-- LOG_DATE -->
<parameter>
<parameterName value=":timestamp"/>
<dbType value="DateTime"/>
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<!-- MESSAGE -->
<parameter>
<parameterName value=":message"/>
<dbType value="String"/>
<size value="1000"/>
<layout type="log4net.Layout.PatternLayout" value="%message"/>
</parameter>
<!-- EXCEPTION -->
<parameter>
<parameterName value=":error"/>
<dbType value="String"/>
<size value="4000"/>
<layout type="log4net.Layout.ExceptionLayout"/>
</parameter>
......
</appender>
Hope it helps.

Related

How to write log4net config into appsettings.json?

I have implemented log4net into .NET core 2.0, to log into a text file. As log4net have a config file, which is having XML configuration in it. So, I have created a separate file log4net.config to set its configuration and it is working fine. But I want to set its configuration into appsettings.json. Is it possible to write the log4net configuration into appsettings.json.
<appSettings>
<add key="IsLog" value="True" />
<add key="MaxThreads" value="3" />
</appSettings>
<log4net debug="false">
<root>
<level value="ALL" />
<appender-ref ref="RollingLogFileAppender" />
</root>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="./logs/logfile.txt" />
<appendToFile value="false" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="-1" />
<maximumFileSize value="50GB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
</log4net>
this is my XML configuration.
I have found this question in which it is mentioned that log4net don't support Json projects. Is it possible to write its configuration into appsettings.json.
Maybe it is not the right way but, anyhow I managed to use my log4net.config in appSettings.json. I am putting my answer here so it can help someone if they don't want to use an extra config file for there project.
So what I have done is like by converting my XML into JSON and used the JSON as a string on my appSettings.json after that I use the following code to read the string.
appSettings.json
{
"ConnectionStrings": {
"LoggerConfig": "Config string"
}
}
Json to XML Conversion using Newtonsoft.Json
string xmlElement = _configuration["ConnectionStrings:LoggerConfig"];
XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(xmlElement);
XmlConfigurator.Configure(logRepository, GenerateStreamFromString(doc.InnerXml));
This code is used to convert the JSON into XML but it will not provide the XML content as Element so, I used it as a stream.
For converting the string into a stream I used the following code.
public static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
and it works fine.
Here I used to convert first my XML to JSON and again JSON to XML to
use it as a log4net config, but if anyone wants then use XML
directly as a string, So it will reduce some code.
If you using Microsoft.Extensions.Logging.Log4Net.AspNetCore nuget package, there is a way keep log4net config in appsettings.json (but honestly not very usable).
You can write into appsettings.json (or appsettings.Environment.json for different environments) rules overriding nodes from log4net config file.
Documentation
Example of setting the logging level from appsettings.json.
appsettings.json:
{
"Log4NetCore": {
"PropertyOverrides": [
{
"XPath": "/log4net/root/level",
"Attributes": {
//"value": "ALL"
//"value": "DEBUG"
//"value": "INFO"
"value": "WARN"
//"value": "ERROR"
//"value": "FATAL"
//"value": "OFF"
}
}
]
}
}
You still needs log4net config file with nodes which you want override from appsettings.json:
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="DebugAppender" type="log4net.Appender.DebugAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file value="example.log" />
<appendToFile value="true" />
<maximumFileSize value="100KB" />
<maxSizeRollBackups value="2" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %5level %logger.%method [%line] - MESSAGE: %message%newline %exception" />
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="DebugAppender" />
<appender-ref ref="RollingFile" />
</root>
</log4net>
Registration in Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add these lines
var loggingOptions = this.Configuration.GetSection("Log4NetCore")
.Get<Log4NetProviderOptions>();
loggerFactory.AddLog4Net(loggingOptions);
app.UseMvc();
}
}

Change subject of SmtpAppender programmatically

I have an SmtpAppender like this:
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="MyEmail#example.org" />
<from value="SenderEmail#example.org" />
<subject type="log4net.Util.PatternString" value="This is a subject" />
<smtpHost value="smtp.host.value" />
<authentication value="Basic" />
<port value="587" />
<username value="Username" />
<password value="Password" />
<bufferSize value="1" />
<EnableSsl value="true"/>
<lossy value="false" />
<threshold value="ERROR" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%3thread] %-5level [%class].[%method] - %message%newline" />
</layout>
</appender>
I would like to change the subject of the log mail in my code to a string I would specify so it suits my needs better than a standard subject.
Right now I achieve it like this. First, I find the SmtpAppender by name:
public static IAppender FindAppenderByName(string name)
{
ILoggerRepository rootRep = LogManager.GetRepository();
foreach (IAppender iApp in rootRep.GetAppenders()) {
if (string.Compare(name, iApp.Name, true) == 0) {
return iApp;
}
}
return null;
}
and then I can change the subject like this:
IAppender appender = FindAppenderByName("SmtpAppender");
SmtpAppender smtpAppender = (SmtpAppender)appender;
smtpAppender.Subject = "An error occured trying to convert the values";
log.Error("Error message");
But this seems to be a bit too complicated since every time I send an error mail I first have to find the appender by name and I don't want to use a global IAppender object as well as that seems like bad practice to me.
Is there a way to use the user-specified error message as a subject or is there an extension method I could use?
My proposal is as follows :
Create a new class extending SmtpAppender
Override SendBuffer
Based on your LoggingEvent, dynamically change the base.Subject
public class CustomSmtpAppender : SmtpAppender
{
protected override void SendBuffer(LoggingEvent[] events)
{
PrepareSubject(events);
base.SendBuffer(events);
}
protected virtual void PrepareSubject(IEnumerable<LoggingEvent> events)
{
//here you can eval events and set base.Subject
}
}

Questions regarding log4net's AdoNetAppender

I have log4net logging to my database after following a few examples and tutorials. A couple of things are unclear to me.
In the example on the log4net page it lists the following node in the configuration area.
<parameter>
<parameterName value="#exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
This parameter appears to pass an empty string to the database even when I log from within an exception.
Can someone clarify what should be logged to this field and when?
Also that example also has 'thread' being logged to the database as a varchar(255). What I get in that field is an integer. I'm not familiar with threads. Can I just store this as an int? Would this end up being a string in some cases?
<parameter>
<parameterName value="#thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
In order for exception to be populated, you need to use the correct overload
for example, if you use
catch (Exception ex)
{
log.Error("Error saving details" , ex);
...
}
then the exception column of your database will be set to ex.ToString()
If you use
log.Error("Error saving details " + ex.ToString());
then exception is not filled it
As for the thread identifier, this should be a string in your database. The default thread id is an integer, but you can explicitly name the thread,
eg
var ts = new System.Threading.ThreadStart(InitialiseDatabase);
var thread = new System.Threading.Thread(ts);
thread.Name = "Initialising Database";
It is a good idea to name your threads, as it makes filtering/indentifying much easier.

Is it possible to use Log4Net to write to log file within a CLR Stored Procedure?

Issue: Unable to debug (write) to a Log File using Log4net inside of a CLR Stored Procedure. Possibly a problem with the way I'm building the CLR project? I'm only importing the DLL's into sql server (create assembly....). Do I need to import the App.Config as well?
DLL Name:
CLRTest.dll
Source Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;
using log4net;
using log4net.Config;
public class MyClass
{
private readonly static ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string sContextConn = "Context Connection=true";
[Microsoft.SqlServer.Server.SqlProcedure]
public static void Select1()
{
XmlConfigurator.Configure();
log.Debug("Begin Select1()...");
using (SqlConnection connection = new SqlConnection(sContextConn))
{
connection.Open();
SqlCommand command = new SqlCommand("select 1", connection);
SqlDataReader r = command.ExecuteReader();
SqlContext.Pipe.Send(r);
}
log.Debug("End Select1()...");
}
}//end MyClass
Log4Net XML Config (App.Config):
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="C:\log\clrsql.log" />
<param name="AppendToFile" value="true" />
<datePattern value="yyyyMMdd-HHmm" />
<param name="rollingStyle" value="Size" />
<param name="maxSizeRollBackups" value="50" />
<param name="maximumFileSize" value="25MB" />
<param name="staticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [t-%t] [%c.%M(%L)] %m%n" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d %-5p [%c.%M(%L)] %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="RollingFileAppender" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
Permissions on SQL Server:
C:\log\ --
NETWORK SERVICE and MyDomain\sqlserveraccount have Full Control on the "log" folder.
SQL Scripts:
drop procedure clr_Select1
go
drop assembly CLRTest
go
create ASSEMBLY CLRTest FROM 'C:\Share\ClrSql\TEST\CLRTest.dll' WITH PERMISSION_SET = unsafe
go
CREATE PROCEDURE clr_Select1
AS EXTERNAL NAME CLRTest.MyClass.Select1
go
exec clr_Select1
SQL Output:
(No column name)
1
The log4net configurator call that you're making actually tries to find the AppDomain.CurrentDomain.SetupInformation.ConfigurationFile. I don't know what the AppDomain is for a stored procedure.
You might want to try a standalone configuration file and use the overload of that configurator.
log4net.Config.XmlConfigurator.Configure(new FileInfo("config.log4net"));

Default values for AdoNetAppender parameter

I am using log4net with AdoNetAppender. It logs all log info into a table. This table actually has 2 Integer columns (can be null).
Here is the relevant part of my log4net config:
<commandText value="INSERT INTO ActivityLog ([Date],[Thread],[Level],[Logger],[Message],[DealID])
VALUES (#log_date,#thread,#log_level,#logger,#message,#DealID)" />
//other parameters hten DealID
<parameter>
<parameterName value="#DealID" />
<dbType value="Int32" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%property{DealID}" />
</layout>
</parameter>
What I found out was if I don't explicitly set using something like log4net.ThreadContext.Properties["DealID"] = DealID; it throws me an exception:
System.FormatException occurred
Message="Failed to convert parameter value from a String to a Int32."
Source="System.Data"
StackTrace:
at System.Data.SqlClient.SqlParameter.CoerceValue(Object value, MetaType destinationType)
InnerException: System.FormatException
Message="Input string was not in a correct format."
Source="mscorlib"
StackTrace:
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at System.Data.SqlClient.SqlParameter.CoerceValue(Object value, MetaType destinationType)
InnerException:
I would have to set it like:
log4net.ThreadContext.Properties["DealID"] = 0;
Is there anyway that I can set a default parameter value in my log4net config for this Int32 field so that I don't need to set it explicitly to 0 if no value is supplied? And it makes me wonder why it does not happen to fields which are set as varchar (though no value is supplied to them).
Change your appender:
<parameter>
<parameterName value="#DealID" />
<dbType value="Int32" />
<layout type="log4net.Layout.RawPropertyLayout"> <!-- notice this -->
<key value="DealID" /> <!-- and notice this instead of the pattern layout -->
</layout>
</parameter>
And to give credit, I found it from this thread. (And a bunch of other searching trying to do the same thing.

Resources