diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4cb4df..a514c825 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +* Add listener for container events and reattach on restart [Pablo] * fix deltas by not using the supervisor as source [Pablo] # v1.11.2 diff --git a/package.json b/package.json index 22c7e46b..b1ec92fe 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "start": "./entry.sh" }, "dependencies": { + "JSONStream": "^1.1.2", "blinking": "~0.0.2", "bluebird": "^2.9.24", "body-parser": "^1.12.0", diff --git a/src/application.coffee b/src/application.coffee index 1dffff5a..889a4ebe 100644 --- a/src/application.coffee +++ b/src/application.coffee @@ -14,6 +14,7 @@ lockFile = Promise.promisifyAll(require('lockfile')) bootstrap = require './bootstrap' TypedError = require 'typed-error' fs = Promise.promisifyAll(require('fs')) +JSONStream = require 'JSONStream' class UpdatesLockedError extends TypedError @@ -67,6 +68,14 @@ logTypes = eventName: 'Application update error' humanName: 'Failed to update application' + appExit: + eventName: 'Application exit' + humanName: 'Application exited' + + appRestart: + eventName: 'Application restart' + humanName: 'Restarting application' + logSystemMessage = (message, obj, eventName) -> logger.log({ message, isSystem: true }) utils.mixpanelTrack(eventName ? message, obj) @@ -644,7 +653,38 @@ application.update = update = (force) -> # Set the updating as finished in its own block, so it never has to worry about other code stopping this. updateStatus.state = UPDATE_IDLE +listenToEvents = do -> + appHasDied = {} + return -> + docker.getEventsAsync() + .then (stream) -> + stream.on 'error', (err) -> + console.error('Error on docker events stream:', err, err.stack) + parser = JSONStream.parse() + parser.on 'error', (err) -> + console.error('Error on docker events JSON stream:', err, err.stack) + parser.on 'data', (data) -> + if data?.Type? && data.Type == 'container' && data.status in ['die', 'start'] + knex('app').select().where({ containerId: data.id }) + .then ([ app ]) -> + if app? + if data.status == 'die' + logSystemEvent(logTypes.appExit, app) + appHasDied[app.containerId] = true + else if data.status == 'start' and appHasDied[app.containerId] + logSystemEvent(logTypes.appRestart, app) + logger.attach(app) + .catch (err) -> + console.error('Error on docker event:', err, err.stack) + parser.on 'end', -> + console.error('Docker events stream ended, this should never happen') + listenToEvents() + stream.pipe(parser) + .catch (err) -> + console.error('Error listening to events:', err, err.stack) + application.initialize = -> + listenToEvents() knex('app').select() .then (apps) -> Promise.map apps, (app) -> diff --git a/src/lib/logger.coffee b/src/lib/logger.coffee index 056b1438..85e4c211 100644 --- a/src/lib/logger.coffee +++ b/src/lib/logger.coffee @@ -51,9 +51,18 @@ exports.disableLogPublishing = (disable) -> exports.log = -> publish(arguments...) -exports.attach = (app) -> - dockerPromise.then (docker) -> - docker.getContainer(app.containerId) - .attachAsync({ stream: true, stdout: true, stderr: true, tty: true }) - .then (stream) -> - stream.pipe(es.split()).on('data', publish) +do -> + attached = {} + exports.attach = (app) -> + if !attached[app.containerId] + dockerPromise.then (docker) -> + docker.getContainer(app.containerId) + .attachAsync({ stream: true, stdout: true, stderr: true, tty: true }) + .then (stream) -> + attached[app.containerId] = true + stream.pipe(es.split()) + .on('data', publish) + .on 'error', -> + attached[app.containerId] = false + .on 'end', -> + attached[app.containerId] = false