Verwenden von Graylog und NLog zum Sammeln von Protokollen aus C # -Anwendungen. Persönliche Erfahrung


KDPV

Habr, willkommen!

Das Folgende ist keinesfalls ein Tutorial oder eine bewährte Methode . Ich beschloss, meine Erfolge nur in der gestellten Frage zusammenzufassen und zu dokumentieren.

Ich hoffe, dass der Inhalt dieses Artikels denjenigen, die Informationen zur Protokollierung suchen , ermöglicht , etwas Neues zu lernen oder eine Entscheidung zu treffen. Und natürlich hoffe ich auf ein konstruktives Feedback von der Community. Es gibt eine Chance, etwas besser zu machen.

Wofür? Für wen?


Ich werde versuchen, in meinen eigenen Worten zu beschreiben, was Protokollierung ist:
Speichern einiger für den Menschen lesbarer Informationen über ein positives oder negatives Ereignis während der Ausführung des Programmcodes, möglicherweise unter Beibehaltung qualitativer oder quantitativer Indikatoren und Daten als Teil der gespeicherten Informationen in Bezug auf den Zeitpunkt des Ereignisses.
Es kam eine komplizierte Formulierung heraus, die im allgemeinen Fall die Aufgabe des Erstellens und Verwaltens von Protokollen vollständig beschreibt. Eine qualitativ hochwertige Protokollierung hilft dem Entwickler, den internen Status des Codes in jeder Phase seines Lebens zu steuern: vom Debuggen bis zur Installation in einer unbekannten Umgebung des Computers des Verbrauchers.

Lassen Sie uns zeigen, wie ich Protokolle schreibe.

Werkzeuge


Wir verzichten auf die Protokollierung mit wenig informativen Textnachrichten wie " Datenbank ist nicht verfügbar ", " Datei konnte nicht gespeichert werden " und dergleichen. Protokolle dieses Typs sind einfach zu erstellen, reichen jedoch häufig nicht aus, um das Wesentliche und die Ursachen des Problems im Code oder in der Umgebung zu verstehen. Irgendwann muss jeder Entwickler nur noch eine strukturierte Protokollierung entwickeln. Wie mein guter Freund und IT-Mentor mir sagte: „ Sammeln Sie alles und schließen Sie dann unnötige ... aus. “ Spezielle Frameworks helfen uns dabei.

Es gibt genug davon. Die meisten von ihnen haben ähnliche Fähigkeiten und Einschränkungen. Es wurden viele Artikel und Rezensionen darüber geschrieben. , Vergleiche und Handbücher. Für die Anmeldung von Anwendungen, die in C # geschrieben wurden, habe ich einmal NLog gewählt . Jetzt erinnere ich mich nicht, warum er es war, aber es ist einfach so passiert.

Diese Plattform ist ziemlich gutDokumentation und äußerst nützliche Anwendung Fälle . Natürlich habe ich NLog zu unterschiedlichen Zeiten und für unterschiedliche Projekte auf unterschiedliche Weise verwendet. Aber irgendwann wurde ein Code geboren, der jetzt als Snippet verwendet wird und sich praktisch nicht ändert.

Implementierung


Zur Sache! Zeigen Sie den Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Newtonsoft.Json;
using NLog;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;
using NLog.Targets.GraylogHttp;
using NLog.Targets.Wrappers;

namespace BIMLIB.Converter
{
	public static class BimlibLogger
	{
		private static readonly ApplicationSettings _settings = BimlibSettingsManager.Instance.AppSettings;
		private static Lazy<LogFactory> _instance = null;
		private static LoggingConfiguration _logConfig = null;

		private static Lazy<LogFactory> Instance
		{
			get
			{
				return _instance ?? (_instance = new Lazy<LogFactory>(BuildLogFactory));
			}
		}

		public static Logger GetLogger()
		{
			return Instance.Value.GetCurrentClassLogger();
		}

