मेरे पास एनएलओजी के साथ एक दिलचस्प, स्पष्ट रूप से अस्थायी, मुद्दा है या शायद जिस तरह से मैं इसका उपयोग कर रहा हूं।

मैं हार्ड डिपेंडेंसीज से बचने के लिए लॉगिंग सर्विस एब्स्ट्रैक्शन बनाने का प्रयास कर रहा हूं, और मैंने अपने एब्स्ट्रैक्शन को NLog.FluentBuilder क्लास पर मॉडल किया है। तो मेरे पास इंटरफेस की एक जोड़ी है:


public interface ILog
    {
    IFluentLogBuilder Trace([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Debug([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Info([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Warn([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Error([CallerFilePath] string callerFilePath = null);
    IFluentLogBuilder Fatal([CallerFilePath] string callerFilePath = null);
    void Shutdown();
    }

public interface IFluentLogBuilder
    {
    IFluentLogBuilder Exception(Exception exception);
    IFluentLogBuilder LoggerName(string loggerName);
    IFluentLogBuilder Message(string message);
    IFluentLogBuilder Message(string format, params object[] args);
    IFluentLogBuilder Message(IFormatProvider provider, string format, params object[] args);
    IFluentLogBuilder Property(string name, object value);
    IFluentLogBuilder Properties(IDictionary<string,object> properties);
    IFluentLogBuilder TimeStamp(DateTime timeStamp);
    IFluentLogBuilder StackTrace(StackTrace stackTrace, int userStackFrame);
    void Write([CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null,
        [CallerLineNumber] int callerLineNumber = default);
    void WriteIf(Func<bool> condition, [CallerMemberName] string callerMemberName = null,
        [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = default);
    void WriteIf(bool condition, [CallerMemberName] string callerMemberName = null,
        [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber = default);
    }

विचार विभिन्न लॉगिंग ढांचे के लिए एडेप्टर बनाने में सक्षम होना है, और निश्चित रूप से मैंने जो पहला बनाया है वह एनएलओजी के लिए है, क्योंकि मैं यही उपयोग करना चाहता हूं। कार्यान्वयन अनिवार्य रूप से एनएलओजी से धाराप्रवाह इंटरफ़ेस का एक कम-डाउन क्लोन है, इसलिए यहां मेरे संस्करण का आंशिक अमूर्तता है:

ILog कार्यान्वयन

    public sealed class LoggingService : ILog
        {
        private static readonly ILogger DefaultLogger = LogManager.GetCurrentClassLogger();

        static LoggingService()
            {
            LogManager.AutoShutdown = true;
            }

        public IFluentLogBuilder Trace([CallerFilePath] string callerFilePath = null)
            {
            return CreateLogBuilder(LogLevel.Trace, callerFilePath);
            }

        // Methods for other log levels elided for clarity...

        private IFluentLogBuilder CreateLogBuilder(LogLevel logLevel, string callerFilePath)
            {
            string name = !string.IsNullOrWhiteSpace(callerFilePath)
                ? Path.GetFileNameWithoutExtension(callerFilePath)
                : null;
            var logger = string.IsNullOrWhiteSpace(name) ? DefaultLogger : LogManager.GetLogger(name);
            var builder = new LogBuilder(logger, logLevel);
            return builder;
            }

        /// <inheritdoc />
        public void Shutdown() => LogManager.Shutdown();
        }

IFluentLogBuilder कार्यान्वयन

    internal sealed class LogBuilder : IFluentLogBuilder
    {
        private readonly LogEventInfo logEvent;
        private readonly ILogger logger;

        public LogBuilder(ILogger logger, LogLevel level)
        {
            if (logger == null)
                throw new ArgumentNullException(nameof(logger));
            if (level == null)
                throw new ArgumentNullException(nameof(level));
            this.logger = logger;
            logEvent = new LogEventInfo { LoggerName = logger.Name, Level = level };
        }

        /// <inheritdoc />
        public IFluentLogBuilder Exception(Exception exception)
        {
            logEvent.Exception = exception;
            return this;
        }

        /// <inheritdoc />
        public IFluentLogBuilder LoggerName(string loggerName)
        {
            logEvent.LoggerName = loggerName;
            return this;
        }

        /// <inheritdoc />
        public IFluentLogBuilder Message(string message)
        {
            logEvent.Message = message;
            return this;
        }

        // Some other builder methods elided for clarity... (they all follow the pattern).

        /// <inheritdoc />
        public void Write([CallerMemberName] string callerMemberName = null,
            [CallerFilePath] string callerFilePath = null,
            [CallerLineNumber] int callerLineNumber = default)
        {
            if (!logger.IsEnabled(logEvent.Level)) return;
            SetCallerInfo(callerMemberName, callerFilePath, callerLineNumber);
            logger.Log(logEvent);
        }

        private void SetCallerInfo(string callerMethodName, string callerFilePath, int callerLineNumber)
        {
            if (callerMethodName != null || callerFilePath != null || callerLineNumber != 0)
                logEvent.SetCallerInfo(null, callerMethodName, callerFilePath, callerLineNumber);
        }

        /// <summary>
        /// Builds and returns the <see cref="LogEventInfo"/> without writing it to the log.
        /// </summary>
        internal LogEventInfo Build() => logEvent;
    }

मैंने कुछ ऐसे तरीकों को छोड़ दिया है जो इस मुद्दे पर कोई अतिरिक्त जानकारी नहीं जोड़ते हैं, मूल रूप से वे सभी एक ही पैटर्न का पालन करते हैं। यह सब बहुत समान है, लेकिन NLog.Fluent.LogBuilder वर्ग के समान नहीं है।

तो नहीं, यह दिलचस्प हो जाता है।

परीक्षण कार्यक्रम

मैंने अपनी लाइब्रेरी में एक नमूना कार्यक्रम के रूप में एक .NET Core 3.0 कंसोल ऐप शामिल किया।

मैंने इसे इसकी संपूर्णता में पुन: प्रस्तुत किया है, लेकिन प्रचुर टिप्पणियों के बिना, जो यहां रास्ते में आ जाएगा।

कार्यक्रम केवल 1000 तक गिना जाता है, प्रत्येक संख्या को प्रिंट करता है, और चीजों को और अधिक रोचक बनाने और अर्थपूर्ण लॉगिंग प्रदर्शित करने के लिए कुछ अपवाद उत्पन्न करता है।

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TA.Utils.Core;

namespace TA.Utils.Logging.NLog.SampleConsoleApp
    {
    class Program
        {
        static readonly List<int> SuperstitiousNumbers = new List<int> {13, 7, 666, 3, 8, 88, 888};

        async static Task Main(string[] args)
            {
            var log = new LoggingService();
            log.Info()
                .Message("Application stating - version {Version}", GitVersion.GitInformationalVersion)
                .Property("SemVer", GitVersion.GitFullSemVer)
                .Property("GitCommit", GitVersion.GitCommitSha)
                .Property("CommitDate", GitVersion.GitCommitDate)
                .Write();
            var seed = DateTime.Now.Millisecond;
            var gameOfChance = new Random(seed);
            log.Debug().Property("seed",seed).Write();

            for (int i = 0; i < 1000; i++)
                {
                try
                    {
                    log.Debug().Message("Starting iteration {iteration}", i).Write();
                    if (SuperstitiousNumbers.Contains(i))
                        {
                        throw new SuperstitiousNumberException($"Skipping {i} because it is a superstitious number");
                        }

                    // There's a small chance of a random "failure"
                    if (gameOfChance.Next(100) < 3)
                        throw new ApplicationException("Random failure");
                    }
                catch (SuperstitiousNumberException ex)
                    {
                    log.Warn()
                        .Message("Superstitious looking number: {number}", i)
                        .Exception(ex)
                        .Property("SuperstitiousNumbers", SuperstitiousNumbers)
                        .Write();
                    }
                catch (ApplicationException ae)
                    {
                    log.Error().Exception(ae).Message("Failed iteration {iteration}", i).Write();
                    }
                await Task.Delay(TimeSpan.FromMilliseconds(1000));
                log.Debug().Message("Finished iteration {iteration}", i).Write();
                }
            log.Info().Message("Program terminated").Write();
            log.Shutdown();
            }
        }
    }

यह प्रोग्राम निम्न लॉग आउटपुट उत्पन्न करता है।

00:01:09.4823 | INFO  | LogBuilder                      | Application stating - version "1.1.1-beta.1+18.Branch.hotfix-1.1.1.Sha.8b8fa5a008c35d4fc21c99d0bd9a01f6d32c9a53"
00:01:09.5339 | DEBUG | LogBuilder                      |
00:01:09.5339 | DEBUG | LogBuilder                      | Starting iteration 0
00:01:10.5636 | DEBUG | LogBuilder                      | Finished iteration 0
00:01:10.5636 | DEBUG | LogBuilder                      | Starting iteration 1
00:01:11.5762 | DEBUG | LogBuilder                      | Finished iteration 1
00:01:11.5762 | DEBUG | LogBuilder                      | Starting iteration 2
00:01:12.5893 | DEBUG | LogBuilder                      | Finished iteration 2
00:01:12.5893 | DEBUG | LogBuilder                      | Starting iteration 3
00:01:12.5893 | WARN  | LogBuilder                      | Superstitious looking number: 3
00:01:13.6325 | DEBUG | LogBuilder                      | Finished iteration 3
00:01:13.6325 | DEBUG | LogBuilder                      | Starting iteration 4
00:01:14.6484 | DEBUG | LogBuilder                      | Finished iteration 4
00:01:14.6484 | DEBUG | LogBuilder                      | Starting iteration 5
00:01:15.6534 | DEBUG | LogBuilder                      | Finished iteration 5
00:01:15.6534 | DEBUG | LogBuilder                      | Starting iteration 6
00:01:16.6653 | DEBUG | LogBuilder                      | Finished iteration 6
00:01:16.6653 | DEBUG | LogBuilder                      | Starting iteration 7
00:01:16.6653 | WARN  | LogBuilder                      | Superstitious looking number: 7
00:01:17.6787 | DEBUG | LogBuilder                      | Finished iteration 7
00:01:17.6787 | DEBUG | LogBuilder                      | Starting iteration 8
00:01:17.6787 | WARN  | LogBuilder                      | Superstitious looking number: 8
00:01:18.6890 | DEBUG | LogBuilder                      | Finished iteration 8
00:01:18.6890 | DEBUG | LogBuilder                      | Starting iteration 9
00:01:19.6926 | DEBUG | LogBuilder                      | Finished iteration 9
00:01:19.6935 | DEBUG | LogBuilder                      | Starting iteration 10
00:01:20.7071 | DEBUG | LogBuilder                      | Finished iteration 10
00:01:20.7071 | DEBUG | LogBuilder                      | Starting iteration 11
00:01:21.7110 | DEBUG | LogBuilder                      | Finished iteration 11
00:01:21.7110 | DEBUG | LogBuilder                      | Starting iteration 12
00:01:22.7367 | DEBUG | LogBuilder                      | Finished iteration 12
00:01:22.7404 | DEBUG | LogBuilder                      | Starting iteration 13
00:01:22.7404 | WARN  | LogBuilder                      | Superstitious looking number: 13
00:01:23.7621 | DEBUG | LogBuilder                      | Finished iteration 13
00:01:23.7621 | DEBUG | LogBuilder                      | Starting iteration 14
00:01:24.7756 | DEBUG | Program                         | Finished iteration 14
00:01:24.7756 | DEBUG | Program                         | Starting iteration 15
00:01:25.7876 | DEBUG | Program                         | Finished iteration 15
00:01:25.7876 | DEBUG | Program                         | Starting iteration 16
00:01:26.8040 | DEBUG | Program                         | Finished iteration 16
00:01:26.8040 | DEBUG | Program                         | Starting iteration 17
00:01:27.8176 | DEBUG | Program                         | Finished iteration 17
00:01:27.8176 | DEBUG | Program                         | Starting iteration 18
00:01:28.8277 | DEBUG | Program                         | Finished iteration 18
00:01:28.8277 | DEBUG | Program                         | Starting iteration 19
00:01:29.8372 | DEBUG | Program                         | Finished iteration 19
00:01:29.8372 | DEBUG | Program                         | Starting iteration 20

तो यहाँ गोमांस है। स्रोत का नाम LogBuilder से Program पुनरावृत्ति 14 के बीच में क्यों बदलता है? यह कभी LogBuilder क्यों था?

एक सवाल मैंने खुद से पूछा: "क्या यह हमेशा एक ही स्थान पर बदलता है" और इसका उत्तर नहीं है। यह प्लस या माइनस एक पुनरावृत्ति से भिन्न होता है।

मेरा अनुमान है कि यह मेरे द्वारा उपयोग किए जा रहे बफ़रिंग लॉग लक्ष्य से संबंधित हो सकता है... यहाँ मेरी NLog.config फ़ाइल है:

<?xml version="1.0" encoding="utf-8"?>

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       autoReload="true">
  <extensions>
    <add assembly="NLog.Targets.Seq"/>
  </extensions>

  <targets async="true" >
    <target xsi:type="ColoredConsole" name="console"
            layout="${time} | ${pad:padding=-5:inner=${uppercase:${level}}} | ${pad:padding=-31:inner=${callsite:className=true:fileName=false:includeSourcePath=false:methodName=false:includeNamespace=false}} | ${message}" >
      <highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGreen" />
      <highlight-row condition="level == LogLevel.Info" foregroundColor="White" />
      <highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" />
      <highlight-row condition="level == LogLevel.Error" foregroundColor="Red" />
      <highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" />
    </target>

    <target name="seq" xsi:type="BufferingWrapper" bufferSize="1000"
            flushTimeout="500" slidingTimeout="false">
      <target xsi:type="Seq" name="seq" serverUrl="http://seq.nowhere.com:5341" apiKey="imnotfallingforthatone">
        <!-- Augment the log data with some extra properties -->
        <property name="ProcessId" value="${processid}" />
        <property name="ProcessName" value="${processname}" />
        <property name="ThreadId" value="${threadid}" as="number" />
        <property name="Machine" value="${machinename}" />
        <property name="Host" value="${hostname}" />
        <property name="User" value="${environment-user}" />
      </target>
    </target>

    <target xsi:type="Trace" name="debug" rawWrite="true">
      <layout>${pad:padding=-5:inner=${uppercase:${level}}}|${pad:padding=-16:fixedLength=true:alignmentOnTruncation=right:inner=${callsite:className=true:fileName=false:includeSourcePath=false:methodName=false:includeNamespace=false}}| ${message}</layout>
    </target>
  </targets>

  <rules>
    <logger name="*" minlevel="Trace" writeTo="console" />
    <logger name="*" minlevel="Trace" writeTo="debug" />
    <logger name="*" minlevel="Trace" writeTo="seq" />
  </rules>
</nlog>

मैं किसी भी अंतर्दृष्टि की सराहना करता हूं जो आप दे सकते हैं। FYI करें प्रोजेक्ट ओपन-सोर्स है अगर आप इसे क्लोन करना चाहते हैं और इसकी जांच करना चाहते हैं आपके आईडीई में।

1
Tim Long 19 जुलाई 2020, 02:14

1 उत्तर

सबसे बढ़िया उत्तर

लकड़हारा नाम इस प्रकार बनाया गया है:

  • Logger.GetLogger(string name) का उपयोग करते समय नाम का उपयोग बिना किसी बदलाव के किया जाता है
  • LogManager.GetCurrentClassLogger() का उपयोग करते समय वर्ग का नाम स्टैकट्रेस पर खोजा जाता है। कुछ मामलों में यह मुश्किल हो सकता है, क्योंकि इनलाइनिंग और एसिंक्स ट्रिक्स से सही नाम ढूंढना मुश्किल हो जाता है।

इस मामले में LogManager.GetCurrentClassLogger() के उपयोग की वास्तव में आवश्यकता नहीं है और मैं इसे Logger.GetLogger(string name) से बदलने की सलाह देता हूं

कृपया यह भी ध्यान दें कि धाराप्रवाह एपीआई का आपका डिज़ाइन थोड़ा अपरंपरागत है - कम से कम आपके उदाहरण में जोड़े गए गुणों के लिए।

3
Julian 19 जुलाई 2020, 19:12