Menggunakan Graylog dan NLog untuk mengumpulkan log dari aplikasi C #. Pengalaman pribadi


KDPV

Habr, selamat datang!

Berikut ini sama sekali bukan tutorial atau praktik terbaik . Saya memutuskan hanya untuk mengumpulkan dan mendokumentasikan pencapaian saya dalam pertanyaan yang diajukan.

Saya berharap bahwa isi artikel ini akan memungkinkan mereka yang mencari informasi tentang logging untuk mempelajari sesuatu yang baru atau membuat keputusan. Dan, tentu saja, saya berharap mendapat umpan balik yang konstruktif dari komunitas. Ini memberi kesempatan untuk melakukan sesuatu yang lebih baik.

Untuk apa? Untuk siapa?


Saya akan mencoba menjelaskan arti pencatatan dengan kata-kata saya sendiri:
Menyimpan beberapa informasi yang dapat dibaca manusia tentang peristiwa positif atau negatif selama pelaksanaan kode program, mungkin dengan pelestarian indikator dan data kualitatif atau kuantitatif sebagai bagian dari informasi yang disimpan dengan mengacu pada waktu acara.
Formulasi rumit keluar, tetapi dalam kasus umum itu sepenuhnya menggambarkan tugas membuat dan memelihara log. Pencatatan dengan kualitas tinggi membantu pengembang untuk mengontrol status internal kode pada setiap tahap hidupnya: dari debug ke instalasi di lingkungan yang tidak diketahui dari komputer konsumen.

Baiklah, mari kita tunjukkan bagaimana saya menulis log.

Alat


Kami menghilangkan pertimbangan penebangan dengan bantuan pesan teks informatif rendah seperti " Database tidak tersedia ", " Gagal menyimpan file " dan sejenisnya. Log jenis ini mudah dibuat, tetapi seringkali tidak cukup untuk memahami esensi dan sumber masalah dalam kode atau lingkungan. Pada titik tertentu, setiap pengembang hanya perlu membuat logging terstruktur. Seperti yang dikatakan teman baik saya dan mentor TI saya: " Kumpulkan semuanya, lalu kecualikan yang tidak perlu ... " Kerangka kerja khusus akan membantu kami dalam hal ini.

Ada cukup banyak. Kebanyakan dari mereka memiliki kemampuan dan keterbatasan yang serupa. Banyak artikel dan ulasan telah ditulis tentangnya., perbandingan dan manual. Untuk masuk aplikasi yang ditulis dalam C # Saya pernah memilih NLog . Sekarang saya tidak ingat mengapa itu karena dia, tetapi itu terjadi begitu saja.

Platform ini memiliki dokumentasi yang sangat baik dan kasus penggunaan yang sangat berguna . Secara alami, pada waktu yang berbeda dan untuk proyek yang berbeda, saya menggunakan NLog dengan cara yang berbeda. Tetapi pada beberapa titik kode lahir yang sekarang digunakan sebagai potongan dan praktis tidak berubah.

Penerapan


Untuk bisnis! Tampilkan kode:
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;
			}
		}
}

Contoh penggunaan:
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 ...;
			}
		}
	}

Beberapa detail


Kode telanjang, meskipun dengan komentar, tidak keren. Karena itu, saya akan menjelaskan sejumlah konvensi dan asumsi:

  1. _settings - objek pengaturan tertentu. Tidak terlalu penting bagaimana tepatnya itu dibentuk, tetapi harus diterima sebelum inisialisasi pertama dari logger.
  2. _logConfig - konfigurasi NLog, dilakukan bukan dalam file NLog.config yang terpisah , tetapi langsung dalam kode.

Seperti yang dikatakan dalam dokumentasi :
NLog hanya akan menghasilkan output jika telah mengkonfigurasi satu (atau lebih) target NLog.
Sebenarnya, metode di wilayah Metode Target bertanggung jawab untuk membuat "tujuan" ini:

FileTarget - untuk menulis log ke file.

Seperti apa bentuk file log?

ColoredConsoleTarget - untuk menampilkan pesan "indah" ke konsol (jika ada).

Seperti apa bentuk konsol warna?

DebuggerTarget - untuk menampilkan pesan logger ke jendela output Visual Studio atau ke debugger pihak ketiga yang terhubung dalam mode debug.

Seperti apa tampilan pesan NLog di debugger?

