// aplLog.cpp: implementation of the CaplLog class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "aplSocketTransport.h"
#include <shlobj.h>
#include <algorithm>
#include "commands.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//#define LOG_2_OUTPUT_DEBIUG

#ifdef _UNICODE
#define NEW_LINE _T("\n")
#else
#define NEW_LINE _T("\r\n")
#endif


bool global_is_first_start=true;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CaplLog::CaplLog()
{
	m_IsPrintThread=false;
	m_isLogTick=false;
	m_LogFileName=_T("");
	m_const_info=_T("");
	InitializeCriticalSection(&m_logManager);
	m_log_global_mutex=0;
	m_isAlwaysLogStart=false;
	m_MaxLogSize=0;
	m_isChangeLogWithDate=false;
	m_TimeForChangeLog=-1;
	m_need_change_log4time = false;
	m_is_single_log=false;
	m_is_csv=false;
	GetAddDescription=0;
	m_ErrLogFile=0;
	m_class_info.Format(_T("CaplLog " POINTER_FORMAT),this);
	InitializeCriticalSection(&m_HandlesProtect);


}

CaplLog::~CaplLog()
{
	if(m_ErrLogFile!=0){fclose(m_ErrLogFile);m_ErrLogFile=0;}
	DeleteCriticalSectionODS(&m_logManager);
	DeleteCriticalSectionODS(&m_HandlesProtect);
	if(m_log_global_mutex!=0) CloseHandle(m_log_global_mutex);
}

bool CaplLog::AddFirstInfo(const CString &first_info)
{
	if(m_LogFileName==_T("")) return false;

	m_stored_info += CorrectLinefeed(first_info);
	m_const_info += CorrectLinefeed(first_info);
	return true;
}

bool CaplLog::SetCVSHeader(LPCTSTR header)
{
	m_const_info = header;
	if(m_const_info.Right(1)!=_T("\n"))m_const_info+=_T("\n");
	m_is_csv = true;
	return true;
}

bool CaplLog::SetFileName(const CString &LogFileName, const CString &first_info, bool is_single_log, void (__cdecl *prevGetAddDescription)(CString &descr))
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	SYSTEMTIME SystemTime;
	// Определяем время запуска
	GetLocalTime(&SystemTime);
	CString text;
	CString mutex_name;
	int pos_slesh;
	PSECURITY_ATTRIBUTES psec_attr=0;
	bool ret_val = true;

	text.Format( _T(" %2i.%02i.%04i %2i:%02i:%02i.%03i") NEW_LINE _T("MainThread: " ) THREAD_FORMAT NEW_LINE,
		SystemTime.wDay,SystemTime.wMonth,SystemTime.wYear,
		SystemTime.wHour,SystemTime.wMinute,SystemTime.wSecond,SystemTime.wMilliseconds,
		GetCurrentThreadId()
		);
	// функция SetFileName может быть вызвана из другой нити одновременно с LogMessage
	EnterCriticalSectionODS(&m_logManager);
	m_LogFileName = LogFileName;

	m_is_single_log = is_single_log;

	if(!m_is_csv)
	{
		m_stored_info = NEW_LINE;
		m_stored_info += APL_T("Лог запущен: ") + text;
		if(first_info != _T(""))
		{
			m_stored_info += APL_T(" Информация о приложении:");
			m_stored_info +=  NEW_LINE;
			m_stored_info += CorrectLinefeed(first_info);
			m_stored_info += NEW_LINE;
		}
		m_const_info += m_stored_info;
		m_const_info += _T("_________________  ____________") NEW_LINE;
	}

	if(m_log_global_mutex!=0)
	{
		// если раньше было задано другое имя для лога - надо удалить старый мутекс со старым именем
		CloseHandle(m_log_global_mutex);
	}
	// имя мутекса формируем из имени файла лога
	mutex_name = _T("Global\\PSS_SERV_LOG__") ;
	mutex_name += LogFileName;
	mutex_name.Replace(_T("\\"),_T("_"));
	mutex_name.Replace(_T(":"),_T("_"));

	CreateSecurityAttributes(MUTEX_ALL_ACCESS,&psec_attr);

	m_log_global_mutex=CreateMutex(psec_attr,FALSE,mutex_name);

	// если мутекс не определили  - будет конфликтовать на уровне открывания 
	// файла... Но все равно, выводть-то в лог надо.... Ну и выведем собщение об ошибке
	if(m_log_global_mutex==0)
	{
		DEBUG_BEEP(1000,100);
		m_stored_info += NEW_LINE;
		m_stored_info += APL_T("!!!!! Не удалось открыть мутекс, защищающий работу с файлом лога! Возможна потеря информации!");
	}

	// проверка существования папки для лога
	pos_slesh = m_LogFileName.ReverseFind(_T('\\'));
	if(pos_slesh!=-1)
	{
		CString folder=m_LogFileName.Left(pos_slesh);
		int err_code=SHCreateDirectoryEx(NULL, folder, NULL);
		if(err_code != ERROR_SUCCESS && err_code != ERROR_FILE_EXISTS && err_code != ERROR_ALREADY_EXISTS)
		{
			ret_val = false;
		}
	}
	// проверяем кодировку текущего файла лога
	FILE *ErrLogFile=0;
	BYTE test_buf[5];
	bool force_change = false;
	ErrLogFile = _fopen(m_LogFileName,_T("rb"));
	if(ErrLogFile)
	{
		size_t readed = fread(test_buf,5,1,ErrLogFile);
		fclose(ErrLogFile);
		if(readed ==5)
		{
			aplTextEncoding file_enc = CaplStringFile::GetBufferEncode(test_buf,5);
#ifdef _UNICODE
			force_change = (file_enc != aplUTF8);
#else // #ifdef _UNICODE
			force_change = (file_enc != aplANSI);
#endif // #ifdef _UNICODE	
		}
	}

	if((m_isAlwaysLogStart || m_log_global_mutex==0) && ret_val || force_change)
	{
		Write2FileWithCheck(_T(""), force_change);
	}
	m_class_info += _T("file: ");
	m_class_info += LogFileName;

	LeaveCriticalSectionODS(&m_logManager);
	GetAddDescription = prevGetAddDescription;

	return ret_val;

}

