NlogManager.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Xml;
  5. using NLog;
  6. using NLog.Config;
  7. using NLog.Filters;
  8. using NLog.Targets;
  9. using NLog.Targets.Wrappers;
  10. using MediaBrowser.Model.Logging;
  11. namespace Emby.Common.Implementations.Logging
  12. {
  13. /// <summary>
  14. /// Class NlogManager
  15. /// </summary>
  16. public class NlogManager : ILogManager
  17. {
  18. #region Private Fields
  19. private LogSeverity _severity = LogSeverity.Debug;
  20. /// <summary>
  21. /// Gets or sets the log directory.
  22. /// </summary>
  23. /// <value>The log directory.</value>
  24. private readonly string LogDirectory;
  25. /// <summary>
  26. /// Gets or sets the log file prefix.
  27. /// </summary>
  28. /// <value>The log file prefix.</value>
  29. private readonly string LogFilePrefix;
  30. #endregion
  31. #region Event Declarations
  32. /// <summary>
  33. /// Occurs when [logger loaded].
  34. /// </summary>
  35. public event EventHandler LoggerLoaded;
  36. #endregion
  37. #region Public Properties
  38. /// <summary>
  39. /// Gets the log file path.
  40. /// </summary>
  41. /// <value>The log file path.</value>
  42. public string LogFilePath { get; private set; }
  43. /// <summary>
  44. /// Gets or sets the exception message prefix.
  45. /// </summary>
  46. /// <value>The exception message prefix.</value>
  47. public string ExceptionMessagePrefix { get; set; }
  48. public string NLogConfigurationFilePath { get; set; }
  49. public LogSeverity LogSeverity
  50. {
  51. get
  52. {
  53. return _severity;
  54. }
  55. set
  56. {
  57. DebugFileWriter(
  58. LogDirectory, String.Format(
  59. "SET LogSeverity, _severity = [{0}], value = [{1}]",
  60. _severity.ToString(),
  61. value.ToString()
  62. ));
  63. var changed = _severity != value;
  64. _severity = value;
  65. if (changed)
  66. {
  67. UpdateLogLevel(value);
  68. }
  69. }
  70. }
  71. #endregion
  72. #region Constructor(s)
  73. /// <summary>
  74. /// Initializes a new instance of the <see cref="NlogManager" /> class.
  75. /// </summary>
  76. /// <param name="logDirectory">The log directory.</param>
  77. /// <param name="logFileNamePrefix">The log file name prefix.</param>
  78. public NlogManager(string logDirectory, string logFileNamePrefix)
  79. {
  80. DebugFileWriter(
  81. logDirectory, String.Format(
  82. "NlogManager constructor called, logDirectory is [{0}], logFileNamePrefix is [{1}], _severity is [{2}].",
  83. logDirectory,
  84. logFileNamePrefix,
  85. _severity.ToString()
  86. ));
  87. LogDirectory = logDirectory;
  88. LogFilePrefix = logFileNamePrefix;
  89. LogManager.Configuration = new LoggingConfiguration();
  90. }
  91. /// <summary>
  92. /// Initializes a new instance of the <see cref="NlogManager" /> class.
  93. /// </summary>
  94. /// <param name="logDirectory">The log directory.</param>
  95. /// <param name="logFileNamePrefix">The log file name prefix.</param>
  96. public NlogManager(string logDirectory, string logFileNamePrefix, LogSeverity initialSeverity) : this(logDirectory, logFileNamePrefix)
  97. {
  98. _severity = initialSeverity;
  99. DebugFileWriter(
  100. logDirectory, String.Format(
  101. "NlogManager constructor called, logDirectory is [{0}], logFileNamePrefix is [{1}], _severity is [{2}].",
  102. logDirectory,
  103. logFileNamePrefix,
  104. _severity.ToString()
  105. ));
  106. }
  107. #endregion
  108. #region Private Methods
  109. /// <summary>
  110. /// Adds the file target.
  111. /// </summary>
  112. /// <param name="path">The path.</param>
  113. /// <param name="level">The level.</param>
  114. private void AddFileTarget(string path, LogSeverity level)
  115. {
  116. DebugFileWriter(
  117. LogDirectory, String.Format(
  118. "AddFileTarget called, path = [{0}], level = [{1}].",
  119. path,
  120. level.ToString()
  121. ));
  122. RemoveTarget("ApplicationLogFileWrapper");
  123. // https://github.com/NLog/NLog/wiki/Performance
  124. var wrapper = new AsyncTargetWrapper
  125. {
  126. OverflowAction = AsyncTargetWrapperOverflowAction.Block,
  127. QueueLimit = 10000,
  128. BatchSize = 500,
  129. TimeToSleepBetweenBatches = 50
  130. };
  131. wrapper.Name = "ApplicationLogFileWrapper";
  132. var logFile = new FileTarget
  133. {
  134. FileName = path,
  135. Layout = "${longdate} ${level} ${logger}: ${message}",
  136. KeepFileOpen = true,
  137. ConcurrentWrites = false
  138. };
  139. logFile.Name = "ApplicationLogFile";
  140. wrapper.WrappedTarget = logFile;
  141. AddLogTarget(wrapper, level);
  142. }
  143. /// <summary>
  144. /// Gets the log level.
  145. /// </summary>
  146. /// <param name="severity">The severity.</param>
  147. /// <returns>LogLevel.</returns>
  148. /// <exception cref="System.ArgumentException">Unrecognized LogSeverity</exception>
  149. private LogLevel GetLogLevel(LogSeverity severity)
  150. {
  151. switch (severity)
  152. {
  153. case LogSeverity.Debug:
  154. return LogLevel.Debug;
  155. case LogSeverity.Error:
  156. return LogLevel.Error;
  157. case LogSeverity.Fatal:
  158. return LogLevel.Fatal;
  159. case LogSeverity.Info:
  160. return LogLevel.Info;
  161. case LogSeverity.Warn:
  162. return LogLevel.Warn;
  163. default:
  164. throw new ArgumentException("Unrecognized LogSeverity");
  165. }
  166. }
  167. private void UpdateLogLevel(LogSeverity newLevel)
  168. {
  169. DebugFileWriter(
  170. LogDirectory, String.Format(
  171. "UpdateLogLevel called, newLevel = [{0}].",
  172. newLevel.ToString()
  173. ));
  174. var level = GetLogLevel(newLevel);
  175. var rules = LogManager.Configuration.LoggingRules;
  176. foreach (var rule in rules)
  177. {
  178. if (!rule.IsLoggingEnabledForLevel(level))
  179. {
  180. rule.EnableLoggingForLevel(level);
  181. }
  182. foreach (var lev in rule.Levels.ToArray())
  183. {
  184. if (lev < level)
  185. {
  186. rule.DisableLoggingForLevel(lev);
  187. }
  188. }
  189. }
  190. }
  191. private void AddCustomFilters(string defaultLoggerNamePattern, LoggingRule defaultRule)
  192. {
  193. DebugFileWriter(
  194. LogDirectory, String.Format(
  195. "AddCustomFilters called, defaultLoggerNamePattern = [{0}], defaultRule.LoggerNamePattern = [{1}].",
  196. defaultLoggerNamePattern,
  197. defaultRule.LoggerNamePattern
  198. ));
  199. try
  200. {
  201. var customConfig = new NLog.Config.XmlLoggingConfiguration(NLogConfigurationFilePath);
  202. DebugFileWriter(
  203. LogDirectory, String.Format(
  204. "Custom Configuration Loaded, Rule Count = [{0}].",
  205. customConfig.LoggingRules.Count.ToString()
  206. ));
  207. foreach (var customRule in customConfig.LoggingRules)
  208. {
  209. DebugFileWriter(
  210. LogDirectory, String.Format(
  211. "Read Custom Rule, LoggerNamePattern = [{0}], Targets = [{1}].",
  212. customRule.LoggerNamePattern,
  213. string.Join(",", customRule.Targets.Select(x => x.Name).ToList())
  214. ));
  215. if (customRule.LoggerNamePattern.Equals(defaultLoggerNamePattern))
  216. {
  217. if (customRule.Targets.Any((arg) => arg.Name.Equals(defaultRule.Targets.First().Name)))
  218. {
  219. DebugFileWriter(
  220. LogDirectory, String.Format(
  221. "Custom rule filters can be applied to this target, Filter Count = [{0}].",
  222. customRule.Filters.Count.ToString()
  223. ));
  224. foreach (ConditionBasedFilter customFilter in customRule.Filters)
  225. {
  226. DebugFileWriter(
  227. LogDirectory, String.Format(
  228. "Read Custom Filter, Filter = [{0}], Action = [{1}], Type = [{2}].",
  229. customFilter.Condition.ToString(),
  230. customFilter.Action.ToString(),
  231. customFilter.GetType().ToString()
  232. ));
  233. defaultRule.Filters.Add(customFilter);
  234. }
  235. }
  236. else
  237. {
  238. DebugFileWriter(
  239. LogDirectory, String.Format(
  240. "Ignoring custom rule as [Target] does not match."
  241. ));
  242. }
  243. }
  244. else
  245. {
  246. DebugFileWriter(
  247. LogDirectory, String.Format(
  248. "Ignoring custom rule as [LoggerNamePattern] does not match."
  249. ));
  250. }
  251. }
  252. }
  253. catch (Exception ex)
  254. {
  255. // Intentionally do nothing, prevent issues affecting normal execution.
  256. DebugFileWriter(
  257. LogDirectory, String.Format(
  258. "Exception in AddCustomFilters, ex.Message = [{0}].",
  259. ex.Message
  260. )
  261. );
  262. }
  263. }
  264. #endregion
  265. #region Public Methods
  266. /// <summary>
  267. /// Gets the logger.
  268. /// </summary>
  269. /// <param name="name">The name.</param>
  270. /// <returns>ILogger.</returns>
  271. public MediaBrowser.Model.Logging.ILogger GetLogger(string name)
  272. {
  273. DebugFileWriter(
  274. LogDirectory, String.Format(
  275. "GetLogger called, name = [{0}].",
  276. name
  277. ));
  278. return new NLogger(name, this);
  279. }
  280. /// <summary>
  281. /// Adds the log target.
  282. /// </summary>
  283. /// <param name="target">The target.</param>
  284. /// <param name="level">The level.</param>
  285. public void AddLogTarget(Target target, LogSeverity level)
  286. {
  287. DebugFileWriter(
  288. LogDirectory, String.Format(
  289. "AddLogTarget called, target.Name = [{0}], level = [{1}].",
  290. target.Name,
  291. level.ToString()
  292. ));
  293. string loggerNamePattern = "*";
  294. var config = LogManager.Configuration;
  295. var rule = new LoggingRule(loggerNamePattern, GetLogLevel(level), target);
  296. config.AddTarget(target.Name, target);
  297. AddCustomFilters(loggerNamePattern, rule);
  298. config.LoggingRules.Add(rule);
  299. LogManager.Configuration = config;
  300. }
  301. /// <summary>
  302. /// Removes the target.
  303. /// </summary>
  304. /// <param name="name">The name.</param>
  305. public void RemoveTarget(string name)
  306. {
  307. DebugFileWriter(
  308. LogDirectory, String.Format(
  309. "RemoveTarget called, name = [{0}].",
  310. name
  311. ));
  312. var config = LogManager.Configuration;
  313. var target = config.FindTargetByName(name);
  314. if (target != null)
  315. {
  316. foreach (var rule in config.LoggingRules.ToList())
  317. {
  318. var contains = rule.Targets.Contains(target);
  319. rule.Targets.Remove(target);
  320. if (contains)
  321. {
  322. config.LoggingRules.Remove(rule);
  323. }
  324. }
  325. config.RemoveTarget(name);
  326. LogManager.Configuration = config;
  327. }
  328. }
  329. public void AddConsoleOutput()
  330. {
  331. DebugFileWriter(
  332. LogDirectory, String.Format(
  333. "AddConsoleOutput called."
  334. ));
  335. RemoveTarget("ConsoleTargetWrapper");
  336. var wrapper = new AsyncTargetWrapper();
  337. wrapper.Name = "ConsoleTargetWrapper";
  338. var target = new ConsoleTarget()
  339. {
  340. Layout = "${level}, ${logger}, ${message}",
  341. Error = false
  342. };
  343. target.Name = "ConsoleTarget";
  344. wrapper.WrappedTarget = target;
  345. AddLogTarget(wrapper, LogSeverity);
  346. }
  347. public void RemoveConsoleOutput()
  348. {
  349. DebugFileWriter(
  350. LogDirectory, String.Format(
  351. "RemoveConsoleOutput called."
  352. ));
  353. RemoveTarget("ConsoleTargetWrapper");
  354. }
  355. /// <summary>
  356. /// Reloads the logger, maintaining the current log level.
  357. /// </summary>
  358. public void ReloadLogger()
  359. {
  360. ReloadLogger(LogSeverity);
  361. }
  362. /// <summary>
  363. /// Reloads the logger, using the specified logging level.
  364. /// </summary>
  365. /// <param name="level">The level.</param>
  366. public void ReloadLogger(LogSeverity level)
  367. {
  368. DebugFileWriter(
  369. LogDirectory, String.Format(
  370. "ReloadLogger called, level = [{0}], LogFilePath (existing) = [{1}].",
  371. level.ToString(),
  372. LogFilePath
  373. ));
  374. LogFilePath = Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Floor(DateTime.Now.Ticks / 10000000) + ".txt");
  375. Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
  376. AddFileTarget(LogFilePath, level);
  377. LogSeverity = level;
  378. if (LoggerLoaded != null)
  379. {
  380. try
  381. {
  382. DebugFileWriter(
  383. LogDirectory, String.Format(
  384. "ReloadLogger called, raised event LoggerLoaded."
  385. ));
  386. LoggerLoaded(this, EventArgs.Empty);
  387. }
  388. catch (Exception ex)
  389. {
  390. GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex);
  391. }
  392. }
  393. }
  394. /// <summary>
  395. /// Flushes this instance.
  396. /// </summary>
  397. public void Flush()
  398. {
  399. DebugFileWriter(
  400. LogDirectory, String.Format(
  401. "Flush called."
  402. ));
  403. LogManager.Flush();
  404. }
  405. #endregion
  406. #region Conditional Debug Methods
  407. /// <summary>
  408. /// DEBUG: Standalone method to write out debug to assist with logger development/troubleshooting.
  409. /// <list type="bullet">
  410. /// <item><description>The output file will be written to the server's log directory.</description></item>
  411. /// <item><description>Calls to the method are safe and will never throw any exceptions.</description></item>
  412. /// <item><description>Method calls will be omitted unless the library is compiled with DEBUG defined.</description></item>
  413. /// </list>
  414. /// </summary>
  415. private static void DebugFileWriter(string logDirectory, string message)
  416. {
  417. #if DEBUG
  418. try
  419. {
  420. System.IO.File.AppendAllText(
  421. Path.Combine(logDirectory, "NlogManager.txt"),
  422. String.Format(
  423. "{0} : {1}{2}",
  424. System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
  425. message,
  426. System.Environment.NewLine
  427. )
  428. );
  429. }
  430. catch (Exception ex)
  431. {
  432. // Intentionally do nothing, prevent issues affecting normal execution.
  433. }
  434. #endif
  435. }
  436. #endregion
  437. }
  438. }