I was looking for a solution to provide logging to my latest project when I came across an article ( http://www.daveoncsharp.com/2009/09/create-a-logger-using-the-trace-listener-in-csharp/ ) that talked about using System.Diagnostics and App.config to log via the Trace method. I was able to successfully implement both the class and the App.config but I'd really like to be able to dynamically assign the value/location (inside initializeData) of the log file and I don't have a clue how to do it. If you have any suggestions, please feel free to post!
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<trace autoflush="true" indentsize="4">
<listeners>
<add name="myListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="fileSizeThreshold=512, fileSizeUnit=kilobytes,
fileAgeThreshold=1, fileAgeUnit=months, fileNameTemplate='{0}\MyApp-{1:MMM-yy}.log'"/>
<remove name="Default" />
</listeners>
</trace>
</system.diagnostics>
</configuration>
Logger Class:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace MyCurrentProject
{
class Logger
{
public void Error(string module, string message)
{
WriteEntry(message, "ERROR:", module);
}
public void Error(Exception ex, string module)
{
WriteEntry(ex.Message, "ERROR:", module);
}
public void Warning(string module, string message)
{
WriteEntry(message, "WARNING:", module);
}
public void Info(string module, string message)
{
WriteEntry(message, "INFO:", module);
}
private void WriteEntry(string message, string type, string module)
{
Trace.WriteLine(
string.Format("{0} {1} [{2}] {3}",
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
type,
module,
message));
}
}
}
RE: Sorry for not being clear... just to be clear, I need the file path that the log file output is save to to be dynamically set. I'd like the path to save to a location in %AppData%. The problem I had with the config was that once I set the value for 'initializeData' I couldn't find a way to change or dynamically set/reset that value. Truthfully... at this point I just want a solution that works and allows me to manage the location of the log file.
Here is a worked example using Systems.Diagnostics, which has two advantages over non-base class libraries. It is always allowed (in large organizations), always available, and to the extent that developers are familiar with the base class library, the next batch of maintenance developers will have heard of it.
using System.Diagnostics;
namespace DynamicTraceLog
{
class Program
{
static void Main(string[] args)
{
//Use TraceSource, not Trace, they are easier to turn off
TraceSource trace = new TraceSource("app");
//SourceSwitches allow you to turn the tracing on and off.
SourceSwitch level =new SourceSwitch("app");
//I assume you want to be dynamic, so probalby some user input would be here:
if(args.Length>0 && args[0]=="Off")
level.Level= SourceLevels.Off;
else
level.Level = SourceLevels.Verbose;
trace.Switch = level;
//remove default listner to improve performance
trace.Listeners.Clear();
//Listeners implement IDisposable
using (TextWriterTraceListener file = new TextWriterTraceListener("log.txt"))
using (ConsoleTraceListener console = new ConsoleTraceListener())
{
//The file will likely be in /bin/Debug/log.txt
trace.Listeners.Add(file);
//So you can see the results in screen
trace.Listeners.Add(console);
//Now trace, the console trace appears immediately.
trace.TraceInformation("Hello world");
//File buffers, it flushes on Dispose or when you say so.
file.Flush();
}
System.Console.ReadKey();
}
}
}
Re: how to format the output
Try either of these two for trace listeners that implement templated trace formating/output using Systems.Diagnostics classes:
http://essentialdiagnostics.codeplex.com
or http://ukadcdiagnostics.codeplex.com/
Systems.Diagnostics doesn't provide for any particular formating or outputing of standard tokens.
I know I may get down voted for this, but I'm going to go off the ranch for a minute and I'm going to suggest you use a different tool from the toolbox. Log4Net is a very powerful and succinct logging framework. For example, below is a console application that uses it and it's fully functional as you see it.
using Com.Foo;
// Import log4net classes.
using log4net;
using log4net.Config;
public class MyApp
{
// Define a static logger variable so that it references the
// Logger instance named "MyApp".
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
static void Main(string[] args)
{
// Set up a simple configuration that logs on the console.
BasicConfigurator.Configure();
log.Info("Entering application.");
Bar bar = new Bar();
bar.DoIt();
log.Info("Exiting application.");
}
}
But let's say we wanted to do that with a configuration file just like you're implying you would like to use. Well, that's pretty straight forward! Below is the configuration we'd place in the App.config to accomplish the same thing:
<log4net>
<!-- A1 is set to be a ConsoleAppender -->
<appender name="A1" type="log4net.Appender.ConsoleAppender">
<!-- A1 uses PatternLayout -->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message%newline" />
</layout>
</appender>
<!-- Set root logger level to DEBUG and its only appender to A1 -->
<root>
<level value="DEBUG" />
<appender-ref ref="A1" />
</root>
</log4net>
Then that configuration can be used like this:
using Com.Foo;
// Import log4net classes.
using log4net;
using log4net.Config;
public class MyApp
{
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
static void Main(string[] args)
{
// BasicConfigurator replaced with XmlConfigurator.
XmlConfigurator.Configure(new System.IO.FileInfo(args[0]));
log.Info("Entering application.");
Bar bar = new Bar();
bar.DoIt();
log.Info("Exiting application.");
}
}
And don't let the pattern stuff catch you off guard, it's just configuring what you're coding above so that you can have some consistency in the messages. It really keeps it easy though because all you ever have to log is the information that needs to be plugged into the pattern and the pattern is then encapsulated away.
This is a crash course in Log4Net, but the reason I'm really recommending it is because in the two examples above you see that they logged to the console, but you have a myriad of possible loggers, just look at this list:
log4net.Appender.AdoNetAppender: Writes logging events to a database using either prepared statements or stored procedures.
log4net.Appender.AnsiColorTerminalAppender: Writes color highlighted logging events to a an ANSI terminal window.
log4net.Appender.AspNetTraceAppender: Writes logging events to the ASP trace context. These can then be rendered at the end of the ASP page or on the ASP trace page.
log4net.Appender.ColoredConsoleAppender: Writes color highlighted logging events to the application's Windows Console.
log4net.Appender.ConsoleAppender: Writes logging events to the application's Console. The events may go to either the standard our stream or the standard error stream.
log4net.Appender.DebugAppender: Writes logging events to the .NET system.
log4net.Appender.EventLogAppender: Writes logging events to the Windows Event Log.
log4net.Appender.FileAppender: Writes logging events to a file in the file system.
log4net.Appender.LocalSyslogAppender: Writes logging events to the local syslog service (UNIX only).
log4net.Appender.MemoryAppender: Stores logging events in an in memory buffer.
log4net.Appender.NetSendAppender: Writes logging events to the Windows Messenger service. These messages are displayed in a dialog on a users terminal.
log4net.Appender.OutputDebugStringAppender: Writes logging events to the debugger. If the application has no debugger, the system debugger displays the string. If the application has no debugger and the system debugger is not active, the message is ignored.
log4net.Appender.RemoteSyslogAppender: Writes logging events to a remote syslog service using UDP networking.
log4net.Appender.RemotingAppender: Writes logging events to a remoting sink using .NET remoting.
log4net.Appender.RollingFileAppender: Writes logging events to a file in the file system. The RollingFileAppender can be configured to log to multiple files based upon date or file size constraints.
log4net.Appender.SmtpAppender: Sends logging events to an email address.
log4net.Appender.SmtpPickupDirAppender: Sends logging events to an email address but writes the emails to a configurable directory rather than sending them directly via SMTP.
log4net.Appender.TelnetAppender: Clients connect via Telnet to receive logging events.
log4net.Appender.TraceAppender: Writes logging events to the .NET trace system.
log4net.Appender.UdpAppender: Sends logging events as connectionless UDP datagrams to a remote host or a multicast group using a UdpClient.
So, as you can see, it's extremely capable OOB. I hope this post has been helpful.
Related
Let's say I'm working on a little batch-processing console app in VB.Net. I want to be able to structure the app like this:
Sub WorkerMethod()
'Do some work
Trace.WriteLine("Work progress")
'Do more work
Trace.WriteLine("Another progress update")
'...
End Sub
Sub Main()
'Do any setup, like confirm the user wants to continue or whatever
WorkerMethod()
End Sub
Note that I'm using Trace rather than Console for my output. This is because the worker method may be called from elsewhere, or even live in a different assembly, and I want to be able to attach different trace listeners to it. So how can I connect the console to the trace?
I can already do it by defining a simple class (shown below) and adding an instance to the Trace's listeners collection, but I'm wondering if there's a more accepted or built in way to accomplish this:
Public Class ConsoleTrace
Inherits Diagnostics.TraceListener
Public Overloads Overrides Sub Write(ByVal message As String)
Console.Write(message)
End Sub
Public Overloads Overrides Sub WriteLine(ByVal message As String)
Console.WriteLine(message)
End Sub
End Class
You can add the following to your exe's .config file.
<?xml version="1.0"?>
<configuration>
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add name="logListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="cat.log" />
<add name="consoleListener" type="System.Diagnostics.ConsoleTraceListener"/>
</listeners>
</trace>
</system.diagnostics>
</configuration>
I included the TextWriter as well, in case you're interested in logging to a file.
Joel,
You could do this instead of the app config method:
Trace.Listeners.Add(new ConsoleTraceListener());
or this, if you want to manage adding or removing the listener during the life of the app:
ConsoleTraceListener listener = new ConsoleTraceListener();
Trace.Listeners.Add(listener);
Trace.WriteLine("Howdy");
Trace.Listeners.Remove(listener);
Trace.Close();
Great solution, but I have a situation where I have different dll's being run by the same calling exe, so I don't want to modify the calling exe's .config file. I want each dll to handle it's own alteration of the trace output.
Easy enough:
Stream outResultsFile = File.Create ("output.txt");
var textListener = new TextWriterTraceListener (outResultsFile);
Trace.Listeners.Add (textListener);
This will, of course, output Trace output to the "output.txt" file.
I'm using the Simple.Data ORM to hook up a database from within the Visual Studio environment that's defined in a local sql file (named convertcsv.sql). I'm following the instructions detailed here, and thus far, I've installed Simple.Data.SqlServer and Simple.Data.Ado via NuGet, have the following in my App XML file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<connectionStrings>
<add name="Simple.Data.Properties.Settings.DefaultConnectionString"
connectionString="convertcsv" />
</connectionStrings>
I have the following in my Program.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Simple.Data;
namespace DatabaseWalkthrough{
class Program{
static void Main(string[] args){
var db = Database.Open();
var tmp = db.mytable.FindAll(db.mytable.NC == 505);
foreach (var d in tmp){
Console.WriteLine(d.NC);
Console.ReadKey();
}
}
}
}
NOTE: In the convertcsv.sql file, there is only one table (mytable) and NC is one of its well-defined fields containing integral values.
At the line where the foreach loop is initialized, the debugger alerts the following error:
Additional information: Format of the initialization string does not conform to specification starting at index 0.
A little bit of sleuthing reveals this Stack Overflow question a similar problem, but that particular instance of the error appears to have been caused by improper credentials to access a database on a server, whereas I'm trying to propagate a database into my C# app in Visual Studio to update it.
I have the following hypotheses for why this could be an issue:
1) The App config XML file cannot locate the convertcsv.sql file. Somewhat unlikely since I preemptively added this file into all of the project subdirectories.
2) Missing parameters - this is possible since I've seen some variation for how DB connections are arranged in the App config file, but the Simple.Data documentation is somewhat translucent.
Any other ideas?
I use NLog (with logentries.com) for logging in my WPF app, but some external components of my app accepts only filesystem path as log output. It is possible, to create "virtual path/file" which will be associated with NLog and every line appended to this "virtual file" will be routed directly to the Nlog (and then to logentries.com)?
Currently I'm using temporary file on disk, monitor changes of this file and resends updated content to the NLog, but it's not very effective.
I think you should write a custom target for that. It's only a few lines!
[Target("MyFirst")]
public sealed class MyFirstTarget: TargetWithLayout
{
public MyFirstTarget()
{
}
protected override void Write(LogEventInfo logEvent)
{
string logMessage = this.Layout.Render(logEvent);
// TODO - write me logMessage to file
}
}
See a news post related to this.
There's a WP library we offer that can be used for sending log events to Logentries
le-windows-phone
Hope this helps!
Ardi
Logentries Support Engineer
I have a Netduino and it is currently outputting accelerometer data to the System. Diagnostics.Debug window. I am waiting on a USB->232 converter in the mail to properly get the data from the device into my app via a serial port but I was wondering just for quick testing purposes if anyone knows if it is possible to read the data from the Debug window back into my app?
EDIT - Solution:
I am posting this here for anyone who want my solution. I originally thought that Nuf's answer worked "Data written to System.Diagnostics.Debug can be captured with TraceListener class. MSDN has short tutorial how to set it up." but I found out that the Listener can only get data from within it's own application. Since I was using a Netduino, the Debug output was from a different program which meant the trace listener could not read it. So I found a way to read the text in the Output box directly.
Base on code from MSDN:
You will need to 3 references to your project. They are located in the .Net reference tab - EnvDTE, EnvDTE80, and extensibility.
using EnvDTE80;
using EnvDTE;
using Extensibility;
public static string ReadDebugBox()
{
EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.10.0");
string data = "";
OutputWindow ow = dte.ToolWindows.OutputWindow;
OutputWindowPane owP;
TextDocument owPTxtDoc;
EditPoint2 strtPt;
owP = ow.OutputWindowPanes.Item("Debug");
owP.Activate();
owPTxtDoc = owP.TextDocument;
strtPt = (EditPoint2)owPTxtDoc.StartPoint.CreateEditPoint();
return strtPt.GetText(owPTxtDoc.EndPoint);
}
public static void ClearDebugBox()
{
EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.10.0");
OutputWindow ow = dte.ToolWindows.OutputWindow;
OutputWindowPane owP;
TextDocument owPTxtDoc;
EditPoint2 strtPt;
owP = ow.OutputWindowPanes.Item("Debug");
owP.Activate();
owP.Clear();
}
There may be better ways of doing it but this is just one that worked for me so i thought I would share it.
Data written to System.Diagnostics.Debug can be captured with TraceListener class. MSDN has short tutorial how to set it up.
There are two ways to set this up, one way is to declaratively set this up in your app.config file. There's a lot of advantages to doing this such as not needing to recompile your application when changes are needed.
<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<add name="configConsoleListener"
type="System.Diagnostics.ConsoleTraceListener" />
</listeners>
</trace>
</system.diagnostics>
</configuration>
Alternatively, you can also do this inside of your code to output Debug traces to your application's console window.
Debug.Listeners.Add(new ConsoleTraceListener());
I have a class library which is deployed on an ISP server to be consumed by an ASP.NET web service. I'd like to keep track of any errors and in this case the windows event log is inaccessible to me. So I thought I'd write to a txt file using the StreamWriter class. Problem is if I don't give an absolute path and just a file name it tries to write to C:\Windows\System32, and that's no good to me.
How can I tell it to use maybe the data directory or the application root? Any thoughts?
Use Server.MapPath to get a path relative to the web application.
using (FileStream fs = new FileStream(Server.MapPath("~/logs/logfile.txt"),
FileMode.Append)) {
//do logging here.
}
While some of the previous posters have suggested using reflection to get the executing assembly, I'm not sure whether or not that will net you the web application or the w3wp process. If it's the latter, you're still going to end up trying to write to the System32 folder.
Here is what I used to use, it's a little clunky but it gets the job done:
using System;
using System.Collections.Generic;
using System.Web.UI;
public static class Logger
{
private static readonly Page Pge = new Page();
private static readonly string Path = Pge.Server.MapPath("~/yourLogPath/Log.txt");
private const string LineBreaker = "\r\n\r======================================================================================= \r\n\r";
public static void LogError(string myMessage, Exception e)
{
const LogSeverity severity = LogSeverity.Error;
string messageToWrite = string.Format("{0} {1}: {2} \r\n\r {3}\r\n\r {4}{5}", DateTime.Now, severity, myMessage, e.Message, e.StackTrace, LineBreaker);
System.IO.File.AppendAllText(Path, messageToWrite);
}
}
I had this class in it's own project, separate from the website itself, and I used it in all of my other non website projects...
Edit:
Btw LogSeverity is just an enum I made up...
In my web product, in the web.config I specify an appSettings block like this:
<configuration>
<appSettings>
<add key="MyLogPath" value="LogPath" />
</appSettings>
</configuration>
which you can use from the code like
ConfigurationManager.AppSettings["MyLogPath"]
then you can have the installer configure it to wherever you want. you probably don't want the log files in your application directory.
Try checking out:
Application.StartupPath;
Here's a link to the docs
Gets the path for the executable file
that started the application, not
including the executable name.
string path = Application.StartupPath;
Note: you'll still need to add a file name.
You can find out the path of your executable by doing this:
string path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);