GraylogHttpTarget - tujuannya adalah untuk mengirim pesan ke server dengan Graylog diinstal di sana .

Seperti apa tampilan pesan Graylog?

Dalam artikel pertama saya di Habr, saya menyebutkan penggunaan Graylog. Saya ingin memikirkan tujuan terakhir dalam daftar. Dialah yang memungkinkan Anda untuk mengirim pesan dari aplikasi ke layanan Graylog menggunakan perpustakaan NLog. Saya akan mencoba memberi tahu Anda betapa saya menyukai alat ini secara pribadi.

Tentang Graylog (sedikit)


Deskripsi pemasangan dan konfigurasi layanan tidak akan dimasukkan dalam artikel ini. Saya hanya bisa mengatakan bahwa saya menempatkannya di buruh pelabuhan. Ini juga dijelaskan dalam dokumentasi :

Saya memberi Anda file penulisan saya
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

Saya juga akan menambahkan contoh pengaturan "Input" di Graylog:

Graylog mendengarkan lalu lintas http di port 12201


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


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

Tentang GELF


GELF - Graylog Extended Format Format. Format pesan yang diterima Graylog.

Untuk mengirim sesuatu dalam format GELF ke url tertentu, Anda harus menggunakan target tambahan . Mereka disajikan di bagian Integrasi dokumentasi .

Saya memilih NLog.Target.GraylogHttp . Diinstal nuget di proyek dan dapat menggunakan NLog.Target.GraylogHttp.GraylogHttpTarget dalam konfigurasi logger-nya.

Sangat tata letak untuk pesan saya sudah disederhanakan ke pesan , tapi penuh ContextProperties nya target'a lebar berkisar dari bidang tambahan:

new TargetPropertyWithContext()  { Name = "someParamName1", Layout = "someStringInfo" },
new TargetPropertyWithContext()  { Name = "someParamName2", Layout = "someNLogLayoutRendered" },
new TargetPropertyWithContext()  { Name = "someParamName3", Layout = (string)SomeMethodThatGetsInfo() }
Saya secara khusus menggunakan kode umum untuk membuat prinsip ini jelas: β€œnama arbitrer + nilai arbitrer”. Nilai dapat berhasil "ditambang" dengan beberapa metode sendiri. Misalnya, membaca pengaturan jaringan atau ruang kosong pada drive sistem. Masa bodo ...

Bonus


Ada pendapat (dan saya tidak membantahnya) bahwa log paling berguna ketika menangkap kesalahan. Konstruksi coba / tangkap telah digunakan dengan sukses dan dalam banyak kasus membenarkan diri mereka sendiri. "Objek kesalahan" itu sendiri diisi dengan informasi debug, data tentang rantai peristiwa ketika itu terjadi, dan lain-lain. Lebih mudah untuk membaca objek seperti itu tidak selalu berhasil.

Bagi saya sendiri, saya mengembangkan solusi ini:

Metode bonus ke kelas logger untuk mengirim bidang dari objek ke 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;
		}


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


Seperti apa di Graylog?
exeption_json_data β€” exception, json.



exception, .



Hasil


Sebagai hasil dari penerapan pendekatan yang dijelaskan dan implementasi yang wajar dari panggilan logger ke dalam tubuh kode, kami mendapatkan koleksi log terpusat dengan antarmuka yang mudah digunakan untuk membaca, menguraikan, dan menganalisisnya. Kami memiliki alat yang mudah untuk pemberitahuan tentang nilai-nilai tertentu dalam log.
Juga, file-file log yang biasa, yang juga bisa dibaca kembali, menarik kesimpulan, jangan hilang. Ini bukan pesaing atau pengganti sistem pemantauan. Tapi sekarang situasi "aplikasi saya berjalan di 100+ komputer yang sangat berbeda di 3+ ​​negara asing dan tiba-tiba pecah di suatu tempat" diselesaikan sedikit lebih mudah dan lebih cepat.

Itu semua?


Pada prinsipnya, saya memberi tahu Anda semua yang Anda butuhkan untuk pencatatan aplikasi yang relatif nyaman. Siap berdiskusi, dapatkan tips.

Terima kasih sudah membaca. Sampai jumpa di komentar.

UPD : menambahkan target AsyncWrapper ke pekerjaan berdasarkan saranjustmara

All Articles