diff --git a/app/main/appium.js b/app/main/appium.js index 70d71856e..ecc77794b 100644 --- a/app/main/appium.js +++ b/app/main/appium.js @@ -5,6 +5,7 @@ import { main as appiumServer } from 'appium'; import { getDefaultArgs, getParser } from 'appium/build/lib/parser'; import path from 'path'; import wd from 'wd'; +import { fs, tempDir } from 'appium-support'; import settings from '../settings'; import autoUpdaterController from './auto-updater'; import AppiumMethodHandler from './appium-method-handler'; @@ -20,10 +21,19 @@ var batchedLogs = []; let sessionDrivers = {}; let appiumHandlers = {}; +let logFile; // Delete saved server args, don't start until a server has been started settings.deleteSync('SERVER_ARGS'); +async function deleteLogfile () { + if (logFile) { + try { + await fs.rimraf(logFile); + } catch (ign) { } + } +} + /** * Kill session associated with session browser window */ @@ -46,6 +56,14 @@ async function killSession (sessionWinID) { function connectStartServer (win) { ipcMain.on('start-server', async (event, args) => { + // log the server logs to a file + try { + const dir = await tempDir.openDir(); + logFile = path.resolve(dir, 'appium-server-logs.txt'); + win.webContents.send('path-to-logs', logFile); + win.on('close', deleteLogfile); + } catch (ign) { } + // clean up args object for appium log purposes (so it doesn't show in // non-default args list if (args.defaultCapabilities && @@ -60,9 +78,16 @@ function connectStartServer (win) { args.throwInsteadOfExit = true; // set up our log watcher - logWatcher = setInterval(() => { + logWatcher = setInterval(async () => { if (batchedLogs.length) { - win.webContents.send('appium-log-line', batchedLogs); + try { + await fs.writeFile( + logFile, + batchedLogs.map((log) => `[${log.level}] ${log.msg}`).join('\n'), + {flag: 'a'} + ); + win.webContents.send('appium-log-line', batchedLogs); + } catch (ign) { } batchedLogs = []; } }, LOG_SEND_INTERVAL_MS); @@ -90,6 +115,7 @@ function connectStopServer (win) { } catch (e) { win.webContents.send('appium-stop-error', e.message); } + clearInterval(logWatcher); await settings.delete('SERVER_ARGS'); }); diff --git a/app/renderer/actions/ServerMonitor.js b/app/renderer/actions/ServerMonitor.js index 82cd545b0..e2c326be6 100644 --- a/app/renderer/actions/ServerMonitor.js +++ b/app/renderer/actions/ServerMonitor.js @@ -1,4 +1,4 @@ -import { ipcRenderer } from 'electron'; +import { ipcRenderer, shell } from 'electron'; import { push } from 'react-router-redux'; export const SERVER_STOP_REQ = 'SERVER_STOP_REQ'; @@ -86,4 +86,15 @@ export function startSession () { dispatch({type: START_SESSION_REQUEST}); ipcRenderer.send('create-new-session-window'); }; +} + +export function getRawLogs () { + return (dispatch, getState) => { + const logfilePath = getState().startServer.logfilePath; + if (logfilePath) { + shell.openExternal(`file://${logfilePath}`); + } else { + alert('An error has occurred: Logs not available'); + } + }; } \ No newline at end of file diff --git a/app/renderer/actions/StartServer.js b/app/renderer/actions/StartServer.js index 66280289e..63dec9341 100644 --- a/app/renderer/actions/StartServer.js +++ b/app/renderer/actions/StartServer.js @@ -14,6 +14,7 @@ export const PRESET_SAVE_OK = 'PRESET_SAVE_OK'; export const GET_PRESETS = 'GET_PRESETS'; export const PRESET_DELETE_REQ = 'PRESET_DELETE_REQ'; export const PRESET_DELETE_OK = 'PRESET_DELETE_OK'; +export const SET_LOGFILE_PATH = 'SET_LOGFILE_PATH'; export const PRESETS = 'presets'; @@ -47,6 +48,7 @@ export function startServer (evt) { }); dispatch(clearLogs()); + ipcRenderer.once('path-to-logs', (event, logfilePath) => dispatch({type: SET_LOGFILE_PATH, logfilePath})); ipcRenderer.send('start-server', serverArgs); }; } @@ -104,4 +106,4 @@ export function deletePreset (name) { } dispatch({type: PRESET_DELETE_OK, presets}); }; -} +} \ No newline at end of file diff --git a/app/renderer/components/ServerMonitor/ServerMonitor.css b/app/renderer/components/ServerMonitor/ServerMonitor.css index 77168c582..fb88f2a6f 100644 --- a/app/renderer/components/ServerMonitor/ServerMonitor.css +++ b/app/renderer/components/ServerMonitor/ServerMonitor.css @@ -57,13 +57,20 @@ margin-right: 10px; } -.stopButton { + +.serverButton { color: #a0a0a0 !important; background-image: none !important; background-color: #252525 !important; border-color: #a0a0a0 !important; } +.getRawLogsButton { + position: absolute; + right: 0; + margin: 0 1em 0 0; +} + .term { white-space: pre-wrap; width: 100%; diff --git a/app/renderer/components/ServerMonitor/ServerMonitor.js b/app/renderer/components/ServerMonitor/ServerMonitor.js index 49eecb127..e8e56aee1 100644 --- a/app/renderer/components/ServerMonitor/ServerMonitor.js +++ b/app/renderer/components/ServerMonitor/ServerMonitor.js @@ -31,15 +31,15 @@ class StopButton extends Component { render () { const {serverStatus, stopServer, closeMonitor} = this.props; - let btn = ; if (serverStatus === STATUS_STOPPED) { - btn = ; } else if (serverStatus === STATUS_STOPPING) { btn = ; + className={styles.serverButton} type="disabled">Stopping...; } return btn; } @@ -54,7 +54,7 @@ class StartSessionButton extends Component { render () { const {serverStatus, startSession} = this.props; if (serverStatus !== STATUS_STOPPED && serverStatus !== STATUS_STOPPING) { - return ; } else { @@ -63,6 +63,14 @@ class StartSessionButton extends Component { } } +class GetRawLogsButton extends Component { + render () { + return ; + } +} + export default class ServerMonitor extends Component { static propTypes = { @@ -149,6 +157,7 @@ export default class ServerMonitor extends Component {
this._term = c}> + {logLineSection} {lastSection}
diff --git a/app/renderer/reducers/ServerMonitor.js b/app/renderer/reducers/ServerMonitor.js index 7bf6180ad..f94f65349 100644 --- a/app/renderer/reducers/ServerMonitor.js +++ b/app/renderer/reducers/ServerMonitor.js @@ -18,6 +18,7 @@ const initialState = { }; export default function serverMonitor (state = initialState, action) { + let logLines; switch (action.type) { case SERVER_STOP_REQ: return {...state, serverStatus: STATUS_STOPPING}; @@ -40,8 +41,7 @@ export default function serverMonitor (state = initialState, action) { sessionId: action.sessionUUID, }; case LOGS_RECEIVED: - // TODO: We should dump logs to a txt file that can be exported - var logLines = state.logLines.concat(action.logs.map((l) => { + logLines = state.logLines.concat(action.logs.map((l) => { // attach a timestamp to each log line here when it comes in l.timestamp = moment().format('YYYY-MM-DD hh:mm:ss'); return l; diff --git a/app/renderer/reducers/StartServer.js b/app/renderer/reducers/StartServer.js index 3728b4ead..a1f9360fe 100644 --- a/app/renderer/reducers/StartServer.js +++ b/app/renderer/reducers/StartServer.js @@ -1,6 +1,6 @@ import { SERVER_START_REQ, SERVER_START_OK, SERVER_START_ERR, GET_PRESETS, UPDATE_ARGS, SWITCH_TAB, PRESET_SAVE_REQ, PRESET_SAVE_OK, - PRESET_DELETE_REQ, PRESET_DELETE_OK + PRESET_DELETE_REQ, PRESET_DELETE_OK, SET_LOGFILE_PATH, } from '../actions/StartServer'; import { ipcRenderer } from 'electron'; @@ -50,6 +50,8 @@ export default function startServer (state = initialState, action) { return {...state, presetDeleting: true}; case PRESET_DELETE_OK: return {...state, presetDeleting: false, presets: action.presets}; + case SET_LOGFILE_PATH: + return {...state, logfilePath: action.logfilePath}; default: return state; } diff --git a/package.json b/package.json index d670af498..fe93ee4cd 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "ansi-to-html": "0.5", "antd": "2.10.1", "appium": "1.6.5", + "appium-support": "^2.8.2", "bluebird": "3.x", "css-modules-require-hook": "4.x", "electron-debug": "1.x",