Мой статический класс Logger пишет лог в текстовый файл. Файл лога новый каждый день и на каждого пользователя.
Возможная проблема: файл будет занят, запись не пройдет.
Вопрос: Как организовать запись. Чтобы было быстро и записи не пропадали если файл был занят.
Чтобы не было проблем с занятым файлом, к нему должно быть обращение только из одного места.
Файловый поток при этом не должен постоянно открываться и закрываться, т. к. это медленные операции. Отрываем его один раз и дальше просто используем.
Чтобы запись в лог отрабатывала максимально быстро, можно поступить так: пишем не сразу в файл, а в промежуточную потокобезопасную очередь. Отдельная задача выгребает сообщения из очереди и пишет в файл (БД, посылает по сети).
internal static class Logger
{
private static BlockingCollection<string> _blockingCollection;
private static string _filename = "log.txt";
private static Task _task;
static Logger()
{
_blockingCollection = new BlockingCollection<string>();
_task = Task.Factory.StartNew(() =>
{
using (var streamWriter = new StreamWriter(_filename, true, Encoding.UTF8))
{
streamWriter.AutoFlush = true;
foreach (var s in _blockingCollection.GetConsumingEnumerable())
streamWriter.WriteLine(s);
}
},
TaskCreationOptions.LongRunning);
}
public static void WriteLog(string action, int errorCode, string errorDiscription)
{
_blockingCollection.Add($"{DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff")} действие: {action}, код: {errorCode.ToString()}, описание: { errorDiscription} ");
}
public static void Flush()
{
_blockingCollection.CompleteAdding();
_task.Wait();
}
}
Здесь в статическом конструкторе логгера создаётся потокобезопасная коллекция, в основе которой по умолчанию лежит очередь (что нам и нужно). Здесь же запускаем Task. Открываем файл на запись.
Метод WriteLog отрабатывает максимально быстро - он всего лишь кладёт сообщение в очередь.
Задача будет ожидать в цикле foreach на методе GetConsumingEnumerable: как только появляются новые данные - он будет выполняться. Выход из цикла произойдёт только после вызова метода CompleteAdding. Для этого предусмотрен метод Flush, который желательно вызвать при завершении программы. При этом блокирующая коллекция получит сигнал о завершении поступления данных. После чего закроется файл и завершится задача.
Но это не обязательно: обратите внимание на AutoFlush = true - это не даст потерять данные при неожиданном закрытии программы (краше кода).
Можно использовать примитивы синхронизации. Например lock - она позволяет ограничить доступ к разделяемому ресурсу только одному потоку.
Пример:
object obj;
string filePath = "c:/text.txt";
// Здесь секция разделяемого ресурса блокируется в случае если в этот блок
// попадает поток. Остальные потоки остаются в очереди
lock(obj)
{
using(FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write)
{
// Ну а здесь код записи данных в файл
}
}
Запись производится в файл с нескольких приложений при одновременной попытки записи. Если нет доступа записи в файл, ожидает 100 мс, и так по циклу. Быстроты как хотелось не будет, зато есть гарантия записи.
// Упрощеный вариант с отображением в консоли
internal static class LoggerAddin
{
public static string AppPath { get; set; } = @"C:\Windows\";// Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RBSOFT", "AddInRBSOFTDeviceServiceKKT");
public static string CurrentAppPath
{
get
{
return Path.Combine(
AppPath,
"Logs",
DateTime.Now.Year.ToString(),
DateTime.Now.Month.ToString(),
DateTime.Now.Day.ToString()
);
}
}
public static void WriteLog(string action, int errorCod, string errorDiscription)
{
string fileName = Path.Combine(CurrentAppPath, "Log-" + DateTime.Today.ToString("yy-MM-dd") + ".txt");
if (!Directory.Exists(CurrentAppPath))
Directory.CreateDirectory(CurrentAppPath);
bool repit = true;
// Console.WriteLine(DateTime.Now.ToString());
while (repit)
{
try
{
using (var stream = new StreamWriter(fileName, true, Encoding.UTF8))
{
stream.WriteLine($"{DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff")} действие: {action}, код: {errorCod.ToString()}, описание: { errorDiscription} ");
}
repit = false;
// Console.WriteLine("Ready");
}
catch (IOException)
{
Console.WriteLine("IOException");
}
catch (UnauthorizedAccessException )
{
//Console.WriteLine("UnauthorizedAccessException");
repit = false;
}
catch (Exception ex)
{
//Console.WriteLine(ex.Message);
repit = false;
}
if (repit)
{
Thread.Sleep(100);
}
}
//Console.WriteLine(DateTime.Now.ToString());
}
}
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости