অবিচ্ছিন্নভাবে লগিং - এটি কীভাবে করা উচিত?


11

আমি যে কয়েকটি পরিষেবাদিতে কাজ করি সেখানে অনেকগুলি লগিং করা হচ্ছে। পরিষেবাগুলি ডাব্লুসিএফ পরিষেবাদি (বেশিরভাগ) যা। নেট ইভেন্টলগার শ্রেণি ব্যবহার করে।

আমি এই পরিষেবাদির কর্মক্ষমতা উন্নত করার প্রক্রিয়ায় রয়েছি, এবং আমি ভেবেছিলাম যে অবিচ্ছিন্নভাবে লগ ইন করা কর্মক্ষমতাকে উপকৃত করবে।

আমি যখন একাধিক থ্রেড লগ করতে বলি তখন কী ঘটে যায় তা সম্পর্কে আমি অসচেতন।

আমার চিন্তাভাবনাগুলি হ'ল আসল প্রক্রিয়াটি চালিয়ে যাওয়ার সাথে সাথে আমার এখনই কল করা একই লগ পদ্ধতিটি চালু করা উচিত তবে একটি নতুন থ্রেড ব্যবহার করে তা করা উচিত।

সে সম্পর্কে কিছু প্রশ্ন:

ঠিক আছে?

কোন ডাউনসাইড আছে?

এটি কি অন্যভাবে করা উচিত?

সম্ভবত এটি এত দ্রুত যে এটি চেষ্টা করেও মূল্য দেয় না?


1
লগিংয়ের পারফরম্যান্সে একটি পরিমাপযোগ্য প্রভাব রয়েছে তা জানতে আপনি রানটাইম (গুলি) প্রোফাইল করেছেন? কম্পিউটারগুলি কেবল এতটাই জটিল যে এই ভেবে যে কোনও কিছু ধীর হতে পারে, দুবার পরিমাপ করা উচিত এবং একবারে কাটতে হবে কোনও পেশায় ভাল পরামর্শ =)
প্যাট্রিক হিউজেস

@ পেট্রিক হিউজেস - একটি নির্দিষ্ট অনুরোধের ভিত্তিতে আমার পরীক্ষাগুলি থেকে কিছু পরিসংখ্যান: (১ (!!) লগ বার্তা, 90 মিমি পরে কিছু ধরণের সরল থ্রেডিংয়ের আগে 150 মিমি। সুতরাং এটি 40% দ্রুত।
মিথির

উত্তর:


14

আই \ হে অপারেশনের জন্য পৃথক থ্রেড যুক্তিসঙ্গত মনে হচ্ছে।

উদাহরণস্বরূপ, ব্যবহারকারীরা একই ইউআই থ্রেডে কোন বোতাম টিপেছে তা লগ করা ভাল হবে না। এই ধরনের UI 'তে এলোমেলোভাবে স্তব্ধ হবে এবং ধীর আছে কর্মক্ষমতা অনুভূত

সমাধানটি হ'ল এর প্রক্রিয়াজাতকরণ থেকে ইভেন্টটি ডিকুয়াল করা।

গেম ডেভলপমেন্ট ওয়ার্ল্ড থেকে প্রযোজক-গ্রাহক সমস্যা এবং ইভেন্টের ক্যু সম্পর্কে প্রচুর তথ্য এখানে রয়েছে

প্রায়শই এমন একটি কোড থাকে

///Never do this!!!
public void WriteLog_Like_Bastard(string msg)
{
    lock (_lockBecauseILoveThreadContention)
    {
        File.WriteAllText("c:\\superApp.log", msg);
    }
}

এই পদ্ধতির সাহায্যে থ্রেড কনটেন্ট হবে। সমস্ত প্রক্রিয়াজাতকরণ থ্রেড লক পেতে এবং একই ফাইলটিতে একবারে লিখতে সক্ষম হতে লড়াই করবে।

কেউ কেউ লকগুলি সরাতে চেষ্টা করতে পারেন।

public void Log_Like_Dumbass(string msg)
{
      try 
      {  File.Append("c:\\superApp.log", msg); }
        catch (Exception ex) 
        {
            MessageBox.Show("Log file may be locked by other process...")
        }
      }    
}