bool CaplLog::LogMessage(LPCTSTR ErrDescr, bool is_warning, bool is_time, bool is_write, bool is_line,
						 LPCTSTR funct, LPCTSTR class_info, LPCTSTR ExtDescr,
						 LPCTSTR srs_file, LPCTSTR srs_date, DWORD srs_line)
{
	SYSTEMTIME SystemTime;
	if(m_LogFileName==_T(""))
		return false;

	bool res=true;
	bool mutex_my=false;
	bool mutex_waited=true;
	CString thread_str=_T(""),tick_str=_T(""),datetime=_T(""),log_t=_T(""),log_d=_T("");
	CString intro=_T(""),srs_text=_T("");
	if(!is_line )
	{
		// если не в одну линию - выводим время
		is_time=true;
	}
	
	CString sErrDescr = ErrDescr;
	long fp = sErrDescr.Find(_T("\n"));
	bool need_line_inline = (fp != -1 && fp < sErrDescr.GetLength() - 1);
	if(need_line_inline)
	{
		if(sErrDescr.Right(1)==_T('\n'))sErrDescr.Delete(sErrDescr.GetLength() - 1,1);
		if(sErrDescr.Right(1)==_T('\r'))sErrDescr.Delete(sErrDescr.GetLength() - 1,1);
		sErrDescr.Replace(_T("\r\n"),_T("\n"));
#ifdef _UNICODE
		sErrDescr.Replace(_T("\n"),_T("\n\t"));
		sErrDescr += _T("\n");
		intro = _T("\n\t");
#else
		sErrDescr.Replace(_T("\n"),_T("\r\n\t"));
		sErrDescr += _T("\r\n");
		intro = _T("\r\n\t");
#endif
	}
	//if(sErrDescr.Right(1)==_T("\t"))sErrDescr.Delete(sErrDescr.GetLength() - 1,1);
	if(is_warning)
	{
		if(is_time)
		{
			intro+=_T("Msg: ");
		}
	}
	else
	{
		intro+=_T("Err: ");
		is_line=false;//ошибка должна категорически отличаться от простого сообщения!
	}

	if(is_line)
	{
		// Название функции, в которой произошло (если время не выводится - имя функции и класс так же не выводятся)
		if( !aplIsStringEmpty(funct) && is_time)
		{
			log_d += CString(_T("Fun: ")) + funct + _T(" \t");
		}
		
		// описание ошибки или сообщения
		log_d += intro + sErrDescr;

		// описание класса в котором произошло (если время не выводится - имя функции и класс так же не выводятся)
		if( !aplIsStringEmpty(class_info) && is_time)
		{
			log_d += CString(_T("\tCls: ")) + class_info + CString(_T(" "));
		}
	}
	else
	{
		// описание ошибки или сообщения
		log_d += CorrectLinefeed(intro+sErrDescr);

		// Название функции, в которой произошло 
		if(/*log_item.m_funct != _T("")*/ funct!=0 && *funct!=0 && is_time)
		{
			log_d += CString(_T("\tFun: ")) + funct + _T(" ");
		}
		
		// описание класса в котором произошло
		if(/*log_item.m_class_info != _T("")*/ class_info!=0 && *class_info!=0 && is_time)
		{
			log_d += CString(_T("\tCls: ")) + class_info + CString(_T(" "));
		}
	}

	// дополнительное описание (функция обработчик, класс обработчик, исходники где произошло и т.п.)
	if(!aplIsStringEmpty(ExtDescr))
	{
		log_d+=CorrectLinefeed(ExtDescr);
	}

	if(!aplIsStringEmpty(srs_file))
	{
		srs_text.Format( APL_T("\tSrc: %s от %s str %i"), srs_file, srs_date, srs_line);
		log_d+=srs_text;
	}

	log_d = CorrectLinefeed(log_d);// следующее сообщение должно быть с начала строки

	// защита от обращения к файлу лога в рамках одного процесса
	// в том числе, и от смены имени файла лога параллелльно обращению к переменной имени - из другой нити
	EnterCriticalSectionODS(&m_logManager);

	// защита от обращения к файлу лога в рамках нескольких процессов
	if(m_log_global_mutex!=0)
	{
		do{
			switch(WaitForSingleObject(m_log_global_mutex,100))
			{
			case WAIT_FAILED:
				mutex_waited=false;
				break;
			case WAIT_TIMEOUT:
				break;
			case WAIT_ABANDONED:
			case WAIT_OBJECT_0:
				mutex_my=true;
				mutex_waited=false;
			}
		}while(mutex_waited);
	}

	// время, нить, тик
	GetLocalTime(&SystemTime);
	if(m_isLogTick && is_time)
	{
		tick_str.Format(_T("(%08u) "),GetTickCount());
	}
	if(m_IsPrintThread )
	{
		thread_str.Format(THREAD_FORMAT _T(" "),GetCurrentThreadId());
	}
	if(is_time)
	{
		datetime.Format(_T("%2i.%02i.%04i %2i:%02i:%02i.%03i "),
			SystemTime.wDay,SystemTime.wMonth,SystemTime.wYear,
			SystemTime.wHour,SystemTime.wMinute,SystemTime.wSecond,SystemTime.wMilliseconds);
	}
	else
	{
		datetime = _T("\t");
	}
	log_t=datetime+tick_str+thread_str;

	if(is_write)
	{
		res=Write2FileWithCheck(log_t+log_d);
	}
	else
	{
		m_stored_info+=log_t+log_d;
	}
	if(m_log_global_mutex!=0 && mutex_my)
	{
		ReleaseMutex(m_log_global_mutex);
	}
	LeaveCriticalSectionODS(&m_logManager);

	return res;
}


