استخدام Graylog و NLog لجمع السجلات من تطبيقات C #. خبرة شخصية


KDPV

هبر ، مرحبا!

ما يلي ليس بأي شكل من الأشكال تعليمي أو أفضل الممارسات . قررت فقط تجميع وتوثيق إنجازاتي في السؤال المطروح.

آمل أن تسمح محتويات هذه المقالة لأولئك الذين يبحثون عن معلومات حول تسجيل الدخول بتعلم شيء جديد أو اتخاذ قرار ما. وبالطبع ، آمل أن أحصل على تعليقات بناءة من المجتمع. يعطي فرصة للقيام بشيء أفضل.

على ماذا؟ لمن؟


سأحاول أن أصف ما هو التسجيل في كلماتي الخاصة:
حفظ بعض المعلومات التي يمكن قراءتها عن الإنسان حول حدث إيجابي أو سلبي أثناء تنفيذ رمز البرنامج ، ربما مع الحفاظ على المؤشرات والبيانات النوعية أو الكمية كجزء من المعلومات المخزنة مع الإشارة إلى وقت الحدث.
ظهرت صيغة معقدة ، ولكنها في الحالة العامة تصف بشكل كامل مهمة إنشاء السجلات وحفظها. يساعد التسجيل عالي الجودة المطور على التحكم في الحالة الداخلية للكود في أي مرحلة من حياته: من التصحيح إلى التثبيت في بيئة غير معروفة لجهاز الكمبيوتر الخاص بالمستهلك.

حسنًا ، دعنا نظهر كيف أكتب السجلات.

أدوات


نحذف النظر في التسجيل باستخدام رسائل نصية منخفضة المعلومات مثل " قاعدة البيانات غير متاحة " و " فشل حفظ الملف " وما شابه. من السهل إنشاء سجلات من هذا النوع ، ولكنها غالبًا ما تكون غير كافية لفهم جوهر المشكلة ومصادرها في التعليمات البرمجية أو البيئة. في مرحلة ما ، يحتاج كل مطور للتو إلى تسجيل منظم. كما أخبرني صديقي العزيز وموجه تكنولوجيا المعلومات: " اجمع كل شيء ، ثم استبعد ما هو غير ضروري ... " ستساعدنا الأطر المتخصصة في ذلك.

هناك ما يكفي منهم. معظمهم لديهم قدرات وقيود مماثلة. تمت كتابة الكثير من المقالات والمراجعات عنها.والمقارنات والأدلة. لتسجيل الدخول في التطبيقات المكتوبة في C # اخترت مرة واحدة NLog . الآن لا أتذكر لماذا كان هو ، لكنه حدث ذلك.

هذه المنصة لديها وثائق جيدة للغاية وحالات استخدام مفيدة للغاية . بطبيعة الحال ، في أوقات مختلفة ومشاريع مختلفة ، استخدمت NLog بطرق مختلفة. ولكن في مرحلة ما وُلد رمز يستخدم الآن كمقتطف ولا يتغير عمليًا.

التنفيذ


إلى الأعمال! أظهر الكود:
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;
			}
		}
}

مثال للاستخدام:
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 ...;
			}
		}
	}

بعض التفاصيل


الرمز العاري ، وإن كان مع تعليقات ، ليس رائعًا. لذلك ، سأصف عددًا من الاتفاقيات والافتراضات:

  1. إعدادات _s - كائن معين من الإعدادات. ليس من المهم للغاية كيف يتم تشكيله بالضبط ، ولكن يجب أن يتم استلامه قبل التهيئة الأولى للمسجل.
  2. _logConfig - تكوين NLog ، لا يتم تنفيذه في ملف NLog.config منفصل ، ولكن مباشرةً في التعليمات البرمجية.

كما تقول الوثائق :
لن ينتج NLog الإخراج إلا إذا قمت بتكوين واحد (أو أكثر) من أهداف NLog.
في الواقع ، تعتبر الأساليب الموجودة في منطقة " أساليب الاستهداف" مسؤولة عن إنشاء "الأهداف" التالية:

FileTarget - لكتابة السجل إلى ملف.

كيف يبدو ملف السجل؟

ColoredConsoleTarget - لإخراج الرسائل "الجميلة" إلى وحدة التحكم (إن وجدت).

كيف تبدو وحدة التحكم بالألوان؟

DebuggerTarget - لإخراج رسائل المسجل إلى نافذة إخراج Visual Studio أو إلى مصحح أخطاء تابع لجهة خارجية في وضع التصحيح.

كيف تبدو رسائل NLog في المصحح؟

