#include "pgAdmin3.h" #include "log/Storage.h" #include "utils/utffile.h" #include #include int Storage::getCountStore() { return storage.size(); } int Storage::getCountFilter() { int cnt = frows.size(); if (cnt == 0 && !IsFilter()) cnt = getCountStore(); return cnt; } LineFilter Storage::getLineFilter(wxString strflt,wxString fn) { LineFilter lf; lf.col = -1; if (strflt.IsEmpty()) { return lf; } int col = wxAtoi(strflt.BeforeFirst(':')); lf.col = col; wxString l = strflt.AfterFirst(':'); wxString val = l.AfterFirst(' '); int flags = 0; int i = 0; if (l[i] == '!') { flags = FL_REVERSE; i++; } if (l[i] == '~') flags = flags | FL_CONTAINS; lf.flags = flags; lf.val = val; lf.NameFilter = fn; return lf; } wxString Storage::_strwhere(int flags) { wxString expr = "= "; if ((flags & FL_CONTAINS) == FL_CONTAINS) expr = "~ "; if ((flags & FL_REVERSE) == FL_REVERSE) expr = "!" + expr; return expr; } wxString Storage::LineFilterToStr(LineFilter& lf) { if (lf.col == -1) { return ""; } wxString expr = _strwhere(lf.flags)+lf.val; expr = wxString::Format("%d:%s", lf.col, expr); return expr; } wxString Storage::getFilterString(std::deque arr) { wxString str; for (auto fl : arr) { if (!str.IsEmpty()) str += "\n"; if (fl.col != -1) str += LineFilterToStr(fl); } return str; } void Storage::addLineFilterStr(wxString strflt,wxString fn) { LineFilter lf = getLineFilter(strflt,fn); filterload.push_back(lf); } void Storage::Reset() { filterload.clear(); storage.clear(); // hash Group to row hashKeyToRow.clear(); hashKeyToCount.clear(); hashKeyTotal.clear(); //filter setting frows.clear(); fCol.Clear(); fFlags.Clear(); fVal.Clear(); // признак ошибок. err_msg=wxEmptyString; currhost= wxEmptyString; // режим группировки groupFilterUse = false; faddgroup = false; prevRow = -1; detailGroup = -1; // m_cacheIndex = -1; ClearRowsStat(); // load filter wxString tempDir = wxStandardPaths::Get().GetUserConfigDir() + wxFileName::GetPathSeparator() + "postgresql" + wxFileName::GetPathSeparator(); wxString f = tempDir + "filter_load.txt"; if (wxFileExists(f)) { wxString str; wxUtfFile file(f, wxFile::read, wxFONTENCODING_UTF8); if (file.IsOpened()) { file.Read(str); file.Close(); wxStringTokenizer tk(str, "\n", wxTOKEN_RET_EMPTY_ALL); wxString Fname = wxEmptyString; bool end = false; LineFilter lf; int cc = 1; while (tk.HasMoreTokens()) { wxString l = tk.GetNextToken(); if (end && l.IsEmpty()) { continue; }; if ((!l.IsEmpty()) && (l[0] == '#')) { Fname = l.substr(1); if (Fname.Contains("LoadSkip")) Fname = wxString::Format("LoadSkip %d", cc++); continue; } if (end && Fname.IsEmpty()) Fname = wxString::Format("LoadSkip %d", cc++); addLineFilterStr(l, Fname); Fname = wxEmptyString; end = l.IsEmpty(); } if (!end) addLineFilterStr("", ""); } } } Storage::Storage() { bgErr[MyConst::iconIndex::log] = wxNullColour; bgErr[MyConst::iconIndex::war] = wxNullColour; bgErr[MyConst::iconIndex::user] = wxColor(250, 196, 186); bgErr[MyConst::iconIndex::error] = wxColor(220, 150, 150); bgErr[MyConst::iconIndex::fatal] = wxColor(209, 100, 100); bgErr[MyConst::iconIndex::panic] = wxColor(246, 10, 10); colname[MyConst::colField::logappname] = "Application Name"; colname[MyConst::colField::loguser] = "User db"; colname[MyConst::colField::logbtype] = "Backend Type"; colname[MyConst::colField::logdb] = "Db name"; colname[MyConst::colField::logDetail] = "Detail"; colname[MyConst::colField::logHint] = "Hint"; colname[MyConst::colField::loghost] = "Client Host"; colname[MyConst::colField::logpid] = "Pid Server process"; colname[MyConst::colField::logMessage] = "Message"; colname[MyConst::colField::logSERVER] = "DB host"; colname[MyConst::colField::logSessiontime] = "Session time"; colname[MyConst::colField::logSeverity] = "Severity"; colname[MyConst::colField::logSqlstate] = "Sql state"; colname[MyConst::colField::logtag] = "Tag"; colname[MyConst::colField::logtime] = "Time"; Reset(); } void Storage::removeFilter(wxString FilterName) { bool add = false; int j = 0; int p = 0; for (auto fl : filterload) { if (!fl.NameFilter.IsEmpty()) if (fl.NameFilter == FilterName) { add = true; } if (add) j++; if (add && fl.col == -1) break; if (!add) p++; } int l = 0; for (auto i = filterload.begin(); i != filterload.end(); ++i) { if (l == p) { for (int y = 0; y < j; y++) i = filterload.erase(i); break; } l++; } } std::deque Storage::getFilter(wxString FilterName) { std::deque rez; bool add = false; for (auto fl : filterload) { if (add && fl.col == -1) break; if (!fl.NameFilter.IsEmpty()) if (fl.NameFilter==FilterName) { add = true; } if (add) rez.push_back(fl); } return rez; } int Storage::getFilterNames(wxArrayString& arr) { std::deque rez; bool add = false; int i = 0; for (auto fl : filterload) { if (!fl.NameFilter.IsEmpty()) { arr.Add(fl.NameFilter); i++; } } return i; } void Storage::saveFilters() { if (filterload.size() == 0) return; wxString tempDir = wxStandardPaths::Get().GetUserConfigDir() + wxFileName::GetPathSeparator() + "postgresql" + wxFileName::GetPathSeparator(); wxString f = tempDir + "filter_load.txt"; wxUtfFile file(f, wxFile::write, wxFONTENCODING_UTF8); if (file.IsOpened()) { wxString text; for (auto lf:filterload) { if (!lf.NameFilter.IsEmpty()) { text.Append("#"+lf.NameFilter+"\n"); } wxString s = LineFilterToStr(lf); if (!s.IsEmpty()) text.Append(s); text.Append("\n"); } if ((file.Write(text) == 0)) wxMessageBox(_("Query text incomplete.\nQuery contained characters that could not be converted to the local charset.\nPlease correct the data or try using UTF8 instead.")); file.Close(); } } void Storage::getLineToCache(int row, bool filter) { int r = row; if (filter && IsFilter()) row = frows[row]; m_cacheLine = storage.at(row); m_cacheIndex = r; } int Storage::GetSeverityIndex(int rowvisual) { if (rowvisual != m_cacheIndex) getLineToCache(rowvisual, true); return m_cacheLine.icon; } wxColor& Storage::GetBgColorLine(int rowvisual) { if (rowvisual != m_cacheIndex) getLineToCache(rowvisual, true); int i = m_cacheLine.icon; return bgErr[i]; } void Storage::DropColFilter(int index) { if (index == -1) { fCol.Clear(); fVal.Clear(); fFlags.Clear(); } else { fCol.RemoveAt(index); fVal.RemoveAt(index); fFlags.RemoveAt(index); } // оставшиеся фильтры будут применены по всему storage frows.clear(); } wxString Storage::GetStringFilterExpr(int positionArrayFilter,bool addNumCol) { wxString expr; expr = _strwhere(fFlags[positionArrayFilter]); expr = expr + fVal[positionArrayFilter]; if (addNumCol) { expr = wxString::Format("%d:%s", fCol[positionArrayFilter], expr); return expr; } if (expr.Len() > 50) expr = expr.substr(0, 50); return expr; } int Storage::testFilter(MyConst::colField col, int position) { int k = 0; for (auto i : fCol) { if (i == col && (position <= k)) return k; k++; } return -1; } bool Storage::CompareFilterLine(int row, bool filter) { int c = 0; MyConst::colField f; wxString valFlt; wxString valField; bool reverse = false; bool rez = false; int flags = 0; for (auto i : fCol) { flags = fFlags[c]; reverse = (flags & FL_REVERSE) == FL_REVERSE; valFlt = fVal[c++]; f = (MyConst::colField) i; valField = GetFieldStorage(row, f, filter); if ((flags & FL_CONTAINS) == 0) rez = valField != valFlt; else rez = !valField.Contains(valFlt); if (reverse) rez = !rez; if (rez) { return false; } } // Показываем строку только если она стала новой группой if (IsGroupFilter()) { // если проверяем не добавленную строку то никаих проверок if (filter) return true; // в детальном режиме новые группы не показываем if (faddgroup && detailGroup != -1) return false; // Если это не детальная информация то тоже не показываем if (detailGroup != -1 && detailGroup == prevRow && !filter) { // новая строка попадает в детальную группу // сдвинем вершину на новую строку detailGroup = row; return true; } if (!faddgroup) return false; } return true; } //номер строка из из отфильтрованных void Storage::setDetailGroupRow(int rowGroup) { if (IsGroupFilter()) { // if (rowGroup != -1) detailGroup = frows[rowGroup]; else detailGroup = -1; } else detailGroup = -1; } // для указанной строки проверяем из strage (без фильтра) bool Storage::ApplyFilter(int row) { //if (!IsFilter()) return true; if (row != -1) { // verify row if (!IsFilter()) return true; // filter enable if (CompareFilterLine(row, false)) { frows.push_back(row); faddgroup = false; return true; } if (detailGroup != -1) return false; if (IsGroupFilter()) { // подменить в фильтре строк на новую for (int i = 0; i < frows.size(); i++) { // тут потенциальная проблема производительности if (frows[i] != prevRow) continue; frows[i] = row; return false; } } // no visible return false; } if (!IsFilter()) { frows.clear(); return true; } bool f = false; faddgroup = true; if (IsGroupFilter()) { // при включенном GroupFilter смотрим только строки hashKeyToRow frows.clear(); MyHashToRow::iterator it; for (it = hashKeyToRow.begin(); it != hashKeyToRow.end(); ++it) { int keyhash = it->first, lastrow = it->second; // do something useful with key and value if (detailGroup != -1) { if (detailGroup == lastrow) { int l = lastrow; while (l != -1) { frows.push_back(l); if (l != m_cacheIndex) getLineToCache(l, false); l = m_cacheLine.prevRowGroup; } break; } } else frows.push_back(lastrow); //frows.push_back(keyhash); } sort(frows.begin(), frows.end()); } if (frows.size() > 0) { // набор фильтра изменился перепроверим отфильтрованные строки ещё раз std::deque tmp; for (int i = 0; i < frows.size(); i++) { if (CompareFilterLine(i, true)) { tmp.push_back(frows[i]); f = true; }; } if (f) frows = tmp; else frows.clear(); } else { // набор фильтроывнных строк пустой проверим все строки на соответствие фильтру for (int i = 0; i < storage.size(); i++) { if (CompareFilterLine(i, false)) { frows.push_back(i); f = true; }; } } faddgroup = false; return f; } int Storage::SetFilterArray(std::deque arr) { for (auto fl: arr) { if (fl.col != -1) { fCol.Add(fl.col); fVal.Add(fl.val); fFlags.Add(fl.flags); } } ApplyFilter(); return frows.size(); } int Storage::SetFilter(int col, wxString& val, int flags) { fCol.Add(col); fVal.Add(val); fFlags.Add(flags); ApplyFilter(); return frows.size(); } wxString Storage::GetField(int row, MyConst::colField col) { return GetFieldStorage(row, col, IsFilter()); } wxArrayString Storage::GetAllFields(int row,bool isfilter) { wxArrayString a; wxString f; for (int i = 0; i < MyConst::colField::Col_Max;i++) { f= GetFieldStorage(row, (MyConst::colField) i, isfilter); a.Add(colname[i]); a.Add(f); } return a; } wxString Storage::get_field(Line& l, MyConst::colField col) { wxString val; switch (col) { case MyConst::colField::logtime: return l.text.substr(l.logtime.s, l.logtime.l); break; case MyConst::colField::loguser: return l.text.substr(l.loguser.s, l.loguser.l); break; case MyConst::colField::logdb: return l.text.substr(l.logdb.s, l.logdb.l); break; case MyConst::colField::logpid: return l.text.substr(l.logpid.s, l.logpid.l); break; case MyConst::colField::loghost: return l.text.substr(l.loghost.s, l.loghost.l); break; case MyConst::colField::logtag: return l.text.substr(l.logtag.s, l.logtag.l); break; case MyConst::colField::logSessiontime: return l.text.substr(l.logSessiontime.s, l.logSessiontime.l); break; case MyConst::colField::logSeverity: return l.text.substr(l.logSeverity.s, l.logSeverity.l); break; case MyConst::colField::logSqlstate: return l.text.substr(l.logSqlstate.s, l.logSqlstate.l); break; case MyConst::colField::logMessage: return l.text.substr(l.logMessage.s, l.logMessage.l); break; case MyConst::colField::logDetail: return l.text.substr(l.logDetail.s, l.logDetail.l); break; case MyConst::colField::logHint: return l.text.substr(l.logHint.s, l.logHint.l); break; case MyConst::colField::logappname: val = l.text.substr(l.logappname.s, l.logappname.l); if (val.IsEmpty()) val = l.text.substr(l.logbtype.s, l.logbtype.l); return val; break; case MyConst::colField::logbtype: return l.text.substr(l.logbtype.s, l.logbtype.l); break; case MyConst::colField::logSERVER: return l.text.substr(l.logSERVER.s, l.logSERVER.l); break; default: break; } return "bad"; } wxString Storage::GetFieldStorage(int row, MyConst::colField col, bool filter) { if (row != m_cacheIndex) getLineToCache(row, filter); return get_field(m_cacheLine, col); } wxString replace_bind_parameters(const wxString& values_param, const wxString& targetstr, int& replacecount) { wxString str; str = targetstr; // bind parameter parse int idx = values_param.Find(':'); // unnamed portal with parameters: $1 = '15558430', $2 = '70469' #define FIND_DOLLS 1 #define FIND_QUOTES 2 #define FIND_OUTQUOTES 3 #define NUM_PARAMETER 4 int mode = FIND_DOLLS; wxString nstr; int n = 0; wxArrayString arS; for (int j = idx + 1; j < values_param.Len(); j++) { wxChar c = values_param[j]; if (mode == FIND_DOLLS) { if (c == '$') mode = NUM_PARAMETER; continue; } if (mode == NUM_PARAMETER) { if (c != '=') continue; j = j + 2; nstr = ""; n++; if (values_param[j] == '\'') mode = FIND_OUTQUOTES; else { // NULL arS.Add("NULL"); mode = FIND_DOLLS; } continue; } if (mode == FIND_OUTQUOTES) { if (c != '\'') { nstr.Append(c); continue; } if (((j + 1) < values_param.Len()) && values_param[j + 1] == '\'') { j++; nstr.Append(c); nstr.Append(c); continue; } mode = FIND_DOLLS; arS.Add("'" + nstr + "'"); continue; } } // replace $N in query int c = 0; for (int k = arS.GetCount() - 1; k >= 0; k--) { c = c + str.Replace(wxString::Format("$%d", k + 1), arS[k]); } replacecount = c; return str; } Line Storage::getLineParse(const wxString& str, bool csv,const wxString &host) { Line st; if (csv) { CSVTokenizer tk(str); // Get the fields from the CSV log. wxString t; wxString logTime = tk.GetNextToken(); //fields.Add(logTime); st.logtime = { 0,static_cast(logTime.Len()) }; wxString logUser = tk.GetNextToken(); t = logTime; st.loguser = { static_cast(t.Len()),static_cast(logUser.Len()) }; t += logUser; wxString logDatabase = tk.GetNextToken(); if (logDatabase.IsEmpty()) logDatabase = host; st.logdb = { static_cast(t.Len()),static_cast(logDatabase.Len()) }; t += logDatabase; wxString logPid = tk.GetNextToken(); st.logpid = { static_cast(t.Len()),static_cast(logPid.Len()) }; t += logPid; wxString logSession; wxString logCmdcount; wxString logSegment; wxString logHost = tk.GetNextToken(); // Postgres puts port with Hostname // delete port logHost = logHost.BeforeFirst(':'); st.loghost = { static_cast(t.Len()),static_cast(logHost.Len()) }; t += logHost; logSession = tk.GetNextToken(); wxString logLineNumber = tk.GetNextToken(); wxString logTag = tk.GetNextToken(); st.logtag = { static_cast(t.Len()),static_cast(logTag.Len()) }; t += logTag; wxString logSessiontime = tk.GetNextToken(); st.logSessiontime = { static_cast(t.Len()),static_cast(logSessiontime.Len()) }; t += logSessiontime; wxString logVXid = tk.GetNextToken(); wxString logTransaction = tk.GetNextToken(); wxString logSeverity = tk.GetNextToken(); //fields.Add(logSeverity); st.logSeverity = { static_cast(t.Len()),static_cast(logSeverity.Len()) }; t += logSeverity; wxString logState = tk.GetNextToken(); //fields.Add(logState); st.logSqlstate = { static_cast(t.Len()),static_cast(logState.Len()) }; t += logState; wxString logMessage = tk.GetNextToken(); //fields.Add(logMessage); wxString logDetail = tk.GetNextToken(); //fields.Add(logDetail); wxString logHint = tk.GetNextToken(); //fields.Add(logHint); wxString logQuery = tk.GetNextToken(); wxString logQuerypos = tk.GetNextToken(); wxString logContext = tk.GetNextToken(); wxString logDebug = tk.GetNextToken(); wxString logCursorpos = tk.GetNextToken(); if (!logDebug.IsEmpty() && logHint.IsEmpty()) logHint = logDebug; if (!logContext.IsEmpty() && logDetail.IsEmpty()) logDetail = logContext; //variant 1 if (!logContext.IsEmpty()&& (logContext.BeforeFirst('=').AfterFirst(':')==" $1 ")) { int c = 0; wxString newstr = replace_bind_parameters(logContext, logDebug, c); if (c>0) logHint = newstr; } // varint 2 if (logDetail.BeforeFirst('=').AfterFirst(':') == " $1 ") { int c = 0; wxString newstr = replace_bind_parameters(logDetail, logMessage, c); if (c > 0) logMessage = newstr; } st.logDetail = { static_cast(t.Len()),static_cast(logDetail.Len()) }; t += logDetail; st.logHint = { static_cast(t.Len()),static_cast(logHint.Len()) }; t += logHint; st.logMessage = { static_cast(t.Len()),static_cast(logMessage.Len()) }; t += logMessage; //location logCursorpos = tk.GetNextToken(); wxString logApp = tk.GetNextToken(); //fields.Add(logApp); st.logappname = { static_cast(t.Len()),static_cast(logApp.Len()) }; t += logApp; wxString logType = tk.GetNextToken(); st.logbtype = { static_cast(t.Len()),static_cast(logType.Len()) }; t += logType; logCursorpos = host; st.logSERVER = { static_cast(t.Len()),static_cast(logCursorpos.Len()) }; t += logCursorpos; //fields.Add(logType); //st.logType = { t.Len(),logType.Len() }; //t += logType; st.type = MyConst::ltype::SIMPLE_TEXT; int i = MyConst::iconIndex::log; if (logSeverity == "WARNING") i = MyConst::iconIndex::war; if (logSeverity == "ERROR") i = MyConst::iconIndex::error; if (logSeverity == "FATAL") i = MyConst::iconIndex::fatal; if (logSeverity == "PANIC") i = MyConst::iconIndex::panic; st.icon = i; st.visible = 1; st.text = t; } return st; } // получение обобщенной ключевой строки wxString Storage::getStrGroup(wxString source) { int i = 0; int l = source.Length(); wxString n; bool quote = false; while (i < l) { wxChar c = source[i++]; if (c >= '0' && c <= '9' && !quote) continue; if (c == '"') { if ((i < l) && source[i] == c) { n.Append(c, 2); //n.Append(c); i++; continue; } else quote = !quote; } n.Append(c); } return n; } int Storage::GetTotalCountGroup(int rowfilter) { if (rowfilter != m_cacheIndex) getLineToCache(rowfilter, true); MyHashToRow::const_iterator it = hashKeyTotal.find(m_cacheLine.hash); int a = 0; if (it != hashKeyTotal.end()) { a = hashKeyTotal[m_cacheLine.hash]; } if (a==0) { int h = m_cacheLine.hash; int lastrow=hashKeyToRow[h]; a = 0; m_cacheIndex = -1; while (lastrow!=-1) { getLineToCache(lastrow, false); lastrow = m_cacheLine.prevRowGroup; a++; } hashKeyTotal[h]=a; m_cacheIndex = -1; } return a; } void Storage::ClearCount(int rowfilter) { if (rowfilter != m_cacheIndex) getLineToCache(rowfilter, true); MyHashToRow::const_iterator it = hashKeyTotal.find(m_cacheLine.hash); int cnt = hashKeyToCount[m_cacheLine.hash]; int a = 0; if (it != hashKeyTotal.end()) { a = hashKeyTotal[m_cacheLine.hash]; } hashKeyTotal[m_cacheLine.hash] = a + cnt; hashKeyToCount[m_cacheLine.hash] = 0; } int Storage::getCountGroup(int row) { wxString valField = GetFieldStorage(row, MyConst::colField::logtime, false); int h = m_cacheLine.hash; int c = hashKeyToCount[h]; return c; } bool Storage::checkFilter(Line& l) { if (filterload.size() > 0) { wxString sf; bool rez=true; int last = filterload.size() - 1; int i = 0; int cc = 0; //int line = 0; //bool skip = false; for (auto fl:filterload) { if (!fl.NameFilter.IsEmpty()) if (!fl.NameFilter.Contains("LoadSkip")) rez= false; if (fl.col == -1) { if (rez || (last==i)) break; rez = true; } else { if (rez) { sf = get_field(l, (MyConst::colField)fl.col); int flags = fl.flags; bool reverse = (flags & FL_REVERSE) == FL_REVERSE; if ((flags & FL_CONTAINS) == 0) rez = sf == fl.val; else rez = sf.Contains(fl.val); if (reverse) rez = !rez; cc++; } } i++; } if (cc == 0) return false; return rez; } return false; } bool Storage::AddLineTextCSV(const wxString& strcsv) { Line st = getLineParse(strcsv, true,GetHost()); if (checkFilter(st)) { rowsignore++; return false; } rowsadd++; wxString msg = st.text.substr(st.logMessage.s, st.logMessage.l); if (st.icon > MyConst::iconIndex::war) SetErrMsgFlag(true); wxString gstr = getStrGroup(msg); int h = getHashString(gstr); MyHashToRow::const_iterator it = hashKeyToRow.find(h); prevRow = -1; int cnt = 1; faddgroup = true; if (it != hashKeyToRow.end()) { faddgroup = false; prevRow = it->second; cnt = hashKeyToCount[h]; cnt++; } int nextrow = storage.size(); st.prevRowGroup = prevRow; st.hash = h; hashKeyToRow[h] = nextrow; hashKeyToCount[h] = cnt; storage.push_back(st); return true; }