bool CaplLog::LogCSV(CStringArray &data, bool is_date_time, bool is_proc_thread)
{
	SYSTEMTIME SystemTime;
	if(m_LogFileName==_T(""))
		return false;

	bool res=true;
	bool mutex_my=false;
	bool mutex_waited=true;
	CString str=_T(""),log_t=_T(""),log_d=_T(""), line=_T("");

	// защита от обращения к файлу лога в рамках одного процесса
	// в том числе, и от смены имени файла лога параллелльно обращению к переменной имени - из другой нити
	EnterCriticalSectionODS(&m_logManager);

	// защита от обращения к файлу лога в рамках нескольких процессов
	if(m_log_global_mutex!=0)
	{
		do{
			switch(WaitForSingleObject(m_log_global_mutex,100))
			{
			case WAIT_FAILED:
				mutex_waited=false;
				break;
			case WAIT_TIMEOUT:
				break;
			case WAIT_ABANDONED:
			case WAIT_OBJECT_0:
				mutex_my=true;
				mutex_waited=false;
			}
		}while(mutex_waited);
	}

	if(is_date_time)
	{
		// время, нить, тик
		GetLocalTime(&SystemTime);
		if(m_isLogTick)
			str.Format(_T("(%08u)"),GetTickCount());
		else
			str.Empty();

		line.Format(_T("\"%02i.%02i.%04i %2i:%02i:%02i.%03i%s\""),
			SystemTime.wDay, SystemTime.wMonth, SystemTime.wYear,
			SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds, str);

	}
	if(m_IsPrintThread )
	{
		str.Format(_T("\"") LOGSERV_THREAD_FORMAT, GetCurrentProcessId(), GetCurrentThreadId());
		str.Trim(); str+=_T("\"");
		if(is_date_time)line+=_T(";");
		line += str;
	}
	for(int i=0;i<data.GetSize();i++)
	{
		if(i!=0 || m_IsPrintThread || is_date_time)line+=_T(";");
		str=data[i];
		str.Replace(_T("\n"), _T("\\n"));
		str.Replace(_T("\r"), _T("\\r"));
		str.Replace(_T("\""), _T("\\\""));
		line += _T("\"")+str+_T("\"");
	}
	line += _T("\n");

	res=Write2FileWithCheck(line);
	if(m_log_global_mutex!=0 && mutex_my)
	{
		ReleaseMutex(m_log_global_mutex);
	}
	LeaveCriticalSectionODS(&m_logManager);

	return res;
}

