I know how to use nlog to log my information in a file but now I would like to redirect my log to a ListView (C#) and do some actions. So I directed my log to a method as explained in the documentation nlog. It works.
<?xml version="1.0" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="msgbox" xsi:type="MethodCall" className="SomeNamespace.MyClass, MyAssembly" methodName="LogMethod">
<parameter layout="${level}" />
<parameter layout="${message}" />
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="msgbox" />
</rules>
</nlog>
Console.WriteLine works. It's not my problem.
namespace SomeNamespace
{
using System;
public class MyClass
{
public static void LogMethod(string level, string message)
{
Console.WriteLine("l: {0} m: {1}", level, message);
// logListView.Items.Add(Message);
// Do some other actions
}
}
}
I would like to add a line to my logListView (see commented line) but I can't because logListView is not static. How so? How do I proceed ?
One solution would be to add a static member to MyClass, like this:
public class MyClass
{
public static ListView MyListView { get; set; }
public static void LogMethod(string level, string message)
{
Console.WriteLine("l: {0} m: {1}", level, message);
var logListView = MyListView;
if (logListView != null) {
logListView.Items.Add(Message);
}
// Do some other actions
}
}
You can set the value of MyListView from some other part of your application when it is available.
While this solution would work, I would not prefer it because it is counter-intuitive. What you are doing here is declaring in static configuration a log target that is not meaningful at all in a static context: you want to log to a UI control that has not been created, there is no good way to refer to it until the application's UI has been shown, and the UI will be shown at some point or (academically speaking) maybe not at all.
I believe it is preferable to create your own log target class deriving from Target or TargetWithLayout. You can pass any parameters necessary (e.g. the ListView instance) to the log target's constructor, and add the log target programmatically at the point where the values of these parameters become known (i.e. the UI is shown and we have a ListView we can refer to).
Related
Spent more than 4 hours trying to figure out why etw logs wont show up in my tables. I still am unable to figure out why my logs are not showing up in azure table.
EDIT
The service is hosted as cloud service.
Here is how my diagnostics xml looks like. This is auto generated by selecting options in visual studio
<?xml version="1.0" encoding="utf-8"?>
<DiagnosticsConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
<PublicConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
<WadCfg>
<DiagnosticMonitorConfiguration overallQuotaInMB="4096">
<EtwProviders>
<EtwEventSourceProviderConfiguration provider="AzureEventSource">
<Event id="1" eventDestination="Error" />
<Event id="2" eventDestination="Warning" />
<Event id="3" eventDestination="Debug" />
<Event id="4" eventDestination="Performance" />
<DefaultEvents eventDestination="Default" />
</EtwEventSourceProviderConfiguration>
</EtwProviders>
<Logs scheduledTransferPeriod="PT2M" />
</DiagnosticMonitorConfiguration>
</WadCfg>
<StorageAccount />
</PublicConfig>
<PrivateConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
<StorageAccount endpoint="" />
</PrivateConfig>
<IsEnabled>true</IsEnabled>
</DiagnosticsConfiguration>
This is how my class looks like
using Microsoft.Diagnostics.Tracing;
namespace CommonUtils
internal sealed class AzureEventSource : EventSource, ILogger
{
public AzureEventSource(Type owner) : base(owner.FullName)
{
}
private bool IsInputInvalid(String message)
{
return String.IsNullOrWhiteSpace(message);
}
public void Error(string message, params object[] args)
{
if (IsInputInvalid(message))
{
throw new ArgumentNullException("message");
}
this.WriteEvent(1, string.Format(message, args));
}
}
}
I am pretty sure I am missing something simple.
Any help will be greatly appreciated
Assuming you're referring to Microsoft.Diagnostics.Tracing.EventSource in your above example.
You're missing EventSource Event attributes for your class and method respectively.
A simple hello world of eventsource looks like this.
[EventSource(Name = "HelloEventSource")]
internal sealed class HelloEventSource : EventSource
{
[Event(1, Level = EventLevel.Informational, Message = "Hello World! {0}")]
public void HelloWorld(string name)
{
WriteEvent(1, name);
}
}
You should fix the tracing part of your code and check the logs are getting generated locally on your machine/vm.
Sending the events via diagnostics pipeline of your choice comes next.
I recently added the log4net package in to my WCF web application. now I have few questions I:
I added the log4net package through the VS 2013 package install and
it added whole package. My guess is that log4net is just the dll and I can add it to my project just by adding the dll?
when I added log4net package a packages.config file has been added to my project with this content:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.3" targetFramework="net45" />
</packages>
what is it? can I remove it?
Can I use the log4net all over my project without defining it in top of each class?
I want to add a extra field to message part. for example I want to log the events like this:
Log.Debug(IP,"This is a DEBUG level message. Typically your most VERBOSE level.");
and the log file show it like this:
72 DEBUG 2015-06-16 08:17:41,713 [10] [(null)] [InsertDate] - from source IP(192.168.1.1) This is a DEBUG level message. Typically your most VERBOSE level.
I also added %line in to conversionPattern but it doesn't work. every time it start from 72. How can this be fixed?
How can log the application stop and start in the wcf application?
You actually asked 4 questions in 1.
First, you need to understand that you have used a NuGet package manager and it is a good practice to include available packages using NuGet but not manually as it automatically places package in a proper place, lets you add this package easily to other projects of solution, provides versioning etc.
Read more about NuGet. For example, here.
Now about your questions:
First: No, log4net package contains not only a log4net.dll file. It also has log4net.xml file. Yes, you can add these two files manually without using NuGet package manager.
Second: It is a list of NuGet packages in your project. You can only remove it if you are not going to use NuGet. However, it is not a good idea - let it be there, it is a single light-weight XML-file.
Third: It's a question of OOP. Create a singleton global logger, for example:
public static class LoggingFactory
{
private static ILog _logger;
private static ILog CreateLogger()
{
// Some log4net initialization
return LogManager.GetLogger("Logger");
}
public static ILog GetLogger()
{
return _logger ?? (_logger = CreateLogger());
}
}
public class AnyClassOfYourWholeSolution
{
LoggingFactory.GetLogger().DebugFormat("{0} {1}", a, b);
}
Fourth: It is a question of OOP again. Create an adapter and use it:
public interface ILogger
{
void Debug(string ip, string message);
void Info(string ip, string message);
void Error(string ip, string message);
}
public class Log4NetLogger : ILogger
{
private ILog _logger;
public Log4NetLogger()
{
// Some log4net initialization
_logger = LogManager.GetLogger("Logger");
}
public void Debug(string ip, string message)
{
// ...
}
public void Info(string ip, string message)
{
// ...
}
public void Error(string ip, string message)
{
// ...
}
}
public static class LoggingFactory
{
private static ILogger _loggerAdapter;
private static Initialize(ILogger adapter)
{
_loggerAdapter = adapter;
}
public static GetLogger()
{
return _logger;
}
}
public class BeginningOfYourProject
{
// Main() or Global.asax or Program.cs etc.
LoggingFactory.Initialize(new Log4NetLogger());
}
public class AnyClassOfYourWholeProject
{
LoggingFactory.GetLogger().Debug(ip, message);
}
Or you can extract ip to the properties of log4net:
// During initialization
log4net.ThreadContext.Properties["ip"] = ip;
// log4net Config
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%n%-5p %d %t IP: %property{ip} %m" />
</layout>
It is all up to your fantasy.
When I obfuscate this form, and "debug" it
public partial class Form1 : Form
{
public void Form1()
{
InitializeComponents();
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
Console.WriteLine("Name: "+this.Name);
Console.WriteLine("FullName: "+this.GetType().FullName);
}
}
The output is like this:
Name: Form1
FullName: #Yab.#Zab
Question
Why is FullName obfuscated?
Form1 is public so I would expect SmartAssembly to ignore it.
Extra info
Form1 is public partial and so is the designer.cs
My SmartAssembly setup is like this:
<ApplicationName />
<Destination DestinationFileName=".\bin.obfuscated\MyProject.Form1.exe" />
<Assemblies>
<Assembly AssemblyName="MyProject.Form1, Culture=neutral, PublicKeyToken=omitted">
<Merging>
<ResourcesCompression Compress="0" />
<MemberRefsProxy />
<Pruning />
<Obfuscation Obfuscate="1">
<Exclusions />
</Obfuscation>
<ControlFlow Obfuscate="1" />
</Merging>
</Assembly>
</Assemblies>
<Options>
<Obfuscation FieldsNameMangling="2" NameMangling="1" />
<ExceptionReporting />
<FeatureUsageReporting Template="res:SmartUsageWithUIConsentFirstRun1033.dll" />
<StrongNameSigning KeyFileName="PathToKeyFile" Sign="1" />
<OtherProtections />
<StringsEncoding />
<OtherOptimizations />
<Debugging />
</Options>
Firstly, a public class isn't ignored by SmartAssembly in an application project (it will be ignored in a library project).
Secondly, the Name property of your form is a property set at runtime. In your case case, it may be initialized to "Form1" somewhere in your code, maybe in the designer.
This value can be changed anytime, example :
public Form1()
{
InitializeComponent();
this.Name = "foo";
}
So SmartAssembly cannot obfuscate this value, it would be wrong and would change the behavior of your code.
When SmartAssembly obfuscates your code, it only changes the names of the types, fields and methods. When your try to get the name of your type, it's logical to get the obfuscated name of your type.
I have the following method in my apiController:
public IEnumerable<something> GetData(DataProvider dataProvider)
{
return dataProvider.GetData();
}
What I need is to invoke this method from javascript and pass it a parameter of DataProvider derived type. I can handle this by passing string, e.g. "FirstProvider" and than write N number of if's in GetData() method to create an instance of proper type.
But is there some way that I can write in web.config file something like:
<DataProviders>
<type = FirstDataProvider, alias = "FirstProvider">
<type = SecondDataProvider, alias = "SecondProvider">
</DataProviders>
Change getData method to:
public IEnumerable<something> GetData(string dataProviderAlias)
{
// get provider type by it's alias from web congfig,
// then instantiated and call:
return dataProvider.GetData();
}
And then find and instantiate the type by it's alias?
Note: I accepted the answer below cause it's pointed me in a right direction, but msdn says that IConfigurationSectionHandler is deprecated.
So I used ConfigurationSection, ConfigurationElementCollection, ConfigurationElement classes instead to build custom config section.
You can store arbitrary data in web.config in the appSettings element:
<configuration>
<appSettings>
<add key="FirstAlias" value="FirstProvider" />
<add key="SecondAlias" value="SecondProvider" />
</appSettings>
</configuration>
And you can then read the values using:
String firstAlias = System.Configuration.ConfigurationManager.AppSettings["FirstAlias"];
String secondAlias = System.Configuration.ConfigurationManager.AppSettings["SecondAlias"];
It's built-in. It's supported. It's where you're supposed to store custom data.
First of all, you can only store valid xml in web.config. <type = FirstDataProvider, alias = "FirstProvider"> is not valid xml.
Second, there are a lot of moving pieces. Please follow the steps carefully -
web.config
Make sure you enter the proper namespace for DataProviders. type="YOUR_APPLICATION.DataProviders".
<configuration>
<configSections>
<section name="DataProviders" type="WebApplication2010.DataProviders"
requirePermission="false"/>
</configSections>
<DataProviders>
<Provider type="FirstDataProvider" alias="FirstProvider"/>
<Provider type="SecondDataProvider" alias="SecondProvider"/>
</DataProviders>
....
</configuration>
Code
public class DataProviders : IConfigurationSectionHandler
{
private static bool _initialized;
public static List<Provider> _providers;
public object Create(object parent, object configContext, XmlNode section)
{
XmlNodeList providers = section.SelectNodes("Provider");
_providers = new List<Provider>();
foreach (XmlNode provider in providers)
{
_providers.Add(new Provider
{
Type = provider.Attributes["type"].Value,
Alias = provider.Attributes["alias"].Value,
});
}
return null;
}
public static void Init()
{
if (!_initialized)
{
ConfigurationManager.GetSection("DataProviders");
_initialized = true;
}
}
public static IEnumerable<Provider> GetData(string dataProviderAlias)
{
return _providers.Where(p => p.Alias == dataProviderAlias);
}
}
public class Provider
{
public string Type { get; set; }
public string Alias { get; set; }
}
Global.asax
For good design practice, you want to read data from web.config only once, and store them in static variables. Therefore, you want to initialize inside Application_BeginRequest of Global.asax.
public class Global : System.Web.HttpApplication
{
void Application_BeginRequest(object sender, EventArgs e)
{
DataProviders.Init();
}
}
Usage
var providers = DataProviders.GetData("FirstProvider").ToList();
Well, I'm not sure if I understand what you want to achieve, but to implement your idea you need a custom section handler.
http://msdn.microsoft.com/en-us/library/2tw134k3(v=vs.100).aspx
In case that you want to create a database connection for specific dataprovider, see this similar question:
ASP.NET: How to create a connection from a web.config ConnectionString?
In my case, I needed to store two byte[] variables in my Web.Config file. Since it must be valid XML data, I simply stored the contents of the arrays like so:
<appSettings>
<add key="Array1" value="0,1,2,3,4,5,6,7,8,9" />
<add key="Array2" value="0,1,2,3,4,5,6,7,8,9" />
</appSettings>
I then call a function that reads this into a C# string, split it into a string[], and parse each string element as a byte into a resulting byte[] and return it.
I use Common.Logging as a wrapper around NLog 2.0. I've done this so that I can replace NLog with another logging provider in the future.
I also use PostSharp to not write a try catch block everytime I need one. I have a class that inherits the OnMethodBoundaryAspect:
[Serializable]
public class LogMethodAttribute : OnMethodBoundaryAspect
{
private ILog logger;
public LogMethodAttribute()
{
this.logger = LogManager.GetCurrentClassLogger();
}
public override void OnEntry(MethodExecutionArgs args)
{
logger.Debug(string.Format("Entering {0}.{1}.", args.Method.DeclaringType.Name, args.Method.Name));
}
public override void OnExit(MethodExecutionArgs args)
{
logger.Debug(string.Format("Leaving {0}.{1}.", args.Method.DeclaringType.Name, args.Method.Name));
}
public override void OnException(MethodExecutionArgs args)
{
logger.Error(args.Exception.Message,args.Exception);
}
}
I have configured Common.Logging as follows in my web.config:
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.NLog.NLogLoggerFactoryAdapter, Common.Logging.NLog20">
<arg key="configType" value="FILE" />
<arg key="configFile" value="~/NLog.config" />
</factoryAdapter>
</logging>
</common>
NLog.Config looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwExceptions="true"
internalLogLevel="Debug"
internalLogToConsoleError="true"
internalLogFile="c:\new projects/nlog-app.txt"
>
<!--
See http://nlog-project.org/wiki/Configuration_file
for information on customizing logging rules and outputs.
-->
<targets>
<target name="database"
xsi:type="Database"
commandText="INSERT INTO LogEvent(EventDateTime, EventLevel, UserName, MachineName, EventMessage, ErrorSource, ErrorClass, ErrorMethod, ErrorMessage, InnerErrorMessage) VALUES(#EventDateTime, #EventLevel, #UserName, #MachineName, #EventMessage, #ErrorSource, #ErrorClass, #ErrorMethod, #ErrorMessage, #InnerErrorMessage)"
dbProvider="System.Data.SqlClient">
<connectionString>
Data Source=...;Initial Catalog=myDB;User Id=user;Password=pass;
</connectionString>
<installConnectionString>
Data Source=...;Initial Catalog=myDB;User Id=user;Password=pass;
</installConnectionString>
<!-- parameters for the command -->
<parameter name="#EventDateTime" layout="${date:s}" />
<parameter name="#EventLevel" layout="${level}" />
<parameter name="#UserName" layout="${identity}" />
<parameter name="#MachineName" layout="${machinename}" />
<parameter name="#EventMessage" layout="${message}" />
<parameter name="#ErrorSource" layout="${event-context:item=error-source}" />
<parameter name="#ErrorClass" layout="${event-context:item=error-class}" />
<parameter name="#ErrorMethod" layout="${event-context:item=error-method}" />
<parameter name="#ErrorMessage" layout="${event-context:item=error-message}" />
<parameter name="#InnerErrorMessage" layout="${event-context:item=inner-error-message}" />
<!-- commands to install database -->
<install-command>
<text>CREATE DATABASE myDB</text>
<connectionString> Data Source=...;Initial Catalog=myDB;User Id=user;Password=pass;</connectionString>
<ignoreFailures>true</ignoreFailures>
</install-command>
<install-command>
<text>
CREATE TABLE LogEvent(
EventId int primary key not null identity(1,1),
EventDateTime datetime,
EventLevel nvarchar(50),
UserName nvarchar(50),
MachineName nvarchar(1024),
EventMessage nvarchar(MAX),
ErrorSource nvarchar(1024),
ErrorClass nvarchar(1024),
ErrorMethod nvarchar(1024),
ErrorMessage nvarchar(MAX),
InnerErrorMessage nvarchar(MAX));
</text>
</install-command>
<!-- commands to uninstall database -->
<uninstall-command>
<text>DROP DATABASE myDB</text>
<connectionString> Data Source=...;Initial Catalog=myDB;User Id=user;Password=pass;</connectionString>
<ignoreFailures>true</ignoreFailures>
</uninstall-command>
</target>
</targets>
<rules>
<logger name="*" levels="Error" writeTo="database" />
</rules>
</nlog>
The problem is that nothing is inserted in my table. When I put a logger in for example my HomeController on the index page and I call my logger.Error("an error") it adds a record to my table.
Can somebody help me?
Are you decorating your controller methods with the LogMethodAttribute that you created?
Also, you'll want to adjust your logger rule to include more levels outside of just "Error", otherwise that's all you'll log.
Give this a try:
<rules>
<logger name="*" minLevel="Trace" writeTo="database" />
</rules>
Edit:
Have you tried moving your logger initialization into your method?
public override void OnEntry(MethodExecutionArgs args)
{
this.logger = LogManager.GetCurrentClassLogger();
logger.Debug(string.Format("Entering {0}.{1}.", args.Method.DeclaringType.Name, args.Method.Name));
}
Per Donald Belcham's Pluralsight course, aspect constructors are not executed at runtime, so perhaps your logger is not getting set properly.
add a static property logger in your class Aspect
public class LogAspect : OnMethodBoundaryAspect
{
/// <summary>
/// Gets or sets the logger.
/// </summary>
public static ILogger logger { get; set; }
set logger variable in your application init method with your ILogger class and exclude all methods before this initialization with AttributeExclude.
[LogAspect(AttributeExclude = true)]
protected void Application_Start()
{
_windsorContainer = new WindsorContainer();
ApplicationDependencyInstaller.RegisterLoggingFacility(_windsorContainer);
LogAspect.logger = _windsorContainer.Resolve<ILogger>();