		private static LogFactory BuildLogFactory()
		{
			LoggingConfiguration config = _logConfig ?? new LoggingConfiguration();
			//     .   .
			string headerStr = _settings.LogHeader;
			//    .       - , : https://github.com/drewnoakes/figgle
			Layout header = headerStr;

			//      : https://nlog-project.org/config/?tab=layout-renderers
			//     :
			// -   : 2020-04-24 19:13:51.1620 [BIMLIB.Converter.MainClass.Main] -> I : Service starting...
			// -  : 2020-04-22 09:55:33.1132 [BIMLIB.Converter.Converter.ClearFile] -> E : mscorlib
			//{
			//	"Type":"System.IO.FileNotFoundException", "Message":" 'D:\\path\\to\\file\\file_name.ifc'  .", "FileName":"D:\\path\\to\\file\\file_name.ifc", "Data":{
			//	}, "TargetSite":"Void WinIOError(Int32, System.String)", "StackTrace":"    System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)\r\n    System.IO.FileInfo.get_Length()\r\n    BIMLIB.Converter.Converter.ClearFile(String path)", "Source":"mscorlib", "HResult":-2147024894}

			Layout layout = "${longdate} [${callsite:className=true:includeNamespace=true:fileName=false:includeSourcePath=false:methodName=true:cleanNamesOfAnonymousDelegates=true:cleanNamesOfAsyncContinuations=true}] -> ${level:format=FirstCharacter} : ${message}${onexception:inner=${newline}${exception:format=@}}";

			#region Targets ----------------------------

			#region FileTarget ----------------------------

			Target fileTarget = FileTarget(header, layout).MakeAsyncTarget();
			config.AddTarget(fileTarget);
			config.AddRuleForAllLevels(fileTarget);

			#endregion

			#region ConsoleTarget ----------------------------

			Target consoleTarget = ConsoleTarget(header, layout).MakeAsyncTarget();
			config.AddTarget(consoleTarget);
			config.AddRuleForAllLevels(consoleTarget);

			#endregion

			#region DebuggerTarget ----------------------------

			Target debugTarget = DebuggerTarget(header, layout).MakeAsyncTarget();
			config.AddTarget(debugTarget);
			config.AddRuleForAllLevels(debugTarget);

			#endregion

			#region GelfTarget ----------------------------

			//       Graylog  ,    
			if (_settings.Statistics)
			{
				Target gelfTarget = GelfTarget(headerStr).MakeAsyncTarget();
				config.AddTarget(gelfTarget);
				config.AddRuleForAllLevels(gelfTarget);
			}

			#endregion

			#endregion

			LogFactory logFactory = new LogFactory
			{
				Configuration = config
			};

			try
			{
				// ,           
				config.LoggingRules.ToList().ForEach(r => r.SetLoggingLevels(LogLevel.AllLevels.Min(), LogLevel.AllLevels.Max()));

				_logConfig = config;
			}
			catch (Exception ex)
			{
				//  ,        
				Debug.Write(ex);
			}

			return logFactory;
		}

		#region Target Methods

		private static FileTarget FileTarget(Layout header, Layout layout)
		{
			#region FileTarget ----------------------------

			FileTarget fileTarget = new FileTarget("log_file_target")
			{
				ArchiveAboveSize = 1048576,
				ArchiveDateFormat = "yyyy.MM.dd_HH.mm.ss",
				ArchiveEvery = FileArchivePeriod.Day,
				ArchiveFileName = GetApplicationLogAndArchivePath(false),
				ArchiveNumbering = ArchiveNumberingMode.Date,
				ArchiveOldFileOnStartup = false,
				AutoFlush = true,
				ConcurrentWrites = true,
				DeleteOldFileOnStartup = false,
				EnableArchiveFileCompression = true,
				EnableFileDelete = true,
				Encoding = Encoding.UTF8,
				FileName = GetApplicationLogAndArchivePath(true),
				Header = header,
				Layout = layout,
				MaxArchiveFiles = 100,
				OpenFileCacheTimeout = 30,
				OpenFileFlushTimeout = 30,
				OptimizeBufferReuse = true
			};

			#endregion

			return fileTarget;
		}

		private static ColoredConsoleTarget ConsoleTarget(Layout header, Layout layout)
		{
			#region ConsoleTarget ----------------------------

			ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget("log_console_target")
			{
				Encoding = Encoding.UTF8,
				EnableAnsiOutput = false,

				UseDefaultRowHighlightingRules = true,

				Layout = layout,
				Header = header
			};

			ConsoleWordHighlightingRule dateHighLightRule = new ConsoleWordHighlightingRule
			{
				Regex = @"^(?=\d).+(?=\s\[)",
				ForegroundColor = ConsoleOutputColor.Yellow
			};

			ConsoleWordHighlightingRule methodsHighLightRule = new ConsoleWordHighlightingRule
			{
				Regex = @"(?<=\[).+(?=\])",
				ForegroundColor = ConsoleOutputColor.Blue
			};

			ConsoleWordHighlightingRule levelHighLightRule = new ConsoleWordHighlightingRule
			{
				Regex = @"(?<=>).+(?=\s:)",
				ForegroundColor = ConsoleOutputColor.Red
			};

			ConsoleWordHighlightingRule messageHighLightRule = new ConsoleWordHighlightingRule
			{
				Regex = @"(?<=\s:\s).+",
				ForegroundColor = ConsoleOutputColor.Green
			};

			consoleTarget.WordHighlightingRules.Add(dateHighLightRule);
			consoleTarget.WordHighlightingRules.Add(methodsHighLightRule);
			consoleTarget.WordHighlightingRules.Add(levelHighLightRule);
			consoleTarget.WordHighlightingRules.Add(messageHighLightRule);

			#endregion

			return consoleTarget;
		}