void CaplLog::ClearFirst()
{
	global_is_first_start=false;
}

bool CaplLog::Write2FileWithCheck(LPCTSTR line, bool force_change)
{
	bool res=true;
#ifdef LOG_2_OUTPUT_DEBIUG
	if(m_stored_info != _T(""))
	{
		OutputDebugString(m_stored_info + line + _T("\n"));
		m_stored_info = _T("");
	}

#else //#ifdef LOG_2_OUTPUT_DEBIUG
	WIN32_FIND_DATA FindFileData;
	HANDLE founded_handle;
	SYSTEMTIME SystemTime;

#ifdef _UNICODE
	TCHAR* attr_open = _T("a+t, ccs=UTF-8");
#else // #ifdef _UNICODE
	TCHAR* attr_open = _T("ab");
#endif // #ifdef _UNICODE

	bool force_change_log = force_change || (m_isChangeLogWithDate && global_is_first_start);

	if(m_TimeForChangeLog!=-1)
	{
		COleDateTime odt = COleDateTime::GetCurrentTime();
		if(odt.GetHour() != m_TimeForChangeLog)
		{
			m_need_change_log4time = true;
		}
		else if(m_need_change_log4time)
		{
			m_need_change_log4time = false;
			force_change_log = true;
			m_isChangeLogWithDate = true;
		}
	}

	// проверяем размер лога
	founded_handle = FindFirstFile(m_LogFileName, &FindFileData);
	if(founded_handle==INVALID_HANDLE_VALUE && m_is_csv)
		m_stored_info += m_const_info;
	else if(m_MaxLogSize>0 || force_change_log)
	{
		CString LogFileName_prev = m_LogFileName + _T("_prev");
		if(m_LogFileName.Right(4) == _T(".log"))
		{
			LogFileName_prev = m_LogFileName.Left(m_LogFileName.GetLength() - 3) + _T("prev.log");
		}
		// если размер лога больше заданного, или если 
		// логи меняются с датами - то при новом старте сервера
		if(founded_handle != INVALID_HANDLE_VALUE)
		{ 
			if(FindFileData.nFileSizeLow>m_MaxLogSize || force_change_log)
			{
				CString log_copy_name,log_ext,log_ch_date;
				int pos;
				FindClose(founded_handle);
				founded_handle=FindFirstFile(LogFileName_prev,&FindFileData);
				if(founded_handle!=INVALID_HANDLE_VALUE)
				{
					FindClose(founded_handle);
					DeleteFile(LogFileName_prev);
				}
				if(m_isChangeLogWithDate)
				{
					GetLocalTime(&SystemTime);
					pos = m_LogFileName.ReverseFind(_T('.'));
					log_ext = m_LogFileName.Right(m_LogFileName.GetLength() - pos - 1);

					log_ch_date.Format(_T("-%4i-%02i-%02i--%2i-%02i-%02i"),
						SystemTime.wYear,SystemTime.wMonth,SystemTime.wDay,
						SystemTime.wHour,SystemTime.wMinute,SystemTime.wSecond);

					log_copy_name=m_LogFileName.Left(pos) + log_ch_date + _T(".") + log_ext;
				}
				else
				{
					log_copy_name=LogFileName_prev;
				}

				founded_handle = FindFirstFile(m_LogFileName,&FindFileData);
				if(founded_handle != INVALID_HANDLE_VALUE)
				{
					FindClose(founded_handle);
					if(m_ErrLogFile != 0)
					{
						fclose(m_ErrLogFile);
						m_ErrLogFile = 0;
					}
					bool need_rename_new_log = MoveFile(m_LogFileName,log_copy_name) == FALSE;

					if(m_is_csv)
						m_stored_info += m_const_info;
					else
					{
						// в новый лог надо вывести кой-какую предварительную информацию, чтобы не разорвалась связь времен
						bool st_i = (m_stored_info == _T(""));
						if(need_rename_new_log)
						{
							m_stored_info += APL_T("!!! Предыдущий лог заблокирован! Меняем свое имя!");
							m_stored_info += NEW_LINE;
							m_LogFileName = log_copy_name;
						}
						else
						{
							m_stored_info += APL_T("!!! Предыдущий лог был сохранен под именем ") + log_copy_name + NEW_LINE;
						}
						if(st_i)
						{
							// а если m_stored_info был не пустой - скорей всего это первый старт сервера, 
							// вся информация уже в логе
							m_stored_info += APL_T("_________________ Данные о старте лога: ____________");
							m_stored_info += NEW_LINE;
							m_stored_info += m_const_info;
						}
					}
				}
				if(GetAddDescription != 0)
				{
					CString tmp_buf;
					GetAddDescription(tmp_buf);
					m_stored_info += tmp_buf;
				}

				if(m_isChangeLogWithDate && m_NumCopies > 0)
				{
					pos = m_LogFileName.ReverseFind(_T('\\'));
					log_ext = m_LogFileName.Right(m_LogFileName.GetLength() - pos - 1);
					CString log_mask=m_LogFileName.Left(pos+1) + _T("*.") + log_ext;
					DeleteOldestFiles(log_mask, m_NumCopies);
				}

			}
			else
			{
				FindClose(founded_handle);
			}
		}
	}
	if(m_ErrLogFile==0)
	{
		m_ErrLogFile = _fopen(m_LogFileName, attr_open);
	}
	if(m_ErrLogFile!=0)
	{
		if(m_stored_info != _T(""))
		{
			CString tmp = CorrectLinefeed(m_stored_info);

			_fprintf(m_ErrLogFile, _T("%s"), tmp);
			m_stored_info = _T("");
		}
		if(!aplIsStringEmpty(line))
		{
#ifdef _UNICODE
			CString line1 = line;
			line1.Replace(L"\r\n",L"\n");
			_fputs((LPCTSTR)line1,m_ErrLogFile);
#else // #ifdef _UNICODE
			_fputs(line,m_ErrLogFile);
#endif // #ifdef _UNICODE

		}

		fflush(m_ErrLogFile);
		if(!m_is_single_log)
		{
			fclose(m_ErrLogFile);
			m_ErrLogFile = 0;
		}
		global_is_first_start=false;
	}
	else
	{
		res=false;
	}
#endif //#ifdef LOG_2_OUTPUT_DEBIUG
	return res;
}