যদি একই সাথে 2 টি থ্রেড পদ্ধতিতে প্রবেশ করে তবে ফলাফলটি ভবিষ্যদ্বাণী করা সম্ভব নয়।

সুতরাং অবশেষে বিকাশকারীরা লগিং একেবারে অক্ষম করবে ...

এটা ঠিক করা সম্ভব?

হ্যাঁ.

আমাদের ইন্টারফেস আছে বলুন:

 public interface ILogger
 {
    void Debug(string message);
    // ... etc
    void Fatal(string message);
 }

পরিবর্তে লক জন্য অপেক্ষা এবং ফাইল অপারেশন প্রত্যেক সময় যখন ব্লক সম্পাদন করতে ILoggerবলা হয় আমরা নতুন যোগ হবে LogMessage করার Penging বার্তা সারি এবং আরো গুরুত্বপূর্ণ বিষয় ফিরে:

public class AsyncLogger : ILogger
{
    private readonly BlockingCollection<LogMessage> _pendingMessages;
    private readonly Type _loggerFor;
    private readonly IThreadAdapter _threadAdapter;

    public AsyncLogger(BlockingCollection<LogMessage> pendingMessages, Type loggerFor, IThreadAdapter threadAdapter)
    {
        _pendingMessages = pendingMessages;
        _loggerFor = loggerFor;
        _threadAdapter = threadAdapter;
    }

    public void Debug(string message)
    {
        Push(LoggingLevel.Debug, message);
    }

    public void Fatal(string message)
    {
        Push(LoggingLevel.Fatal, message);
    }

    private void Push(LoggingLevel importance, string message)
    {
        // since we do not know when our log entry will be written to disk, remember current time
        var timestamp = DateTime.Now;
        var threadId = _threadAdapter.GetCurrentThreadId();

        // adds message to the queue in lock-free manner and immediately returns control to caller
        _pendingMessages.Add(LogMessage.Create(timestamp, importance, message, _loggerFor, threadId));
    }
}

আমরা এই সাধারণ অ্যাসিনক্রোনাস লগার দিয়ে কাজ করেছি ।

পরবর্তী পদক্ষেপটি আগত বার্তাগুলি প্রক্রিয়া করা।

সরলতার জন্য, নতুন থ্রেড শুরু করা যাক এবং অ্যাপ্লিকেশনটি প্রস্থান না হওয়া বা অ্যাসিঙ্ক্রোনাস লগার মুলতুবি থাকা সারিটিতে নতুন বার্তা যুক্ত না হওয়া পর্যন্ত চিরতরে অপেক্ষা করুন ।

public class LoggingQueueDispatcher : IQueueDispatcher
{
    private readonly BlockingCollection<LogMessage> _pendingMessages;
    private readonly IEnumerable<ILogListener> _listeners;
    private readonly IThreadAdapter _threadAdapter;
    private readonly ILogger _logger;
    private Thread _dispatcherThread;

    public LoggingQueueDispatcher(BlockingCollection<LogMessage> pendingMessages, IEnumerable<ILogListener> listeners, IThreadAdapter threadAdapter, ILogger logger)
    {
        _pendingMessages = pendingMessages;
        _listeners = listeners;
        _threadAdapter = threadAdapter;
        _logger = logger;
    }

    public void Start()
    {
        //  Here I use 'new' operator, only to simplify example. Should be using interface  '_threadAdapter.CreateBackgroundThread' to allow unit testing
        Thread thread = new Thread(MessageLoop);
        thread.Name = "LoggingQueueDispatcher Thread";
        thread.IsBackground = true;

        thread.Start();
        _logger.Debug("Asked to start log message Dispatcher ");

        _dispatcherThread = thread;
    }

    public bool WaitForCompletion(TimeSpan timeout)
    {
        return _dispatcherThread.Join(timeout);
    }

