Форум: "Основная";
Текущий архив: 2006.01.15;
Скачать: [xml.tar.bz2];
ВнизРеализация лог-файла. Найти похожие ветки
← →
Владислав © (2005-12-08 13:59) [0]Приветствую, Мастера.
Необходимо реализовать запись строковых данных в лог файл. Лог файл должен иметь ограниченный размер. Новые строки добавляются в конец файла. При превышении размера лог-файла установленного ограничения, из начала файла должны удаляться старые строки.
Кроме этого, хотелось бы, чтобы буфер файла периодически сбрасывался на диск, чтобы можно было просматривать лог во время работы приложения.
Что-то особенное, для обеспечения целостности данных не нужно, но и половину строки (обрезанную строку) тоже не хотелось бы оставлять.
Какие есть варианты решения этой задачи?
На уме пока только "самое очевидное" :) Сдвигание информации, содержащейся в файле в начало, при превышении размера, и дописывание новой информации в конец файла. Но что-то мне подсказывает, что это не есть хорошо :)
← →
Владислав © (2005-12-08 14:00) [1]Забыл указать.
Если это важно, то Win9x, Win2k, WinXP, D7.
← →
Digitman © (2005-12-08 14:05) [2]
> Лог файл должен иметь ограниченный размер
уточни - какой ...
это важно для простоты решения
← →
dracula © (2005-12-08 14:05) [3]1. Делаеш StringList, и просто начинаешь удалять первые строки из него, при этом добавляя последнии и переодически сохраняешь его в файл.
2. Создаёшь файл открытый на запись и чтение, его можно будет просматривать и в тоже время изменять, делаешь то же самое что и со списком строк, удаляешь первую строку добавляешь в конец новую, но первый вариант мне кажется проще будет.
Можно ещё кучу всего придумать... ну может кто ещё чего добавит.
← →
Fay © (2005-12-08 14:06) [4]2 Владислав © (08.12.05 13:59)
> Сдвигание информации, содержащейся в файле в начало,
> при превышении размера, и дописывание новой информации
> в конец файла
Это именно то, что и
> Новые строки добавляются в конец файла. При превышении
> размера лог-файла установленного ограничения, из
> начала файла должны удаляться старые строки.
← →
dracula © (2005-12-08 14:09) [5]Да это точно =)
← →
Владислав © (2005-12-08 14:14) [6]
> Digitman © (08.12.05 14:05) [2]
>
> уточни - какой ...
>
> это важно для простоты решения
Этот параметр задается пользователем. По умолчанию лог файл вообще отключен, а размер файла установлен 50 кБ. Сколько же может установить пользователь, я заранее знать не могу. У этой опции ограничение (2^31 - 1)кБ.
← →
Владислав © (2005-12-08 14:15) [7]
> Fay © (08.12.05 14:06) [4]
Я утверждал обратное? :)
← →
Anatoly Podgoretsky © (2005-12-08 14:21) [8]Владислав © (08.12.05 14:14) [6]
При таких размерах надо очень потрудиться, что бы это было оптимально и не нанесло вреда, ни писателю, ни читателю и без ощутимых задержек. При таких размерах тяжеловать будет и с локальной базой.
← →
Владислав © (2005-12-08 14:30) [9]
> Anatoly Podgoretsky © (08.12.05 14:21) [8]
Вот я и задумался...
Сейчас при превышении размера файл просто обрезается до нулевого размера и запись начинается с начала. По моему мнению, пользы в таком логе очень мало, ведь у большинства пользователей ограничение будет установлено небольшим, и лог будет постоянно перетираться.
← →
Anatoly Podgoretsky © (2005-12-08 14:42) [10]Владислав © (08.12.05 14:30) [9]
Ну зачем тебе такие большие логи?
Сделай логи по периоду, например один день. Тогда весь лог можно будет загнать и в StringList, да и обрезать ничего не надо будет.
Режим работы простой
1. открыть файл (TFileStream) в режиме записи и разрешение только на чтение для других программ.
2. записать строку
3. закрыть файл.
Другие программы открывают только в режиме чтения, без блокировки на чтения-записи
Если же держать файл постоянно открытым, то это не позволит другим программам его исказить, но могут быть иногда проблемы, если потребуется монопольное открытие, но в этом случае проблемы и для писателя.
Не закрытие файла может привести к потерям данных в аварийных ситуациях, если это допустимо, то держи постоянно открытым.
Даже синхронизировать не нужно.
← →
Digitman © (2005-12-08 14:42) [11]
> Владислав © (08.12.05 14:14) [6]
TFileStream - инструмент очевидного решения
← →
Владислав © (2005-12-08 15:04) [12]Мне вот придумался такой алгоритм.
Когда размер лога достиг ограничения, сдвигаем данные файла в начало таким образом.
Расчитываем размер блока удаляемых данных в начале файла исходя из размера ограничения. Чем больше ограничение лога, тем больше размер блока.
Сдвигаем данные. Уменьшаем размер файла.
Ищем в начале файла первое вхождение окончания строки. Все, что перед, заменяем пробелами.
В итоге получается файл, у которого, возможно, первая строчка будет выглядеть как пустой. Т.е. "читабельность" сохраниться. Операции "сжатия" лог файла в случае с большими файлами будут редкими, в случае с небольшими файлами хоть и более частыми, но по времени это будет не накладно.
Критика приветствуется ;)
← →
Игорь Шевченко © (2005-12-08 15:06) [13]
> Когда размер лога достиг ограничения
Сбрасываем его в архив и начинаем новый :)
← →
Владислав © (2005-12-08 15:10) [14]
> Игорь Шевченко © (08.12.05 15:06) [13]
>
> > Когда размер лога достиг ограничения
>
>
> Сбрасываем его в архив и начинаем новый :)
Не пойдет :) Тогда придется нарисовать опции, в которых можно будет задавать каталог для архивов, ограничение на количество файлов в архиве, а я жутко не люблю делать интерфейс :) Уж лучше... даже не знаю... Лучше чего-нибудь придумать :)
← →
Digitman © (2005-12-08 15:15) [15]
> Критика приветствуется
Скажи, а что ты все же под "сдвигом" подразумеваешь ?
Если копирование полного образа файла в АП процесса приложения и последующие Move-like-операции с памятью, то идея эта критику попросту не выдерживает - АП процесса не резиновое.
открываешь ReadFileStream,
делаешь соотв.расчеты,
открываешь WriteFileStream,
по результатам расчетов делаешь нужное смещение позиции в ReadFileStream отн-но начала,
выполняешь WriteFileStream.CopyFrom(ReadFileStream, вычисленный размер)
дозаписываешь в хвост WriteFileStream разделитель строк и саму строку
закрываешь оба стрима.
← →
Владислав © (2005-12-08 15:21) [16]
> Digitman © (08.12.05 15:15) [15]
>
> > Критика приветствуется
>
>
> Скажи, а что ты все же под "сдвигом" подразумеваешь ?
> Если копирование полного образа файла в АП процесса приложения
> и последующие Move-like-операции с памятью
Ясен пень, что нет (мне, по крайней мере, ясен) :)
Пока два варианта.
1. MMF.
2. TFileStream.
← →
Владислав © (2005-12-08 15:25) [17]У меня вообще губа не дура. Меня вполне бы устроил вариант, если бы система поддерживала "зацикленные" файлы. Записали последний блок файла, система взяла и передвинула указатель начала файла на второй его блок, первый блок присоединила к концу файла и продолжила запись в уже новый последний блок.
:)
P.S.: Это не про windows, это фантастика :)
← →
Digitman © (2005-12-08 15:34) [18]
> 1. MMF.
не выдерживает критики по той же причине - "нерезиновости" АП.
← →
Владислав © (2005-12-08 15:38) [19]Именно по причине "нерезиновости" АП TStream выделяет буфер максимальным размером $F000 для копирования? Я так тоже смогу :)
Кстати, а почему именно $F000? Магия?..
← →
Digitman © (2005-12-08 15:47) [20]
> а почему именно $F000? Магия?..
а где ты такую цифирь узрел именно в реализации TStream ?
это вообще абстрактный класс, потому ни о каких заранее выделеляемых классом буферах там не может идти и речи ..
если кто-то и выделяет какие-то буферы, так это наследники TStream, каковым (непрямым) и является TFileStream .. но в теле TFileStream.Create нет ни намека ни на какие $F000
← →
Digitman © (2005-12-08 15:50) [21]
> Я так тоже смогу
а зачем изобретать велосипед, если его уже изобрели в виде того же TFileStream ?
к тому же вероятность наличия свободного региона в АП процесса для организации буфера в 60к неизмеримо выше, нежели вероятность распределения там же непрерывного блока в сотни и тысячи мегабайт
← →
Владислав © (2005-12-08 15:55) [22]
> Digitman © (08.12.05 15:47) [20]
>
> > а почему именно $F000? Магия?..
>
>
> а где ты такую цифирь узрел именно в реализации TStream
> ?function TStream.CopyFrom(Source: TStream; Count: Int64): Int64;
const
MaxBufSize = $F000;
...
if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count;
GetMem(Buffer, BufSize);
try
...
Source.ReadBuffer(Buffer^, N);
WriteBuffer(Buffer^, N);
...
> Digitman © (08.12.05 15:50) [21]
>
> нежели вероятность распределения там же непрерывного блока
> в сотни и тысячи мегабайт
Сергей, я же нигде не утверждал, что собираюсь проецировать весь файл ;)
← →
umbra © (2005-12-08 16:00) [23]
$F000 = 4096 = странице памяти
← →
umbra © (2005-12-08 16:03) [24]прошу прощения, увлекся ;
$F000 = 61440 = 15 страниц памяти
правда, неясно, что из этого следует :)
← →
Digitman © (2005-12-08 16:05) [25]
> Владислав © (08.12.05 15:55) [22]
Ах вон ты о чем)
Ну так, наверно, по той же самой (упомянутой) причине здесь 60к фигурирует... ровно 15 страниц (почему не 16 - у Борланда свои соображения на этот счет) ... общий размер не превышает гранулярности сегмента ... всё чин по чину) ..
> не утверждал, что собираюсь проецировать весь файл
да ну а смысл-то в чем - проектировать шматками ?
при этом ты лишаешься самого главного удобства : весь файловый образ перед как на ладони, в виде массива байт, твори с ним все что угодно в один присест ...
← →
Anatoly Podgoretsky © (2005-12-08 16:12) [26]16 страниц = 1 сегмент полный, но одна страница может использоваться в служебных целях, поэтому до меньшей величины.
← →
Digitman © (2005-12-08 16:17) [27]
> Anatoly Podgoretsky © (08.12.05 16:12) [26]
у меня тоже эта мысль промелькнула
← →
Игорь Шевченко © (2005-12-08 16:34) [28]Владислав © (08.12.05 15:10) [14]
> Не пойдет :) Тогда придется нарисовать опции, в которых
> можно будет задавать каталог для архивов, ограничение на
> количество файлов в архиве, а я жутко не люблю делать интерфейс
> :) Уж лучше... даже не знаю... Лучше чего-нибудь придумать
> :)
За то время, пока ты здесь дискутируешь, уже давно бы сделал интерфейс для архива. Или просто два файла - один горячий, другой холодный и они меняются местами по заполнению.
← →
Владислав © (2005-12-08 16:51) [29]Игорь, из задачи же вроде бы ясно, что это: "Или просто два файла - один горячий, другой холодный и они меняются местами по заполнению" не подходит.
← →
Игорь Шевченко © (2005-12-08 17:03) [30]Владислав © (08.12.05 16:51) [29]
Мне из задачи видно, что ты хочешь странного :) Например, хочешь полную аналогию буфера экрана. Так для него алгоритмы давно известны и если очень хочется извратиться, то, сам понимаешь, адаптация их к некоему буферу фиксированного размера - дело рутинное. Плюс запись всего буфера целиков в файл по добавлению новой записи в лог.
Вопрос только в одном - а стоит ли овчинка выделки ? Сдается мне, что на скроллинг ты потратишь больше времени и нервов, чем на применение более простого решения.
← →
WondeRu © (2005-12-08 17:09) [31]а почему нельзя по циклу в файл писать... выделить для каждой записи предположим 100 байт...
че-нить вроде такогоprocedure writelog(s: string)
var
LogBlock: String[100];
begin
FillChar(@LogBlock,100," ");
LogBlock := s;
if Position > N
then
Position := 0
else
Inc(Position);
writeblocktoposition(Position * 100, LogBlock);//тут твоя функция записи в файл
end;
← →
Владислав © (2005-12-09 14:25) [32]Огромное спасибо всем за советы.
Вот что получилось в итоге.
Критика приветствуется ;)unit LogFileU;
interface
uses
Windows, Classes, SysUtils, Forms;
type
ELogFileException = class(Exception);
TLogFile = class(TObject)
private
FLogFileThread: Pointer;
FStream: TFileStream;
FReadStream: TFileStream;
FFileName: string;
FMaxSize: Integer;
FDecreaseSize: Integer;
FSilentExceptions: Boolean;
private
procedure CreateLogStream;
procedure FreeLogStream;
function GetReadStream: TFileStream;
procedure FreeReadStream;
procedure SeekStreamToEnd;
procedure SetFileName(const Value: string);
procedure SetMaxSize(const Value: Integer);
procedure CheckMaxSize(Increase: Integer);
procedure CalculateDecreaseSize;
procedure DecreaseLogStream;
procedure WriteBuffer(const Buffer; Size: Integer);
property ReadStream: TFileStream read GetReadStream;
procedure RunLogFlush;
public
constructor Create(const AFileName: string; AMaxSize: Integer);
destructor Destroy; override;
procedure ClearLog;
procedure Write(const AString: string);
procedure WriteLine(const AString: string);
property FileName: string read FFileName write SetFileName;
property MaxSize: Integer read FMaxSize write SetMaxSize;
property SilentExceptions: Boolean
read FSilentExceptions write FSilentExceptions;
end;
Продолжение следует...
← →
Владислав © (2005-12-09 14:25) [33]
implementation
const
EOL = ""#$0D#$0A"";
ErrWhileEventWaitingMsg = "Log file thread waiting error";
function CloseAndNilHandle(var AHandle: THandle): Boolean;
var
TempHandle: THandle;
begin
TempHandle := AHandle;
Result := TempHandle = 0;
if not Result then
begin
AHandle := 0;
Result := CloseHandle(TempHandle);
end;
end;
type
TLogFileThread = class(TThread)
private
FLogFile: TLogFile;
FWorkEvent: THandle;
FException: Exception;
FLogChanged: Boolean;
private
procedure ShowException;
procedure FlushLogFile;
protected
procedure Execute; override;
public
constructor Create(ALogFile: TLogFile);
destructor Destroy; override;
procedure StopAndWaitFor;
procedure SetLogChanged;
end;
{ TLogFileThread }
constructor TLogFileThread.Create(ALogFile: TLogFile);
begin
FLogFile := ALogFile;
FWorkEvent := CreateEvent(nil, False, False, nil);
if FWorkEvent = 0 then
Terminate;
inherited Create(False);
Priority := tpIdle;
end;
destructor TLogFileThread.Destroy;
begin
CloseAndNilHandle(FWorkEvent);
inherited;
end;
procedure TLogFileThread.Execute;
var
WaitResult: DWORD;
begin
try
while not Terminated do
begin
WaitResult := WaitForSingleObject(FWorkEvent, INFINITE);
if WaitResult = WAIT_OBJECT_0 then
begin
if not Terminated then
begin
if FLogChanged then
Synchronize(FlushLogFile)
end
else
Break;
end
else
raise ELogFileException.Create(ErrWhileEventWaitingMsg);
end;
except
on E: Exception do
begin
FException := E;
Synchronize(ShowException);
end
end
end;
procedure TLogFileThread.FlushLogFile;
begin
{$B-}
if (FLogFile <> nil) and (FLogFile.FStream <> nil) then
FlushFileBuffers(FLogFile.FStream.Handle);
FLogChanged := False;
end;
procedure TLogFileThread.SetLogChanged;
begin
if not FLogChanged then
begin
FLogChanged := True;
if FWorkEvent <> 0 then
SetEvent(FWorkEvent);
end;
end;
procedure TLogFileThread.ShowException;
begin
{$B-}
if (FLogFile <> nil) and (not FLogFile.SilentExceptions) then
Application.ShowException(FException);
end;
procedure TLogFileThread.StopAndWaitFor;
begin
Terminate;
if FWorkEvent <> 0 then
SetEvent(FWorkEvent);
Priority := tpNormal;
WaitFor;
end;
{ TLogFile }
constructor TLogFile.Create(const AFileName: string; AMaxSize: Integer);
begin
FSilentExceptions := True;
FLogFileThread := TLogFileThread.Create(Self);
MaxSize := AMaxSize;
FileName := AFileName;
end;
destructor TLogFile.Destroy;
var
Thread: TLogFileThread;
begin
FreeReadStream;
FreeLogStream;
Thread := TLogFileThread(FLogFileThread);
if Thread <> nil then
begin
Thread.StopAndWaitFor;
FreeAndNil(Thread);
end;
inherited;
end;
procedure TLogFile.CreateLogStream;
begin
if FStream = nil then
begin
if not FileExists(FFileName) then
with TFileStream.Create(FFileName, fmCreate or fmShareDenyWrite) do
Free;
FStream := TFileStream.Create(FFileName, fmOpenReadWrite or fmShareDenyWrite);
end;
SeekStreamToEnd;
end;
procedure TLogFile.FreeLogStream;
begin
FreeAndNil(FStream);
end;
procedure TLogFile.SeekStreamToEnd;
begin
if FStream <> nil then
FStream.Seek(0, soEnd);
end;
function TLogFile.GetReadStream: TFileStream;
begin
if FReadStream = nil then
FReadStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyNone);
Result := FReadStream;
end;
procedure TLogFile.FreeReadStream;
begin
FreeAndNil(FReadStream);
end;
procedure TLogFile.RunLogFlush;
begin
TLogFileThread(FLogFileThread).SetLogChanged;
end;
procedure TLogFile.ClearLog;
begin
if FStream <> nil then
FStream.Size := 0;
end;
procedure TLogFile.SetFileName(const Value: string);
begin
if FFileName <> Value then
begin
FreeReadStream;
FreeLogStream;
FFileName := Value;
CreateLogStream;
end;
end;
procedure TLogFile.SetMaxSize(const Value: Integer);
begin
if FMaxSize <> Value then
begin
FMaxSize := Value;
if (FMaxSize > 0) and (FMaxSize < 1024) then
FMaxSize := 1024;
FMaxSize := ((FMaxSize + 1023) shr 10) shl 10;
CalculateDecreaseSize;
CheckMaxSize(0);
end;
end;
procedure TLogFile.CalculateDecreaseSize;
var
DecreaseSize: Integer;
begin
if FMaxSize > 0 then
begin
DecreaseSize := FMaxSize shr 6;
if DecreaseSize < 128 then
DecreaseSize := 128;
end
else
DecreaseSize := 0;
FDecreaseSize := DecreaseSize;
end;
procedure TLogFile.DecreaseLogStream;
var
LocalReadStream: TFileStream;
SizeToMove: Int64;
MoveFrom: Int64;
Size: Int64;
begin
if (FDecreaseSize > 0) and (FMaxSize > 0) then
begin
Size := FStream.Size;
SizeToMove := FMaxSize - FDecreaseSize;
MoveFrom := Size - SizeToMove;
if MoveFrom > 0 then
begin
MoveFrom := ((MoveFrom + 3) shr 2) shl 2;
SizeToMove := Size - MoveFrom;
Size := SizeToMove;
LocalReadStream := ReadStream;
LocalReadStream.Seek(MoveFrom, soBeginning);
FStream.Seek(0, soBeginning);
FStream.CopyFrom(LocalReadStream, SizeToMove);
FStream.Size := Size;
SeekStreamToEnd;
end;
end;
end;
procedure TLogFile.CheckMaxSize(Increase: Integer);
begin
if FStream <> nil then
begin
if FMaxSize > 0 then
begin
if FStream.Size + Increase > FMaxSize then
DecreaseLogStream;
end;
end;
end;
procedure TLogFile.WriteBuffer(const Buffer; Size: Integer);
begin
if FStream <> nil then
begin
CheckMaxSize(Size);
FStream.Write(Buffer, Size);
RunLogFlush;
end;
end;
procedure TLogFile.Write(const AString: string);
var
StrLen: Integer;
begin
try
if FStream <> nil then
begin
StrLen := Length(AString);
if StrLen > 0 then
begin
CheckMaxSize(StrLen);
WriteBuffer(Pointer(AString)^, StrLen)
end;
end;
except
on E: Exception do
begin
if not FSilentExceptions then
raise;
end;
end;
end;
procedure TLogFile.WriteLine(const AString: string);
var
StrLen: Integer;
EolLen: Integer;
begin
try
if FStream <> nil then
begin
StrLen := Length(AString);
EolLen := Length(EOL);
CheckMaxSize(StrLen + EolLen);
if StrLen > 0 then
WriteBuffer(Pointer(AString)^, StrLen);
WriteBuffer(EOL, EolLen);
end;
except
on E: Exception do
begin
if not FSilentExceptions then
raise;
end;
end;
end;
end.
← →
Alexander Panov © (2005-12-09 14:40) [34]Ну и тут посмотри... так... на всякий случай.
http://kladovka.net.ru/delphibase/?action=viewfunc&topic=fileini&id=10563
← →
Владислав © (2005-12-09 15:07) [35]
> Alexander Panov © (09.12.05 14:40) [34]
Посмотрел. Идея понравилась. Где ж Вы раньше были? :)
Только не такой уж он безопасный.
← →
Alexander Panov © (2005-12-09 15:20) [36]Владислав © (09.12.05 15:07) [35]
Только не такой уж он безопасный.
Ну если есть места опасные, то сделай замечание;)
← →
Владислав © (2005-12-09 15:26) [37]
> Alexander Panov © (09.12.05 15:20) [36]
>
> Ну если есть места опасные, то сделай замечание;)
Куда делать замечания?
← →
Alexander Panov © (2005-12-09 15:28) [38]Владислав © (09.12.05 15:26) [37]
Куда делать замечания?
Лучше здесь;)
← →
Владислав © (2005-12-09 15:47) [39]Будем оффтопить...
Разбор полетов курсанта... упс...
:)
Вот, в общем, то, что бросилось в глаза:
1).constructor TPushPop.Create;
begin
// Нехватило памяти на следующем выделении
// Результат описан в деструкторе
New(FRootItem);
...
destructor TPushPop.Destroy;
begin
// Вход в неинициализированную критическую секцию
Lock;
// Обращение к указателю на нулевой адрес
while FRootItem.Next<>FLastItem do DeleteItem0;
...
// Прочие варианты тоже можно спрогнозировать
2).procedure TPushPop.Push(const Str: String);
var
p: PItemList;
pi: PChar;
begin
// Выделили память для хранения строки
pi := AllocMem(Length(Str)+1);
move(Str[1],pi^,Length(Str));
// Если на следующей строчке кода произошло исключение
// Память для хранения строки "улетела в никуда". Утечка.
Lock;
try
// Или здесь может возникнуть исключение.
// Память, выделенная для строки, тоже утечет
New(p);
← →
Игорь Шевченко © (2005-12-09 16:14) [40]Владислав © (09.12.05 14:25) [33]
> function CloseAndNilHandle(var AHandle: THandle): Boolean;
>
> var
> TempHandle: THandle;
> begin
> TempHandle := AHandle;
> Result := TempHandle = 0;
> if not Result then
> begin
> AHandle := 0;
> Result := CloseHandle(TempHandle);
> end;
> end;
Народу непонятно, зачем нужна временная переменная.
> FWorkEvent := CreateEvent(nil, False, False, nil);
> if FWorkEvent = 0 then
> Terminate;
А почему объект не создался, никого нафиг не интересует, верно ?
> destructor TLogFileThread.Destroy;
> begin
> CloseAndNilHandle(FWorkEvent);
> inherited;
> end;
Нафига, собственно, обнулять значение поля FWorkEvent в деструкторе ? Отмазки про наследников не катят - поле приватное :)
> if FWorkEvent <> 0 then
> SetEvent(FWorkEvent);
А эта проверка зачем ?
В конструкторе сделан страшный Terminate, если FWorkEvent = 0, поток, насколько я понимаю, выполняться не будет, Wait тоже не выполнится и SetEvent будет сделан впустую
Страницы: 1 2 вся ветка
Форум: "Основная";
Текущий архив: 2006.01.15;
Скачать: [xml.tar.bz2];
Память: 0.61 MB
Время: 0.015 c