可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
What are the best or most useful configurations for logging with NLog? (These can be simple or complex, as long as they\'re useful.)
I\'m thinking of examples like automatically rolling over log files at a certain size, changing the layout (log message) whether or not there is an exception, escalating the log level once an error has occurred, etc.
Here are some links:
- NLog Demo
- Examples in the source
回答1:
Some of these fall into the category of general NLog (or logging) tips rather than strictly configuration suggestions.
Here are some general logging links from here at SO (you might have seen some or all of these already):
log4net vs. Nlog
Logging best practices
What's the point of a logging facade?
Why do loggers recommend using a logger per class?
Use the common pattern of naming your logger based on the class Logger logger = LogManager.GetCurrentClassLogger()
. This gives you a high degree of granularity in your loggers and gives you great flexibility in the configuration of the loggers (control globally, by namespace, by specific logger name, etc).
Use non-classname-based loggers where appropriate. Maybe you have one function for which you really want to control the logging separately. Maybe you have some cross-cutting logging concerns (performance logging).
If you don\'t use classname-based logging, consider naming your loggers in some kind of hierarchical structure (maybe by functional area) so that you can maintain greater flexibility in your configuration. For example, you might have a \"database\" functional area, an \"analysis\" FA, and a \"ui\" FA. Each of these might have sub-areas. So, you might request loggers like this:
Logger logger = LogManager.GetLogger(\"Database.Connect\");
Logger logger = LogManager.GetLogger(\"Database.Query\");
Logger logger = LogManager.GetLogger(\"Database.SQL\");
Logger logger = LogManager.GetLogger(\"Analysis.Financial\");
Logger logger = LogManager.GetLogger(\"Analysis.Personnel\");
Logger logger = LogManager.GetLogger(\"Analysis.Inventory\");
And so on. With hierarchical loggers, you can configure logging globally (the \"*\" or root logger), by FA (Database, Analysis, UI), or by subarea (Database.Connect, etc).
Loggers have many configuration options:
<logger name=\"Name.Space.Class1\" minlevel=\"Debug\" writeTo=\"f1\" />
<logger name=\"Name.Space.Class1\" levels=\"Debug,Error\" writeTo=\"f1\" />
<logger name=\"Name.Space.*\" writeTo=\"f3,f4\" />
<logger name=\"Name.Space.*\" minlevel=\"Debug\" maxlevel=\"Error\" final=\"true\" />
See the NLog help for more info on exactly what each of the options means. Probably the most notable items here are the ability to wildcard logger rules, the concept that multiple logger rules can \"execute\" for a single logging statement, and that a logger rule can be marked as \"final\" so subsequent rules will not execute for a given logging statement.
Use the GlobalDiagnosticContext, MappedDiagnosticContext, and NestedDiagnosticContext to add additional context to your output.
Use \"variable\" in your config file to simplify. For example, you might define variables for your layouts and then reference the variable in the target configuration rather than specify the layout directly.
<variable name=\"brief\" value=\"${longdate} | ${level} | ${logger} | ${message}\"/>
<variable name=\"verbose\" value=\"${longdate} | ${machinename} | ${processid} | ${processname} | ${level} | ${logger} | ${message}\"/>
<targets>
<target name=\"file\" xsi:type=\"File\" layout=\"${verbose}\" fileName=\"${basedir}/${shortdate}.log\" />
<target name=\"console\" xsi:type=\"ColoredConsole\" layout=\"${brief}\" />
</targets>
Or, you could create a \"custom\" set of properties to add to a layout.
<variable name=\"mycontext\" value=\"${gdc:item=appname} , ${mdc:item=threadprop}\"/>
<variable name=\"fmt1withcontext\" value=\"${longdate} | ${level} | ${logger} | [${mycontext}] |${message}\"/>
<variable name=\"fmt2withcontext\" value=\"${shortdate} | ${level} | ${logger} | [${mycontext}] |${message}\"/>
Or, you can do stuff like create \"day\" or \"month\" layout renderers strictly via configuration:
<variable name=\"day\" value=\"${date:format=dddd}\"/>
<variable name=\"month\" value=\"${date:format=MMMM}\"/>
<variable name=\"fmt\" value=\"${longdate} | ${level} | ${logger} | ${day} | ${month} | ${message}\"/>
<targets>
<target name=\"console\" xsi:type=\"ColoredConsole\" layout=\"${fmt}\" />
</targets>
You can also use layout renders to define your filename:
<variable name=\"day\" value=\"${date:format=dddd}\"/>
<targets>
<target name=\"file\" xsi:type=\"File\" layout=\"${verbose}\" fileName=\"${basedir}/${day}.log\" />
</targets>
If you roll your file daily, each file could be named \"Monday.log\", \"Tuesday.log\", etc.
Don\'t be afraid to write your own layout renderer. It is easy and allows you to add your own context information to the log file via configuration. For example, here is a layout renderer (based on NLog 1.x, not 2.0) that can add the Trace.CorrelationManager.ActivityId to the log:
[LayoutRenderer(\"ActivityId\")]
class ActivityIdLayoutRenderer : LayoutRenderer
{
int estimatedSize = Guid.Empty.ToString().Length;
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append(Trace.CorrelationManager.ActivityId);
}
protected override int GetEstimatedBufferSize(LogEventInfo logEvent)
{
return estimatedSize;
}
}
Tell NLog where your NLog extensions (what assembly) like this:
<extensions>
<add assembly=\"MyNLogExtensions\"/>
</extensions>
Use the custom layout renderer like this:
<variable name=\"fmt\" value=\"${longdate} | ${ActivityId} | ${message}\"/>
Use async targets:
<nlog>
<targets async=\"true\">
<!-- all targets in this section will automatically be asynchronous -->
</targets>
</nlog>
And default target wrappers:
<nlog>
<targets>
<default-wrapper xsi:type=\"BufferingWrapper\" bufferSize=\"100\"/>
<target name=\"f1\" xsi:type=\"File\" fileName=\"f1.txt\"/>
<target name=\"f2\" xsi:type=\"File\" fileName=\"f2.txt\"/>
</targets>
<targets>
<default-wrapper xsi:type=\"AsyncWrapper\">
<wrapper xsi:type=\"RetryingWrapper\"/>
</default-wrapper>
<target name=\"n1\" xsi:type=\"Network\" address=\"tcp://localhost:4001\"/>
<target name=\"n2\" xsi:type=\"Network\" address=\"tcp://localhost:4002\"/>
<target name=\"n3\" xsi:type=\"Network\" address=\"tcp://localhost:4003\"/>
</targets>
</nlog>
where appropriate. See the NLog docs for more info on those.
Tell NLog to watch and automatically reload the configuration if it changes:
<nlog autoReload=\"true\" />
There are several configuration options to help with troubleshooting NLog
<nlog throwExceptions=\"true\" />
<nlog internalLogFile=\"file.txt\" />
<nlog internalLogLevel=\"Trace|Debug|Info|Warn|Error|Fatal\" />
<nlog internalLogToConsole=\"false|true\" />
<nlog internalLogToConsoleError=\"false|true\" />
See NLog Help for more info.
NLog 2.0 adds LayoutRenderer wrappers that allow additional processing to be performed on the output of a layout renderer (such as trimming whitespace, uppercasing, lowercasing, etc).
Don\'t be afraid to wrap the logger if you want insulate your code from a hard dependency on NLog, but wrap correctly. There are examples of how to wrap in the NLog\'s github repository. Another reason to wrap might be that you want to automatically add specific context information to each logged message (by putting it into LogEventInfo.Context).
There are pros and cons to wrapping (or abstracting) NLog (or any other logging framework for that matter). With a little effort, you can find plenty of info here on SO presenting both sides.
If you are considering wrapping, consider using Common.Logging. It works pretty well and allows you to easily switch to another logging framework if you desire to do so. Also if you are considering wrapping, think about how you will handle the context objects (GDC, MDC, NDC). Common.Logging does not currently support an abstraction for them, but it is supposedly in the queue of capabilities to add.
回答2:
Treating exceptions differently
We often want to get more information when there is an exception. The following configuration has two targets, a file and the console, which filter on whether or not there is any exception info. (EDIT: Jarek has posted about a new method of doing this in vNext.)
The key is to have a wrapper target with xsi:type=\"FilteringWrapper\" condition=\"length(\'${exception}\')>0\"
<nlog xmlns=\"http://www.nlog-project.org/schemas/NLog.mono2.xsd\"
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
autoReload=\"true\"
internalLogLevel=\"Warn\"
internalLogFile=\"nlog log.log\"
>
<variable name=\"VerboseLayout\"
value=\"${longdate} ${level:upperCase=true} ${message}
(${callsite:includSourcePath=true})\" />
<variable name=\"ExceptionVerboseLayout\"
value=\"${VerboseLayout} (${stacktrace:topFrames=10})
${exception:format=ToString}\" />
<targets async=\"true\">
<target name=\"file\" xsi:type=\"File\" fileName=\"log.log\"
layout=\"${VerboseLayout}\">
</target>
<target name=\"fileAsException\"
xsi:type=\"FilteringWrapper\"
condition=\"length(\'${exception}\')>0\">
<target xsi:type=\"File\"
fileName=\"log.log\"
layout=\"${ExceptionVerboseLayout}\" />
</target>
<target xsi:type=\"ColoredConsole\"
name=\"console\"
layout=\"${NormalLayout}\"/>
<target xsi:type=\"FilteringWrapper\"
condition=\"length(\'${exception}\')>0\"
name=\"consoleException\">
<target xsi:type=\"ColoredConsole\"
layout=\"${ExceptionVerboseLayout}\" />
</target>
</targets>
<rules>
<logger name=\"*\" minlevel=\"Trace\" writeTo=\"console,consoleException\" />
<logger name=\"*\" minlevel=\"Warn\" writeTo=\"file,fileAsException\" />
</rules>
</nlog>
回答3:
回答4:
Configure NLog via XML, but Programmatically
What? Did you know that you can specify the NLog XML directly to NLog from your app, as opposed to having NLog read it from the config file? Well, you can. Let\'s say that you have a distributed app and you want to use the same configuration everywhere. You could keep a config file in each location and maintain it separately, you could maintain one in a central location and push it out to the satellite locations, or you could probably do a lot of other things. Or, you could store your XML in a database, get it at app startup, and configure NLog directly with that XML (maybe checking back periodically to see if it had changed).
string xml = @\"<nlog>
<targets>
<target name=\'console\' type=\'Console\' layout=\'${message}\' />
</targets>
<rules>
<logger name=\'*\' minlevel=\'Error\' writeTo=\'console\' />
</rules>
</nlog>\";
StringReader sr = new StringReader(xml);
XmlReader xr = XmlReader.Create(sr);
XmlLoggingConfiguration config = new XmlLoggingConfiguration(xr, null);
LogManager.Configuration = config;
//NLog is now configured just as if the XML above had been in NLog.config or app.config
logger.Trace(\"Hello - Trace\"); //Won\'t log
logger.Debug(\"Hello - Debug\"); //Won\'t log
logger.Info(\"Hello - Info\"); //Won\'t log
logger.Warn(\"Hello - Warn\"); //Won\'t log
logger.Error(\"Hello - Error\"); //Will log
logger.Fatal(\"Hello - Fatal\"); //Will log
//Now let\'s change the config (the root logging level) ...
string xml2 = @\"<nlog>
<targets>
<target name=\'console\' type=\'Console\' layout=\'${message}\' />
</targets>
<rules>
<logger name=\'*\' minlevel=\'Trace\' writeTo=\'console\' />
</rules>
</nlog>\";
StringReader sr2 = new StringReader(xml2);
XmlReader xr2 = XmlReader.Create(sr2);
XmlLoggingConfiguration config2 = new XmlLoggingConfiguration(xr2, null);
LogManager.Configuration = config2;
logger.Trace(\"Hello - Trace\"); //Will log
logger.Debug(\"Hello - Debug\"); //Will log
logger.Info(\"Hello - Info\"); //Will log
logger.Warn(\"Hello - Warn\"); //Will log
logger.Error(\"Hello - Error\"); //Will log
logger.Fatal(\"Hello - Fatal\"); //Will log
I\'m not sure how robust this is, but this example provides a useful starting point for people that might want to try configuring like this.
回答5:
Logging different levels depending on whether or not there is an error
This example allows you to get more information when there is an error in your code. Basically, it buffers messages and only outputs those at a certain log level (e.g. Warn) unless a certain condition is met (e.g. there has been an error, so the log level is >= Error), then it will output more info (e.g. all messages from log levels >= Trace). Because the messages are buffered, this lets you gather trace information about what happened before an Error or ErrorException was logged - very useful!
I adapted this one from an example in the source code. I was thrown at first because I left out the AspNetBufferingWrapper
(since mine isn\'t an ASP app) - it turns out that the PostFilteringWrapper requires some buffered target. Note that the target-ref
element used in the above-linked example cannot be used in NLog 1.0 (I am using 1.0 Refresh for a .NET 4.0 app); it is necessary to put your target inside the wrapper block. Also note that the logic syntax (i.e. greater-than or less-than symbols, < and >) has to use the symbols, not the XML escapes for those symbols (i.e. >
and <
) or else NLog will error.
app.config:
<?xml version=\"1.0\"?>
<configuration>
<configSections>
<section name=\"nlog\" type=\"NLog.Config.ConfigSectionHandler, NLog\"/>
</configSections>
<nlog xmlns=\"http://www.nlog-project.org/schemas/NLog.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
throwExceptions=\"true\" internalLogToConsole=\"true\" internalLogLevel=\"Warn\" internalLogFile=\"nlog.log\">
<variable name=\"appTitle\" value=\"My app\"/>
<variable name=\"csvPath\" value=\"${specialfolder:folder=Desktop:file=${appTitle} log.csv}\"/>
<targets async=\"true\">
<!--The following will keep the default number of log messages in a buffer and write out certain levels if there is an error and other levels if there is not. Messages that appeared before the error (in code) will be included, since they are buffered.-->
<wrapper-target xsi:type=\"BufferingWrapper\" name=\"smartLog\">
<wrapper-target xsi:type=\"PostFilteringWrapper\">
<!--<target-ref name=\"fileAsCsv\"/>-->
<target xsi:type=\"File\" fileName=\"${csvPath}\"
archiveAboveSize=\"4194304\" concurrentWrites=\"false\" maxArchiveFiles=\"1\" archiveNumbering=\"Sequence\"
>
<layout xsi:type=\"CsvLayout\" delimiter=\"Tab\" withHeader=\"false\">
<column name=\"time\" layout=\"${longdate}\" />
<column name=\"level\" layout=\"${level:upperCase=true}\"/>
<column name=\"message\" layout=\"${message}\" />
<column name=\"callsite\" layout=\"${callsite:includeSourcePath=true}\" />
<column name=\"stacktrace\" layout=\"${stacktrace:topFrames=10}\" />
<column name=\"exception\" layout=\"${exception:format=ToString}\"/>
<!--<column name=\"logger\" layout=\"${logger}\"/>-->
</layout>
</target>
<!--during normal execution only log certain messages-->
<defaultFilter>level >= LogLevel.Warn</defaultFilter>
<!--if there is at least one error, log everything from trace level-->
<when exists=\"level >= LogLevel.Error\" filter=\"level >= LogLevel.Trace\" />
</wrapper-target>
</wrapper-target>
</targets>
<rules>
<logger name=\"*\" minlevel=\"Trace\" writeTo=\"smartLog\"/>
</rules>
</nlog>
</configuration>
回答6:
I provided a couple of reasonably interesting answers to this question:
Nlog - Generating Header Section for a log file
Adding a Header:
The question wanted to know how to add a header to the log file. Using config entries like this allow you to define the header format separately from the format of the rest of the log entries. Use a single logger, perhaps called \"headerlogger\" to log a single message at the start of the application and you get your header:
Define the header and file layouts:
<variable name=\"HeaderLayout\" value=\"This is the header. Start time = ${longdate} Machine = ${machinename} Product version = ${gdc:item=version}\"/>
<variable name=\"FileLayout\" value=\"${longdate} | ${logger} | ${level} | ${message}\" />
Define the targets using the layouts:
<target name=\"fileHeader\" xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${HeaderLayout}\" />
<target name=\"file\" xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${InfoLayout}\" />
Define the loggers:
<rules>
<logger name=\"headerlogger\" minlevel=\"Trace\" writeTo=\"fileHeader\" final=\"true\" />
<logger name=\"*\" minlevel=\"Trace\" writeTo=\"file\" />
</rules>
Write the header, probably early in the program:
GlobalDiagnosticsContext.Set(\"version\", \"01.00.00.25\");
LogManager.GetLogger(\"headerlogger\").Info(\"It doesn\'t matter what this is because the header format does not include the message, although it could\");
This is largely just another version of the \"Treating exceptions differently\" idea.
Log each log level with a different layout
Similarly, the poster wanted to know how to change the format per logging level. It wasn\'t clear to me what the end goal was (and whether it could be achieved in a \"better\" way), but I was able to provide a configuration that did what he asked:
<variable name=\"TraceLayout\" value=\"This is a TRACE - ${longdate} | ${logger} | ${level} | ${message}\"/>
<variable name=\"DebugLayout\" value=\"This is a DEBUG - ${longdate} | ${logger} | ${level} | ${message}\"/>
<variable name=\"InfoLayout\" value=\"This is an INFO - ${longdate} | ${logger} | ${level} | ${message}\"/>
<variable name=\"WarnLayout\" value=\"This is a WARN - ${longdate} | ${logger} | ${level} | ${message}\"/>
<variable name=\"ErrorLayout\" value=\"This is an ERROR - ${longdate} | ${logger} | ${level} | ${message}\"/>
<variable name=\"FatalLayout\" value=\"This is a FATAL - ${longdate} | ${logger} | ${level} | ${message}\"/>
<targets>
<target name=\"fileAsTrace\" xsi:type=\"FilteringWrapper\" condition=\"level==LogLevel.Trace\">
<target xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${TraceLayout}\" />
</target>
<target name=\"fileAsDebug\" xsi:type=\"FilteringWrapper\" condition=\"level==LogLevel.Debug\">
<target xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${DebugLayout}\" />
</target>
<target name=\"fileAsInfo\" xsi:type=\"FilteringWrapper\" condition=\"level==LogLevel.Info\">
<target xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${InfoLayout}\" />
</target>
<target name=\"fileAsWarn\" xsi:type=\"FilteringWrapper\" condition=\"level==LogLevel.Warn\">
<target xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${WarnLayout}\" />
</target>
<target name=\"fileAsError\" xsi:type=\"FilteringWrapper\" condition=\"level==LogLevel.Error\">
<target xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${ErrorLayout}\" />
</target>
<target name=\"fileAsFatal\" xsi:type=\"FilteringWrapper\" condition=\"level==LogLevel.Fatal\">
<target xsi:type=\"File\" fileName=\"xxx.log\" layout=\"${FatalLayout}\" />
</target>
</targets>
<rules>
<logger name=\"*\" minlevel=\"Trace\" writeTo=\"fileAsTrace,fileAsDebug,fileAsInfo,fileAsWarn,fileAsError,fileAsFatal\" />
<logger name=\"*\" minlevel=\"Info\" writeTo=\"dbg\" />
</rules>
Again, very similar to Treating exceptions differently.
回答7:
Log to Twitter
Based on this post about a log4net Twitter Appender, I thought I would try my hand at writing a NLog Twitter Target (using NLog 1.0 refresh, not 2.0). Alas, so far I have not been able to get a Tweet to actually post successfully. I don\'t know if it is something wrong in my code, Twitter, our company\'s internet connection/firewall, or what. I am posting the code here in case someone is interested in trying it out. Note that there are three different \"Post\" methods. The first one that I tried is PostMessageToTwitter. PostMessageToTwitter is essentially the same as PostLoggingEvent in the orignal post. If I use that I get a 401 exception. PostMessageBasic gets the same exception. PostMessage runs with no errors, but the message still does not make it up to Twitter. PostMessage and PostMessageBasic are based on examples that I found here on SO.
FYI - I just now found a comment by @Jason Diller to an answer in this post that says that twitter is going to turn off basic authentication \"next month\". This was back in May 2010 and it is now December 2010, so I guess that could be why this is not working.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Web;
using System.IO;
using NLog;
using NLog.Targets;
using NLog.Config;
namespace NLogExtensions
{
[Target(\"TwitterTarget\")]
public class TwitterTarget : TargetWithLayout
{
private const string REQUEST_CONTENT_TYPE = \"application/x-www-form-urlencoded\";
private const string REQUEST_METHOD = \"POST\";
// The source attribute has been removed from the Twitter API,
// unless you\'re using OAuth.
// Even if you are using OAuth, there\'s still an approval process.
// Not worth it; \"API\" will work for now!
// private const string TWITTER_SOURCE_NAME = \"Log4Net\";
private const string TWITTER_UPDATE_URL_FORMAT = \"http://twitter.com/statuses/update.xml?status={0}\";
[RequiredParameter]
public string TwitterUserName { get; set; }
[RequiredParameter]
public string TwitterPassword { get; set; }
protected override void Write(LogEventInfo logEvent)
{
if (string.IsNullOrWhiteSpace(TwitterUserName) || string.IsNullOrWhiteSpace(TwitterPassword)) return;
string msg = this.CompiledLayout.GetFormattedMessage(logEvent);
if (string.IsNullOrWhiteSpace(msg)) return;
try
{
//PostMessageToTwitter(msg);
PostMessageBasic(msg);
}
catch (Exception ex)
{
//Should probably do something here ...
}
}
private void PostMessageBasic(string msg)
{
// Create a webclient with the twitter account credentials, which will be used to set the HTTP header for basic authentication
WebClient client = new WebClient { Credentials = new NetworkCredential { UserName = TwitterUserName, Password = TwitterPassword } };
// Don\'t wait to receive a 100 Continue HTTP response from the server before sending out the message body
ServicePointManager.Expect100Continue = false;
// Construct the message body
byte[] messageBody = Encoding.ASCII.GetBytes(\"status=\" + msg);
// Send the HTTP headers and message body (a.k.a. Post the data)
client.UploadData(@\"http://twitter.com/statuses/update.xml\", messageBody);
}
private void PostMessage(string msg)
{
string user = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(TwitterUserName + \":\" + TwitterPassword));
byte [] bytes = System.Text.Encoding.UTF8.GetBytes(\"status=\" + msg.ToTweet());
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(\"http://twitter.com/statuses/update.xml\");
request.Method = \"POST\";
request.ServicePoint.Expect100Continue = false;
request.Headers.Add(\"Authorization\", \"Basic \" + user);
request.ContentType = \"application/x-www-form-urlencoded\";
request.ContentLength = bytes.Length;
Stream reqStream = request.GetRequestStream();
reqStream.Write(bytes, 0, bytes.Length);
reqStream.Close();
}
private void PostMessageToTwitter(string msg)
{
var updateRequest = HttpWebRequest.Create(string.Format(TWITTER_UPDATE_URL_FORMAT,
HttpUtility.UrlEncode(msg.ToTweet()))) as HttpWebRequest;
updateRequest.ContentLength = 0;
updateRequest.ContentType = REQUEST_CONTENT_TYPE;
updateRequest.Credentials = new NetworkCredential(TwitterUserName, TwitterPassword);
updateRequest.Method = REQUEST_METHOD;
updateRequest.ServicePoint.Expect100Continue = false;
var updateResponse = updateRequest.GetResponse() as HttpWebResponse;
if (updateResponse.StatusCode != HttpStatusCode.OK && updateResponse.StatusCode != HttpStatusCode.Continue)
{
throw new Exception(string.Format(\"An error occurred while invoking the Twitter REST API [Response Code: {0}]\", updateResponse.StatusCode));
}
}
}
public static class Extensions
{
public static string ToTweet(this string s)
{
if (string.IsNullOrEmpty(s) || s.Length < 140)
{
return s;
}
return s.Substring(0, 137) + \"...\";
}
}
}
Configure it like this:
Tell NLog the assembly containing the target:
<extensions>
<add assembly=\"NLogExtensions\"/>
</extensions>
Configure the target:
<targets>
<target name=\"twitter\" type=\"TwitterTarget\" TwitterUserName=\"yourtwittername\" TwitterPassword=\"yourtwitterpassword\" layout=\"${longdate} ${logger} ${level} ${message}\" />
</targets>
If someone tries this out and has success, post back with your findings.
回答8:
Reporting to an external website/database
I wanted a way to simply and automatically report errors (since users often don\'t) from our applications. The easiest solution I could come up with was a public URL - a web page which could take input and store it to a database - that is sent data upon an application error. (The database could then be checked by a dev or a script to know if there are new errors.)
I wrote the web page in PHP and created a mysql database, user, and table to store the data. I decided on four user variables, an id, and a timestamp. The possible variables (either included in the URL or as POST data) are:
app
(application name)
msg
(message - e.g. Exception occurred ...)
dev
(developer - e.g. Pat)
src
(source - this would come from a variable pertaining to the machine on which the app was running, e.g. Environment.MachineName
or some such)
log
(a log file or verbose message)
(All of the variables are optional, but nothing is reported if none of them are set - so if you just visit the website URL nothing is sent to the db.)
To send the data to the URL, I used NLog\'s WebService
target. (Note, I had a few problems with this target at first. It wasn\'t until I looked at the source that I figured out that my url
could not end with a /
.)
All in all, it\'s not a bad system for keeping tabs on external apps. (Of course, the polite thing to do is to inform your users that you will be reporting possibly sensitive data and to give them a way to opt in/out.)
MySQL stuff
(The db user has only INSERT
privileges on this one table in its own database.)
CREATE TABLE `reports` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ts` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`applicationName` text,
`message` text,
`developer` text,
`source` text,
`logData` longtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT=\'storage place for reports from external applications\'
Website code
(PHP 5.3 or 5.2 with PDO enabled, file is index.php
in /report
folder)
<?php
$app = $_REQUEST[\'app\'];
$msg = $_REQUEST[\'msg\'];
$dev = $_REQUEST[\'dev\'];
$src = $_REQUEST[\'src\'];
$log = $_REQUEST[\'log\'];
$dbData =
array( \':app\' => $app,
\':msg\' => $msg,
\':dev\' => $dev,
\':src\' => $src,
\':log\' => $log
);
//print_r($dbData); // For debugging only! This could allow XSS attacks.
if(isEmpty($dbData)) die(\"No data provided\");
try {
$db = new PDO(\"mysql:host=$host;dbname=reporting\", \"reporter\", $pass, array(
PDO::ATTR_PERSISTENT => true
));
$s = $db->prepare(\"INSERT INTO reporting.reports
(
applicationName,
message,
developer,
source,
logData
)
VALUES
(
:app,
:msg,
:dev,
:src,
:log
);\"
);
$s->execute($dbData);
print \"Added report to database\";
} catch (PDOException $e) {
// Sensitive information can be displayed if this exception isn\'t handled
//print \"Error!: \" . $e->getMessage() . \"<br/>\";
die(\"PDO error\");
}
function isEmpty($array = array()) {
foreach ($array as $element) {
if (!empty($element)) {
return false;
}
}
return true;
}
?>
App code (NLog config file)
<nlog xmlns=\"http://www.nlog-project.org/schemas/NLog.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
throwExceptions=\"true\" internalLogToConsole=\"true\" internalLogLevel=\"Warn\" internalLogFile=\"nlog.log\">
<variable name=\"appTitle\" value=\"My External App\"/>
<variable name=\"csvPath\" value=\"${specialfolder:folder=Desktop:file=${appTitle} log.csv}\"/>
<variable name=\"developer\" value=\"Pat\"/>
<targets async=\"true\">
<!--The following will keep the default number of log messages in a buffer and write out certain levels if there is an error and other levels if there is not. Messages that appeared before the error (in code) will be included, since they are buffered.-->
<wrapper-target xsi:type=\"BufferingWrapper\" name=\"smartLog\">
<wrapper-target xsi:type=\"PostFilteringWrapper\">
<target xsi:type=\"File\" fileName=\"${csvPath}\"
archiveAboveSize=\"4194304\" concurrentWrites=\"false\" maxArchiveFiles=\"1\" archiveNumbering=\"Sequence\"
>
<layout xsi:type=\"CsvLayout\" delimiter=\"Comma\" withHeader=\"false\">
<column name=\"time\" layout=\"${longdate}\" />
<column name=\"level\" layout=\"${level:upperCase=true}\"/>
<column name=\"message\" layout=\"${message}\" />
<column name=\"callsite\" layout=\"${callsite:includeSourcePath=true}\" />
<column name=\"stacktrace\" layout=\"${stacktrace:topFrames=10}\" />
<column name=\"exception\" layout=\"${exception:format=ToString}\"/>
<!--<column name=\"logger\" layout=\"${logger}\"/>-->
</layout>
</target>
<!--during normal execution only log certain messages-->
<defaultFilter>level >= LogLevel.Warn</defaultFilter>
<!--if there is at least one error, log everything from trace level-->
<when exists=\"level >= LogLevel.Error\" filter=\"level >= LogLevel.Trace\" />
</wrapper-target>
</wrapper-target>
<target xsi:type=\"WebService\" name=\"web\"
url=\"http://example.com/report\"
methodName=\"\"
namespace=\"\"
protocol=\"HttpPost\"
>
<parameter name=\"app\" layout=\"${appTitle}\"/>
<parameter name=\"msg\" layout=\"${message}\"/>
<parameter name=\"dev\" layout=\"${developer}\"/>
<parameter name=\"src\" layout=\"${environment:variable=UserName} (${windows-identity}) on ${machinename} running os ${environment:variable=OSVersion} with CLR v${environment:variable=Version}\"/>
<parameter name=\"log\" layout=\"${file-contents:fileName=${csvPath}}\"/>
</target>
</targets>
<rules>
<logger name=\"*\" minlevel=\"Trace\" writeTo=\"smartLog\"/>
<logger name=\"*\" minlevel=\"Error\" writeTo=\"web\"/>
</rules>
</nlog>
Note: there may be some issues with the size of the log file, but I haven\'t figured out a simple way to truncate it (e.g. a la *nix\'s tail
command).
回答9:
Easier Way To Log each log level with a different layout using Conditional Layouts
<variable name=\"VerboseLayout\" value=\"${level:uppercase=true}: ${longdate} | ${logger} :
${when:when=level == LogLevel.Trace:inner=MONITOR_TRACE ${message}}
${when:when=level == LogLevel.Debug:inner=MONITOR_DEBUG ${message}}
${when:when=level == LogLevel.Info:inner=MONITOR_INFO ${message}}
${when:when=level == LogLevel.Warn:inner=MONITOR_WARN ${message}}
${when:when=level == LogLevel.Error:inner=MONITOR_ERROR ${message}}
${when:when=level == LogLevel.Fatal:inner=MONITOR_CRITICAL ${message}} |
${exception:format=tostring} | ${newline} ${newline}\" />
See https://github.com/NLog/NLog/wiki/When-Filter for syntax
回答10:
Log from Silverlight
When using NLog with Silverlight you can send the trace to the server side via the provided web service. You can also write to a local file in the Isolated Storage, which come in handy if the web server is unavailable. See here for details, i.e. use something like this to make yourself a target:
namespace NLogTargets
{
[Target(\"IsolatedStorageTarget\")]
public sealed class IsolatedStorageTarget : TargetWithLayout
{
IsolatedStorageFile _storageFile = null;
string _fileName = \"Nlog.log\"; // Default. Configurable through the \'filename\' attribute in nlog.config
public IsolatedStorageTarget()
{
}
~IsolatedStorageTarget()
{
if (_storageFile != null)
{
_storageFile.Dispose();
_storageFile = null;
}
}
public string filename
{
set
{
_fileName = value;
}
get
{
return _fileName;
}
}
protected override void Write(LogEventInfo logEvent)
{
try
{
writeToIsolatedStorage(this.Layout.Render(logEvent));
}
catch (Exception e)
{
// Not much to do about his....
}
}
public void writeToIsolatedStorage(string msg)
{
if (_storageFile == null)
_storageFile = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFile isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
// The isolated storage is limited in size. So, when approaching the limit
// simply purge the log file. (Yeah yeah, the file should be circular, I know...)
if (_storageFile.AvailableFreeSpace < msg.Length * 100)
{
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(_fileName, FileMode.Truncate, FileAccess.Write, isolatedStorage))
{ }
}
// Write to isolated storage
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(_fileName, FileMode.Append, FileAccess.Write, isolatedStorage))
{
using (TextWriter writer = new StreamWriter(stream))
{
writer.WriteLine(msg);
}
}
}
}
}
}