1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @fileoverview 7 * Module for sending log entries to the server. 8 */ 9 10'use strict'; 11 12/** @suppress {duplicate} */ 13var remoting = remoting || {}; 14 15/** 16 * @constructor 17 */ 18remoting.LogToServer = function() { 19 /** @type Array.<string> */ 20 this.pendingEntries = []; 21 /** @type {remoting.StatsAccumulator} */ 22 this.statsAccumulator = new remoting.StatsAccumulator(); 23 /** @type string */ 24 this.sessionId = ''; 25 /** @type number */ 26 this.sessionIdGenerationTime = 0; 27 /** @type number */ 28 this.sessionStartTime = 0; 29}; 30 31// Constants used for generating a session ID. 32/** @private */ 33remoting.LogToServer.SESSION_ID_ALPHABET_ = 34 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; 35/** @private */ 36remoting.LogToServer.SESSION_ID_LEN_ = 20; 37 38// The maximum age of a session ID, in milliseconds. 39remoting.LogToServer.MAX_SESSION_ID_AGE = 24 * 60 * 60 * 1000; 40 41// The time over which to accumulate connection statistics before logging them 42// to the server, in milliseconds. 43remoting.LogToServer.CONNECTION_STATS_ACCUMULATE_TIME = 60 * 1000; 44 45/** 46 * Logs a client session state change. 47 * 48 * @param {remoting.ClientSession.State} state 49 * @param {remoting.Error} connectionError 50 * @param {remoting.ClientSession.Mode} mode 51 */ 52remoting.LogToServer.prototype.logClientSessionStateChange = 53 function(state, connectionError, mode) { 54 this.maybeExpireSessionId(mode); 55 // Maybe set the session ID and start time. 56 if (remoting.LogToServer.isStartOfSession(state)) { 57 if (this.sessionId == '') { 58 this.setSessionId(); 59 } 60 if (this.sessionStartTime == 0) { 61 this.sessionStartTime = new Date().getTime(); 62 } 63 } 64 // Log the session state change. 65 var entry = remoting.ServerLogEntry.makeClientSessionStateChange( 66 state, connectionError, mode); 67 entry.addHostFields(); 68 entry.addChromeVersionField(); 69 entry.addWebappVersionField(); 70 entry.addSessionIdField(this.sessionId); 71 // Maybe clear the session start time, and log the session duration. 72 if (remoting.LogToServer.shouldAddDuration(state) && 73 (this.sessionStartTime != 0)) { 74 entry.addSessionDurationField( 75 (new Date().getTime() - this.sessionStartTime) / 1000.0); 76 if (remoting.LogToServer.isEndOfSession(state)) { 77 this.sessionStartTime = 0; 78 } 79 } 80 this.log(entry); 81 // Don't accumulate connection statistics across state changes. 82 this.logAccumulatedStatistics(mode); 83 this.statsAccumulator.empty(); 84 // Maybe clear the session ID. 85 if (remoting.LogToServer.isEndOfSession(state)) { 86 this.clearSessionId(); 87 } 88}; 89 90/** 91 * Whether a session state is one of the states that occurs at the start of 92 * a session. 93 * 94 * @private 95 * @param {remoting.ClientSession.State} state 96 * @return {boolean} 97 */ 98remoting.LogToServer.isStartOfSession = function(state) { 99 return ((state == remoting.ClientSession.State.CONNECTING) || 100 (state == remoting.ClientSession.State.INITIALIZING) || 101 (state == remoting.ClientSession.State.CONNECTED)); 102}; 103 104/** 105 * Whether a session state is one of the states that occurs at the end of 106 * a session. 107 * 108 * @private 109 * @param {remoting.ClientSession.State} state 110 * @return {boolean} 111 */ 112remoting.LogToServer.isEndOfSession = function(state) { 113 return ((state == remoting.ClientSession.State.CLOSED) || 114 (state == remoting.ClientSession.State.FAILED) || 115 (state == remoting.ClientSession.State.CONNECTION_DROPPED) || 116 (state == remoting.ClientSession.State.CONNECTION_CANCELED)); 117}; 118 119/** 120 * Whether the duration should be added to the log entry for this state. 121 * 122 * @private 123 * @param {remoting.ClientSession.State} state 124 * @return {boolean} 125 */ 126remoting.LogToServer.shouldAddDuration = function(state) { 127 // Duration is added to log entries at the end of the session, as well as at 128 // some intermediate states where it is relevant (e.g. to determine how long 129 // it took for a session to become CONNECTED). 130 return (remoting.LogToServer.isEndOfSession(state) || 131 (state == remoting.ClientSession.State.CONNECTED)); 132}; 133 134/** 135 * Logs connection statistics. 136 * @param {Object.<string, number>} stats the connection statistics 137 * @param {remoting.ClientSession.Mode} mode 138 */ 139remoting.LogToServer.prototype.logStatistics = function(stats, mode) { 140 this.maybeExpireSessionId(mode); 141 // Store the statistics. 142 this.statsAccumulator.add(stats); 143 // Send statistics to the server if they've been accumulating for at least 144 // 60 seconds. 145 if (this.statsAccumulator.getTimeSinceFirstValue() >= 146 remoting.LogToServer.CONNECTION_STATS_ACCUMULATE_TIME) { 147 this.logAccumulatedStatistics(mode); 148 } 149}; 150 151/** 152 * Moves connection statistics from the accumulator to the log server. 153 * 154 * If all the statistics are zero, then the accumulator is still emptied, 155 * but the statistics are not sent to the log server. 156 * 157 * @private 158 * @param {remoting.ClientSession.Mode} mode 159 */ 160remoting.LogToServer.prototype.logAccumulatedStatistics = function(mode) { 161 var entry = remoting.ServerLogEntry.makeStats(this.statsAccumulator, mode); 162 if (entry) { 163 entry.addHostFields(); 164 entry.addChromeVersionField(); 165 entry.addWebappVersionField(); 166 entry.addSessionIdField(this.sessionId); 167 this.log(entry); 168 } 169 this.statsAccumulator.empty(); 170}; 171 172/** 173 * Sends a log entry to the server. 174 * 175 * @private 176 * @param {remoting.ServerLogEntry} entry 177 */ 178remoting.LogToServer.prototype.log = function(entry) { 179 // Send the stanza to the debug log. 180 console.log('Enqueueing log entry:'); 181 entry.toDebugLog(1); 182 // Store a stanza for the entry. 183 this.pendingEntries.push(entry.toStanza()); 184 // Send all pending entries to the server. 185 console.log('Sending ' + this.pendingEntries.length + ' log ' + 186 ((this.pendingEntries.length == 1) ? 'entry' : 'entries') + 187 ' to the server.'); 188 var stanza = '<cli:iq to="' + 189 remoting.settings.DIRECTORY_BOT_JID + '" type="set" ' + 190 'xmlns:cli="jabber:client"><gr:log xmlns:gr="google:remoting">'; 191 while (this.pendingEntries.length > 0) { 192 stanza += /** @type string */ this.pendingEntries.shift(); 193 } 194 stanza += '</gr:log></cli:iq>'; 195 remoting.wcsSandbox.sendIq(stanza); 196}; 197 198/** 199 * Sets the session ID to a random string. 200 * 201 * @private 202 */ 203remoting.LogToServer.prototype.setSessionId = function() { 204 this.sessionId = remoting.LogToServer.generateSessionId(); 205 this.sessionIdGenerationTime = new Date().getTime(); 206}; 207 208/** 209 * Clears the session ID. 210 * 211 * @private 212 */ 213remoting.LogToServer.prototype.clearSessionId = function() { 214 this.sessionId = ''; 215 this.sessionIdGenerationTime = 0; 216}; 217 218/** 219 * Sets a new session ID, if the current session ID has reached its maximum age. 220 * 221 * This method also logs the old and new session IDs to the server, in separate 222 * log entries. 223 * 224 * @private 225 * @param {remoting.ClientSession.Mode} mode 226 */ 227remoting.LogToServer.prototype.maybeExpireSessionId = function(mode) { 228 if ((this.sessionId != '') && 229 (new Date().getTime() - this.sessionIdGenerationTime >= 230 remoting.LogToServer.MAX_SESSION_ID_AGE)) { 231 // Log the old session ID. 232 var entry = remoting.ServerLogEntry.makeSessionIdOld(this.sessionId, mode); 233 this.log(entry); 234 // Generate a new session ID. 235 this.setSessionId(); 236 // Log the new session ID. 237 entry = remoting.ServerLogEntry.makeSessionIdNew(this.sessionId, mode); 238 this.log(entry); 239 } 240}; 241 242/** 243 * Generates a string that can be used as a session ID. 244 * 245 * @private 246 * @return {string} a session ID 247 */ 248remoting.LogToServer.generateSessionId = function() { 249 var idArray = []; 250 for (var i = 0; i < remoting.LogToServer.SESSION_ID_LEN_; i++) { 251 var index = 252 Math.random() * remoting.LogToServer.SESSION_ID_ALPHABET_.length; 253 idArray.push( 254 remoting.LogToServer.SESSION_ID_ALPHABET_.slice(index, index + 1)); 255 } 256 return idArray.join(''); 257}; 258