CString CaplLog::GetLogLevelName(aplLogLevel level)
{
	CString name = _T("?");
	switch(level)
	{
	case LogLevelOnlyCriticalError: name = _T("LogLevelOnlyCriticalError"); break;
	case LogLevelAllError: name = _T(" LogLevelAllError"); break;
	case LogLevelFunctionInfo: name = _T("LogLevelFunctionInfo"); break;
	case LogLevelAllMessage: name = _T("LogLevelAllMessage"); break;
	}
	return _T("\"") + name + _T("\"");
}

CString CaplLog::CorrectLinefeed(LPCTSTR message)
{
	CString tmp = message;
	if(tmp.Right(1) == _T("\n"))tmp.TrimRight(_T("\n"));
	if(tmp.Right(1) == _T("\r"))tmp.TrimRight(_T("\r"));
#ifdef _UNICODE
	tmp += _T("\n");
#else
	tmp += _T("\r\n");
#endif
	return tmp;
}

void CaplLog::ClearEmergencyHandles()
{
	EnterCriticalSectionODS(&m_HandlesProtect);
	if(m_EmergencyLogPregion!=0){UnmapViewOfFile(m_EmergencyLogPregion);m_EmergencyLogPregion=0;}                     
	if(m_hFileEmergencyLogMapping!=0){CloseHandle(m_hFileEmergencyLogMapping);m_hFileEmergencyLogMapping=0;}				
	if(m_Emergency_log_mutex!=0){CloseHandle(m_Emergency_log_mutex);m_Emergency_log_mutex=0;};		
	m_Emergency_log_data = 0;
	m_Emergency_log_last_active = 0;
	LeaveCriticalSectionODS(&m_HandlesProtect);
}

bool CaplLog::InitEmergencyLog(bool global)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	bool retval=true;
	CString err_mess=_T("");

	HANDLE hFile = INVALID_HANDLE_VALUE;

	// имена для шар аварийного лога
	CString my_server_id_txt;

	if(m_EmergencyLogPregion!=0) return true;

	if(global)
		my_server_id_txt.Format(GLOBAL_SERVER_ID_FORMAT,GetCurrentProcessId());
	else
		my_server_id_txt.Format(LOCAL_SERVER_ID_FORMAT,GetCurrentProcessId());


	CString name_Emergency_log_mutex = my_server_id_txt + EMERGENCY_LOG_MUTEX;
	CString name_EmergencyLogPregion = my_server_id_txt + EMERGENCY_LOG_SHARED_MEMORY_NAME;
#ifdef _UNICODE
	name_Emergency_log_mutex += L"_U";
	name_EmergencyLogPregion += L"_U";
