Менеджер ILogger (из пространства имен Ultima.Log) предназначен для использования функционала библиотеки Serilog, посредством которой реализовано центральное журналирование.

DevBookmark_Scripts Интерфейс менеджера ILogger содержит набор методов для журналирования событий с разным уровнем важности: Debug(), Info(), Verbose(), Warn(), Error() и Fatal().

Serilog использует простой DSL для того, чтобы задавать имена дополнительным свойствам журнального события. Синтаксисом этот DSL похож на строки форматирования для метода string.Format:

Logger.Debug("Saving document {DocumentID}, created by {UserID}", docId, userId);

В результате такого вызова зажурналируется событие с двумя свойствами скалярных типов: DocumentID и UserID. Синтаксис string.Format с номерами параметров вроде {DocumentID} тоже поддерживается.

Именованный логгер можно получить как обычным способом:

LogManager.GetLogger("MyLoggerName");

Так и без участия ILogManager (из пространства имен Ultimaю.Log):

var newLogger = Logger.Named("NewLoggerName");

Более интересный пример использования Serilog – сохранение свойства структурного типа (поддерживаются массивы, классы, структуры, хэш-таблицы, справочники и документы):

var parameters = new Dictionary<stringobject>();

parameters["id"] = id;

parameters["limit"] = maxValues;

...

 

Logger.Verbose("Executing command: {SQL:l} with parameters: {@Parameters}",

 sqlCommand, parameters);

В данном случае к журнальному событию будет прикреплен весь список параметров, и в логе можно будет найти запись о выполнении команды с определенным значением нужного параметра.

При журналировании исключений будет сохранена их внутренняя структура, в том числе свойства HResult, CallStack и вся цепочка InnerException. Чтобы зажурналировать исключение, нужно передать его первым параметром в методы журналирования:

Logger.Error(ex, "Cannot execute user task: {Message}", ex.Message);

Методы журналирования исключений позволяют добавлять в текст сообщения любые дополнительные параметры.

Для журналирования справочников и документов добавлен специальный деструктуризатор. Работает он почти так же, как стандартный, только вырезает из объектов излишнюю информацию: дескрипторы классов, свойства IsPropertyChanged и т.п.:

Logger.Debug("Brand #{BrandID} contents: {@Brand}", brand.ID, brand);

 

// сообщение в журнале

// Brand #12 contents: Brand { ID: 12, Name: "Pineapple" }

Следует с осторожностью использовать и не злоупотреблять журналированием документов, поскольку документы обычно существенно тяжелее справочников, и сохранение их в журнал очень быстро приведет к исчерпанию места в хранилище. Типичный результат журналирования документа может выглядеть так:

