[misc] OError.tag all the errors in async contexts

See the docs of OError.tag:
https://github.com/overleaf/o-error#long-stack-traces-with-oerrortag
(currently at 221dd902e7bfa0ee92de1ea5a3cbf3152c3ceeb4)

I am tagging all errors at each async hop. Most of the controller code
 will only ever see already tagged errors -- or new errors created in
 our app code. They should have enough info that we do not need to tag
 them again.
This commit is contained in:
Jakob Ackermann
2020-08-24 10:12:06 +01:00
parent c7b9dadd16
commit 537e97be73
8 changed files with 63 additions and 58 deletions
@@ -1,6 +1,7 @@
const logger = require('logger-sharelatex') const logger = require('logger-sharelatex')
const metrics = require('metrics-sharelatex') const metrics = require('metrics-sharelatex')
const settings = require('settings-sharelatex') const settings = require('settings-sharelatex')
const OError = require('@overleaf/o-error')
const ClientMap = new Map() // for each redis client, store a Map of subscribed channels (channelname -> subscribe promise) const ClientMap = new Map() // for each redis client, store a Map of subscribed channels (channelname -> subscribe promise)
@@ -35,6 +36,8 @@ module.exports = {
.catch(function (err) { .catch(function (err) {
logger.error({ channel, err }, 'failed to subscribe to channel') logger.error({ channel, err }, 'failed to subscribe to channel')
metrics.inc(`subscribe.failed.${baseChannel}`) metrics.inc(`subscribe.failed.${baseChannel}`)
// add context for the stack-trace at the call-site
OError.tag(err, 'failed to subscribe to channel', { channel })
}) })
return p return p
} }
@@ -5,6 +5,7 @@ const async = require('async')
const Settings = require('settings-sharelatex') const Settings = require('settings-sharelatex')
const logger = require('logger-sharelatex') const logger = require('logger-sharelatex')
const redis = require('redis-sharelatex') const redis = require('redis-sharelatex')
const OError = require('@overleaf/o-error')
const rclient = redis.createClient(Settings.redis.realtime) const rclient = redis.createClient(Settings.redis.realtime)
const Keys = Settings.redis.realtime.key_schema const Keys = Settings.redis.realtime.key_schema
@@ -67,10 +68,7 @@ module.exports = {
multi.exec(function (err) { multi.exec(function (err) {
if (err) { if (err) {
logger.err( OError.tag(err, 'problem marking user as connected')
{ err, project_id, client_id },
'problem marking user as connected'
)
} }
callback(err) callback(err)
}) })
@@ -104,7 +102,12 @@ module.exports = {
multi.srem(Keys.clientsInProject({ project_id }), client_id) multi.srem(Keys.clientsInProject({ project_id }), client_id)
multi.expire(Keys.clientsInProject({ project_id }), FOUR_DAYS_IN_S) multi.expire(Keys.clientsInProject({ project_id }), FOUR_DAYS_IN_S)
multi.del(Keys.connectedUser({ project_id, client_id })) multi.del(Keys.connectedUser({ project_id, client_id }))
multi.exec(callback) multi.exec(function (err) {
if (err) {
OError.tag(err, 'problem marking user as disconnected')
}
callback(err)
})
}, },
_getConnectedUser(project_id, client_id, callback) { _getConnectedUser(project_id, client_id, callback) {
@@ -112,6 +115,12 @@ module.exports = {
err, err,
result result
) { ) {
if (err) {
OError.tag(err, 'problem fetching connected user details', {
other_client_id: client_id
})
return callback(err)
}
if (!(result && result.user_id)) { if (!(result && result.user_id)) {
result = { result = {
connected: false, connected: false,
@@ -126,15 +135,10 @@ module.exports = {
try { try {
result.cursorData = JSON.parse(result.cursorData) result.cursorData = JSON.parse(result.cursorData)
} catch (e) { } catch (e) {
logger.error( OError.tag(e, 'error parsing cursorData JSON', {
{ other_client_id: client_id,
err: e, cursorData: result.cursorData
project_id, })
client_id,
cursorData: result.cursorData
},
'error parsing cursorData JSON'
)
return callback(e) return callback(e)
} }
} }
@@ -150,6 +154,7 @@ module.exports = {
results results
) { ) {
if (err) { if (err) {
OError.tag(err, 'problem getting clients in project')
return callback(err) return callback(err)
} }
const jobs = results.map((client_id) => (cb) => const jobs = results.map((client_id) => (cb) =>
@@ -157,6 +162,7 @@ module.exports = {
) )
async.series(jobs, function (err, users) { async.series(jobs, function (err, users) {
if (err) { if (err) {
OError.tag(err, 'problem getting connected users')
return callback(err) return callback(err)
} }
users = users.filter( users = users.filter(
@@ -3,6 +3,7 @@
*/ */
const request = require('request') const request = require('request')
const _ = require('underscore') const _ = require('underscore')
const OError = require('@overleaf/o-error')
const logger = require('logger-sharelatex') const logger = require('logger-sharelatex')
const settings = require('settings-sharelatex') const settings = require('settings-sharelatex')
const metrics = require('metrics-sharelatex') const metrics = require('metrics-sharelatex')
@@ -23,10 +24,7 @@ const DocumentUpdaterManager = {
request.get(url, function (err, res, body) { request.get(url, function (err, res, body) {
timer.done() timer.done()
if (err) { if (err) {
logger.error( OError.tag(err, 'error getting doc from doc updater')
{ err, url, project_id, doc_id },
'error getting doc from doc updater'
)
return callback(err) return callback(err)
} }
if (res.statusCode >= 200 && res.statusCode < 300) { if (res.statusCode >= 200 && res.statusCode < 300) {
@@ -37,6 +35,7 @@ const DocumentUpdaterManager = {
try { try {
body = JSON.parse(body) body = JSON.parse(body)
} catch (error) { } catch (error) {
OError.tag(error, 'error parsing doc updater response')
return callback(error) return callback(error)
} }
body = body || {} body = body || {}
@@ -79,10 +78,7 @@ const DocumentUpdaterManager = {
request.del(url, function (err, res) { request.del(url, function (err, res) {
timer.done() timer.done()
if (err) { if (err) {
logger.error( OError.tag(err, 'error deleting project from document updater')
{ err, project_id },
'error deleting project from document updater'
)
callback(err) callback(err)
} else if (res.statusCode >= 200 && res.statusCode < 300) { } else if (res.statusCode >= 200 && res.statusCode < 300) {
logger.log({ project_id }, 'deleted project from document updater') logger.log({ project_id }, 'deleted project from document updater')
@@ -140,9 +136,15 @@ const DocumentUpdaterManager = {
error error
) { ) {
if (error) { if (error) {
OError.tag(error, 'error pushing update into redis')
return callback(error) return callback(error)
} }
rclient.rpush('pending-updates-list', doc_key, callback) rclient.rpush('pending-updates-list', doc_key, function (error) {
if (error) {
OError.tag(error, 'error pushing doc_id into redis')
}
callback(error)
})
}) })
} }
} }
+5
View File
@@ -4,6 +4,7 @@
const logger = require('logger-sharelatex') const logger = require('logger-sharelatex')
const metrics = require('metrics-sharelatex') const metrics = require('metrics-sharelatex')
const { EventEmitter } = require('events') const { EventEmitter } = require('events')
const OError = require('@overleaf/o-error')
const IdMap = new Map() // keep track of whether ids are from projects or docs const IdMap = new Map() // keep track of whether ids are from projects or docs
const RoomEvents = new EventEmitter() // emits {project,doc}-active and {project,doc}-empty events const RoomEvents = new EventEmitter() // emits {project,doc}-active and {project,doc}-empty events
@@ -65,6 +66,10 @@ module.exports = {
logger.log({ entity, id }, 'room is now active') logger.log({ entity, id }, 'room is now active')
RoomEvents.once(`${entity}-subscribed-${id}`, function (err) { RoomEvents.once(`${entity}-subscribed-${id}`, function (err) {
// only allow the client to join when all the relevant channels have subscribed // only allow the client to join when all the relevant channels have subscribed
if (err) {
OError.tag(err, 'error joining', { entity, id })
return callback(err)
}
logger.log( logger.log(
{ client: client.id, entity, id, beforeCount }, { client: client.id, entity, id, beforeCount },
'client joined new room and subscribed to channel' 'client joined new room and subscribed to channel'
@@ -1,3 +1,4 @@
const OError = require('@overleaf/o-error')
const { EventEmitter } = require('events') const { EventEmitter } = require('events')
module.exports = function (io, sessionStore, cookieParser, cookieName) { module.exports = function (io, sessionStore, cookieParser, cookieName) {
@@ -17,6 +18,9 @@ module.exports = function (io, sessionStore, cookieParser, cookieName) {
} }
sessionStore.get(sessionId, function (error, session) { sessionStore.get(sessionId, function (error, session) {
if (error) { if (error) {
OError.tag(error, 'error getting session from sessionStore', {
sessionId
})
return next(error, socket) return next(error, socket)
} }
if (!session) { if (!session) {
@@ -2,6 +2,7 @@
camelcase, camelcase,
*/ */
const request = require('request') const request = require('request')
const OError = require('@overleaf/o-error')
const settings = require('settings-sharelatex') const settings = require('settings-sharelatex')
const logger = require('logger-sharelatex') const logger = require('logger-sharelatex')
const { CodedError } = require('./Errors') const { CodedError } = require('./Errors')
@@ -30,6 +31,7 @@ module.exports = {
}, },
function (error, response, data) { function (error, response, data) {
if (error) { if (error) {
OError.tag(error, 'join project request failed')
return callback(error) return callback(error)
} }
let err let err
@@ -1,6 +1,7 @@
/* eslint-disable /* eslint-disable
camelcase, camelcase,
*/ */
const OError = require('@overleaf/o-error')
const logger = require('logger-sharelatex') const logger = require('logger-sharelatex')
const metrics = require('metrics-sharelatex') const metrics = require('metrics-sharelatex')
const WebApiManager = require('./WebApiManager') const WebApiManager = require('./WebApiManager')
@@ -91,7 +92,14 @@ module.exports = WebsocketController = {
client.publicId, client.publicId,
user, user,
null, null,
function () {} function (err) {
if (err) {
logger.warn(
{ err, project_id, user_id, client_id: client.id },
'background cursor update failed'
)
}
}
) )
}) })
}, },
@@ -229,17 +237,7 @@ module.exports = WebsocketController = {
try { try {
line = encodeForWebsockets(line) line = encodeForWebsockets(line)
} catch (err) { } catch (err) {
logger.err( OError.tag(err, 'error encoding line uri component', { line })
{
err,
project_id,
doc_id,
fromVersion,
line,
client_id: client.id
},
'error encoding line uri component'
)
return callback(err) return callback(err)
} }
escapedLines.push(line) escapedLines.push(line)
@@ -260,17 +258,9 @@ module.exports = WebsocketController = {
} }
} }
} catch (err) { } catch (err) {
logger.err( OError.tag(err, 'error encoding range uri component', {
{ ranges
err, })
project_id,
doc_id,
fromVersion,
ranges,
client_id: client.id
},
'error encoding range uri component'
)
return callback(err) return callback(err)
} }
} }
@@ -538,16 +528,9 @@ module.exports = WebsocketController = {
} }
if (error) { if (error) {
logger.error( OError.tag(error, 'document was not available for update', {
{ version: update.v
err: error, })
project_id,
doc_id,
client_id: client.id,
version: update.v
},
'document was not available for update'
)
client.disconnect() client.disconnect()
} }
callback(error) callback(error)
@@ -1464,8 +1464,8 @@ describe('WebsocketController', function () {
return this.client.disconnect.called.should.equal(true) return this.client.disconnect.called.should.equal(true)
}) })
it('should log an error', function () { it('should not log an error', function () {
return this.logger.error.called.should.equal(true) return this.logger.error.called.should.equal(false)
}) })
return it('should call the callback with the error', function () { return it('should call the callback with the error', function () {