// // Copyright 2005 The Android Open Source Project // // Display runtime log output. // // For compilers that support precompilation, include "wx/wx.h". #include "wx/wxprec.h" // Otherwise, include all standard headers #ifndef WX_PRECOMP # include "wx/wx.h" #endif #include "wx/image.h" // needed for Windows build #include "wx/dcbuffer.h" #include "LogWindow.h" #include "LogMessage.h" #include "LogPrefsDialog.h" #include "MyApp.h" #include "Preferences.h" #include "Resource.h" #include "UserEventMessage.h" #include <errno.h> static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...); static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args); using namespace android; #if 0 // experiment -- works on Win32, but not with GTK class MyTextCtrl : public wxTextCtrl { public: MyTextCtrl(wxWindow* parent, wxWindowID id, const wxString& value, const wxPoint& pos, const wxSize& size, int style = 0) : wxTextCtrl(parent, id, value, pos, size, style) { printf("***************** MyTextCtrl!\n"); } void OnScroll(wxScrollWinEvent& event); void OnScrollBottom(wxScrollWinEvent& event); private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(MyTextCtrl, wxTextCtrl) EVT_SCROLLWIN(MyTextCtrl::OnScroll) EVT_SCROLLWIN_BOTTOM(MyTextCtrl::OnScrollBottom) END_EVENT_TABLE() void MyTextCtrl::OnScroll(wxScrollWinEvent& event) { printf("OnScroll!\n"); } void MyTextCtrl::OnScrollBottom(wxScrollWinEvent& event) { printf("OnScrollBottom!\n"); } #endif BEGIN_EVENT_TABLE(LogWindow, wxDialog) EVT_CLOSE(LogWindow::OnClose) EVT_MOVE(LogWindow::OnMove) EVT_COMBOBOX(IDC_LOG_LEVEL, LogWindow::OnLogLevel) EVT_BUTTON(IDC_LOG_CLEAR, LogWindow::OnLogClear) EVT_BUTTON(IDC_LOG_PAUSE, LogWindow::OnLogPause) EVT_BUTTON(IDC_LOG_PREFS, LogWindow::OnLogPrefs) END_EVENT_TABLE() /* * Information about log levels. * * Each entry here corresponds to an entry in the combo box. The first * letter of each name should be unique. */ static const struct { wxString name; android_LogPriority priority; } gLogLevels[] = { { wxT("Verbose"), ANDROID_LOG_VERBOSE }, { wxT("Debug"), ANDROID_LOG_DEBUG }, { wxT("Info"), ANDROID_LOG_INFO }, { wxT("Warn"), ANDROID_LOG_WARN }, { wxT("Error"), ANDROID_LOG_ERROR } }; /* * Create a new LogWindow. This should be a child of the main frame. */ LogWindow::LogWindow(wxWindow* parent) : wxDialog(parent, wxID_ANY, wxT("Log Output"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxSYSTEM_MENU | wxCLOSE_BOX | wxRESIZE_BORDER), mDisplayArray(NULL), mMaxDisplayMsgs(0), mPaused(false), mMinPriority(ANDROID_LOG_VERBOSE), mHeaderFormat(LogPrefsDialog::kHFFull), mSingleLine(false), mExtraSpacing(0), mPointSize(10), mUseColor(true), mFontMonospace(true), mWriteFile(false), mTruncateOld(true), mLogFp(NULL), mNewlyShown(false), mLastPosition(wxDefaultPosition), mVisible(false) { ConstructControls(); Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); int poolSize = 10240; // 10MB pPrefs->GetInt("log-pool-size-kbytes", &poolSize); assert(poolSize > 0); mPool.Resize(poolSize * 1024); mMaxDisplayMsgs = 1000; pPrefs->GetInt("log-display-msg-count", &mMaxDisplayMsgs); assert(mMaxDisplayMsgs > 0); mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); mTopPtr = -1; mNextPtr = 0; int tmpInt = (int) mHeaderFormat; pPrefs->GetInt("log-header-format", &tmpInt); mHeaderFormat = (LogPrefsDialog::HeaderFormat) tmpInt; pPrefs->GetBool("log-single-line", &mSingleLine); pPrefs->GetInt("log-extra-spacing", &mExtraSpacing); pPrefs->GetInt("log-point-size", &mPointSize); pPrefs->GetBool("log-use-color", &mUseColor); pPrefs->SetBool("log-font-monospace", &mFontMonospace); SetTextStyle(); mFileName = wxT("/tmp/android-log.txt"); pPrefs->GetBool("log-write-file", &mWriteFile); pPrefs->GetString("log-filename", /*ref*/mFileName); pPrefs->GetBool("log-truncate-old", &mTruncateOld); PrepareLogFile(); } /* * Destroy everything we own. */ LogWindow::~LogWindow(void) { ClearDisplay(); delete[] mDisplayArray; if (mLogFp != NULL) fclose(mLogFp); } /* * Set the text style, based on our preferences. */ void LogWindow::SetTextStyle(void) { wxTextCtrl* pTextCtrl; pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); wxTextAttr style; style = pTextCtrl->GetDefaultStyle(); if (mFontMonospace) { wxFont font(mPointSize, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); style.SetFont(font); } else { wxFont font(mPointSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); style.SetFont(font); } pTextCtrl->SetDefaultStyle(style); } /* * Set up the goodies in the window. * * Also initializes mMinPriority. */ void LogWindow::ConstructControls(void) { Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); wxPanel* base = new wxPanel(this, wxID_ANY); wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL); wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* configPrioritySizer = new wxBoxSizer(wxHORIZONTAL); wxGridSizer* configSizer = new wxGridSizer(4, 1); /* * Configure log level combo box. */ wxComboBox* logLevel; int defaultLogLevel = 1; pPrefs->GetInt("log-display-level", &defaultLogLevel); logLevel = new wxComboBox(base, IDC_LOG_LEVEL, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY /*| wxSUNKEN_BORDER*/); for (int i = 0; i < NELEM(gLogLevels); i++) { logLevel->Append(gLogLevels[i].name); logLevel->SetClientData(i, (void*) gLogLevels[i].priority); } logLevel->SetSelection(defaultLogLevel); mMinPriority = gLogLevels[defaultLogLevel].priority; /* * Set up stuff at the bottom, starting with the options * at the bottom left. */ configPrioritySizer->Add(new wxStaticText(base, wxID_ANY, wxT("Log level:"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT), 0, wxALIGN_CENTER_VERTICAL); configPrioritySizer->AddSpacer(kInterSpacing); configPrioritySizer->Add(logLevel); wxButton* clear = new wxButton(base, IDC_LOG_CLEAR, wxT("&Clear"), wxDefaultPosition, wxDefaultSize, 0); wxButton* pause = new wxButton(base, IDC_LOG_PAUSE, wxT("&Pause"), wxDefaultPosition, wxDefaultSize, 0); wxButton* prefs = new wxButton(base, IDC_LOG_PREFS, wxT("C&onfigure"), wxDefaultPosition, wxDefaultSize, 0); configSizer->Add(configPrioritySizer, 0, wxALIGN_LEFT); configSizer->Add(clear, 0, wxALIGN_CENTER); configSizer->Add(pause, 0, wxALIGN_CENTER); configSizer->Add(prefs, 0, wxALIGN_RIGHT); /* * Create text ctrl. */ wxTextCtrl* pTextCtrl; pTextCtrl = new wxTextCtrl(base, IDC_LOG_TEXT, wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxTE_NOHIDESEL | wxHSCROLL); /* * Add components to master sizer. */ masterSizer->AddSpacer(kEdgeSpacing); masterSizer->Add(pTextCtrl, 1, wxEXPAND); masterSizer->AddSpacer(kInterSpacing); masterSizer->Add(configSizer, 0, wxEXPAND); masterSizer->AddSpacer(kEdgeSpacing); /* * Indent from sides. */ indentSizer->AddSpacer(kEdgeSpacing); indentSizer->Add(masterSizer, 1, wxEXPAND); indentSizer->AddSpacer(kEdgeSpacing); base->SetSizer(indentSizer); indentSizer->Fit(this); // shrink-to-fit indentSizer->SetSizeHints(this); // define minimum size } /* * In some cases, this means the user has clicked on our "close" button. * We don't really even want one, but both WinXP and KDE put one on our * window whether we want it or not. So, we make it work as a "hide" * button instead. * * This also gets called when the app is shutting down, and we do want * to destroy ourselves then, saving various information about our state. */ void LogWindow::OnClose(wxCloseEvent& event) { /* just hide the window, unless we're shutting down */ if (event.CanVeto()) { event.Veto(); Show(false); return; } /* * Save some preferences. */ SaveWindowPrefs(); /* if we can't veto the Close(), destroy ourselves */ Destroy(); } /* * Save all of our preferences to the config file. */ void LogWindow::SaveWindowPrefs(void) { Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); /* * Save shown/hidden state. */ pPrefs->SetBool("window-log-show", IsShown()); /* * Limits and formatting prefs. */ pPrefs->SetInt("log-display-msg-count", mMaxDisplayMsgs); pPrefs->SetInt("log-pool-size-kbytes", mPool.GetMaxSize() / 1024); pPrefs->SetInt("log-header-format", mHeaderFormat); pPrefs->SetBool("log-single-line", mSingleLine); pPrefs->SetInt("log-extra-spacing", mExtraSpacing); pPrefs->SetInt("log-point-size", mPointSize); pPrefs->SetBool("log-use-color", mUseColor); pPrefs->SetBool("log-font-monospace", mFontMonospace); pPrefs->SetBool("log-write-file", mWriteFile); pPrefs->SetString("log-filename", mFileName.ToAscii()); pPrefs->SetBool("log-truncate-old", mTruncateOld); /* * Save window size and position. */ wxPoint posn; wxSize size; assert(pPrefs != NULL); posn = GetPosition(); size = GetSize(); pPrefs->SetInt("window-log-x", posn.x); pPrefs->SetInt("window-log-y", posn.y); pPrefs->SetInt("window-log-width", size.GetWidth()); pPrefs->SetInt("window-log-height", size.GetHeight()); /* * Save current setting of debug level combo box. */ wxComboBox* pCombo; int selection; pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); selection = pCombo->GetSelection(); pPrefs->SetInt("log-display-level", selection); } /* * Return the desired position and size. */ /*static*/ wxRect LogWindow::GetPrefWindowRect(void) { Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); int x, y, width, height; assert(pPrefs != NULL); x = y = 10; width = 500; height = 200; /* these don't modify the arg if the pref doesn't exist */ pPrefs->GetInt("window-log-x", &x); pPrefs->GetInt("window-log-y", &y); pPrefs->GetInt("window-log-width", &width); pPrefs->GetInt("window-log-height", &height); return wxRect(x, y, width, height); } /* * Under Linux+GTK, the first time you show the window, it appears where * it's supposed to. If you then hide it and show it again, it gets * moved on top of the parent window. After that, you can reposition it * and it remembers its position across hide/show. * * To counter this annoyance, we save the position when we hide, and * reset the position after a show. The "newly shown" flag ensures that * we only reposition the window as the result of a Show(true) call. * * Sometimes, something helpful will shift the window over if it's * partially straddling a seam between two monitors. I don't see an easy * way to block this, and I'm not sure I want to anyway. */ void LogWindow::OnMove(wxMoveEvent& event) { wxPoint point; point = event.GetPosition(); //printf("Sim: log window is at (%d,%d) (new=%d)\n", point.x, point.y, // mNewlyShown); if (mNewlyShown) { if (mLastPosition == wxDefaultPosition) { //printf("Sim: no last position established\n"); } else { Move(mLastPosition); } mNewlyShown = false; } } /* * Set the "newly shown" flag. */ bool LogWindow::Show(bool show) { if (show) { mNewlyShown = true; Redisplay(); } else { mLastPosition = GetPosition(); } mVisible = show; return wxDialog::Show(show); } /* * User has adjusted the log level. Update the display appropriately. * * This is a wxEVT_COMMAND_COMBOBOX_SELECTED event. */ void LogWindow::OnLogLevel(wxCommandEvent& event) { int selection; android_LogPriority priority; selection = event.GetInt(); wxComboBox* pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); priority = (android_LogPriority) (long)pCombo->GetClientData(event.GetInt()); printf("Sim: log level selected: %d (%s)\n", (int) priority, (const char*) gLogLevels[selection].name.ToAscii()); mMinPriority = priority; Redisplay(); } /* * Clear out the log. */ void LogWindow::OnLogClear(wxCommandEvent& event) { ClearDisplay(); mPool.Clear(); } /* * Handle the pause/resume button. * * If we're un-pausing, we need to get caught up. */ void LogWindow::OnLogPause(wxCommandEvent& event) { mPaused = !mPaused; wxButton* pButton = (wxButton*) FindWindow(IDC_LOG_PAUSE); if (mPaused) { pButton->SetLabel(wxT("&Resume")); mPool.SetBookmark(); } else { pButton->SetLabel(wxT("&Pause")); LogMessage* pMsg = mPool.GetBookmark(); if (pMsg == NULL) { /* bookmarked item fell out of pool */ printf("--- bookmark was lost, redisplaying\n"); Redisplay(); } else { /* * The bookmark points to the last item added to the display. * We want to chase its "prev" pointer to walk toward the head * of the list, adding items from oldest to newest. */ pMsg = pMsg->GetPrev(); while (pMsg != NULL) { if (FilterMatches(pMsg)) AddToDisplay(pMsg); pMsg = pMsg->GetPrev(); } } } } /* * Open log preferences dialog. */ void LogWindow::OnLogPrefs(wxCommandEvent& event) { LogPrefsDialog dialog(this); /* * Set up the dialog. */ dialog.mHeaderFormat = mHeaderFormat; dialog.mSingleLine = mSingleLine; dialog.mExtraSpacing = mExtraSpacing; dialog.mPointSize = mPointSize; dialog.mUseColor = mUseColor; dialog.mFontMonospace = mFontMonospace; dialog.mDisplayMax = mMaxDisplayMsgs; dialog.mPoolSizeKB = mPool.GetMaxSize() / 1024; dialog.mWriteFile = mWriteFile; dialog.mFileName = mFileName; dialog.mTruncateOld = mTruncateOld; /* * Show it. If they hit "OK", copy the updated values out, and * re-display the log output. */ if (dialog.ShowModal() == wxID_OK) { /* discard old display arra */ ClearDisplay(); delete[] mDisplayArray; mHeaderFormat = dialog.mHeaderFormat; mSingleLine = dialog.mSingleLine; mExtraSpacing = dialog.mExtraSpacing; mPointSize = dialog.mPointSize; mUseColor = dialog.mUseColor; mFontMonospace = dialog.mFontMonospace; assert(dialog.mDisplayMax > 0); assert(dialog.mPoolSizeKB > 0); mMaxDisplayMsgs = dialog.mDisplayMax; mPool.Resize(dialog.mPoolSizeKB * 1024); mWriteFile = dialog.mWriteFile; if (mLogFp != NULL && mFileName != dialog.mFileName) { printf("--- log file name changed, closing\n"); fclose(mLogFp); mLogFp = NULL; } mFileName = dialog.mFileName; mTruncateOld = dialog.mTruncateOld; mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); Redisplay(); PrepareLogFile(); } } /* * Handle a log message "user event". This should only be called in * the main UI thread. * * We take ownership of "*pLogMessage". */ void LogWindow::AddLogMessage(LogMessage* pLogMessage) { mPool.Add(pLogMessage); if (!mPaused && mVisible && FilterMatches(pLogMessage)) { /* * Thought: keep a reference to the previous message. If it * matches in most fields (all except timestamp?), hold it and * increment a counter. If we get a message that doesn't match, * or a timer elapses, synthesize a "previous message repeated N * times" string. */ AddToDisplay(pLogMessage); } // release the initial ref caused by allocation pLogMessage->Release(); if (mLogFp != NULL) LogToFile(pLogMessage); } /* * Clear out the display, releasing any log messages held in the display * array. */ void LogWindow::ClearDisplay(void) { wxTextCtrl* pTextCtrl; pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); pTextCtrl->Clear(); /* * Just run through the entire array. */ for (int i = 0; i < mMaxDisplayMsgs; i++) { if (mDisplayArray[i] != NULL) { mDisplayArray[i]->Release(); mDisplayArray[i] = NULL; } } mTopPtr = -1; mNextPtr = 0; } /* * Clear the current display and regenerate it from the log pool. We need * to do this whenever we change filters or log message formatting. */ void LogWindow::Redisplay(void) { /* * Freeze output rendering so it doesn't flash during update. Doesn't * seem to help for GTK, and it leaves garbage on the screen in WinXP, * so I'm leaving it commented out. */ //wxTextCtrl* pText = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); //pText->Freeze(); //printf("--- redisplay\n"); ClearDisplay(); /* * Set up the default wxWidgets text style stuff. */ SetTextStyle(); /* * Here's the plan: * - Start at the head of the pool (where the most recently added * items are). * - Check to see if the current item passes our filter. If it does, * increment the "found count". * - Continue in this manner until we run out of pool or have * sufficient items to fill the screen. * - Starting from the current position, walk back toward the head, * adding the items that meet the current filter criteria. * * Don't forget that the log pool could be empty. */ LogMessage* pMsg = mPool.GetHead(); if (pMsg != NULL) { int foundCount = 0; // note this stops before it runs off the end while (pMsg->GetNext() != NULL && foundCount < mMaxDisplayMsgs) { if (FilterMatches(pMsg)) foundCount++; pMsg = pMsg->GetNext(); } while (pMsg != NULL) { if (FilterMatches(pMsg)) AddToDisplay(pMsg); pMsg = pMsg->GetPrev(); } } //pText->Thaw(); } /* * Returns "true" if the currently specified filters would allow this * message to be shown. */ bool LogWindow::FilterMatches(const LogMessage* pLogMessage) { if (pLogMessage->GetPriority() >= mMinPriority) return true; else return false; } /* * Realloc the array of pointers, and remove anything from the display * that should no longer be there. */ void LogWindow::SetMaxDisplayMsgs(int max) { Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); pPrefs->SetInt("log-display-msg-count", max); } /* * Add the message to the display array and to the screen. */ void LogWindow::AddToDisplay(LogMessage* pLogMessage) { wxTextCtrl* pTextCtrl; pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); if (mNextPtr == mTopPtr) { /* * The display array is full. * * We need to eliminate the topmost entry. This requires removing * it from the array and removing the text from the wxTextCtrl. */ pTextCtrl->Remove(0, mDisplayArray[mTopPtr]->GetTextCtrlLen()); mDisplayArray[mTopPtr]->Release(); mTopPtr = (mTopPtr + 1) % mMaxDisplayMsgs; } /* * Add formatted text to the text ctrl. Track how much actual space * is required. The space may be different on Win32 (CRLF-based) vs. * GTK (LF-based), so we need to measure it, not compute it from the * text string. */ long lastBefore, lastAfter; //long insertBefore; //insertBefore = pTextCtrl->GetInsertionPoint(); lastBefore = pTextCtrl->GetLastPosition(); FormatMessage(pLogMessage, pTextCtrl); lastAfter = pTextCtrl->GetLastPosition(); pLogMessage->SetTextCtrlLen(lastAfter - lastBefore); /* * If we restore the old insertion point, we will be glued to where * we were. This is okay until we start deleting text from the top, * at which point we need to adjust it to retain our position. * * If we set the insertion point to the bottom, we effectively * implement "scroll to bottom on output". * * If we don't set it at all, we get slightly strange behavior out * of GTK, which seems to be par for the course here. */ //pTextCtrl->SetInsertionPoint(insertBefore); // restore insertion pt pTextCtrl->SetInsertionPoint(lastAfter); /* add it to array, claim ownership */ mDisplayArray[mNextPtr] = pLogMessage; pLogMessage->Acquire(); /* adjust pointers */ if (mTopPtr < 0) // first time only mTopPtr = 0; mNextPtr = (mNextPtr + 1) % mMaxDisplayMsgs; } /* * Return a human-readable string for the priority level. Always returns * a valid string. */ static const wxCharBuffer GetPriorityString(android_LogPriority priority) { int idx; idx = (int) priority - (int) ANDROID_LOG_VERBOSE; if (idx < 0 || idx >= NELEM(gLogLevels)) return "?unknown?"; return gLogLevels[idx].name.ToAscii(); } /* * Format a message and write it to the text control. */ void LogWindow::FormatMessage(const LogMessage* pLogMessage, wxTextCtrl* pTextCtrl) { #if defined(HAVE_LOCALTIME_R) struct tm tmBuf; #endif struct tm* ptm; char timeBuf[32]; char msgBuf[256]; int msgLen = 0; char* outBuf; char priChar; LogPrefsDialog::HeaderFormat headerFmt; headerFmt = mHeaderFormat; if (pLogMessage->GetInternal()) headerFmt = LogPrefsDialog::kHFInternal; priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; /* * Get the current date/time in pretty form * * It's often useful when examining a log with "less" to jump to * a specific point in the file by searching for the date/time stamp. * For this reason it's very annoying to have regexp meta characters * in the time stamp. Don't use forward slashes, parenthesis, * brackets, asterisks, or other special chars here. */ time_t when = pLogMessage->GetWhen(); const char* fmt = NULL; #if defined(HAVE_LOCALTIME_R) ptm = localtime_r(&when, &tmBuf); #else ptm = localtime(&when); #endif switch (headerFmt) { case LogPrefsDialog::kHFFull: case LogPrefsDialog::kHFInternal: fmt = "%m-%d %H:%M:%S"; break; case LogPrefsDialog::kHFBrief: case LogPrefsDialog::kHFMinimal: fmt = "%H:%M:%S"; break; default: break; } if (fmt != NULL) strftime(timeBuf, sizeof(timeBuf), fmt, ptm); else strcpy(timeBuf, "-"); const int kMaxExtraNewlines = 2; char hdrNewline[2]; char finalNewlines[kMaxExtraNewlines+1 +1]; if (mSingleLine) hdrNewline[0] = ' '; else hdrNewline[0] = '\n'; hdrNewline[1] = '\0'; assert(mExtraSpacing <= kMaxExtraNewlines); int i; for (i = 0; i < mExtraSpacing+1; i++) finalNewlines[i] = '\n'; finalNewlines[i] = '\0'; wxTextAttr msgColor; switch (pLogMessage->GetPriority()) { case ANDROID_LOG_WARN: msgColor.SetTextColour(*wxBLUE); break; case ANDROID_LOG_ERROR: msgColor.SetTextColour(*wxRED); break; case ANDROID_LOG_VERBOSE: case ANDROID_LOG_DEBUG: case ANDROID_LOG_INFO: default: msgColor.SetTextColour(*wxBLACK); break; } if (pLogMessage->GetInternal()) msgColor.SetTextColour(*wxGREEN); /* * Construct a buffer containing the log header. */ bool splitHeader = true; outBuf = msgBuf; switch (headerFmt) { case LogPrefsDialog::kHFFull: splitHeader = true; msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), "[ %s %5d %c/%-6.6s]%s", timeBuf, pLogMessage->GetPid(), priChar, pLogMessage->GetTag(), hdrNewline); break; case LogPrefsDialog::kHFBrief: splitHeader = true; msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), "[%s %5d]%s", timeBuf, pLogMessage->GetPid(), hdrNewline); break; case LogPrefsDialog::kHFMinimal: splitHeader = false; msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), "%s %5d- %s", timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); break; case LogPrefsDialog::kHFInternal: splitHeader = false; msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), "[%s] %s", timeBuf, pLogMessage->GetMsg()); break; default: fprintf(stderr, "Sim: unexpected header format %d\n", headerFmt); assert(false); break; } if (msgLen < 0) { fprintf(stderr, "WHOOPS\n"); assert(outBuf == msgBuf); return; } if (splitHeader) { if (mUseColor) pTextCtrl->SetDefaultStyle(wxTextAttr(*wxLIGHT_GREY)); pTextCtrl->AppendText(wxString::FromAscii(outBuf)); if (mUseColor) pTextCtrl->SetDefaultStyle(msgColor); pTextCtrl->AppendText(wxString::FromAscii(pLogMessage->GetMsg())); if (mUseColor) pTextCtrl->SetDefaultStyle(*wxBLACK); pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); } else { if (mUseColor) pTextCtrl->SetDefaultStyle(msgColor); pTextCtrl->AppendText(wxString::FromAscii(outBuf)); if (mUseColor) pTextCtrl->SetDefaultStyle(*wxBLACK); pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); } /* if we allocated storage for this message, free it */ if (outBuf != msgBuf) free(outBuf); } /* * Write the message to the log file. * * We can't just do this in FormatMessage(), because that re-writes all * messages on the display whenever the output format or filter changes. * * Use a one-log-per-line format here to make "grep" useful. */ void LogWindow::LogToFile(const LogMessage* pLogMessage) { #if defined(HAVE_LOCALTIME_R) struct tm tmBuf; #endif struct tm* ptm; char timeBuf[32]; char msgBuf[256]; int msgLen; char* outBuf; char priChar; assert(mLogFp != NULL); time_t when = pLogMessage->GetWhen(); #if defined(HAVE_LOCALTIME_R) ptm = localtime_r(&when, &tmBuf); #else ptm = localtime(&when); #endif strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; outBuf = msgBuf; if (pLogMessage->GetInternal()) { msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), "[%s %5d *] %s\n", timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); } else { msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), "[%s %5d %c] %s)\n", timeBuf, pLogMessage->GetPid(), priChar, pLogMessage->GetMsg()); } if (fwrite(outBuf, msgLen, 1, mLogFp) != 1) fprintf(stderr, "Sim: WARNING: partial log write\n"); fflush(mLogFp); /* if we allocated storage for this message, free it */ if (outBuf != msgBuf) free(outBuf); } /* * Get the modification date of a file. */ static bool GetFileModDate(const char* fileName, time_t* pModWhen) { struct stat sb; if (stat(fileName, &sb) < 0) return false; *pModWhen = sb.st_mtime; return true; } /* * Open or close the log file as appropriate. */ void LogWindow::PrepareLogFile(void) { const int kLogFileMaxAge = 8 * 60 * 60; // 8 hours if (!mWriteFile && mLogFp != NULL) { printf("Sim: closing log file\n"); fclose(mLogFp); mLogFp = NULL; } else if (mWriteFile && mLogFp == NULL) { printf("Sim: opening log file '%s'\n", (const char*)mFileName.ToAscii()); time_t now, modWhen = 0; const char* openFlags; now = time(NULL); if (!mTruncateOld || (GetFileModDate(mFileName.ToAscii(), &modWhen) && modWhen + kLogFileMaxAge > now)) { if (modWhen != 0) { printf("--- log file is %.3f hours old, appending\n", (now - modWhen) / 3600.0); } openFlags = "a"; // open for append (text mode) } else { if (modWhen != 0) { printf("--- log file is %.3f hours old, truncating\n", (now - modWhen) / 3600.0); } openFlags = "w"; // open for writing, truncate (text mode) } mLogFp = fopen(mFileName.ToAscii(), openFlags); if (mLogFp == NULL) { fprintf(stderr, "Sim: failed opening log file '%s': %s\n", (const char*) mFileName.ToAscii(), strerror(errno)); } else { fprintf(mLogFp, "\n\n"); fflush(mLogFp); } } } /* * Add a new log message. * * This function can be called from any thread. It makes a copy of the * stuff in "*pBundle" and sends it to the main UI thread. */ /*static*/ void LogWindow::PostLogMsg(const android_LogBundle* pBundle) { LogMessage* pNewMessage = LogMessage::Create(pBundle); SendToWindow(pNewMessage); } /* * Post a simple string to the log. */ /*static*/ void LogWindow::PostLogMsg(const char* msg) { LogMessage* pNewMessage = LogMessage::Create(msg); SendToWindow(pNewMessage); } /* * Post a simple wxString to the log. */ /*static*/ void LogWindow::PostLogMsg(const wxString& msg) { LogMessage* pNewMessage = LogMessage::Create(msg.ToAscii()); SendToWindow(pNewMessage); } /* * Send a log message to the log window. */ /*static*/ void LogWindow::SendToWindow(LogMessage* pMessage) { if (pMessage != NULL) { wxWindow* pMainFrame = ((MyApp*)wxTheApp)->GetMainFrame(); UserEventMessage* pUem = new UserEventMessage; pUem->CreateLogMessage(pMessage); UserEvent uev(0, (void*) pUem); pMainFrame->AddPendingEvent(uev); } else { fprintf(stderr, "Sim: failed to add new log message\n"); } } /* * This is a sanity check. We need to stop somewhere to avoid trashing * the system on bad input. */ #define kMaxLen 65536 #define VSNPRINTF vsnprintf // used to worry about _vsnprintf /* * Print a formatted message into a buffer. Pass in a buffer to try to use. * * If the buffer isn't big enough to hold the message, allocate storage * with malloc() and return that instead. The caller is responsible for * freeing the storage. * * Returns the length of the string, or -1 if the printf call failed. */ static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args) { int charsOut; char* localBuf = NULL; assert(pBuf != NULL && *pBuf != NULL); assert(bufLen > 0); assert(format != NULL); while (1) { /* * In some versions of libc, vsnprintf only returns 0 or -1, where * -1 indicates the the buffer wasn't big enough. In glibc 2.1 * and later, it returns the actual size needed. * * MinGW is just returning -1, so we have to retry there. */ char* newBuf; charsOut = VSNPRINTF(*pBuf, bufLen, format, args); if (charsOut >= 0 && charsOut < bufLen) break; //fprintf(stderr, "EXCEED: %d vs %d\n", charsOut, bufLen); if (charsOut < 0) { /* exact size not known, double previous size */ bufLen *= 2; if (bufLen > kMaxLen) goto fail; } else { /* exact size known, just use that */ bufLen = charsOut + 1; } //fprintf(stderr, "RETRY at %d\n", bufLen); newBuf = (char*) realloc(localBuf, bufLen); if (newBuf == NULL) goto fail; *pBuf = localBuf = newBuf; } // On platforms where snprintf() doesn't return the number of // characters output, we would need to call strlen() here. return charsOut; fail: if (localBuf != NULL) { free(localBuf); *pBuf = NULL; } return -1; } /* * Variable-arg form of the above. */ static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...) { va_list args; int result; va_start(args, format); result = android_vsnprintfBuffer(pBuf, bufLen, format, args); va_end(args); return result; }