    private void MessageLoop()
    {
        _logger.Debug("Entering dispatcher message loop...");
        var cancellationToken = new CancellationTokenSource();
        LogMessage message;

        while (_pendingMessages.TryTake(out message, Timeout.Infinite, cancellationToken.Token))
        {
            // !!!!! Now it is safe to use File.AppendAllText("c:\\my.log") without ever using lock or forcing important threads to wait.
            // this is example, do not use in production
            foreach (var listener in _listeners)
            {
                listener.Log(message);
            }
        }

    }
}

আমি কাস্টম শ্রোতার শৃঙ্খলা পেরিয়ে যাচ্ছি। আপনি সম্ভবত কল লগিং ফ্রেমওয়ার্ক ( log4netইত্যাদি ইত্যাদি) প্রেরণ করতে চাইতে পারেন

এখানে কোডের বাকী অংশটি রয়েছে:

public enum LoggingLevel
{
    Debug,
    // ... etc
    Fatal,
}


public class LogMessage
{
    public DateTime Timestamp { get; private set; }
    public LoggingLevel Importance { get; private set; }
    public string Message { get; private set; }
    public Type Source { get; private set; }
    public int ThreadId { get; private set; }

    private LogMessage(DateTime timestamp, LoggingLevel importance, string message, Type source, int threadId)
    {
        Timestamp = timestamp;
        Message = message;
        Source = source;
        ThreadId = threadId;
        Importance = importance;
    }

    public static LogMessage Create(DateTime timestamp, LoggingLevel importance, string message, Type source, int threadId)
    {
        return  new LogMessage(timestamp, importance, message, source, threadId);
    }

    public override string ToString()
    {
        return string.Format("{0}  [TID:{4}] {1:h:mm:ss} ({2})\t{3}", Importance, Timestamp, Source, Message, ThreadId);
    }
}

public class LoggerFactory : ILoggerFactory
{
    private readonly BlockingCollection<LogMessage> _pendingMessages;
    private readonly IThreadAdapter _threadAdapter;

    private readonly ConcurrentDictionary<Type, ILogger> _loggersCache = new ConcurrentDictionary<Type, ILogger>();


    public LoggerFactory(BlockingCollection<LogMessage> pendingMessages, IThreadAdapter threadAdapter)
    {
        _pendingMessages = pendingMessages;
        _threadAdapter = threadAdapter;
    }

    public ILogger For(Type loggerFor)
    {
        return _loggersCache.GetOrAdd(loggerFor, new AsyncLogger(_pendingMessages, loggerFor, _threadAdapter));
    }
}

public class ThreadAdapter : IThreadAdapter
{
    public int GetCurrentThreadId()
    {
        return Thread.CurrentThread.ManagedThreadId;
    }
}

public class ConsoleLogListener : ILogListener
{
    public void Log(LogMessage message)
    {
        Console.WriteLine(message.ToString());
        Debug.WriteLine(message.ToString());
    }
}

public class SimpleTextFileLogger : ILogListener
{
    private readonly IFileSystem _fileSystem;
    private readonly string _userRoamingPath;
    private readonly string _logFileName;
    private FileStream _fileStream;

    public SimpleTextFileLogger(IFileSystem fileSystem, string userRoamingPath, string logFileName)
    {
        _fileSystem = fileSystem;
        _userRoamingPath = userRoamingPath;
        _logFileName = logFileName;
    }

    public void Start()
    {
        _fileStream = new FileStream(_fileSystem.Path.Combine(_userRoamingPath, _logFileName), FileMode.Append);
    }

    public void Stop()
    {
        if (_fileStream != null)
        {
            _fileStream.Dispose();
        }
    }

    public void Log(LogMessage message)
    {
        var bytes = Encoding.UTF8.GetBytes(message.ToString() + Environment.NewLine);
        _fileStream.Write(bytes, 0, bytes.Length);
    }
}

public interface ILoggerFactory
{
    ILogger For(Type loggerFor);
}

