How to log exception as structured JSON with NLog - c#

This is the code:
try
{
throw new AssertionException("This is exceptional");
}
catch (AssertionException ex)
{
// Rider nags me to write Error logs with exceptions this way
logger.Error(ex, "Houston, we have a problem!");
}
And the config is this:
<targets async="false">
<target name="NoPiiLog" xsi:type="Memory">
<layout type="SuppressPiiJsonLayout" includeAllProperties="true" maxRecursionLimit="2"
excludeProperties="UserId,EmailId" excludeEmptyProperties="true">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level:uppercase=true}" />
<attribute name="message" layout="${message:raw=true}" />
<attribute name="exception">
<layout type="JSonLayout" includeAllProperties="true" maxRecursionLimit="2"
excludeProperties="UserId,EmailId" excludeEmptyProperties="true" />
</attribute>
<attribute name="Properties">
<layout type='JsonLayout' includeAllProperties="true" maxRecursionLimit="2"
excludeEmptyProperties="true"/>
</attribute>
</layout>
</target>
SuppressPiiJsonLayout is a class I wrote and registered that takes the final JSON and replaces all the PII key values with "*****". What I can't do is to log the exceptionunless I include "{exception}" in the message string and the exception object in the params, like this:
logger.Error(ex, "Houston, we have a problem! {exception}", ex);
This is because the exception does not get into the Params or the Properties of the LogEvent, it is in an Exception member on the LogEvent. The docs don't cover structured logging of exceptions. https://github.com/NLog/NLog/wiki/How-to-Log-Exceptions
So that you don't think my suppressor is screwing things up, here is that code:
[Layout("SuppressPiiJsonLayout")]
[ThreadAgnostic]
[ThreadSafe]
public class SuppressPiiJsonLayout : NLog.Layouts.JsonLayout
{
/// <summary>
/// This digs thru the JObject and replaces the values with "*****". It recurses thru all the JObjects it
/// finds along the way, replacing the values in ExcludeProperties
/// </summary>
/// <param name="logEvent">The logging event.</param>
private void CleanKeys(JObject obj)
{
foreach (var entry in obj)
{
if(this.ExcludeProperties.Contains(entry.Key))
{
obj[entry.Key] = "*****";
}
else
{
if (obj[entry.Key] is JObject)
{
this.CleanKeys((JObject)obj[entry.Key]);
}
}
}
}
/// <summary>
/// This intercepts the rendering of the formatted message after it is turned into JSON and then cleans
/// all of the "ExcludeProperties" from the JSON about to be outputted, recursing thru all the objects
/// it finds. Unlike the ExcludeProperties of the JSONLayout which is practically useless. It might not be
/// the most efficient thing, but considering how immutable anonymous objects are, it is definitely the
/// only way I found to implement it.
/// </summary>
/// <param name="logEvent">The logging event.</param>
/// <param name="target"><see cref="StringBuilder"/> for the result</param>
protected override void RenderFormattedMessage(LogEventInfo logEvent, StringBuilder target)
{
var intercept = new StringBuilder();
base.RenderFormattedMessage(logEvent, intercept);
var j = JObject.Parse(intercept.ToString());
this.CleanKeys(j); // Remove all the ExcludeProperties it finds recursively
target.Append(j.ToString(Formatting.None)); // And then pass the modified version to the target for writing
}
}
I guess I could alter this code to make ex.Exception a named parameter, but it seems like NLOG should already be handling this so I'm kind of stumped.