		private static DebuggerTarget DebuggerTarget(Layout header, Layout layout)
		{
			#region DebuggerTarget ----------------------------

			DebuggerTarget debugTarget = new DebuggerTarget("log_debug_target")
			{
				Layout = layout,
				Header = header
			};

			#endregion

			return debugTarget;
		}

		private static GraylogHttpTarget GelfTarget(string header)
		{
			#region GelfTarget ----------------------------

			Layout gelfCommonLayout = "${message}";

			IList<TargetPropertyWithContext> gelfParameterInfos =
				new List<TargetPropertyWithContext>()
				{
					//     : https://nlog-project.org/config/?tab=layout-renderers
					new TargetPropertyWithContext()  { Name = "appdomain", Layout = "${appdomain}" },
					new TargetPropertyWithContext()  { Name = "assembly-version", Layout = "${assembly-version}" },
					new TargetPropertyWithContext()  { Name = "activityid", Layout = "${activityid}" },
					new TargetPropertyWithContext()  { Name = "callsite", Layout = "${callsite}" },
					new TargetPropertyWithContext()  { Name = "callsite-linenumber", Layout = "${callsite-linenumber}" },
					new TargetPropertyWithContext()  { Name = "environment-user", Layout = "${environment-user:userName=true:domain=true}" },
					new TargetPropertyWithContext()  { Name = "exeption_json_data", Layout = "${onexception:inner=${exception:format=@}}" },
					new TargetPropertyWithContext()  { Name = "frameWorkInfo", Layout = $"{RuntimeInformation.FrameworkDescription} ({RuntimeInformation.ProcessArchitecture})" },
					new TargetPropertyWithContext()  { Name = "guid", Layout = "${guid:format=N}" },
					new TargetPropertyWithContext()  { Name = "hostname", Layout = "${hostname}" },
					new TargetPropertyWithContext()  { Name = "identity", Layout = "${identity:authType=true:separator=\n:name=true:isAuthenticated=true}" },
					new TargetPropertyWithContext()  { Name = "level_name", Layout = "${level:format=Name}" },
					new TargetPropertyWithContext()  { Name = "local-ip", Layout = "${local-ip:addressFamily=InterNetwork}" },
					new TargetPropertyWithContext()  { Name = "logger", Layout = "${logger:shortName=false}" },
					new TargetPropertyWithContext()  { Name = "machinename", Layout = "${machinename}" },
					new TargetPropertyWithContext()  { Name = "osInfo", Layout = $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})" },
					new TargetPropertyWithContext()  { Name = "processid", Layout = "${processid}" },
					new TargetPropertyWithContext()  { Name = "processinfo_MainWindowHandle", Layout = "${processinfo:property=MainWindowHandle}" },
					new TargetPropertyWithContext()  { Name = "processinfo_PagedMemorySize", Layout = "${processinfo:property=PagedMemorySize}" },
					new TargetPropertyWithContext()  { Name = "processname", Layout = "${processname:fullName=true}" },
					new TargetPropertyWithContext()  { Name = "processtime", Layout = "${processtime:invariant=false}" },
					new TargetPropertyWithContext()  { Name = "sequenceid", Layout = "${sequenceid}" },
					new TargetPropertyWithContext()  { Name = "stacktrace", Layout = "${stacktrace:format=Raw:topFrames=3:skipFrames=0:separator=
}" },
					new TargetPropertyWithContext()  { Name = "threadid", Layout = "${threadid}" },
					new TargetPropertyWithContext()  { Name = "threadname", Layout = "${threadname}" },
					new TargetPropertyWithContext()  { Name = "timestamp", Layout = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}" },
					new TargetPropertyWithContext()  { Name = "timestamp_local", Layout = @"${date:universalTime=false:format=yyyy-MM-dd HH\:mm\:ss zzz}" },
					new TargetPropertyWithContext()  { Name = "windows-identity", Layout = "${windows-identity:userName=true:domain=true}" }
				};