#endif // #ifdef _UNICODE

	// аттрибуты доступа к объектам синхронизации
	PSECURITY_ATTRIBUTES psec_attr=0;

	// очищаем предыдущие переменные
	ClearEmergencyHandles();
	EnterCriticalSectionODS(&m_HandlesProtect);

	try
	{
		// создаем объект проекция файла - его сначала
		CreateSecurityAttributes(FILE_MAP_ALL_ACCESS, &psec_attr);

		m_hFileEmergencyLogMapping = CreateFileMapping(hFile, psec_attr, PAGE_READWRITE, 0, EMERGENCY_LOG_BUFFER_SIZE, name_EmergencyLogPregion); 
		if(m_hFileEmergencyLogMapping == 0)
		{
			if(global)
			{
				// оракловый/pg сервер должен запускаться под админом, да и ему эжти хендлы обязательно нужны
				APL_TRANSPORT_THROW_EX_SYS(0, APL_T("Ошибка создания m_hFileEmergencyLogMapping"));
			}
			else
			{
				// лайт не получил хендлы? Эти не так важны, пускай...
				return false;
			}
		}

		m_EmergencyLogPregion = MapViewOfFile(m_hFileEmergencyLogMapping,FILE_MAP_WRITE,0,0,0);
		memset(m_EmergencyLogPregion,0,EMERGENCY_LOG_BUFFER_SIZE);

		// переменные управляющие буфером
		m_Emergency_log_last_active = ((long*)m_EmergencyLogPregion);
		*m_Emergency_log_last_active = 0;

		m_Emergency_log_data = (BYTE*)m_EmergencyLogPregion+4;
		m_Emergency_log_size = EMERGENCY_LOG_BUFFER_SIZE - 4;

		CreateSecurityAttributes(MUTEX_ALL_ACCESS,&psec_attr);

		m_Emergency_log_mutex=CreateMutex(psec_attr,FALSE,name_Emergency_log_mutex);
		if(m_Emergency_log_mutex==0){APL_TRANSPORT_THROW_EX_SYS(0,_T("m_Emergency_log_mutex ==0"));}

	}
	catch(SaplErrorDescription err)
	{
		err_mess=err.Print();
		// и очищаем переменные, ибо работать все равно не получится
		ClearEmergencyHandles();
		retval=false;
	}
	LeaveCriticalSectionODS(&m_HandlesProtect);

	if(!retval)LogMessage(err_mess,false);

	return retval;
}

bool CaplLog::LogMess2EmergencyLog(LPCTSTR Mess)
{
	DWORD res, size, size_rest, size_ost;
	bool mutex_my=false,mutex_waited=true;

	if(aplIsStringEmpty(Mess)) return false;

	if(m_Emergency_log_mutex!=0)
	{
		size = _strlen(Mess) * sizeof(TCHAR);
		if(size > EMERGENCY_LOG_BUFFER_SIZE/10)
		{
			CString str;
			str.Format(APL_T("Сообщение для Emergency Log размером %i байт слишком большое. Сохраняем его в общий лог"), size);
			LogMessage(str);
			str = Mess;
			LogMessage(str);
			//PrintMyEmergencyLog(); - а вот сам лог стека в такой ситуации не нужен, не несет большого смысла
			return false;
		}

		// ждем буфер
		do{
			res=WaitForSingleObject(m_Emergency_log_mutex,100);
			switch(res)
			{
			case WAIT_FAILED:
				mutex_waited=false;
				{LogMessage(_T("Error waiting m_Emergency_log_mutex"));}
				break;
			case WAIT_TIMEOUT:
				{LogMessage(_T("Timeout waiting m_Emergency_log_mutex"));}
				break;
			case WAIT_ABANDONED:
			case WAIT_OBJECT_0:
				mutex_my=true;
				mutex_waited=false;
			}
		}while(mutex_waited);

		if(!mutex_my) return false;
		// при завершении отладки ожидание мутекса в одной нити может наложиться на удаление объектов в другой.
		if(m_Emergency_log_mutex==0 || m_Emergency_log_last_active==0 || m_Emergency_log_data == 0) return false;
		// Определяем, влезут ли до конца свободного буфера наши данные + 4 байта (размер новых данных)
		// Причем писать мы будем не с *m_Emergency_log_last_active, а с *m_Emergency_log_last_active + 4
		// так как по адресу *m_Emergency_log_last_active смещение предыдущей записи
		if(*m_Emergency_log_last_active + 4 + size + 4 > m_Emergency_log_size)
		{
			// мудрим с хвостами и остатками
			// проверяем - что не влезает, данные или их размер
			if(*m_Emergency_log_last_active + 4 + size > m_Emergency_log_size)
			{
				// данные тоже не влезают. Бьем их на части
				// определим размер хвоста, который пойдет с начала буфера
				size_rest = *m_Emergency_log_last_active + 4 + size - m_Emergency_log_size;
				// писать в конец буфера будем, соответсвенно, меньше данных
				size_ost = size - size_rest;
				// пишем данные двумя порциями. Сначала начало сообщения в остаток буфера
				memcpy(m_Emergency_log_data + *m_Emergency_log_last_active + 4, ((const char*)Mess), size_ost);
				// потом остаток сообщения - в начало буфера
				memcpy(m_Emergency_log_data, ((const char*)Mess) + size_ost, size_rest);
				// 
				// размер записываем отступив size_rest с начала буфера
				memcpy(m_Emergency_log_data + size_rest, &size, 4);
				// и новый указатель на размер последней порции - в самом начале буфера + size_rest
				*m_Emergency_log_last_active = size_rest;
			}
			else 
			{
				// данные влезают, проблема в размере порции
				// пишем данные в остаток буфера
				memcpy(m_Emergency_log_data + *m_Emergency_log_last_active + 4, ((const char*)Mess), size);

				size_rest = m_Emergency_log_size - (*m_Emergency_log_last_active + 4 + size);
				if(size_rest > 0 )
				{
					// от конца данных до конца буфера остается место. Надо забить его нулями
					memset(m_Emergency_log_data + *m_Emergency_log_last_active + 4 + size, 0, size_rest);
					// размер порции увеличим на этот остаток
					size += size_rest;
				}
				// а размер записываем уже с начала буфера
				memcpy(m_Emergency_log_data , &size, 4);
				// и новый указатель на размер последней порции - он в самом начале буфера
				*m_Emergency_log_last_active = 0;
			}
		}
		else
		{
			// пишем - все влезет
			// сначала данные
			memcpy(m_Emergency_log_data + *m_Emergency_log_last_active + 4, ((const char*)Mess), size);
			// потом их размер
			memcpy(m_Emergency_log_data + *m_Emergency_log_last_active + 4 + size, &size, 4);
			// и новый указатель на размер последней порции
			*m_Emergency_log_last_active += size + 4;
		}

		if(m_Emergency_log_mutex!=0 && mutex_my)
		{
			ReleaseMutex(m_Emergency_log_mutex);
		}
	}

	return true;
}