Document #101187 is saved. Document contents: SaleDocument { ActualInvoiceDocumentID: null, AgentID: 636, AllowPartialRelease: False, Amount: 5500, AmountDistributionTypeID: null, ArticlesQuantity: 3, ClientDueOnDelivery: 5500, Comments: "noreply", Convertation: null, CreationDate: 02/06/2015 22:21:53, CreatorID: 7, CustomerSupplyContractID: null, DeadDate: 02/11/2015 22:21:53, Deleted: False, DeliveryActive: False, Description: "Sales (Picking) #101187, 2/6/2015", ExplicitDeadDate: False, FirmID: 14, ID: 101187, InterstoreResupply: False, KickbackAgentID: null, KickbackAmount: null, KickbackTypeID: null, ManagerID: null, OfficeID: 38, PickupDate: null, PowerOfAttorneyID: null, PriceTypeID: 1, PriceZoneID: 6, RowsCount: 5, SaleTerminalID: null, StoreID: 522, SubtypeID: 4131, TotalsList: "4251", TransactionDate: 02/06/2015 22:21:53, TypeID: 4111, Version: 12, Volume: 0.001442, Weight: 2.70, ActualInvoiceDocument: null, Agent: null, AmountDistributionType: null, Creator: null, CustomerSupplyContract: null, Firm: null, KickbackAgent: null, KickbackType: null, Manager: null, Office: null, PowerOfAttorney: null, PriceType: null, PriceZone: null, SaleTerminal: null, Store: null, Subtype: null, Type: null, ArticleBarcodes: [], ArticleCcds: [ArticleCcdTablePartRow { ArticleID: 491967, CcdID: 1, Checked: False, Deleted: False, DocumentDeleted: False, DocumentID: 101187, ID: 5686087, Quantity: 1, TablePartEntryID: 12422, TransactionDate: 02/06/2015 22:21:53, Article: null, Ccd: null, Document: null, TablePartEntry: null }, ArticleCcdTablePartRow { ArticleID: 491964, CcdID: 53, Checked: False, Deleted: False, DocumentDeleted: False, DocumentID: 101187, ID: 5686088, Quantity: 2, TablePartEntryID: 12422, TransactionDate: 02/06/2015 22:21:53, Article: null, Ccd: null, Document: null, TablePartEntry: null }], Articles: [SaleArticleTablePartRow { Amount: 3300, ArticleID: 491967, Checked: False, Deleted: False, DocumentDeleted: False, DocumentID: 101187, ID: 5686089, NotReservedQuantity: 0, OriginalPrice: 3300, ReservedQuantity: 1, ResupplyQuantity: 0, SalePrice: 3300, SaleQuantity: 1, TablePartEntryID: 5229, TransactionDate: 02/06/2015 22:21:53, Article: null, Document: null, TablePartEntry: null }, SaleArticleTablePartRow { Amount: 2200, ArticleID: 491964, Checked: False, Deleted: False, DocumentDeleted: False, DocumentID: 101187, ID: 5686090, NotReservedQuantity: 0, OriginalPrice: 1100, ReservedQuantity: 2, ResupplyQuantity: 0, SalePrice: 1100, SaleQuantity: 2, TablePartEntryID: 5229, TransactionDate: 02/06/2015 22:21:53, Article: null, Document: null, TablePartEntry: null }, SaleArticleTablePartRow { Amount: 0, ArticleID: 492060, Checked: False, Deleted: False, DocumentDeleted: False, DocumentID: 101187, ID: 5686091, NotReservedQuantity: 1, OriginalPrice: 8700, ReservedQuantity: 0, ResupplyQuantity: 0, SalePrice: 8700, SaleQuantity: 0, TablePartEntryID: 5229, TransactionDate: 02/06/2015 22:21:53, Article: null, Document: null, TablePartEntry: null }, SaleArticleTablePartRow { Amount: 0, ArticleID: 492075, Checked: False, Deleted: False, DocumentDeleted: False, DocumentID: 101187, ID: 5686092, NotReservedQuantity: 1, OriginalPrice: 8700, ReservedQuantity: 0, ResupplyQuantity: 0, SalePrice: 8700, SaleQuantity: 0, TablePartEntryID: 5229, TransactionDate: 02/06/2015 22:21:53, Article: null, Document: null, TablePartEntry: null }, SaleArticleTablePartRow { Amount: 0, ArticleID: 492077, Checked: False, Deleted: False, DocumentDeleted: False, DocumentID: 101187, ID: 5686093, NotReservedQuantity: 1, OriginalPrice: 8600, ReservedQuantity: 0, ResupplyQuantity: 0, SalePrice: 8600, SaleQuantity: 0, TablePartEntryID: 5229, TransactionDate: 02/06/2015 22:21:53, Article: null, Document: null, TablePartEntry: null }], Delivery: [], DeliveryWizard: [], DistribKickback: [], ExcludedArticles: [] }

Если все-таки необходимо сохранить в логе документ, но не хочется дублировать его содержимое в текстовом сообщении, можно сделать так, чтобы он добавился только в свойства, а в тексте сообщения не упоминался. Добиться этого можно с помощью метода With:

var doc = DocumentManager.GetDocument(documentId);

Logger.With("Document", doc).

 Debug(@"Document #{DocumentID} is saved. Document contents: {$DocTitle}",

 doc.ID, doc);

 

// сообщение в журнале:

// Document #101187 is saved. Document contents: "Sales (Picking) #101187, 2/6/2015"

Этим же методом можно отметить группу журнальных событий, относящихся к одной теме. Например, во время пересчета цен добавить ко всем сообщениям код товара:

logger = Logger.With("ArticleID", 123);

logger.Info("Recalculating prices...");

logger.Warn(ex, "Can't recalculate prices due to {Cause}", ex.Message);

И, наконец, последний вариант – добавление свойства во все сообщения внутри блока using. Он позволяет не передавать логгер туда-обратно между методами или службами:

// у всех событий внутри блока using будет добавлено свойство ArticleID 

using (LogManager.With("ArticleID", article.ID))

{

 Logger.Debug("Processing article: {ArticleName}", article.Name);

 PriceService.RecalculatePrices(article.ID);

 DescriptionService.ValidateDescription(article.ID);

 PhotoService.MakePhotos(article.ID);

 Logger.Debug("Done with article: {ArticleName}", article.Name);

}