			GraylogHttpTarget gelfUdpTarget = new GraylogHttpTarget
			{
				AddNLogLevelName = true,
				Facility = header,
				GraylogServer = _settings.LogServerAddress,
				IncludeCallSite = true,
				IncludeCallSiteStackTrace = true,
				IncludeEventProperties = true,
				Layout = gelfCommonLayout,
				Name = "GelfHttp",
				OptimizeBufferReuse = true
			};

			foreach (TargetPropertyWithContext gelfParameterInfo in gelfParameterInfos)
			{
				gelfUdpTarget.ContextProperties.Add(gelfParameterInfo);
			}

			#endregion

			return gelfUdpTarget;
		}

		//   https://github.com/nlog/NLog/wiki/AsyncWrapper-target    
		private static Target MakeAsyncTarget(this Target targ)
		{
			return new AsyncTargetWrapper
			{
				BatchSize = 100,
				ForceLockingQueue = true,
				FullBatchSizeWriteLimit = 5,
				Name = targ.Name,
				OptimizeBufferReuse = true,
				OverflowAction = AsyncTargetWrapperOverflowAction.Grow,
				QueueLimit = 10000,
				TimeToSleepBetweenBatches = 1,
				WrappedTarget = targ
			};
		}

		#endregion

		private static string GetApplicationLogAndArchivePath(bool isLog)
		{
			string addition;

			if (!isLog)
			{
				addition = ".{#}.zip";
			}
			else
			{
				addition = ".log";
			}

			try
			{
				if (!Directory.Exists(_settings.LogsFolder))
				{
					Directory.CreateDirectory(_settings.LogsFolder);
				}

				return Path.Combine(_settings.LogsFolder, _settings.ProductName + addition);
			}
			catch (Exception ex)
			{
				Debug.Write(ex);
				return string.Empty;
			}
		}
}

Anwendungsbeispiel:
using NLog;
...

public static class Downloader
	{
		...
		private static readonly Logger log = BimlibLogger.GetLogger();
		...
		public static string GetFileToConvert(ConverterRequest.Inputitem inputItem)
		{
			...
			try
			{
				...
				log.Debug($"Downloaded file: {downloadedFile}, size: {new FileInfo(downloadedFile).Length / 1e6}Mb");
			}
			catch (Exception ex)
			{
				log.Error(ex, ex.Source);
				return ...;
			}
		}
	}

Ein paar Details


Der bloße Code, wenn auch mit Kommentaren, ist nicht cool. Daher werde ich eine Reihe von Konventionen und Annahmen beschreiben:

  1. _settings - ein bestimmtes Objekt von Einstellungen. Es ist nicht zu wichtig, wie genau es gebildet wird, aber es muss vor der ersten Initialisierung des Loggers empfangen werden.
  2. _logConfig - NLog-Konfiguration, die nicht in einer separaten NLog.config- Datei , sondern direkt im Code durchgeführt wird.

Wie die Dokumentation sagt :
NLog erzeugt nur dann eine Ausgabe, wenn ein (oder mehrere) NLog-Ziele konfiguriert wurden.
Eigentlich sind die Methoden in der Zielmethoden Region sind verantwortlich für die Erstellung dieser „Ziele“:

FileTarget - das Protokoll in eine Datei zu schreiben.

Wie sieht eine Protokolldatei aus?

ColoredConsoleTarget - um "schöne" Nachrichten an die Konsole auszugeben (falls vorhanden).

Wie sieht eine Farbkonsole aus?

DebuggerTarget - zum Ausgeben von Logger-Nachrichten an das Visual Studio-Ausgabefenster oder an einen verbundenen Debugger eines Drittanbieters im Debug-Modus.

Wie sehen NLog-Nachrichten im Debugger aus?

GraylogHttpTarget - Ziel ist es, Nachrichten an den Server zu senden, auf denen Graylog installiert ist .

Wie sieht eine Graylog-Nachricht aus?

In meinem ersten Artikel über Habr erwähnte ich die Verwendung von Graylog. Ich möchte auf das letzte Ziel auf der Liste eingehen. Sie ermöglicht es Ihnen, mithilfe der NLog-Bibliothek Nachrichten aus der Anwendung an den Graylog-Dienst zu senden. Ich werde versuchen, Ihnen zu sagen, wie mir dieses Tool persönlich gefallen hat.

Über Graylog (ein wenig)


