Merge pull request #4950 from overleaf/bg-realtime-log-relevelling

realtime log re-levelling

GitOrigin-RevId: 81d86ba648e7fc1436b638b67371b35fd831a62f
This commit is contained in:
Brian Gough 2021-09-14 09:36:24 +01:00 committed by Copybot
parent fd36c41368
commit 618cf99548
22 changed files with 150 additions and 81 deletions

View file

@ -27,10 +27,10 @@ const DeploymentManager = require('./app/js/DeploymentManager')
// NOTE: debug is invoked for every blob that is put on the wire // NOTE: debug is invoked for every blob that is put on the wire
const socketIoLogger = { const socketIoLogger = {
error(...message) { error(...message) {
logger.info({ fromSocketIo: true, originalLevel: 'error' }, ...message) logger.debug({ fromSocketIo: true, originalLevel: 'error' }, ...message)
}, },
warn(...message) { warn(...message) {
logger.info({ fromSocketIo: true, originalLevel: 'warn' }, ...message) logger.debug({ fromSocketIo: true, originalLevel: 'warn' }, ...message)
}, },
info() {}, info() {},
debug() {}, debug() {},
@ -60,7 +60,6 @@ const sessionSockets = new SessionSockets(
) )
Metrics.injectMetricsRoute(app) Metrics.injectMetricsRoute(app)
app.use(Metrics.http.monitor(logger))
io.configure(function () { io.configure(function () {
io.enable('browser client minification') io.enable('browser client minification')
@ -102,7 +101,7 @@ app.get('/status', function (req, res) {
app.get('/debug/events', function (req, res) { app.get('/debug/events', function (req, res) {
Settings.debugEvents = parseInt(req.query.count, 10) || 20 Settings.debugEvents = parseInt(req.query.count, 10) || 20
logger.log({ count: Settings.debugEvents }, 'starting debug mode') logger.info({ count: Settings.debugEvents }, 'starting debug mode')
res.send(`debug mode will log next ${Settings.debugEvents} events`) res.send(`debug mode will log next ${Settings.debugEvents} events`)
}) })
@ -137,6 +136,9 @@ app.get(
app.get('/health_check/redis', healthCheck) app.get('/health_check/redis', healthCheck)
// log http requests for routes defined from this point onwards
app.use(Metrics.http.monitor(logger))
const Router = require('./app/js/Router') const Router = require('./app/js/Router')
Router.configure(app, io, sessionSockets) Router.configure(app, io, sessionSockets)
@ -162,10 +164,10 @@ Error.stackTraceLimit = 10
function shutdownCleanly(signal) { function shutdownCleanly(signal) {
const connectedClients = io.sockets.clients().length const connectedClients = io.sockets.clients().length
if (connectedClients === 0) { if (connectedClients === 0) {
logger.warn('no clients connected, exiting') logger.info('no clients connected, exiting')
process.exit() process.exit()
} else { } else {
logger.warn( logger.info(
{ connectedClients }, { connectedClients },
'clients still connected, not shutting down yet' 'clients still connected, not shutting down yet'
) )
@ -175,18 +177,18 @@ function shutdownCleanly(signal) {
function drainAndShutdown(signal) { function drainAndShutdown(signal) {
if (Settings.shutDownInProgress) { if (Settings.shutDownInProgress) {
logger.warn({ signal }, 'shutdown already in progress, ignoring signal') logger.info({ signal }, 'shutdown already in progress, ignoring signal')
} else { } else {
Settings.shutDownInProgress = true Settings.shutDownInProgress = true
const { statusCheckInterval } = Settings const { statusCheckInterval } = Settings
if (statusCheckInterval) { if (statusCheckInterval) {
logger.warn( logger.info(
{ signal }, { signal },
`received interrupt, delay drain by ${statusCheckInterval}ms` `received interrupt, delay drain by ${statusCheckInterval}ms`
) )
} }
setTimeout(function () { setTimeout(function () {
logger.warn( logger.info(
{ signal }, { signal },
`received interrupt, starting drain over ${shutdownDrainTimeWindow} mins` `received interrupt, starting drain over ${shutdownDrainTimeWindow} mins`
) )
@ -194,7 +196,7 @@ function drainAndShutdown(signal) {
setTimeout(() => { setTimeout(() => {
const staleClients = io.sockets.clients() const staleClients = io.sockets.clients()
if (staleClients.length !== 0) { if (staleClients.length !== 0) {
logger.warn( logger.info(
{ staleClients: staleClients.map(client => client.id) }, { staleClients: staleClients.map(client => client.id) },
'forcefully disconnecting stale clients' 'forcefully disconnecting stale clients'
) )
@ -214,7 +216,7 @@ function drainAndShutdown(signal) {
Settings.shutDownInProgress = false Settings.shutDownInProgress = false
const shutdownDrainTimeWindow = parseInt(Settings.shutdownDrainTimeWindow, 10) const shutdownDrainTimeWindow = parseInt(Settings.shutdownDrainTimeWindow, 10)
if (Settings.shutdownDrainTimeWindow) { if (Settings.shutdownDrainTimeWindow) {
logger.log({ shutdownDrainTimeWindow }, 'shutdownDrainTimeWindow enabled') logger.info({ shutdownDrainTimeWindow }, 'shutdownDrainTimeWindow enabled')
for (const signal of [ for (const signal of [
'SIGINT', 'SIGINT',
'SIGHUP', 'SIGHUP',
@ -255,7 +257,7 @@ if (Settings.shutdownDrainTimeWindow) {
} }
if (Settings.continualPubsubTraffic) { if (Settings.continualPubsubTraffic) {
logger.warn('continualPubsubTraffic enabled') logger.debug('continualPubsubTraffic enabled')
const pubsubClient = redis.createClient(Settings.redis.pubsub) const pubsubClient = redis.createClient(Settings.redis.pubsub)
const clusterClient = redis.createClient(Settings.redis.websessions) const clusterClient = redis.createClient(Settings.redis.websessions)

View file

@ -31,7 +31,7 @@ module.exports = {
} }
}) })
.then(function () { .then(function () {
logger.log({ channel }, 'subscribed to channel') logger.debug({ channel }, 'subscribed to channel')
metrics.inc(`subscribe.${baseChannel}`) metrics.inc(`subscribe.${baseChannel}`)
}) })
.catch(function (err) { .catch(function (err) {
@ -50,7 +50,7 @@ module.exports = {
actualSubscribe actualSubscribe
) )
clientChannelMap.set(channel, subscribePromise) clientChannelMap.set(channel, subscribePromise)
logger.log({ channel }, 'planned to subscribe to channel') logger.debug({ channel }, 'planned to subscribe to channel')
return subscribePromise return subscribePromise
}, },
@ -67,7 +67,7 @@ module.exports = {
} }
}) })
.then(function () { .then(function () {
logger.log({ channel }, 'unsubscribed from channel') logger.debug({ channel }, 'unsubscribed from channel')
metrics.inc(`unsubscribe.${baseChannel}`) metrics.inc(`unsubscribe.${baseChannel}`)
}) })
.catch(function (err) { .catch(function (err) {
@ -82,7 +82,7 @@ module.exports = {
actualUnsubscribe actualUnsubscribe
) )
clientChannelMap.set(channel, unsubscribePromise) clientChannelMap.set(channel, unsubscribePromise)
logger.log({ channel }, 'planned to unsubscribe from channel') logger.debug({ channel }, 'planned to unsubscribe from channel')
return unsubscribePromise return unsubscribePromise
}, },

View file

@ -21,7 +21,10 @@ module.exports = {
// update. This way we don't care if the connected_user key has expired when // update. This way we don't care if the connected_user key has expired when
// we receive a cursor update. // we receive a cursor update.
updateUserPosition(project_id, client_id, user, cursorData, callback) { updateUserPosition(project_id, client_id, user, cursorData, callback) {
logger.log({ project_id, client_id }, 'marking user as joined or connected') logger.debug(
{ project_id, client_id },
'marking user as joined or connected'
)
const multi = rclient.multi() const multi = rclient.multi()
@ -75,7 +78,7 @@ module.exports = {
}, },
refreshClient(project_id, client_id) { refreshClient(project_id, client_id) {
logger.log({ project_id, client_id }, 'refreshing connected client') logger.debug({ project_id, client_id }, 'refreshing connected client')
const multi = rclient.multi() const multi = rclient.multi()
multi.hset( multi.hset(
Keys.connectedUser({ project_id, client_id }), Keys.connectedUser({ project_id, client_id }),
@ -97,7 +100,7 @@ module.exports = {
}, },
markUserAsDisconnected(project_id, client_id, callback) { markUserAsDisconnected(project_id, client_id, callback) {
logger.log({ project_id, client_id }, 'marking user as disconnected') logger.debug({ project_id, client_id }, 'marking user as disconnected')
const multi = rclient.multi() const multi = rclient.multi()
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)

View file

@ -16,10 +16,10 @@ function updateDeploymentStatus(fileContent) {
if (closed && !settings.serviceIsClosed) { if (closed && !settings.serviceIsClosed) {
settings.serviceIsClosed = true settings.serviceIsClosed = true
serviceCloseTime = Date.now() + 60 * 1000 // delay closing by 1 minute serviceCloseTime = Date.now() + 60 * 1000 // delay closing by 1 minute
logger.warn({ fileContent }, 'closing service') logger.info({ fileContent }, 'closing service')
} else if (!closed && settings.serviceIsClosed) { } else if (!closed && settings.serviceIsClosed) {
settings.serviceIsClosed = false settings.serviceIsClosed = false
logger.warn({ fileContent }, 'opening service') logger.info({ fileContent }, 'opening service')
} }
} }
@ -45,7 +45,7 @@ function checkStatusFileSync() {
module.exports = { module.exports = {
initialise() { initialise() {
if (statusFile && deploymentColour) { if (statusFile && deploymentColour) {
logger.log( logger.info(
{ statusFile, deploymentColour, interval: FILE_CHECK_INTERVAL }, { statusFile, deploymentColour, interval: FILE_CHECK_INTERVAL },
'monitoring deployment status file' 'monitoring deployment status file'
) )

View file

@ -18,7 +18,7 @@ module.exports = DocumentUpdaterController = {
rclientList: RedisClientManager.createClientList(settings.redis.pubsub), rclientList: RedisClientManager.createClientList(settings.redis.pubsub),
listenForUpdatesFromDocumentUpdater(io) { listenForUpdatesFromDocumentUpdater(io) {
logger.log( logger.debug(
{ rclients: this.rclientList.length }, { rclients: this.rclientList.length },
'listening for applied-ops events' 'listening for applied-ops events'
) )
@ -112,7 +112,7 @@ module.exports = DocumentUpdaterController = {
return return
} }
// send updates to clients // send updates to clients
logger.log( logger.debug(
{ {
doc_id, doc_id,
version: update.v, version: update.v,
@ -127,7 +127,7 @@ module.exports = DocumentUpdaterController = {
if (!seen[client.id]) { if (!seen[client.id]) {
seen[client.id] = true seen[client.id] = true
if (client.publicId === update.meta.source) { if (client.publicId === update.meta.source) {
logger.log( logger.debug(
{ {
doc_id, doc_id,
version: update.v, version: update.v,
@ -138,7 +138,7 @@ module.exports = DocumentUpdaterController = {
client.emit('otUpdateApplied', { v: update.v, doc: update.doc }) client.emit('otUpdateApplied', { v: update.v, doc: update.doc })
} else if (!update.dup) { } else if (!update.dup) {
// Duplicate ops should just be sent back to sending client for acknowledgement // Duplicate ops should just be sent back to sending client for acknowledgement
logger.log( logger.debug(
{ {
doc_id, doc_id,
version: update.v, version: update.v,
@ -153,7 +153,7 @@ module.exports = DocumentUpdaterController = {
} }
if (Object.keys(seen).length < clientList.length) { if (Object.keys(seen).length < clientList.length) {
metrics.inc('socket-io.duplicate-clients', 0.1) metrics.inc('socket-io.duplicate-clients', 0.1)
logger.log( logger.debug(
{ {
doc_id, doc_id,
socketIoClients: clientList.map(client => client.id), socketIoClients: clientList.map(client => client.id),

View file

@ -23,7 +23,7 @@ const DocumentUpdaterManager = {
getDocument(project_id, doc_id, fromVersion, callback) { getDocument(project_id, doc_id, fromVersion, callback) {
const timer = new metrics.Timer('get-document') const timer = new metrics.Timer('get-document')
const url = `${settings.apis.documentupdater.url}/project/${project_id}/doc/${doc_id}?fromVersion=${fromVersion}` const url = `${settings.apis.documentupdater.url}/project/${project_id}/doc/${doc_id}?fromVersion=${fromVersion}`
logger.log( logger.debug(
{ project_id, doc_id, fromVersion }, { project_id, doc_id, fromVersion },
'getting doc from document updater' 'getting doc from document updater'
) )
@ -34,7 +34,7 @@ const DocumentUpdaterManager = {
return callback(err) return callback(err)
} }
if (res.statusCode >= 200 && res.statusCode < 300) { if (res.statusCode >= 200 && res.statusCode < 300) {
logger.log( logger.debug(
{ project_id, doc_id }, { project_id, doc_id },
'got doc from document document updater' 'got doc from document document updater'
) )
@ -63,7 +63,7 @@ const DocumentUpdaterManager = {
flushProjectToMongoAndDelete(project_id, callback) { flushProjectToMongoAndDelete(project_id, callback) {
// this method is called when the last connected user leaves the project // this method is called when the last connected user leaves the project
logger.log({ project_id }, 'deleting project from document updater') logger.debug({ project_id }, 'deleting project from document updater')
const timer = new metrics.Timer('delete.mongo.project') const timer = new metrics.Timer('delete.mongo.project')
// flush the project in the background when all users have left // flush the project in the background when all users have left
const url = const url =
@ -75,7 +75,7 @@ const DocumentUpdaterManager = {
OError.tag(err, 'error deleting project from document updater') OError.tag(err, '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.debug({ project_id }, 'deleted project from document updater')
callback(null) callback(null)
} else { } else {
callback( callback(

View file

@ -10,7 +10,7 @@ module.exports = {
startDrain(io, rate, callback) { startDrain(io, rate, callback) {
// Clear out any old interval // Clear out any old interval
clearInterval(this.interval) clearInterval(this.interval)
logger.log({ rate }, 'starting drain') logger.info({ rate }, 'starting drain')
if (rate === 0) { if (rate === 0) {
return return
} }
@ -38,7 +38,7 @@ module.exports = {
for (const client of io.sockets.clients()) { for (const client of io.sockets.clients()) {
if (!this.RECONNECTED_CLIENTS[client.id]) { if (!this.RECONNECTED_CLIENTS[client.id]) {
this.RECONNECTED_CLIENTS[client.id] = true this.RECONNECTED_CLIENTS[client.id] = true
logger.log( logger.debug(
{ client_id: client.id }, { client_id: client.id },
'Asking client to reconnect gracefully' 'Asking client to reconnect gracefully'
) )
@ -51,7 +51,7 @@ module.exports = {
} }
} }
if (drainedCount < N) { if (drainedCount < N) {
logger.log('All clients have been told to reconnectGracefully') logger.info('All clients have been told to reconnectGracefully')
return true return true
} }
return false return false

View file

@ -21,7 +21,7 @@ module.exports = EventLogger = {
debugEvent(channel, message) { debugEvent(channel, message) {
if (settings.debugEvents > 0) { if (settings.debugEvents > 0) {
logger.log({ channel, message, counter: COUNTER++ }, 'logging event') logger.info({ channel, message, counter: COUNTER++ }, 'logging event')
settings.debugEvents-- settings.debugEvents--
} }
}, },

View file

@ -7,7 +7,7 @@ const logger = require('logger-sharelatex')
module.exports = { module.exports = {
sendMessage(req, res) { sendMessage(req, res) {
logger.log({ message: req.params.message }, 'sending message') logger.debug({ message: req.params.message }, 'sending message')
if (Array.isArray(req.body)) { if (Array.isArray(req.body)) {
for (const payload of req.body) { for (const payload of req.body) {
WebsocketLoadBalancer.emitToRoom( WebsocketLoadBalancer.emitToRoom(
@ -30,7 +30,7 @@ module.exports = {
const io = req.app.get('io') const io = req.app.get('io')
let rate = req.query.rate || '4' let rate = req.query.rate || '4'
rate = parseFloat(rate) || 0 rate = parseFloat(rate) || 0
logger.log({ rate }, 'setting client drain rate') logger.info({ rate }, 'setting client drain rate')
DrainManager.startDrain(io, rate) DrainManager.startDrain(io, rate)
res.sendStatus(204) res.sendStatus(204)
}, },
@ -41,11 +41,11 @@ module.exports = {
const client = io.sockets.sockets[client_id] const client = io.sockets.sockets[client_id]
if (!client) { if (!client) {
logger.info({ client_id }, 'api: client already disconnected') logger.debug({ client_id }, 'api: client already disconnected')
res.sendStatus(404) res.sendStatus(404)
return return
} }
logger.warn({ client_id }, 'api: requesting client disconnect') logger.info({ client_id }, 'api: requesting client disconnect')
client.on('disconnect', () => res.sendStatus(204)) client.on('disconnect', () => res.sendStatus(204))
client.disconnect() client.disconnect()
}, },

View file

@ -12,7 +12,7 @@ module.exports = {
: x.host : x.host
? 'single' ? 'single'
: 'unknown' : 'unknown'
logger.log({ redis: redisType }, 'creating redis client') logger.debug({ redis: redisType }, 'creating redis client')
return redis.createClient(x) return redis.createClient(x)
}) })
}, },

View file

@ -39,7 +39,7 @@ module.exports = {
// explicitly, and then socket.io will just regard this as a client that // explicitly, and then socket.io will just regard this as a client that
// has not joined any rooms and do a final disconnection. // has not joined any rooms and do a final disconnection.
const roomsToLeave = this._roomsClientIsIn(client) const roomsToLeave = this._roomsClientIsIn(client)
logger.log({ client: client.id, roomsToLeave }, 'client leaving project') logger.debug({ client: client.id, roomsToLeave }, 'client leaving project')
for (const id of roomsToLeave) { for (const id of roomsToLeave) {
const entity = IdMap.get(id) const entity = IdMap.get(id)
this.leaveEntity(client, entity, id) this.leaveEntity(client, entity, id)
@ -63,14 +63,14 @@ module.exports = {
client.join(id) client.join(id)
// is this a new room? if so, subscribe // is this a new room? if so, subscribe
if (beforeCount === 0) { if (beforeCount === 0) {
logger.log({ entity, id }, 'room is now active') logger.debug({ 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) { if (err) {
OError.tag(err, 'error joining', { entity, id }) OError.tag(err, 'error joining', { entity, id })
return callback(err) return callback(err)
} }
logger.log( logger.debug(
{ 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'
) )
@ -81,7 +81,7 @@ module.exports = {
// keep track of the number of listeners // keep track of the number of listeners
metrics.gauge('room-listeners', RoomEvents.eventNames().length) metrics.gauge('room-listeners', RoomEvents.eventNames().length)
} else { } else {
logger.log( logger.debug(
{ client: client.id, entity, id, beforeCount }, { client: client.id, entity, id, beforeCount },
'client joined existing room' 'client joined existing room'
) )
@ -96,7 +96,7 @@ module.exports = {
// This can now happen all the time, as we skip the join for clients that // This can now happen all the time, as we skip the join for clients that
// disconnect before joinProject/joinDoc completed. // disconnect before joinProject/joinDoc completed.
if (!this._clientAlreadyInRoom(client, id)) { if (!this._clientAlreadyInRoom(client, id)) {
logger.log( logger.debug(
{ client: client.id, entity, id }, { client: client.id, entity, id },
'ignoring request from client to leave room it is not in' 'ignoring request from client to leave room it is not in'
) )
@ -104,7 +104,7 @@ module.exports = {
} }
client.leave(id) client.leave(id)
const afterCount = this._clientsInRoom(client, id) const afterCount = this._clientsInRoom(client, id)
logger.log( logger.debug(
{ client: client.id, entity, id, afterCount }, { client: client.id, entity, id, afterCount },
'client left room' 'client left room'
) )
@ -114,7 +114,7 @@ module.exports = {
return return
} }
if (afterCount === 0) { if (afterCount === 0) {
logger.log({ entity, id }, 'room is now empty') logger.debug({ entity, id }, 'room is now empty')
RoomEvents.emit(`${entity}-empty`, id) RoomEvents.emit(`${entity}-empty`, id)
IdMap.delete(id) IdMap.delete(id)
metrics.gauge('room-listeners', RoomEvents.eventNames().length) metrics.gauge('room-listeners', RoomEvents.eventNames().length)

View file

@ -7,6 +7,7 @@ const settings = require('@overleaf/settings')
const WebsocketController = require('./WebsocketController') const WebsocketController = require('./WebsocketController')
const HttpController = require('./HttpController') const HttpController = require('./HttpController')
const HttpApiController = require('./HttpApiController') const HttpApiController = require('./HttpApiController')
const WebsocketAddressManager = require('./WebsocketAddressManager')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const base64id = require('base64id') const base64id = require('base64id')
const { UnexpectedArgumentsError } = require('./Errors') const { UnexpectedArgumentsError } = require('./Errors')
@ -41,7 +42,7 @@ module.exports = Router = {
attrs.err = error attrs.err = error
attrs.method = method attrs.method = method
if (Joi.isError(error)) { if (Joi.isError(error)) {
logger.warn(attrs, 'validation error') logger.info(attrs, 'validation error')
var message = 'invalid' var message = 'invalid'
try { try {
message = error.details[0].message message = error.details[0].message
@ -59,8 +60,8 @@ module.exports = Router = {
const serializedError = { message: error.message, code: error.info.code } const serializedError = { message: error.message, code: error.info.code }
callback(serializedError) callback(serializedError)
} else if (error.message === 'unexpected arguments') { } else if (error.message === 'unexpected arguments') {
// the payload might be very large, put it on level info // the payload might be very large; put it on level debug
logger.log(attrs, 'unexpected arguments') logger.debug(attrs, 'unexpected arguments')
metrics.inc('unexpected-arguments', 1, { status: method }) metrics.inc('unexpected-arguments', 1, { status: method })
const serializedError = { message: error.message } const serializedError = { message: error.message }
callback(serializedError) callback(serializedError)
@ -107,6 +108,15 @@ module.exports = Router = {
configure(app, io, session) { configure(app, io, session) {
app.set('io', io) app.set('io', io)
if (settings.behindProxy) {
app.set('trust proxy', settings.trustedProxyIps)
}
const websocketAddressManager = new WebsocketAddressManager(
settings.behindProxy,
settings.trustedProxyIps
)
app.get('/clients', HttpController.getConnectedClients) app.get('/clients', HttpController.getConnectedClients)
app.get('/clients/:client_id', HttpController.getConnectedClient) app.get('/clients/:client_id', HttpController.getConnectedClient)
@ -180,10 +190,14 @@ module.exports = Router = {
client.publicId = 'P.' + base64id.generateId() client.publicId = 'P.' + base64id.generateId()
client.emit('connectionAccepted', null, client.publicId) client.emit('connectionAccepted', null, client.publicId)
client.remoteIp = websocketAddressManager.getRemoteIp(client.handshake)
const headers = client.handshake && client.handshake.headers
client.userAgent = headers && headers['user-agent']
metrics.inc('socket-io.connection', 1, { status: client.transport }) metrics.inc('socket-io.connection', 1, { status: client.transport })
metrics.gauge('socket-io.clients', io.sockets.clients().length) metrics.gauge('socket-io.clients', io.sockets.clients().length)
logger.log({ session, client_id: client.id }, 'client connected') logger.debug({ session, client_id: client.id }, 'client connected')
let user let user
if (session && session.passport && session.passport.user) { if (session && session.passport && session.passport.user) {

View file

@ -15,7 +15,7 @@ const {
module.exports = { module.exports = {
joinProject(project_id, user, callback) { joinProject(project_id, user, callback) {
const user_id = user._id const user_id = user._id
logger.log({ project_id, user_id }, 'sending join project request to web') logger.debug({ project_id, user_id }, 'sending join project request to web')
const url = `${settings.apis.web.url}/project/${project_id}/join` const url = `${settings.apis.web.url}/project/${project_id}/join`
const headers = {} const headers = {}
if (user.anonymousAccessToken) { if (user.anonymousAccessToken) {

View file

@ -0,0 +1,30 @@
const proxyaddr = require('proxy-addr')
module.exports = class WebsocketAddressManager {
constructor(behindProxy, trustedProxyIps) {
if (behindProxy) {
// parse trustedProxyIps comma-separated list the same way as express
this.trust = proxyaddr.compile(
trustedProxyIps ? trustedProxyIps.split(/ *, */) : []
)
}
}
getRemoteIp(clientHandshake) {
if (this.trust) {
// create a dummy req object using the client handshake and
// connection.remoteAddress for the proxy-addr module to parse
const addressPort = clientHandshake.address
const req = Object.create(clientHandshake, {
connection: {
value: { remoteAddress: addressPort && addressPort.address },
},
})
// return the address parsed from x-forwarded-for
return proxyaddr(req, this.trust)
} else {
// return the address from the client handshake itself
return clientHandshake.address && clientHandshake.address.address
}
}
}

View file

@ -32,8 +32,14 @@ module.exports = WebsocketController = {
} }
const user_id = user._id const user_id = user._id
logger.log( logger.info(
{ user_id, project_id, client_id: client.id }, {
user_id,
project_id,
client_id: client.id,
remote_ip: client.remoteIp,
user_agent: client.userAgent,
},
'user joining project' 'user joining project'
) )
metrics.inc('editor.join-project', 1, { status: client.transport }) metrics.inc('editor.join-project', 1, { status: client.transport })
@ -45,6 +51,10 @@ module.exports = WebsocketController = {
return callback(error) return callback(error)
} }
if (client.disconnected) { if (client.disconnected) {
logger.info(
{ user_id, project_id, client_id: client.id },
'client disconnected before joining project'
)
metrics.inc('editor.join-project.disconnected', 1, { metrics.inc('editor.join-project.disconnected', 1, {
status: 'after-web-api-call', status: 'after-web-api-call',
}) })
@ -72,8 +82,14 @@ module.exports = WebsocketController = {
if (err) { if (err) {
return callback(err) return callback(err)
} }
logger.log( logger.debug(
{ user_id, project_id, client_id: client.id }, {
user_id,
project_id,
client_id: client.id,
privilegeLevel,
isRestrictedUser,
},
'user joined project' 'user joined project'
) )
callback( callback(
@ -114,7 +130,7 @@ module.exports = WebsocketController = {
} // client did not join project } // client did not join project
metrics.inc('editor.leave-project', 1, { status: client.transport }) metrics.inc('editor.leave-project', 1, { status: client.transport })
logger.log( logger.info(
{ project_id, user_id, client_id: client.id }, { project_id, user_id, client_id: client.id },
'client leaving project' 'client leaving project'
) )
@ -171,7 +187,7 @@ module.exports = WebsocketController = {
if (!project_id) { if (!project_id) {
return callback(new NotJoinedError()) return callback(new NotJoinedError())
} }
logger.log( logger.debug(
{ user_id, project_id, doc_id, fromVersion, client_id: client.id }, { user_id, project_id, doc_id, fromVersion, client_id: client.id },
'client joining doc' 'client joining doc'
) )
@ -266,7 +282,7 @@ module.exports = WebsocketController = {
} }
AuthorizationManager.addAccessToDoc(client, doc_id, () => {}) AuthorizationManager.addAccessToDoc(client, doc_id, () => {})
logger.log( logger.debug(
{ {
user_id, user_id,
project_id, project_id,
@ -324,7 +340,7 @@ module.exports = WebsocketController = {
client.joinLeaveEpoch++ client.joinLeaveEpoch++
metrics.inc('editor.leave-doc', 1, { status: client.transport }) metrics.inc('editor.leave-doc', 1, { status: client.transport })
const { project_id, user_id } = client.ol_context const { project_id, user_id } = client.ol_context
logger.log( logger.debug(
{ user_id, project_id, doc_id, client_id: client.id }, { user_id, project_id, doc_id, client_id: client.id },
'client leaving doc' 'client leaving doc'
) )
@ -346,7 +362,7 @@ module.exports = WebsocketController = {
}) })
const { project_id, first_name, last_name, email, user_id } = const { project_id, first_name, last_name, email, user_id } =
client.ol_context client.ol_context
logger.log( logger.debug(
{ user_id, project_id, client_id: client.id, cursorData }, { user_id, project_id, client_id: client.id, cursorData },
'updating client position' 'updating client position'
) )
@ -356,7 +372,7 @@ module.exports = WebsocketController = {
cursorData.doc_id, cursorData.doc_id,
function (error) { function (error) {
if (error) { if (error) {
logger.info( logger.debug(
{ err: error, client_id: client.id, project_id, user_id }, { err: error, client_id: client.id, project_id, user_id },
"silently ignoring unauthorized updateClientPosition. Client likely hasn't called joinProject yet." "silently ignoring unauthorized updateClientPosition. Client likely hasn't called joinProject yet."
) )
@ -420,7 +436,7 @@ module.exports = WebsocketController = {
if (!project_id) { if (!project_id) {
return callback(new NotJoinedError()) return callback(new NotJoinedError())
} }
logger.log( logger.debug(
{ user_id, project_id, client_id: client.id }, { user_id, project_id, client_id: client.id },
'getting connected users' 'getting connected users'
) )
@ -437,7 +453,7 @@ module.exports = WebsocketController = {
if (error) { if (error) {
return callback(error) return callback(error)
} }
logger.log( logger.debug(
{ user_id, project_id, client_id: client.id }, { user_id, project_id, client_id: client.id },
'got connected users' 'got connected users'
) )
@ -477,7 +493,7 @@ module.exports = WebsocketController = {
update.meta.user_id = user_id update.meta.user_id = user_id
metrics.inc('editor.doc-update', 0.3, { status: client.transport }) metrics.inc('editor.doc-update', 0.3, { status: client.transport })
logger.log( logger.debug(
{ {
user_id, user_id,
doc_id, doc_id,

View file

@ -40,7 +40,7 @@ module.exports = WebsocketLoadBalancer = {
message, message,
payload, payload,
}) })
logger.log( logger.debug(
{ room_id, message, payload, length: data.length }, { room_id, message, payload, length: data.length },
'emitting to room' 'emitting to room'
) )
@ -55,7 +55,7 @@ module.exports = WebsocketLoadBalancer = {
}, },
listenForEditorEvents(io) { listenForEditorEvents(io) {
logger.log( logger.debug(
{ rclients: this.rclientSubList.length }, { rclients: this.rclientSubList.length },
'listening for editor events' 'listening for editor events'
) )
@ -102,7 +102,7 @@ module.exports = WebsocketLoadBalancer = {
message.room_id message.room_id
) { ) {
const clientList = io.sockets.clients(message.room_id) const clientList = io.sockets.clients(message.room_id)
logger.log( logger.debug(
{ {
channel, channel,
message: message.message, message: message.message,
@ -142,7 +142,7 @@ module.exports = WebsocketLoadBalancer = {
if (clientList.length === 0) { if (clientList.length === 0) {
return return
} }
logger.log( logger.debug(
{ {
channel, channel,
message: message.message, message: message.message,

View file

@ -162,6 +162,9 @@ const settings = {
catchUncaughtErrors: true, catchUncaughtErrors: true,
shutdownOnUncaughtError: true, shutdownOnUncaughtError: true,
}, },
behindProxy: process.env.BEHIND_PROXY === 'true',
trustedProxyIps: process.env.TRUSTED_PROXY_IPS,
} }
// console.log settings.redis // console.log settings.redis

View file

@ -2484,9 +2484,9 @@
} }
}, },
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-Ua9xNhH0b8pwE3yRbFfXJvfdWF0UHNCdeyb2sbi9Ul/M+r3PTdrz7Cv4SCfZRMjmzEM9PhraqfZFbGTIg3OMyA==" "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
}, },
"fresh": { "fresh": {
"version": "0.5.2", "version": "0.5.2",
@ -4424,11 +4424,11 @@
} }
}, },
"proxy-addr": { "proxy-addr": {
"version": "2.0.6", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"requires": { "requires": {
"forwarded": "~0.1.2", "forwarded": "0.2.0",
"ipaddr.js": "1.9.1" "ipaddr.js": "1.9.1"
} }
}, },

View file

@ -37,6 +37,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"express-session": "^1.17.1", "express-session": "^1.17.1",
"logger-sharelatex": "^2.2.0", "logger-sharelatex": "^2.2.0",
"proxy-addr": "^2.0.7",
"request": "^2.88.2", "request": "^2.88.2",
"socket.io": "https://github.com/overleaf/socket.io/archive/0.9.19-overleaf-5.tar.gz", "socket.io": "https://github.com/overleaf/socket.io/archive/0.9.19-overleaf-5.tar.gz",
"socket.io-client": "https://github.com/overleaf/socket.io-client/archive/0.9.17-overleaf-3.tar.gz", "socket.io-client": "https://github.com/overleaf/socket.io-client/archive/0.9.17-overleaf-3.tar.gz",

View file

@ -30,14 +30,14 @@ describe('leaveDoc', function () {
this.ops = ['mock', 'doc', 'ops'] this.ops = ['mock', 'doc', 'ops']
sinon.spy(logger, 'error') sinon.spy(logger, 'error')
sinon.spy(logger, 'warn') sinon.spy(logger, 'warn')
sinon.spy(logger, 'log') sinon.spy(logger, 'debug')
return (this.other_doc_id = FixturesManager.getRandomId()) return (this.other_doc_id = FixturesManager.getRandomId())
}) })
after(function () { after(function () {
logger.error.restore() // remove the spy logger.error.restore() // remove the spy
logger.warn.restore() logger.warn.restore()
return logger.log.restore() return logger.debug.restore()
}) })
return describe('when joined to a doc', function () { return describe('when joined to a doc', function () {
@ -177,7 +177,7 @@ describe('leaveDoc', function () {
return it('should trigger a low level message only', function () { return it('should trigger a low level message only', function () {
return sinon.assert.calledWith( return sinon.assert.calledWith(
logger.log, logger.debug,
sinon.match.any, sinon.match.any,
'ignoring request from client to leave room it is not in' 'ignoring request from client to leave room it is not in'
) )

View file

@ -42,7 +42,7 @@ module.exports = {
throw error throw error
} }
this.running = true this.running = true
logger.log('clsi running in dev mode') logger.info('clsi running in dev mode')
return (() => { return (() => {
const result = [] const result = []

View file

@ -116,7 +116,7 @@ describe('DrainManager', function () {
}) })
return it('should log out that it reached the end', function () { return it('should log out that it reached the end', function () {
return this.logger.log return this.logger.info
.calledWith('All clients have been told to reconnectGracefully') .calledWith('All clients have been told to reconnectGracefully')
.should.equal(true) .should.equal(true)
}) })