Yes the LogEventInfo.Exception is not included in LogEventInfo.Properties, so it will not be included when using includeAllProperties="true":
Instead of doing this:
<attribute name="exception">
<layout type="JSonLayout" includeAllProperties="true" maxRecursionLimit="2"
excludeProperties="UserId,EmailId" excludeEmptyProperties="true" />
</attribute>
Then you can do this (encode="false" is to avoid encoding the json-output from format=#):
<attribute name="exception" layout="${exception:format=#}" encode="false" />
See also: https://github.com/NLog/NLog/wiki/How-to-use-structured-logging#output-captured-properties

Related

Unable to get NLog to send fields as numbers/ints/longs from code

Our tack is c# .net 4.7 logging through NLog library to a "Graylog" GELF input which of course is storing them in an elasticsearch cluster.
The application utilizes logging heavily, but EVERYTHING is sent through as strings - no matter if it was something other than a string in code.
I want to be able send my custom logger properties as their true data types so that I can aggregate on my statistical number fields.
I DO NOT want to constrain my developers and do this with scripted fields, or pre-mapping an index in elasticsearch - in fact, graylog manages my index for me. Nor do I want to utilize graylog pipeline processor - my poor graylog does enough work. I want them to be able to send stat data as their real data types and have them mapped as "long" or "number".
A code snippet from my stuff:
public void InfoExtended2(String Message, Dictionary<string, int> extrafields, [CallerMemberName] string callerMethodName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
var fileName = callerFilePath.Substring(callerFilePath.LastIndexOf(#"\") + 1).Replace(".cs", "");
var caller = $"{fileName}.{callerMethodName}()Line:{sourceLineNumber}";
var logger = LogManager.GetLogger(caller);
if (!(extrafields.Count == 0))
{
foreach (var field in extrafields)
{
string mykey = field.Key.ToString();
extrafields.TryGetValue(field.Key, out int myvalue);
//be careful whats in object!
if (!logger.Properties.ContainsKey(mykey)) { logger.Properties.Add(mykey, "{#myvalue}"); } else { logger.Properties[mykey] = "{#myvalue}"; }
}
}
logger.Info()
.Message(Message)
.Property("ComeOnNow3", 87)
.Write();
// Create the LogEventInfo object
//LogEventInfo logEvent = new LogEventInfo();
// Now add the event characteristics
//logEvent.Properties["EventCode"] = 2222;
//logEvent.Level = NLog.LogLevel.Info;
//logEvent.Properties["EventCode]."]
//logEvent.Message = "My Message";
////logEvent.Exception = ex;
//logger.Log(logEvent);
//logger.Info("{#Message}", new Dictionary<string, int> { { "key1", 1 }, { "key2", 2 } }); // dict. Result: Test "key1"=1, "key2"=2
}
I've tried a few different ways there with no luck.
If I make a POST GELF call myself with JSON fields representing ints instead of strings, it works. My field is brand new to the index and when I go check the mapping it comes back as a long. I want my code to do that.
Test GELF Call:
{
"version": "1.1",
"host": "example.org",
"short_message": "A short message that helps you identify what is going on",
"full_message": "Backtrace here\n\nmore stuff",
"timestamp": 1385053862.3072,
"level": 1,
"_user_id": 9001,
"_some_info": "foo",
"_some_env_var": "bar"
}
Resulting mapping for user id:
"user_id" : {
"type" : "long"
What our nlog target currently looks like:
<targets>
<target name="gelftcp"
type="gelftcp"
facility="Custom Facility"
remoteaddress="192.168.95.15"
remoteport="12201"
layout="${longdate}${newline}type: ${level:uppercase=true}${newline}class: ${logger}${newline}stacktrace: ${stacktrace}${newline}error message: ${message}${newline}exception: ${exception:format=tostring,data:maxinnerexceptionlevel=10}">
<parameter name="logged_at" layout="${longdate}" />
<parameter name="type" layout="${level:uppercase=true}" />
   <parameter name="class" layout="${logger}" />
<parameter name="CustomInt" layout="${event-properties:item=CustomInt}" as="number" />
   <parameter name="stacktrace" layout="${stacktrace}" />
   <parameter name="error_message" layout="${message}" />
<parameter name="ComeOnNow3" layout="${event-properties:item=ComeOnNow3} " />
   <parameter name="exception" layout="${exception:format=tostring,data:maxinnerexceptionlevel=10}" />
<attribute name="eventProperties" encode="false" >
<layout type='JsonLayout' includeAllProperties="true" maxRecursionLimit="2"/>
</attribute>
<variable name="ComeOnNow2" value ="${event-context:ComeOnNow2}" />
</target>
Take it easy on me, I'm not the original author of this code. Just someone tasked with picking up slack & deploying in its current state.
UPDATE:
Tried the first suggestion. Even added the field I'm testing:
<field name="ComeOnNow3" layout="${threadid}" type="System.Int32" />
Attempted this log entry:
logger.Info()
.Message(Message)
.Property("ComeOnNow3", 87)
.Write();
My mapping still comes back as "keyword".
SOLVED:
The trick was to switch to Http instead of Tcp.
Instead of using "gelftcp". Then you can try out GelfLayout instead:
https://www.nuget.org/packages/NLog.GelfLayout
You can combine it with the TCP network-target:
<nlog>
<extensions>
<add assembly="NLog.Layouts.GelfLayout" />
</extensions>
<targets async="true">
<target type="Network" name="gelftcp" address="tcp://192.168.95.15:12201" newLine="true" lineEnding="Null">
<layout type="GelfLayout" facility="MyFacility">
<field name="threadid" layout="${threadid}" type="System.Int32" />
</layout>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="gelftcp" />
</rules>
</nlog>
It also has some automatic conversion logic for simple NLog LogEventInfo.Properties (Decimal + Double + Integer + Boolean)
More examples can be found here: https://github.com/farzadpanahi/NLog.GelfLayout

How to configure log4net with VSTO project in visual studio

I was trying to build a simple outlook add in.
I created a VSTO project in Visual Studio 2017.
When the project is created by the Visual Studio, there is no app.config or web.config in the solution. I want to use log4net for this project. How should I configure it? I tried to add web.config or app.config for the project. But I was not able to get values from the configure file. I think the project cannot recognize them. I cannot use ConfigurationManager.AppSettings["key"] to get the value from the configure file.
Does anyone know how to use log4net in VSTO project?
Thank you.
Install Log4Net through the NuGet Package Manager. Then create a setting in the project properties, like asdf (you can delete the setting after you update the app.config file with the Log4Net sections), it will then create the app.config for you.
Here's the configuration in app.config for Log4Net I use on most of my projects. I created a new project with a setting asdf and added my standard Log4Net setup.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="ExcelAddIn1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<userSettings>
<ExcelAddIn1.Properties.Settings>
<setting name="asdf" serializeAs="String">
<value>asdf</value>
</setting>
</ExcelAddIn1.Properties.Settings>
</userSettings>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline"/>
</layout>
</appender>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="C:\Temp\MyOfficeAddIn.log"/>
<appendToFile value="true"/>
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date|%-5level|%message%newline"/>
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="FileAppender"/>
</root>
</log4net>
</configuration>
I usually create a class called ErrorHandler and add the following code.
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
using log4net;
using log4net.Config;
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
Then I use the following methods to write to the log file
private static readonly ILog log = LogManager.GetLogger(typeof(ErrorHandler));
/// <summary>
/// Applies a new path for the log file by FileAppender name
/// </summary>
public static void SetLogPath()
{
XmlConfigurator.Configure();
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
string logFileName = System.IO.Path.Combine(Properties.Settings.Default.App_PathLocalData, AssemblyInfo.Product + ".log");
foreach (var a in h.Root.Appenders)
{
if (a is log4net.Appender.FileAppender)
{
if (a.Name.Equals("FileAppender"))
{
log4net.Appender.FileAppender fa = (log4net.Appender.FileAppender)a;
fa.File = logFileName;
fa.ActivateOptions();
}
}
}
}
/// <summary>
/// Create a log record to track which methods are being used.
/// </summary>
public static void CreateLogRecord()
{
try
{
// gather context
var sf = new System.Diagnostics.StackFrame(1);
var caller = sf.GetMethod();
var currentProcedure = caller.Name.Trim();
// handle log record
var logMessage = string.Concat(new Dictionary<string, string>
{
["PROCEDURE"] = currentProcedure,
["USER NAME"] = Environment.UserName,
["MACHINE NAME"] = Environment.MachineName
}.Select(x => $"[{x.Key}]=|{x.Value}|"));
log.Info(logMessage);
}
catch (Exception ex)
{
ErrorHandler.DisplayMessage(ex);
}
}
/// <summary>
/// Used to produce an error message and create a log record
/// <example>
/// <code lang="C#">
/// ErrorHandler.DisplayMessage(ex);
/// </code>
/// </example>
/// </summary>
/// <param name="ex">Represents errors that occur during application execution.</param>
/// <param name="isSilent">Used to show a message to the user and log an error record or just log a record.</param>
/// <remarks></remarks>
public static void DisplayMessage(Exception ex, Boolean isSilent = false)
{
// gather context
var sf = new System.Diagnostics.StackFrame(1);
var caller = sf.GetMethod();
var errorDescription = ex.ToString().Replace("\r\n", " "); // the carriage returns were messing up my log file
var currentProcedure = caller.Name.Trim();
var currentFileName = AssemblyInfo.GetCurrentFileName();
// handle log record
var logMessage = string.Concat(new Dictionary<string, string>
{
["PROCEDURE"] = currentProcedure,
["USER NAME"] = Environment.UserName,
["MACHINE NAME"] = Environment.MachineName,
["FILE NAME"] = currentFileName,
["DESCRIPTION"] = errorDescription,
}.Select(x => $"[{x.Key}]=|{x.Value}|"));
log.Error(logMessage);
// format message
var userMessage = new StringBuilder()
.AppendLine("Contact your system administrator. A record has been created in the log file.")
.AppendLine("Procedure: " + currentProcedure)
.AppendLine("Description: " + errorDescription)
.ToString();
// handle message
if (isSilent == false)
{
MessageBox.Show(userMessage, "Unexpected Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
I have a project in GitHub you can use as an example.

Import XML with Lists of Lists in C#

I want to import this XML file into C#.
<?xml version="1.0" encoding="UTF-8"?>
<YamahaAmplifier>
<Root>
<RootName>SYS</RootName>
<UseRoot>YES</UseRoot>
<Device>
<DeviceName>MODELNAME</DeviceName>
<UseFunction>YES</UseFunction>
<GETCMD>#SYS:MODELNAME=?</GETCMD>
<Parameter>
<Number>1</Number>
<Type>SINGLEVAL</Type>
<Name>Character Code: Ascii, Variable Length (1 to 16 characters)</Name>
</Parameter>
</Device>
<Device>
<DeviceName>SPPATTERN1AMP</DeviceName>
<UseFunction>YES</UseFunction>
<PUTCMD>#SYS:SPPATTERN1AMP=</PUTCMD>
<GETCMD>#SYS:SPPATTERN1AMP=?</GETCMD>
<Parameter>
<Number>1</Number>
<Type>SINGLEVAL</Type>
<Name>7ch Normal</Name>
</Parameter>
<Parameter>
<Number>2</Number>
<Type>SINGLEVAL</Type>
<Name>7ch +1ZONE</Name>
</Parameter>
<Parameter>
<Number>3</Number>
<Type>SINGLEVAL</Type>
<Name>7ch +2ZONE</Name>
</Parameter>
<Parameter>
<Number>4</Number>
<Type>SINGLEVAL</Type>
<Name>7ch +FRONT</Name>
</Parameter>
<Parameter>
<Number>5</Number>
<Type>SINGLEVAL</Type>
<Name>7ch +FPR</Name>
</Parameter>
</Device>
</Root>
<Root>
<RootName>SYS2</RootName>
<UseRoot>YES</UseRoot>
<Device>
<DeviceName>MODELNAME2</DeviceName>
<UseFunction>YES</UseFunction>
<GETCMD>#SYS:MODELNAME=?</GETCMD>
<Parameter>
<Number>1</Number>
<Type>SINGLEVAL</Type>
<Name>Character Code: Ascii, Variable Length (1 to 16 characters)</Name>
</Parameter>
</Device>
<Device>
<DeviceName>SPPATTERN1AMP2</DeviceName>
<UseFunction>YES</UseFunction>
<PUTCMD>#SYS:SPPATTERN1AMP=</PUTCMD>
<GETCMD>#SYS:SPPATTERN1AMP=?</GETCMD>
<Parameter>
<Number>1</Number>
<Type>SINGLEVAL</Type>
<Name>7ch Normal</Name>
</Parameter>
<Parameter>
<Number>2</Number>
<Type>SINGLEVAL</Type>
<Name>7ch +1ZONE</Name>
</Parameter>
<Parameter>
<Number>3</Number>
<Type>SINGLEVAL</Type>
<Name>7ch +2ZONE</Name>
</Parameter>
</Device>
</Root>
</YamahaAmplifier>
I have created these classes to carry it:
[Serializable, XmlRoot("YamahaAmplifier")]
public class YamahaAmplifier
{
[XmlArray(ElementName = "Root")]
public List<Root> Roots = new List<Root>();
}
[Serializable()]
public class Root
{
[XmlElement("RootName")]
public string RootName;
[XmlElement("UseRoot")]
public string UseRoot;
[XmlArray("Device")]
public List<Device> Devices = new List<Device>();
}
[Serializable()]
public class Device
{
[XmlElement("DeviceName")]
public string DeviceName;
[XmlElement("UseFunction")]
public string UseFunction;
[XmlElement("GETCMD")]
public string GETCMD;
[XmlElement("PUTCMD")]
public string PUTCMD;
[XmlArray("Parameter")]
public List<Parameter> Parameters = new List<Parameter>();
}
[Serializable()]
public class Parameter
{
[XmlElement("Number")]
public int Number;
[XmlElement("Type")]
public string Type;
[XmlElement("Name")]
public string Name;
}
And then this code to import to C#:
try
{
using (FileStream reader = new FileStream("C:\\Civview\\YNCA_Command_List_RX-A3000_U.xml", FileMode.Open))
{
XmlSerializer ser = new XmlSerializer(typeof(YamahaAmplifier));
o = ser.Deserialize(reader) as YamahaAmplifier;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
But I get an error and I just cant get around it, have have been testing a number of solutions to get it to work without sucess.
The error I get is:
System.InvalidOperationException: There is an Error in XML document (2, 2). ---> System-MethodAcessException: Attempt by method ´Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderYamahaAmplifier.Read5_YamahaAmplifier(Boolean, Boolean)´ to access method ´HSPI_YAMAHA_RECEIVER.Util+YamahaAmplifier..ctor() failed.
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderYamahaAmplifier.Read5_YamahaAmplifier(Booleanb isNullable, Boolean checkType)
Any suggestions? A better way of doing it? Anything?
Ok, so now I have imported a nice and neat structure of information that I want to use. One part of my program uses all the information in the structure to create a user interface. Another part of the program must decide what to do if one of the buttons are pressed or the sliers are changed. This latter part gets three pieces of information automatically, I cannot change the code that does this. The information I get is the "RootName", "DeviceName" and "Number" under "Parameter" class.
So what I need to do is to search for the matchin RootName, and under that the matching DeviceName and take that DeviceNames string in "PUTCMD" and add the parameter name which corresponds to the Number I get.
Is there a smart way of doing this? Finding the correct Device under the correct Root and then take the data out of that to creat my transmission string.
Greatful for any help in the correct direction.
Thanks, your fix works if I create a new project and just enter the code I posted with your changes. But it does not work in my original program. I will have to continue collecting information to describe the problem.
Copy pasting it back from the test program made it work with one exception. I had done a misstake in the testprogram typing something like roots.Roots[1].Devices.Count() and that worked there. But when copying back to the original program, I had to remove the () since its not a method.

How to you create a custom index in Sitecore 7 to index non Sitecore items (f. ex. Users)

I want to index the Sitecore Users which are not Sitecore Items.
The same way if I want to create custom indexes that combine different fields from different Sitecore items how do I do that. Assuming I have the data returned by some kind of data provider/repository, how I create, configure, index this data.
I have already written some configuration for this index:
<configuration type="Sitecore.ContentSearch.LuceneProvider.LuceneSearchConfiguration, Sitecore.ContentSearch.LuceneProvider">
<indexes hint="list:AddIndex">
<index id="custom_user_index" type="Sitecore.ContentSearch.LuceneProvider.LuceneIndex, Sitecore.ContentSearch.LuceneProvider">
<param desc="name">$(id)</param>
<param desc="folder">$(id)</param>
<!-- This initializes index property store. Id has to be set to the index id -->
<!--<param desc="propertyStore" ref="contentSearch/databasePropertyStore" param1="$(id)" />-->
<commitPolicyExecutor type="Sitecore.ContentSearch.CommitPolicyExecutor, Sitecore.ContentSearch">
<policies hint="list:AddCommitPolicy">
<policy type="Sitecore.ContentSearch.TimeIntervalCommitPolicy, Sitecore.ContentSearch" />
</policies>
</commitPolicyExecutor>
<locations hint="list:AddCrawler">
<crawler type="PT.Forum.Framework.Users.Infrastructure.UserCrawler, PT.Forum.Framework.Users"/>
</locations>
</index>
</indexes>
</configuration>
My crawler does this:
public class UserCrawler : AbstractProviderCrawler
{
public override void Initialize(ISearchIndex index)
{
Assert.ArgumentNotNull(index, "index");
Database = "web";
Assert.IsNotNull(Database, "Database element not set.");
Assert.IsNotNull(Root, "Root element not set.");
base.Initialize(index);
LuceneIndex index2 = index as LuceneIndex;
if (Operations == null)
{
Operations = new LuceneIndexOperations(index2);
CrawlingLog.Log.Info(string.Format("[Index={0}] Initializing LuceneDatabaseCrawler. DB:{1} / Root:{2}", index.Name, Database, Root));
}
var users = new UserRepository().GetByConstraint(UserRepositoryContraints.IsNejTakPlusUser);
index.Configuration = new LuceneIndexConfiguration();
var updateContext = index.CreateUpdateContext();
Operations.Add(new ForumUser(users.First()), updateContext, index.Configuration);
}
}
But the Index configuration is null.
The configuration above uses the DefaultIndexConfiguration which includes several computed fields. Try using an own configuration instead and adding just the fields/computed fields/other settings you need for your index.
Example how to use a custom configuration:
<customSearch>
<myCustomConfiguration type="Sitecore.ContentSearch.LuceneProvider.LuceneIndexConfiguration, Sitecore.ContentSearch.LuceneProvider">
<indexAllFields>false</indexAllFields>
<!-- include parts of default configuration -->
<Analyzer ref="contentSearch/configuration/defaultIndexConfiguration/analyzer" />
<!-- more settings here -->
</myCustomConfiguration>
</customSearch>
<contentSearch>
<configuration type="Sitecore.ContentSearch.LuceneProvider.LuceneSearchConfiguration, Sitecore.ContentSearch.LuceneProvider">
<indexes hint="list:AddIndex">
<index id="custom_user_index" type="Sitecore.ContentSearch.LuceneProvider.LuceneIndex, Sitecore.ContentSearch.LuceneProvider">
<param desc="name">$(id)</param>
<param desc="folder">$(id)</param>
<!-- This initializes index property store. Id has to be set to the index id -->
<!--<param desc="propertyStore" ref="contentSearch/databasePropertyStore" aram1="$(id)" />-->
<Configuration ref="customSearch/MyCustomConfiguration" />
<commitPolicyExecutor type="Sitecore.ContentSearch.CommitPolicyExecutor, Sitecore.ContentSearch">
<policies hint="list:AddCommitPolicy">
<policy type="Sitecore.ContentSearch.TimeIntervalCommitPolicy, Sitecore.ContentSearch" />
</policies>
</commitPolicyExecutor>
<locations hint="list:AddCrawler">
<crawler type="PT.Forum.Framework.Users.Infrastructure.UserCrawler, PT.Forum.Framework.Users"/>
</locations>
</index>
</indexes>
</configuration>
</contentSearch>
If you are going to use incremental updates, you will also need to make sure, Sitecore can match existing documents in lucene. By default, this is done using Sitecore fields.

Set log name in appender of log4net

I have class MyLogger, where I use log4net. How can I modify my appender to save log in specific logname (I want to set it by parameter logName).
public void AddEntry(string source, string logName, string contextInfo, string message, EventLogEntryType eventType)
{
log4net.ILog Log = log4net.LogManager.GetLogger(source);
Log.Error(String.Format("Context Info: {0}{1}{2}{3}", contextInfo, Environment.NewLine, Environment.NewLine, message));
}
<log4net>
<root>
<priority value="ALL" />
<appender-ref ref="EventLogAppender" />
</root>
This is myAppender. Now it writes in common logtype Application.
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger (%property{myContext}) [%level]- %message%newline" />
</layout>
</appender>
</log4net>
I think what you are looking for is this:
<param name="LogName" value="MyLog" />
Some more information can be found here. If you do something like this you may want to have a look at this issue as well. Basically this is about registering your application so that the eventlog knows about it.
Edit (configuration by code):
Did not test it but that ought to do the trick:
foreach (AppenderSkeleton appender in this.Logger.Repository.GetAppenders())
{
var eventlogAppender = appender as EventLogAppender;
if (eventlogAppender != null)
{
eventlogAppender.LogName = "MyLog";
eventlogAppender.ActivateOptions();
break;
}
}
You could add some tests to verify that there is only one EventLogAppender, but probably you do not need to bother to do so.
this works:
<param name="LogName" type="log4net.Util.PatternString" value="%property{LogName}" />

Categories

Resources