Die Beschreibung der Installation und Konfiguration des Dienstes wird in diesem Artikel nicht enthalten. Ich kann nur sagen, dass ich es im Docker bereitgestellt habe. Dies wird auch in der Dokumentation beschrieben :

Ich gebe Ihnen meine Erstellungsdatei
version: '2'
services:
  # MongoDB: https://hub.docker.com/_/mongo/
  mongodb:
    image: mongo:4
    restart: always
    volumes:
    - mongo_data:/data/db:rw
    - /etc/localtime:/etc/localtime  
  # Elasticsearch: https://www.elastic.co/guide/en/elasticsearch/reference/6.x/docker.html
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.2
    restart: always
    volumes:
    - es_data:/usr/share/elasticsearch/data:rw
    - /etc/localtime:/etc/localtime
    environment:
      - http.host=0.0.0.0
      - transport.host=localhost
      - network.host=0.0.0.0
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    mem_limit: 1g
  # Graylog: https://hub.docker.com/r/graylog/graylog/
  # Graylog: I want to have the lastest
  graylog:
    image: graylog/graylog:3.2
    restart: always
    volumes:
    - graylog_data:/usr/share/graylog/data/journal:rw
    - /etc/localtime:/etc/localtime
# :   .     - 
    - /home/MYUSER/graylog-data/plugin/graylog-plugin-telegram-notification-2.3.0.jar:/usr/share/graylog/plugin/graylog-plugin-telegram-notification-2.3.0.jar
    environment:
      # CHANGE ME (must be at least 16 characters)!
      - GRAYLOG_PASSWORD_SECRET=somepasswordpepper
      # Password: MYPASSWORD
      - GRAYLOG_ROOT_PASSWORD_SHA2=SHA2MYPASSWORDPLEASEINANYONLINESERVICE
      - GRAYLOG_HTTP_BIND_ADDRESS=0.0.0.0:9999
      - GRAYLOG_WEB_LISTEN_URI=http://my.ex.ipa.dr:9999/
      - GRAYLOG_HTTP_EXTERNAL_URI=http://my.ex.ipa.dr:9999/
      - GRAYLOG_HTTP_PUBLISH_URI=http://my.ex.ipa.dr:9999/
      - GRAYLOG_ROOT_TIMEZONE=Europe/Moscow
      - GRAYLOG_PLUGIN_DIR=plugin
    links:
      - mongodb:mongo
      - elasticsearch
    depends_on:
      - mongodb
      - elasticsearch
    ports:
      - 5044:5044
      # Graylog web interface and REST API
      - 9999:9999
      # Syslog TCP
      - 1514:1514
      # Syslog UDP
      - 1514:1514/udp
      # GELF TCP
      - 12201:12201
      # GELF UDP
      - 12201:12201/udp
      #Volumes
volumes:
  mongo_data:
    driver: local
  es_data:
    driver: local
  graylog_data:
    driver: local

Ich werde auch ein Beispiel für die Einstellung von „Input“ in Graylog hinzufügen:

Graylog überwacht den http-Verkehr auf Port 12201


Graylog nodes accept data via inputs. Launch or terminate as many inputs as you want here.


my.web.adress.domain 12201 -, . ...

Über GELF


GELF - Graylog Extended Log Format. Das Format der Nachrichten, die Graylog wahrnimmt.

Um etwas im GELF-Format an eine bestimmte URL zu senden, müssen Sie ein zusätzliches Ziel verwenden . Sie werden im Abschnitt Integrationen der Dokumentation vorgestellt .

Ich habe NLog.Targets.GraylogHttp gewählt . Installierte nuget im Projekt und konnte NLog.Targets.GraylogHttp.GraylogHttpTarget in der Konfiguration seines Loggers verwenden.

Das sehr Layout für Nachrichten , die ich habe zu einer vereinfachten Nachricht , aber gefüllt ContextProperties seine target'a breite Palette von zusätzlichen Feldern:

new TargetPropertyWithContext()  { Name = "someParamName1", Layout = "someStringInfo" },
new TargetPropertyWithContext()  { Name = "someParamName2", Layout = "someNLogLayoutRendered" },
new TargetPropertyWithContext()  { Name = "someParamName3", Layout = (string)SomeMethodThatGetsInfo() }
Ich habe speziell verallgemeinerten Code verwendet, um das Prinzip klar zu machen: "beliebiger Name + beliebiger Wert". Der Wert kann mit einer eigenen Methode erfolgreich "abgebaut" werden. Lesen Sie beispielsweise die Netzwerkeinstellungen oder den freien Speicherplatz auf dem Systemlaufwerk. Wie auch immer ...