public interface ILogListener
{
    void Log(LogMessage message);
}

public interface IThreadAdapter
{
    int GetCurrentThreadId();
}

public interface IQueueDispatcher
{
    void Start();
}

প্রবেশ পয়েন্ট:

public static class Program
{
    public static void Main()
    {
        Debug.WriteLine("[Program] Entering Main ...");

        var pendingLogQueue = new BlockingCollection<LogMessage>();


        var threadAdapter = new ThreadAdapter();
        var loggerFactory = new LoggerFactory(pendingLogQueue, threadAdapter);


        var fileSystem = new FileSystem();
        var userRoamingPath = GetUserDataDirectory(fileSystem);

        var simpleTextFileLogger = new SimpleTextFileLogger(fileSystem, userRoamingPath, "log.txt");
        simpleTextFileLogger.Start();
        ILogListener consoleListener = new ConsoleLogListener();
        ILogListener[] listeners = new [] { simpleTextFileLogger , consoleListener};

        var loggingQueueDispatcher = new LoggingQueueDispatcher(pendingLogQueue, listeners, threadAdapter, loggerFactory.For(typeof(LoggingQueueDispatcher)));
        loggingQueueDispatcher.Start();

        var logger = loggerFactory.For(typeof(Console));

        string line;
        while ((line = Console.ReadLine()) != "exit")
        {
            logger.Debug("you have entered: " + line);
        }

        logger.Fatal("Exiting...");

        Debug.WriteLine("[Program] pending LogQueue will be stopped now...");
        pendingLogQueue.CompleteAdding();
        var logQueueCompleted = loggingQueueDispatcher.WaitForCompletion(TimeSpan.FromSeconds(5));

        simpleTextFileLogger.Stop();
        Debug.WriteLine("[Program] Exiting... logQueueCompleted: " + logQueueCompleted);

    }



    private static string GetUserDataDirectory(FileSystem fileSystem)
    {
        var roamingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        var userDataDirectory = fileSystem.Path.Combine(roamingDirectory, "Async Logging Sample");
        if (!fileSystem.Directory.Exists(userDataDirectory))
            fileSystem.Directory.CreateDirectory(userDataDirectory);
        return userDataDirectory;
    }
}

1

লগফাইলে নির্ভরযোগ্যতার জন্য আপনার প্রয়োজনীয়তা এবং কার্য সম্পাদনের প্রয়োজনীয়তাগুলি বিবেচনা করার জন্য মূল কারণগুলি। ডাউনসাইডগুলি পড়ুন। আমি মনে করি এটি উচ্চ কার্যকারিতা পরিস্থিতির জন্য দুর্দান্ত কৌশল।

ঠিক আছে - হ্যাঁ

কোনও ডাউনসাইড রয়েছে - হ্যাঁ - আপনার লগিংয়ের সমালোচনা এবং আপনার বাস্তবায়নের উপর নির্ভর করে নিম্নলিখিতগুলির যে কোনও একটি ঘটতে পারে - ক্রমানুসারে লিখিত লগগুলি, ইভেন্ট ক্রিয়াগুলি সম্পূর্ণ হওয়ার আগে লগ থ্রেড ক্রিয়াকলাপগুলি সম্পূর্ণ হয় না। (এমন একটি দৃশ্যের কল্পনা করুন যেখানে আপনি "ডিবিতে সংযুক্ত হতে শুরু করে" লগইন করেন এবং তারপরে আপনি সার্ভারটি ক্র্যাশ করেছেন, ঘটনাটি ঘটেছে (!) সত্ত্বেও লগ ইভেন্ট কখনই লেখা যায় না)

এটি অন্য কোনও উপায়ে করা উচিত - আপনি এই বিপরীতে মডেলটিকে প্রায় আদর্শ হিসাবে দেখতে চাইবেন