GraylogHttpTarget - الهدف هو إرسال رسائل إلى الخادم مع تثبيت Graylog هناك .

كيف تبدو رسالة Graylog؟

في لقائي الاول المادة على الهبر، أشرت إلى استخدام Graylog. أود أن أطيل في الحديث عن الهدف الأخير في القائمة. هي التي تسمح لك بإرسال رسائل من التطبيق إلى خدمة Graylog باستخدام مكتبة NLog. سأحاول أن أخبرك كيف أحببت هذه الأداة شخصيًا.

حول Graylog (القليل)


لن يتم تضمين وصف تثبيت الخدمة وتكوينها في هذه المقالة. لا يسعني إلا أن أقول أنني نشرته في عامل الميناء. هذا موضح أيضًا في الوثائق :

أقدم لكم ملف الإنشاء الخاص بي
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

سأضيف أيضًا مثالًا على إعداد "الإدخال" في Graylog:

يقوم Graylog بالاستماع إلى حركة مرور http على المنفذ 12201


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


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

حول GELF


GELF - تنسيق سجل Graylog الموسع. تنسيق الرسائل التي يدركها Graylog.

لإرسال شيء بتنسيق GELF إلى عنوان URL معين ، ستحتاج إلى استخدام هدف إضافي . يتم تقديمها في قسم التكامل من الوثائق .

اخترت NLog.Targets.GraylogHttp . تم تثبيت nuget في المشروع وكان قادرًا على استخدام NLog.Targets.GraylogHttp.GraylogHttpTarget في تكوين المسجل.

وغاية التخطيط للرسائل لقد مبسطة ل رسالة ، ولكن ليس صحيحا ContextProperties له target'a مجموعة واسعة من حقول إضافية:

new TargetPropertyWithContext()  { Name = "someParamName1", Layout = "someStringInfo" },
new TargetPropertyWithContext()  { Name = "someParamName2", Layout = "someNLogLayoutRendered" },
new TargetPropertyWithContext()  { Name = "someParamName3", Layout = (string)SomeMethodThatGetsInfo() }
لقد استخدمت بشكل خاص الشفرة المعممة لتوضيح المبدأ: "اسم تعسفي + قيمة اعتباطية". يمكن "استخلاص" القيمة بنجاح من خلال طريقة خاصة بها. على سبيل المثال ، قراءة إعدادات الشبكة أو المساحة الحرة على محرك أقراص النظام. ايا كان ...

علاوة


هناك رأي (وأنا لا أعارضه) مفاده أن السجلات هي الأكثر فائدة عند التقاط الأخطاء. و حاول / catch بنيات وقد استخدمت بنجاح وفي معظم الحالات تبرير أنفسهم. يمتلئ "كائن الخطأ" نفسه بمعلومات التصحيح ، وبيانات حول سلسلة الأحداث عند حدوثها ، وغيرها. من الملائم قراءة مثل هذا الكائن لا يعمل دائمًا.

بنفسي ، قمت بتطوير هذا الحل:

طريقة المكافأة لفئة المسجل لإرسال الحقول من الكائنات إلى 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;
		}


مثال للاستخدام:
log.Debug(BimlibLogger.GetLogEventWithProperties(LogLevel.Debug, log.Name, $"Got the task", message));


كيف يبدو في Graylog؟
exeption_json_dataexception, json.



exception, .



نتيجة


نتيجة لتطبيق النهج الموصوف والتنفيذ المعقول لاستدعاءات المسجل في نص الكود ، نحصل على مجموعة سجلات مركزية مع واجهة ملائمة لقراءتها وتحليلها وتحليلها. لدينا أداة ملائمة للإشعارات حول قيم معينة في السجلات.
أيضًا ، لا تختفي ملفات السجل المعتادة ، والتي يمكن أيضًا إعادة قراءتها واستخلاص النتائج. وهي ليست منافسة أو بديلاً لأنظمة المراقبة. ولكن الآن أصبح الوضع "يعمل تطبيقي على أكثر من 100 جهاز كمبيوتر مختلف جدًا في أكثر من 3 بلدان بلغة أجنبية ويتوقف فجأة في مكان ما" أسهل وأسرع قليلاً.

كل شيء؟


من حيث المبدأ ، قلت لك كل ما تحتاجه لتسجيل تطبيقاتك بشكل مريح نسبيًا. على استعداد لمناقشة الحصول على نصائح.

شكرا للقراءة. أراك في التعليقات.

UPD : إضافة أهداف AsyncWrapper للعمل على النصيحةjustmara

All Articles