void CaplLog::ParseEmergencyLog(PVOID EmergencyLogPregion, DWORD Emergency_log_size, CStringArray &records)
{
	bool changed_zero = false;
	DWORD size;
	CString tmp;
	DWORD begin_pos, end_pos;
	DWORD *Emergency_log_last_active = 0;
	TCHAR* buf;
	DWORD size_beg_buf, size_end_buf;

	//	DWORD Emergency_log_size;
	//	Emergency_log_data = (BYTE*)si->m_EmergencyLogPregion+4;
	BYTE* Emergency_log_data = (BYTE*)EmergencyLogPregion+4;
	//	Emergency_log_size = EMERGENCY_LOG_BUFFER_SIZE - 4;
	//	Emergency_log_last_active = ((long*)si->m_EmergencyLogPregion);
	Emergency_log_last_active = ((DWORD*)EmergencyLogPregion);
	end_pos = *Emergency_log_last_active;

	while(true)
	{
		// копируем 4 байта - размер предыдущей записи
		memcpy(&size, Emergency_log_data + end_pos, 4);
		if(size == 0) break;
		if(size > Emergency_log_size/2) break;

		// проверяем, укладывается ли запись целиком в кусок от конца записи до начала буфера
		if(size < end_pos)
		{
			// укладывается. Определяем смещение начала текущей записи
			begin_pos = end_pos - size;
			// если мы уже переходили через начало буфера - проверяем, что не пошли на второй круг
			// т.е. если начало K-той записи начинается раньше, чем заканчивается блок самой последней 
			// записи - К-тая запись уже испорчена, ее не трогаем
			if(changed_zero && end_pos < *Emergency_log_last_active + 4) break;

			// копируем данные записи из лога
			buf = new TCHAR[size/sizeof(TCHAR) + 1];
			memcpy(buf, Emergency_log_data + begin_pos, size);
			buf[size/sizeof(TCHAR)] = 0;
			tmp = buf;
			records.Add(tmp);

			delete[] buf;

			// определяем смещение размера предыдущей записи
			if(begin_pos == 0)
			{
				end_pos = Emergency_log_size - 4;
			}
			else
			{
				end_pos = begin_pos - 4;
			}
		}
		else
		{
			// переход через начало буфера
			changed_zero = true;
			if(end_pos == 0)
			{
				// данные как раз уложились до конца буфера, а их длинна оказалась перенесена
				begin_pos = Emergency_log_size - size;
				// копируем данные записи из лога
				buf = new TCHAR[size/sizeof(TCHAR) + 1];
				memcpy(buf, Emergency_log_data + begin_pos, size);
				buf[size/sizeof(TCHAR)] = 0;
				tmp = buf;
				records.Add(tmp);
				delete[] buf;
			}
			else
			{
				// самый сложный случай, данные разорваный напополам
				// размер данных в начале буфера
				size_beg_buf = size - end_pos;
				// размер данных в конце буфера
				size_end_buf = size - size_beg_buf;
				// смещение начала данных в конце буфера
				begin_pos = Emergency_log_size - size_end_buf;

				// копируем данные записи из лога
				buf = new TCHAR[size/sizeof(TCHAR) + 1];
				// указатель на char нужен для правильной арифметики указателей
				char *buf_c = (char*)buf;
				memcpy(buf, Emergency_log_data + begin_pos, size_end_buf);
				memcpy(buf_c + size_end_buf, Emergency_log_data, size_beg_buf);
				buf[size/sizeof(TCHAR)] = 0;
				tmp = buf;
				records.Add(tmp);
				delete[] buf;
			}
			// определяем смещение размера предыдущей записи
			end_pos = begin_pos - 4;
		}
	}
}



