// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/> // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. // log_analyserDlg.cpp : implementation file // #include "stdafx.h" #include "log_analyser.h" #include "log_analyserDlg.h" //#include <nel/misc/config_file.h> #include <fstream> #include <algorithm> using namespace std; //using namespace NLMISC; #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif extern CLog_analyserApp theApp; CString LogDateString; /* * Keyboard handler (in edit box) */ afx_msg void CLAEdit::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ) { switch( nChar ) { // Ctrl+G: Go to selected line number case 'G': CViewDialog *view = ((CLog_analyserDlg*)(GetParent()))->getCurrentView(); if ( view ) { if ( (GetKeyState(VK_CONTROL) & 0x8000) != 0 ) { // Get the selected line number CString str; GetWindowText(str); int start, end; GetSel( start, end ); str = str.Mid( start, end-start ); int lineNum = atoi( str ); if ( ! ((lineNum != 0) || (str == "0")) ) break; // GoTo line view->scrollTo( lineNum ); } } break; } // Transmit to Edit Box AND to main window CEdit::OnKeyDown( nChar, nRepCnt, nFlags ); ((CLog_analyserDlg*)(GetParent()))->OnKeyDown( nChar, nRepCnt, nFlags ); } /* * Keyboard handler (everywhere) */ void CLog_analyserDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CViewDialog *view = getCurrentView(); switch ( nChar ) { // Bookmarks handling (TODO) case VK_F2: if ( view ) { if ( (GetKeyState(VK_CONTROL) & 0x8000) != 0 ) view->addBookmark(); else view->recallNextBookmark(); } break; // Ctrl+F, F3: Find case VK_F3: case 'F': if ( view ) { if ( (nChar==VK_F3) || ((GetKeyState(VK_CONTROL) & 0x8000) != 0) ) view->OnButtonFind(); } break; // Ctrl+L: Display back the file list case 'L': if ( (GetKeyState(VK_CONTROL) & 0x8000) != 0 ) { displayFileList(); } break; } CDialog::OnKeyDown(nChar, nRepCnt, nFlags); } ///////////////////////////////////////////////////////////////////////////// // CLog_analyserDlg dialog CLog_analyserDlg::CLog_analyserDlg(CWnd* pParent /*=NULL*/) : CDialog(CLog_analyserDlg::IDD, pParent) { //{{AFX_DATA_INIT(CLog_analyserDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CLog_analyserDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CLog_analyserDlg) DDX_Control(pDX, IDC_SCROLLBAR1, m_ScrollBar); DDX_Control(pDX, IDC_EDIT1, m_Edit); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CLog_analyserDlg, CDialog) //{{AFX_MSG_MAP(CLog_analyserDlg) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_AddView, OnAddView) ON_BN_CLICKED(IDC_ADDTRACEVIEW, OnAddtraceview) ON_BN_CLICKED(IDC_ComputeTraces, OnComputeTraces) ON_WM_VSCROLL() ON_BN_CLICKED(IDC_Reset, OnReset) ON_WM_SIZE() ON_WM_DESTROY() ON_BN_CLICKED(IDC_HelpBtn, OnHelpBtn) ON_WM_LBUTTONUP() ON_WM_DROPFILES() ON_BN_CLICKED(IDC_DispLineHeaders, OnDispLineHeaders) ON_BN_CLICKED(IDC_Analyse, OnAnalyse) ON_WM_KEYDOWN() //}}AFX_MSG_MAP END_MESSAGE_MAP() BEGIN_MESSAGE_MAP(CLAEdit, CEdit) //{{AFX_MSG_MAP(CLAEdit) ON_WM_KEYDOWN() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CLog_analyserDlg message handlers BOOL CLog_analyserDlg::OnInitDialog() { CDialog::OnInitDialog(); CurrentView = NULL; Trace = false; ResizeViewInProgress = -1; ((CButton*)GetDlgItem( IDC_CheckSessions ))->SetCheck( 1 ); ((CButton*)GetDlgItem( IDC_DispLineHeaders ))->SetCheck( 1 ); ((CButton*)GetDlgItem( IDC_DetectCorruptedLines ))->SetCheck( 1 ); /*try { CConfigFile cf; cf.load( "log_analyser.cfg" ); LogDateString = cf.getVar( "LogDateString" ).asString().c_str(); } catch ( EConfigFile& ) {*/ LogDateString = "Log Starting ["; AnalyseFunc = NULL; m_Edit.SetLimitText( ~0 ); //} // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // Add files given in command-line string cmdLine = string(theApp.m_lpCmdLine); vector<CString> v; /*int pos = cmdLine.find_first_of(' '); // TODO: handle "" with blank characters while ( pos != string::npos ) { v.push_back( cmdLine.substr( 0, pos ).c_str() ); cmdLine = cmdLine.substr( pos ); pos = cmdLine.find_first_of(' '); }*/ if ( ! cmdLine.empty() ) { v.push_back( cmdLine.c_str() ); addView( v ); } loadPluginConfiguration(); DragAcceptFiles( true ); return TRUE; // return TRUE unless you set the focus to a control } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CLog_analyserDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CLog_analyserDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } string getFileExtension (const string &filename) { string::size_type pos = filename.find_last_of ('.'); if (pos == string::npos) return ""; else return filename.substr (pos + 1); } /* * Open in the same view */ void CLog_analyserDlg::OnDropFiles( HDROP hDropInfo ) { UINT nbFiles = DragQueryFile( hDropInfo, 0xFFFFFFFF, NULL, 0 ); vector<CString> v; for ( UINT i=0; i!=nbFiles; ++i ) { CString filename; DragQueryFile( hDropInfo, i, filename.GetBufferSetLength( 200 ), 200 ); // Plug-in DLL or log file if ( getFileExtension( string(filename) ) == "dll" ) { if ( addPlugIn( string(filename) ) ) AfxMessageBox( CString("Plugin added: ") + filename ); else AfxMessageBox( CString("Plugin already registered: ") + filename ); } else { v.push_back( filename ); } } if ( ! v.empty() ) addView( v ); } /* * */ bool CLog_analyserDlg::addPlugIn( const std::string& dllName ) { int i = 0; char pluginN [10] = "Plugin0"; CString pn = theApp.GetProfileString( _T(""), _T(pluginN) ); while ( ! pn.IsEmpty() ) { if ( string(pn) == dllName ) return false; // already registered ++i; smprintf( pluginN, 10, "Plugin%d", i ); pn = theApp.GetProfileString( _T(""), _T(pluginN) ); } theApp.WriteProfileString( _T(""), _T(pluginN), dllName.c_str() ); Plugins.push_back( dllName.c_str() ); return true; } /* * */ void CLog_analyserDlg::loadPluginConfiguration() { // Read from the registry free( (void*)theApp.m_pszRegistryKey ); theApp.m_pszRegistryKey = _tcsdup( _T("Nevrax") ); CString pn = theApp.GetProfileString( _T(""), _T("Plugin0") ); char pluginN [10]; int i = 0; while ( ! pn.IsEmpty() ) { Plugins.push_back( pn ); ++i; smprintf( pluginN, 10, "Plugin%d", i ); pn = theApp.GetProfileString( _T(""), _T(pluginN) ); } } /* * */ bool isNumberChar( char c ) { return (c >= '0') && (c <= '9'); } /* * */ void CLog_analyserDlg::OnAddView() { vector<CString> v; addView( v ); } /* * */ void CLog_analyserDlg::addView( std::vector<CString>& pathNames ) { if ( pathNames.empty() ) { CFileDialog openDialog( true, NULL, "log.log", OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT, "Log files (*.log)|*.log|All files|*.*||", this ); CString filenameList; openDialog.m_ofn.lpstrFile = filenameList.GetBufferSetLength( 8192 ); openDialog.m_ofn.nMaxFile = 8192; if ( openDialog.DoModal() == IDOK ) { CWaitCursor wc; // Get the selected filenames CString pathName; POSITION it = openDialog.GetStartPosition(); while ( it != NULL ) { pathNames.push_back( openDialog.GetNextPathName( it ) ); } if ( pathNames.empty() ) return; } else return; } unsigned int i; if ( pathNames.size() > 1 ) { // Sort the filenames for ( i=0; i!=pathNames.size(); ++i ) { // Ensure that a log file without number comes *after* the ones with a number string name = string(pathNames[i]); string::size_type dotpos = name.find_last_of('.'); if ( (dotpos!=string::npos) && (dotpos > 2) ) { if ( ! (isNumberChar(name[dotpos-1]) && isNumberChar(name[dotpos-2]) && isNumberChar(name[dotpos-3])) ) { name = name.substr( 0, dotpos ) + "ZZZ" + name.substr( dotpos ); pathNames[i] = name.c_str(); } } } sort( pathNames.begin(), pathNames.end() ); for ( i=0; i!=pathNames.size(); ++i ) { // Set the original names back string name = pathNames[i]; string::size_type tokenpos = name.find( "ZZZ." ); if ( tokenpos != string::npos ) { name = name.substr( 0, tokenpos ) + name.substr( tokenpos + 3 ); pathNames[i] = name.c_str(); } } } // Display the filenames string names; if ( isLogSeriesEnabled() ) names += "Loading series corresponding to :\r\n"; else names += "Loading files:\r\n"; for ( i=0; i!=pathNames.size(); ++i ) names += string(pathNames[i]) + "\r\n"; displayCurrentLine( names.c_str() ); // Add view and browse sessions if needed CViewDialog *view = onAddCommon( pathNames ); // Set filters FilterDialog.Trace = false; if ( FilterDialog.DoModal() == IDOK ) { view->setFilters( FilterDialog.getPosFilter(), FilterDialog.getNegFilter() ); // Load file view->reload(); } } /* * */ void CLog_analyserDlg::OnAddtraceview() { CFileDialog openDialog( true, NULL, "log.log", OFN_HIDEREADONLY, "Log files (*.log)|*.log|All files|*.*||", this ); if ( openDialog.DoModal() == IDOK ) { vector<CString> pathNames; pathNames.push_back( openDialog.GetPathName() ); CViewDialog *view = onAddCommon( pathNames ); // Set filters FilterDialog.Trace = true; if ( FilterDialog.DoModal() == IDOK ) { view->setFilters( FilterDialog.getPosFilter(), FilterDialog.getNegFilter() ); } // Load file view->reloadTrace(); } } /* * Precondition: !filenames.empty() */ CViewDialog *CLog_analyserDlg::onAddCommon( const vector<CString>& filenames ) { CWaitCursor wc; // Create view CViewDialog *view = new CViewDialog(); view->Create( IDD_View, this ); view->Index = (int)Views.size(); RECT editRect; m_Edit.GetWindowRect( &editRect ); ScreenToClient( &editRect ); RECT parentRect; GetClientRect( &parentRect ); Views.push_back( view ); int i, w = 0; for ( i=0; i!=(int)Views.size(); ++i ) { Views[i]->WidthR = 1.0f/(float)Views.size(); Views[i]->resizeView( (int)Views.size(), editRect.bottom+10, w ); w += (int)(Views[i]->WidthR*(parentRect.right-32)); } view->ShowWindow( SW_SHOW ); // Set params if ( filenames.size() == 1 ) { // One file or a whole log series view->Seriesname = filenames.front(); getLogSeries( filenames.front(), view->Filenames ); } else { // Multiple files view->Seriesname = filenames.front() + "..."; view->Filenames = filenames; } view->LogSessionStartDate = ""; LogSessionsDialog.clear(); if ( ((CButton*)GetDlgItem( IDC_CheckSessions ))->GetCheck() == 1 ) { LogSessionsDialog.addLogSession( "Beginning" ); int nbsessions = 0; for ( i=0; i!=(int)(view->Filenames.size()); ++i ) { // Scan file for log sessions dates ifstream ifs( view->Filenames[i] ); if ( ! ifs.fail() ) { char line [1024]; while ( ! ifs.eof() ) { ifs.getline( line, 1024 ); if ( strstr( line, LogDateString ) != NULL ) { LogSessionsDialog.addLogSession( line ); ++nbsessions; } } } } // Heuristic to bypass the session choice if not needed bool needToChooseSession; switch ( nbsessions ) { case 0: // No 'Log Starting' in the file(s) => no choice needed needToChooseSession = false; break; case 1: { // 1 'Log Starting' => no choice if it's at the beginning (1st line, or 2nd line with blank 1st) ifstream ifs( view->Filenames[0] ); // 1 session => ! Filename.empty() char line [1024]; ifs.getline( line, 1024 ); if ( ! ifs.fail() ) { if ( strstr( line, LogDateString ) != NULL ) needToChooseSession = false; else if ( string(line).empty() ) { if ( ! ifs.fail() ) { ifs.getline( line, 1024 ); needToChooseSession = (strstr( line, LogDateString ) == NULL); } else needToChooseSession = true; } } else needToChooseSession = true; } break; default: // Several 'Log Starting' => always choice needToChooseSession = true; } // Let the user choose the session (if needed) if ( (needToChooseSession) && (LogSessionsDialog.DoModal() == IDOK) ) { view->LogSessionStartDate = LogSessionsDialog.getStartDate(); } } setCurrentView( view->Index ); return view; } /* * Code from NeL misc */ int smprintf( char *buffer, size_t count, const char *format, ... ) { int ret; va_list args; va_start( args, format ); ret = vsnprintf( buffer, count, format, args ); if ( ret == -1 ) { buffer[count-1] = '\0'; } va_end( args ); return( ret ); } /* * */ void CLog_analyserDlg::getLogSeries( const CString& filenameStr, std::vector<CString>& filenameList ) { if ( isLogSeriesEnabled() ) { string filename = filenameStr; unsigned int dotpos = filename.find_last_of ('.'); if ( dotpos != string::npos ) { string start = filename.substr( 0, dotpos ); string end = filename.substr( dotpos ); char numchar [4]; unsigned int i = 0; bool anymore = true; while ( anymore ) { // If filename is my_service.log, try my_service001.log..my_service999.log string npath = start; smprintf( numchar, 4, "%03d", i ); npath += numchar + end; if ( ! ! fstream( npath.c_str(), ios::in ) ) { // File exists => add it filenameList.push_back( npath.c_str() ); if ( i == 999 ) { filenameList.push_back( "<Too many log files in the series>" ); anymore = false; } ++i; } else { // No more files anymore = false; } } } } // At last, add the filename filenameList.push_back( filenameStr ); } /* * */ void CLog_analyserDlg::displayCurrentLine( const CString& line ) { m_Edit.SetSel( 0, -1 ); m_Edit.Clear(); m_Edit.ReplaceSel( line, true ); } /* * */ bool CLog_analyserDlg::selectText( int lineNum, int colNum, int length ) { int index = m_Edit.LineIndex( lineNum ); if ( index != -1 ) { index += colNum; m_Edit.SetSel( index, index + length ); m_Edit.SetFocus(); return true; } else return false; } /* * */ void CLog_analyserDlg::displayFileList() { if ( ! MemorizedFileList.IsEmpty() ) { displayCurrentLine( MemorizedFileList ); } } /* * */ void CLog_analyserDlg::insertTraceLine( int index, char *traceLine ) { /*CString s0; s0.Format( "%s", traceLine ); MessageBox( s0 );*/ char *line = strchr( traceLine, ':' ); char scycle [10]; strncpy( scycle, traceLine, line-traceLine ); int cycle = atoi(scycle); TStampedLine stampedLine; stampedLine.Index = index; stampedLine.Line = CString(traceLine); TraceMap.insert( make_pair( cycle, stampedLine ) ); /*CString s; s.Format( "%d - %s", cycle, line ); MessageBox( s );*/ } /* * */ void CLog_analyserDlg::OnComputeTraces() { CWaitCursor wc; if ( Views.empty() ) return; Trace = true; int j; for ( j=0; j!=(int)Views.size(); ++j ) { Views[j]->clear(); Views[j]->setRedraw( false ); } multimap<int, TStampedLine>::iterator itm = TraceMap.begin(), itmU, itmC; while ( itm != TraceMap.end() ) { // Fill all the views for one cycle itmU = TraceMap.upper_bound( (*itm).first ); for ( itmC=itm; itmC!=itmU; ++itmC ) { TStampedLine& stampedLine = (*itmC).second; Views[stampedLine.Index]->addLine( stampedLine.Line ); } // Get the number of lines of the most filled view int i, maxNbLines=0; for ( i=0; i!=(int)Views.size(); ++i ) { int vnb = Views[i]->getNbLines(); if ( vnb > maxNbLines ) { maxNbLines = vnb; } } // Fill the gaps with blank lines for ( i=0; i!=(int)Views.size(); ++i ) { Views[i]->fillGaps( maxNbLines ); } itm = itmU; } for ( j=0; j!=(int)Views.size(); ++j ) { Views[j]->commitAddedLines(); Views[j]->setRedraw( true ); } m_ScrollBar.SetScrollRange( 0, Views[0]->getNbLines()-Views[0]->getNbVisibleLines() ); m_ScrollBar.ShowWindow( SW_SHOW ); } /* * */ void CLog_analyserDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { if ( Trace ) { int index; switch ( nSBCode ) { case SB_TOP : index = 0; break; case SB_BOTTOM : index = Views[0]->getNbLines()-1; break; case SB_ENDSCROLL : index = -1; break; case SB_LINEDOWN : index = Views[0]->getScrollIndex()+1; break; case SB_LINEUP : index = Views[0]->getScrollIndex()-1; break; case SB_PAGEDOWN : index = Views[0]->getScrollIndex()+Views[0]->getNbVisibleLines(); break; case SB_PAGEUP : index = Views[0]->getScrollIndex()-Views[0]->getNbVisibleLines(); break; case SB_THUMBPOSITION : case SB_THUMBTRACK : index = nPos; break; } if ( index != -1 ) { // Scroll the views for ( int i=0; i!=(int)Views.size(); ++i ) { Views[i]->scrollTo( index ); } pScrollBar->SetScrollPos( index ); } } CDialog::OnVScroll(nSBCode, nPos, pScrollBar); } /* * */ void CLog_analyserDlg::OnReset() { m_Edit.SetSel( 0, -1 ); m_Edit.Clear(); vector<CViewDialog*>::iterator iv; for ( iv=Views.begin(); iv!=Views.end(); ++iv ) { (*iv)->DestroyWindow(); delete (*iv); } Views.clear(); CurrentView = NULL; Trace = false; TraceMap.clear(); m_ScrollBar.ShowWindow( SW_HIDE ); } /* * */ void CLog_analyserDlg::OnDispLineHeaders() { vector<CViewDialog*>::iterator iv; for ( iv=Views.begin(); iv!=Views.end(); ++iv ) { (*iv)->Invalidate(); } } /* * */ void CLog_analyserDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); if ( ::IsWindow(m_Edit) ) { RECT cltRect, editRect, sbRect; GetClientRect( &cltRect ), m_Edit.GetWindowRect( &editRect ); m_ScrollBar.GetWindowRect( &sbRect ); ScreenToClient( &editRect ); ScreenToClient( &sbRect ); editRect.right = cltRect.right-16; sbRect.right += cltRect.right-28-sbRect.left; sbRect.left = cltRect.right-28; sbRect.bottom = cltRect.bottom-12; m_Edit.MoveWindow( &editRect ); m_ScrollBar.MoveWindow( &sbRect ); resizeViews(); } } /* * */ void CLog_analyserDlg::resizeViews() { RECT editRect; m_Edit.GetWindowRect( &editRect ); ScreenToClient( &editRect ); RECT parentRect; GetClientRect( &parentRect ); int i, w = 0; for ( i=0; i!=(int)Views.size(); ++i ) { Views[i]->resizeView( (int)Views.size(), editRect.bottom+10, w ); w += (int)(Views[i]->WidthR*(parentRect.right-32)); } } /* * */ void CLog_analyserDlg::beginResizeView( int index ) { ResizeViewInProgress = index; SetCursor( theApp.LoadStandardCursor( IDC_SIZEWE ) ); SetCapture(); } /* * */ void CLog_analyserDlg::OnLButtonUp(UINT nFlags, CPoint point) { if ( ResizeViewInProgress != -1 ) { if ( ResizeViewInProgress > 0 ) { RECT viewRect, appClientRect; Views[ResizeViewInProgress]->GetWindowRect( &viewRect ); ScreenToClient( &viewRect ); GetClientRect( &appClientRect ); if ( point.x < 0 ) point.x = 10; int deltaPosX = point.x - viewRect.left; float deltaR = (float)deltaPosX / (float)(appClientRect.right-32); if ( -deltaR > Views[ResizeViewInProgress-1]->WidthR ) deltaR = -Views[ResizeViewInProgress-1]->WidthR + 0.01f; if ( deltaR > Views[ResizeViewInProgress]->WidthR ) deltaR = Views[ResizeViewInProgress]->WidthR - 0.01f; Views[ResizeViewInProgress-1]->WidthR += deltaR; Views[ResizeViewInProgress]->WidthR -= deltaR; } ResizeViewInProgress = -1; ReleaseCapture(); SetCursor( theApp.LoadStandardCursor( IDC_ARROW ) ); resizeViews(); } CDialog::OnLButtonUp(nFlags, point); } /* * */ void CLog_analyserDlg::OnDestroy() { OnReset(); CDialog::OnDestroy(); } /* * */ void CLog_analyserDlg::OnHelpBtn() { CString s = "NeL Log Analyser v1.5.0\n(c) 2002-2003 Nevrax\n\n"; s += "Simple Mode: open one or more log files using the button 'Add View...'.\n"; s += "You can make a multiple selection, then the files will be sorted by log order.\n"; s += "If the file(s) being opened contain(s) several log sessions, you can choose one or\n"; s += "choose to display all sessions, if the checkbox 'Browse Log Sessions' is enabled. If the\n"; s += "checkbox 'Browse All File Series' is checked and you choose my_service.log, all log\n"; s += "files of the series beginning with my_service000.log up to the biggest number found,\n"; s += "and ending with my_service.log, will be opened in the same view. If 'Detect corrupted\n"; s += "files' is checked, the possibly malformed lines will be reported.\n"; s += "You can add some positive/negative filters. Finally, you may click a log line to display\n"; s += "it in its entirety in the top field.\n"; s += "Another way to open a file is to pass its filename as an argument. An alternative way to\n"; s += "open one or more files is to drag & drop them onto the main window!.\n"; s += "To actualize a file (which may have changed if a program is still writing into it), just\n"; s += "click 'Filter...' and OK.\n"; s += "Resizing a view is done by dragging its left border.\n"; s += "Line bookmarks: set/remove = Ctrl+F2, recall = F2. They are kept when changing the filter.\n\n"; s += "Trace Mode: open several log files in Trace Format (see below) using the button\n"; s += "'Add Trace View...', you can limit the lines loaded to the ones containing a\n"; s += "specific service shortname (see below). Then click the button 'Compute Traces'\n"; s += "to display the matching lines. The lines are sorted using their gamecycle and\n"; s += "blank lines are filled so that different parallel views have the same timeline.\n"; s += "Use the right scrollbar to scroll all the views at the same time.\n"; s += "The logs in Trace Format should contains some lines that have a substring sTRACE:n:\n"; s += "where s is an optional service name (e.g. FS) and n is the gamecycle of the action\n"; s += "(an integer).\n\n"; s += "Plug-in system: You can provide DLLs to perform some processing or analysis on the current\n"; s += "view. To register a new plug-in, drag-n-drop the DLL on the main window of the Log Analyser.\n"; s += "To unregister a plug-in, see HKEY_CURRENT_USER\\Software\\Nevrax\\log_analyser.INI in the\n"; s += "registry."; MessageBox( s ); } /* * Plug-in activation */ void CLog_analyserDlg::OnAnalyse() { PlugInSelectorDialog.setPluginList( Plugins ); if ( PlugInSelectorDialog.DoModal() == IDOK ) { if ( Views.empty() ) { AfxMessageBox( "This plug-in needs to be applied on the first open view" ); return; } if ( ! PlugInSelectorDialog.AnalyseFunc ) { AfxMessageBox( "Could not load function doAnalyse in dll" ); return; } // Call the plug-in function and get results string resstr, logstr; PlugInSelectorDialog.AnalyseFunc( *(const std::vector<const char *>*)(void*)&(getCurrentView()->Buffer), resstr, logstr ); if ( ! logstr.empty() ) { vector<CString> pl; pl.push_back( "Analyse log" ); onAddCommon( pl ); Views.back()->addText( logstr.c_str() ); Views.back()->commitAddedLines(); } displayCurrentLine( resstr.c_str() ); // Debug checks int nStartChar, nEndChar; m_Edit.GetSel( nStartChar, nEndChar ); if ( nEndChar != (int)resstr.size() ) { CString s; s.Format( "Error: plug-in returned %u characters, only %d displayed", resstr.size(), nEndChar+1 ); AfxMessageBox( s ); } } }