Bonus


Es gibt eine Meinung (und ich bestreite es nicht), dass Protokolle am nützlichsten sind, wenn Fehler abgefangen werden. Die Try / Catch- Konstrukte wurden erfolgreich eingesetzt und rechtfertigen sich in den meisten Fällen. Das "Fehlerobjekt" selbst ist mit Debugging-Informationen, Daten über die Ereigniskette, wenn sie auftritt, und anderen Informationen gefüllt. Es ist bequem zu lesen, dass ein solches Objekt nicht immer funktioniert.

Für mich habe ich diese Lösung entwickelt:

Bonusmethode für die Logger-Klasse zum Senden von Feldern von Objekten an Graylog:
// -. ,    Graylog    - .
		//     objWithProps -            json (  GELF)...
		public static LogEventInfo GetLogEventWithProperties(LogLevel logLevel, string logName, string message, object objWithProps)
		{
			//     ,      
			if (string.IsNullOrEmpty(message))
			{
				message = objWithProps?.GetType()?.Name ?? "custom_json_data";
			}

			LogEventInfo e = new LogEventInfo(logLevel, logName, message);

			object logEventProperty = null;
			//     Newtonsoft.Json: https://www.newtonsoft.com/json
			JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
			{
				ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
				PreserveReferencesHandling = PreserveReferencesHandling.Objects,
				NullValueHandling = NullValueHandling.Include
			};

			try
			{
				//  ""  ()   ...
				logEventProperty = JsonConvert.SerializeObject(objWithProps, Formatting.Indented, jsonSerializerSettings);
			}
			catch (Exception ex)
			{
				Debug.Write(ex);

				try
				{
					// ...,   :  ,   , ...
					IEnumerable<PropertyInfo> objProps = objWithProps?.GetType()?.GetProperties()?.Where(p => p?.GetGetMethod()?.GetParameters()?.Length == 0);

					if (objProps?.Any() == true)
					{
						// ...   ,...
						Dictionary<string, string> objPropsDict =
							objProps
							.ToDictionary(
								x => x?.Name,
								x =>
								{
									string rezVal = string.Empty;
									try
									{
										rezVal = x?.GetValue(objWithProps, null)?.ToString();
									}
									catch (Exception ex0)
									{
										Debug.Write(ex0);
									}
									return rezVal;
								}
							)?
							.OrderBy(x => x.Key)?
							.ToDictionary(obj => obj.Key, obj => obj.Value);
						// ...   Newtonsoft.Json
						logEventProperty = JsonConvert.SerializeObject(objPropsDict, Formatting.Indented, jsonSerializerSettings);
					}
				}
				catch (Exception ex1)
				{
					Debug.Write(ex1);
				}
			}
			//   json-,  Graylog      .
			e.Properties["custom_json_data"] = logEventProperty;
			return e;
		}


Anwendungsbeispiel:
log.Debug(BimlibLogger.GetLogEventWithProperties(LogLevel.Debug, log.Name, $"Got the task", message));


Wie sieht es in Graylog aus?
exeption_json_dataexception, json.



exception, .



Ergebnis


Als Ergebnis der Anwendung des beschriebenen Ansatzes und einer angemessenen Implementierung der Logger-Aufrufe in den Codetext erhalten wir eine zentralisierte Sammlung von Protokollen mit einer praktischen Schnittstelle zum Lesen, Parsen und Analysieren. Wir haben ein praktisches Tool für Benachrichtigungen über bestimmte Werte in den Protokollen.
Auch die üblichen Protokolldateien, die ebenfalls erneut gelesen werden können, ziehen Schlussfolgerungen, verschwinden nicht. Es ist kein Konkurrent oder Ersatz für Überwachungssysteme. Aber jetzt ist die Situation "Meine Anwendung läuft auf über 100 sehr unterschiedlichen Computern in mehr als 3 fremdsprachigen Ländern und bricht plötzlich irgendwo ab" etwas einfacher und viel schneller gelöst.

Das ist alles?


Im Prinzip habe ich Ihnen alles gesagt, was Sie für eine relativ komfortable Protokollierung Ihrer Anwendungen benötigen. Bereit zu diskutieren, Tipps zu bekommen.

Danke fürs Lesen. Wir sehen uns in den Kommentaren.

UPD : AsyncWrapper- Ziele wurden zur Arbeit an den Ratschlägen hinzugefügtJustmara

All Articles