void CaplLog::PrintMyEmergencyLog(CStringArray* records, LPCTSTR description)
{
	DWORD res;
	bool mutex_my = false, mutex_waited = true;
	CStringArray records_int;
	int i;

	if(records != 0)
	{
		records->RemoveAll();
	}

	if(m_Emergency_log_mutex!=0)
	{
		// ждем буфер
		do{
			res=WaitForSingleObject(m_Emergency_log_mutex,100);
			switch(res)
			{
			case WAIT_FAILED:
				mutex_waited=false;
				break;
			case WAIT_TIMEOUT:
				break;
			case WAIT_ABANDONED:
			case WAIT_OBJECT_0:
				mutex_my=true;
				mutex_waited=false;
			}
		}while(mutex_waited);

		ParseEmergencyLog(m_EmergencyLogPregion, EMERGENCY_LOG_BUFFER_SIZE - 4, records_int);
		ReleaseMutex(m_Emergency_log_mutex);

		// переносим данные из временного массива - в лог на диск	
		// это надо делать во второй шаг - так как из аварийного буфера мы их читаем в обратном порядке, а в лог выводим - в прямом
		if(records_int.GetSize() > 0)
		{
			CString tmp_buf, tmp_buf1, datetime;
			// и собственно аварийный лог
			if(records == 0) 
			{

				SYSTEMTIME MessageTime;
				GetLocalTime(&MessageTime);
				datetime.Format(_T("%02i.%02i.%04i %02i:%02i:%02i.%03i (%08u) "),
							MessageTime.wDay,MessageTime.wMonth,MessageTime.wYear,
							MessageTime.wHour,MessageTime.wMinute,MessageTime.wSecond,MessageTime.wMilliseconds,GetTickCount());
				tmp_buf = APL_T("---------------------- Сохраняем лог стека вызовов текущего сервера от ") + datetime + APL_T(" в частный лог процесса");
				if(!aplIsStringEmpty(description))
				{
					tmp_buf+=APL_T("---------------------- Причина вывода лога: ");
					tmp_buf+=description;
				}
				if(GetAddDescription!=0)
				{
					tmp_buf+= APL_T("---------------------- Описание сервера:\n\n");
					GetAddDescription(tmp_buf1);
					tmp_buf+=tmp_buf1;
				}
				LogMessage(tmp_buf);
				Write2FileWithCheck(_T("\n\n") + tmp_buf + _T(" \n\n"));
			}
			for(i=records_int.GetSize()-1;i>=0;i--)
			{
				tmp_buf = records_int.GetAt(i);
				if(records == 0)
				{
					Write2FileWithCheck(tmp_buf + _T("\n"));
				}
				else
				{
					records->Add(tmp_buf);
				}
			}
			if(records == 0) Write2FileWithCheck(APL_T("\n\n---------------------- Конец лога стека вызовов текущего сервера от ") + datetime + _T("\n\n"));
		}
	}
}


struct SFileInfo
{
	CString filePath;
	CTime fileTime;

	SFileInfo(LPCTSTR path)
		: filePath(path), fileTime(0)
	{}
};

// Компаратор для сортировки по дате
bool CompareByTime(const SFileInfo& a, const SFileInfo& b)
{
	return a.fileTime > b.fileTime; // по убывании даты, т.е. в конце будут самые старые с наименьшей датой
}

void CaplLog::DeleteOldestFiles(LPCTSTR fileMask, size_t numSaved)
{
	CFileFind finder;
	BOOL bWorking = finder.FindFile(fileMask);

	CString oldestFilePath;
	CTime oldestTime = CTime::GetCurrentTime(); // инициализируем текущим временем
	bool foundAny = false;
	std::vector<SFileInfo> files; 

	while (bWorking)
	{
		bWorking = finder.FindNextFile();
		if (finder.IsDots() || finder.IsDirectory())
			continue;

		SFileInfo fileInfo(finder.GetFilePath());
		finder.GetLastWriteTime(fileInfo.fileTime);
		files.push_back(fileInfo);
	}
	finder.Close();
	
	if(files.size() > numSaved)
	{
		DWORD i(0);
		std::sort(files.begin(), files.end(), CompareByTime);

		// Итерируемся от этого элемента до конца вектора
		for (std::vector<SFileInfo>::iterator it=files.begin(); it != files.end(); ++it) 
		{
			if(i++<numSaved)continue;
			::DeleteFile(it->filePath);
		}
	}
}