হতে পারে এটি এত দ্রুত যে এটি চেষ্টা করেও মূল্যবান নয় - দ্বিমত পোষণ করে। যদি আপনার কোনও "অ্যাপ্লিকেশন" যুক্তিযুক্ত হয় এবং আপনি কেবলমাত্র ক্রিয়াকলাপের লগগুলি লিখে থাকেন - তবে আপনি লগিং অফ লোড করে তীব্রতর নিম্নতর বিলম্বের আদেশ হবেন। তবে আপনি যদি 1-2 স্টেটমেন্ট লগ করার আগে ফিরে আসার জন্য 5 সিসি ডিবি এসকিউএল কলের উপর নির্ভর করেন তবে উপকারগুলি মিশ্রিত হবে।


1

আমি মনে করি লগিং সাধারণত এটি প্রকৃতির দ্বারা একটি সিনক্রোনাস অপারেশন। আপনি যদি জিনিসগুলি ঘটে সেগুলি লগ করতে চান বা সেগুলি আপনার যুক্তির উপর নির্ভর করে না, তাই কোনও কিছু লগ করার জন্য প্রথমে সেই জিনিসটির মূল্যায়ন করা দরকার।

এটি বলার পরে, আপনি যখন সিপিইউ বাউন্ড অপারেশন করেন তখন আপনি লগগুলি ক্যাশে করে তারপরে একটি থ্রেড তৈরি করে এগুলি ফাইলগুলিতে সংরক্ষণ করে আপনার অ্যাপ্লিকেশনটির কার্যকারিতা উন্নত করতে পারেন।

আপনাকে আপনার চেকপয়েন্টগুলি চতুরতার সাথে সনাক্ত করতে হবে যাতে আপনি সেই ক্যাশে সময়কালে আপনার গুরুত্বপূর্ণ লগিংয়ের তথ্য হারাবেন না।

আপনি যদি আপনার থ্রেডগুলিতে পারফরম্যান্স বাড়াতে চান তবে আপনাকে আইও অপারেশন এবং সিপিইউ অপারেশনগুলিকে ভারসাম্য বজায় রাখতে হবে।

যদি আপনি 10 টি থ্রেড তৈরি করেন যা সমস্ত আইও করে, তবে আপনি কোনও পারফরম্যান্স বুস্ট পাবেন না।


আপনি ক্যাচিং লগগুলি কীভাবে পরামর্শ করবেন? বেশিরভাগ লগ বার্তাগুলিতে তাদের সনাক্ত করার জন্য অনুরোধ-নির্দিষ্ট আইটেম রয়েছে, আমার পরিষেবাতে ঠিক একই অনুরোধ খুব কমই ঘটে।
মিথির

0

লগিং থ্রেডগুলিতে আপনার কম-ল্যাটেন্সি প্রয়োজন হলে অ্যাসিঙ্ক্রোনালি লগইন করা একমাত্র উপায়। সর্বোচ্চ পারফরম্যান্সের জন্য এটি যেভাবে করা হয় তা হ'ল লক-ফ্রি এবং আবর্জনামুক্ত থ্রেড যোগাযোগের জন্য বিঘ্নকারী প্যাটার্ন । এখন আপনি যদি একই ফাইলটিতে একাধিক থ্রেড একই সাথে লগ করার অনুমতি দিতে চান তবে আপনাকে অবশ্যই লগ কলগুলি সিঙ্ক্রোনাইজ করতে হবে এবং লক কনটেন্টে মূল্য দিতে হবে বা লক-ফ্রি মাল্টিপ্লেক্সার ব্যবহার করতে হবে। উদাহরণস্বরূপ, CoralQueue নীচে বর্ণিত হিসাবে একটি সাধারণ মাল্টিপ্লেক্সিং ক্যু সরবরাহ করে:

এখানে চিত্র বর্ণনা লিখুন

আপনি CoralLog এ এক নজর দেখতে পারেন যা অ্যাসিক্রোনাস লগিংয়ের জন্য এই কৌশলগুলি ব্যবহার করে।

দাবি অস্বীকার : আমি কোরালকিউ এবং করাললগের অন্যতম বিকাশকারী।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.