mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-01-21 03:55:23 +00:00
Logger: implement a module that handles all logging to pubnub
This module can also send logs for dependent devices (by passing a specific channel to the "log" function). The log types are also moved to a separate module to be used by modules that perform logging. Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
parent
93832d6540
commit
2953b745ce
122
src/lib/log-types.coffee
Normal file
122
src/lib/log-types.coffee
Normal file
@ -0,0 +1,122 @@
|
||||
module.exports =
|
||||
stopService:
|
||||
eventName: 'Service kill'
|
||||
humanName: 'Killing service'
|
||||
stopServiceSuccess:
|
||||
eventName: 'Service stop'
|
||||
humanName: 'Killed service'
|
||||
stopServiceNoop:
|
||||
eventName: 'Service already stopped'
|
||||
humanName: 'Service is already stopped, removing container'
|
||||
stopRemoveServiceNoop:
|
||||
eventName: 'Service already stopped and container removed'
|
||||
humanName: 'Service is already stopped and the container removed'
|
||||
stopServiceError:
|
||||
eventName: 'Service stop error'
|
||||
humanName: 'Failed to kill service'
|
||||
|
||||
downloadImage:
|
||||
eventName: 'Docker image download'
|
||||
humanName: 'Downloading image'
|
||||
downloadImageDelta:
|
||||
eventName: 'Delta image download'
|
||||
humanName: 'Downloading delta for image'
|
||||
downloadImageSuccess:
|
||||
eventName: 'Image downloaded'
|
||||
humanName: 'Downloaded image'
|
||||
downloadImageError:
|
||||
eventName: 'Image download error'
|
||||
humanName: 'Failed to download image'
|
||||
|
||||
installService:
|
||||
eventName: 'Service install'
|
||||
humanName: 'Installing service'
|
||||
installServiceSuccess:
|
||||
eventName: 'Service installed'
|
||||
humanName: 'Installed service'
|
||||
installServiceError:
|
||||
eventName: 'Service install error'
|
||||
humanName: 'Failed to install service'
|
||||
|
||||
deleteImage:
|
||||
eventName: 'Image removal'
|
||||
humanName: 'Deleting image'
|
||||
deleteImageSuccess:
|
||||
eventName: 'Image removed'
|
||||
humanName: 'Deleted image'
|
||||
deleteImageError:
|
||||
eventName: 'Image removal error'
|
||||
humanName: 'Failed to delete image'
|
||||
imageAlreadyDeleted:
|
||||
eventName: 'Image already deleted'
|
||||
humanName: 'Image already deleted'
|
||||
|
||||
startService:
|
||||
eventName: 'Service start'
|
||||
humanName: 'Starting service'
|
||||
startServiceSuccess:
|
||||
eventName: 'Service started'
|
||||
humanName: 'Started service'
|
||||
startServiceNoop:
|
||||
eventName: 'Service already running'
|
||||
humanName: 'Service is already running'
|
||||
startServiceError:
|
||||
eventName: 'Service start error'
|
||||
humanName: 'Failed to start service'
|
||||
|
||||
updateService:
|
||||
eventName: 'Service update'
|
||||
humanName: 'Updating service'
|
||||
updateServiceError:
|
||||
eventName: 'Service update error'
|
||||
humanName: 'Failed to update service'
|
||||
|
||||
serviceExit:
|
||||
eventName: 'Service exit'
|
||||
humanName: 'Service exited'
|
||||
|
||||
serviceRestart:
|
||||
eventName: 'Service restart'
|
||||
humanName: 'Restarting service'
|
||||
|
||||
updateServiceConfig:
|
||||
eventName: 'Service config update'
|
||||
humanName: 'Updating config for service'
|
||||
updateServiceConfigSuccess:
|
||||
eventName: 'Service config updated'
|
||||
humanName: 'Updated config for service'
|
||||
updateServiceConfigError:
|
||||
eventName: 'Service config update error'
|
||||
humanName: 'Failed to update config for service'
|
||||
|
||||
createVolume:
|
||||
eventName: 'Volume creation'
|
||||
humanName: 'Creating volume'
|
||||
|
||||
createVolumeError:
|
||||
eventName: 'Volume creation error'
|
||||
humanName: 'Error creating volume'
|
||||
|
||||
removeVolume:
|
||||
eventName: 'Volume removal'
|
||||
humanName: 'Removing volume'
|
||||
|
||||
removeVolumeError:
|
||||
eventName: 'Volume removal error'
|
||||
humanName: 'Error removing volume'
|
||||
|
||||
createNetwork:
|
||||
eventName: 'Network creation'
|
||||
humanName: 'Creating network'
|
||||
|
||||
createNetworkError:
|
||||
eventName: 'Network creation error'
|
||||
humanName: 'Error creating network'
|
||||
|
||||
removeNetwork:
|
||||
eventName: 'Network removal'
|
||||
humanName: 'Removing network'
|
||||
|
||||
removeNetworkError:
|
||||
eventName: 'Network removal error'
|
||||
humanName: 'Error removing network'
|
158
src/logger.coffee
Normal file
158
src/logger.coffee
Normal file
@ -0,0 +1,158 @@
|
||||
_ = require 'lodash'
|
||||
PUBNUB = require 'pubnub'
|
||||
Promise = require 'bluebird'
|
||||
es = require 'event-stream'
|
||||
Lock = require 'rwlock'
|
||||
{ checkTruthy } = require './lib/validation'
|
||||
|
||||
LOG_PUBLISH_INTERVAL = 110
|
||||
|
||||
# Pubnub's message size limit is 32KB (unclear on whether it's KB or actually KiB,
|
||||
# but we'll be conservative). So we limit a log message to 2 bytes less to account
|
||||
# for the [ and ] in the array.
|
||||
MAX_LOG_BYTE_SIZE = 30000
|
||||
MAX_MESSAGE_INDEX = 9
|
||||
|
||||
module.exports = class Logger
|
||||
constructor: ({ @eventTracker }) ->
|
||||
@publishQueue = [[]]
|
||||
@messageIndex = 0
|
||||
@publishQueueRemainingBytes = MAX_LOG_BYTE_SIZE
|
||||
@logsOverflow = false
|
||||
_lock = new Lock()
|
||||
@_writeLock = Promise.promisify(_lock.async.writeLock)
|
||||
@attached = {}
|
||||
@offlineMode = false
|
||||
@publishEnabled = false
|
||||
|
||||
init: ({ pubnub, channel, offlineMode, enable }) =>
|
||||
Promise.try =>
|
||||
@pubnub = PUBNUB.init(pubnub)
|
||||
@channel = channel
|
||||
@publishEnabled = checkTruthy(enable)
|
||||
@offlineMode = checkTruthy(offlineMode)
|
||||
setInterval(@doPublish, LOG_PUBLISH_INTERVAL)
|
||||
|
||||
enable: (val) =>
|
||||
@publishEnabled = checkTruthy(val) ? true
|
||||
|
||||
doPublish: =>
|
||||
return if @offlineMode or !@publishEnabled or @publishQueue[0].length is 0
|
||||
message = @publishQueue.shift()
|
||||
@pubnub.publish({ @channel, message })
|
||||
if @publishQueue.length is 0
|
||||
@publishQueue = [[]]
|
||||
@publishQueueRemainingBytes = MAX_LOG_BYTE_SIZE
|
||||
@messageIndex = Math.max(@messageIndex - 1, 0)
|
||||
@logsOverflow = false if @messageIndex < MAX_MESSAGE_INDEX
|
||||
|
||||
publish: (message) =>
|
||||
# Disable sending logs for bandwidth control
|
||||
return if @offlineMode or !@publishEnabled or (@messageIndex >= MAX_MESSAGE_INDEX and @publishQueueRemainingBytes <= 0)
|
||||
if _.isString(message)
|
||||
message = { m: message }
|
||||
|
||||
_.defaults message,
|
||||
t: Date.now()
|
||||
m: ''
|
||||
msgLength = Buffer.byteLength(encodeURIComponent(JSON.stringify(message)), 'utf8')
|
||||
return if msgLength > MAX_LOG_BYTE_SIZE # Unlikely, but we can't allow this
|
||||
remaining = @publishQueueRemainingBytes - msgLength
|
||||
if remaining >= 0
|
||||
@publishQueue[@messageIndex].push(message)
|
||||
@publishQueueRemainingBytes = remaining
|
||||
else if @messageIndex < MAX_MESSAGE_INDEX
|
||||
@messageIndex += 1
|
||||
@publishQueue[@messageIndex] = [ message ]
|
||||
@publishQueueRemainingBytes = MAX_LOG_BYTE_SIZE - msgLength
|
||||
else if !@logsOverflow
|
||||
@logsOverflow = true
|
||||
@messageIndex += 1
|
||||
@publishQueue[@messageIndex] = [ { m: 'Warning! Some logs dropped due to high load', t: Date.now(), s: 1 } ]
|
||||
@publishQueueRemainingBytes = 0
|
||||
|
||||
# log allows publishing logs through the regular way which includes a queue and has a specific channel
|
||||
# or, when a channel is specified, publishing directly to that channel
|
||||
log: (msg, opts = {}) =>
|
||||
if opts.channel?
|
||||
@pubnub.publish({ channel: opts.channel, message: msg })
|
||||
else
|
||||
@publish(msg)
|
||||
|
||||
logSystemMessage: (message, obj, eventName) =>
|
||||
messageObj = { m: message, s: 1 }
|
||||
if obj?.serviceId?
|
||||
messageObj.c = obj.serviceId
|
||||
@log(messageObj)
|
||||
@eventTracker.track(eventName ? message, obj)
|
||||
|
||||
lock: (containerId) =>
|
||||
@_writeLock(containerId)
|
||||
.disposer (release) ->
|
||||
release()
|
||||
|
||||
attach: (docker, containerId, serviceId) =>
|
||||
Promise.using @lock(containerId), =>
|
||||
if !@attached[containerId]
|
||||
docker.getContainer(containerId)
|
||||
.logs({ follow: true, stdout: true, stderr: true, timestamps: true })
|
||||
.then (stream) =>
|
||||
@attached[containerId] = true
|
||||
stream
|
||||
.on 'error', (err) =>
|
||||
console.error('Error on container logs', err, err.stack)
|
||||
@attached[containerId] = false
|
||||
.pipe(es.split())
|
||||
.on 'data', (logLine) =>
|
||||
space = logLine.indexOf(' ')
|
||||
if space > 0
|
||||
msg = { t: logLine.substr(0, space), m: logLine.substr(space + 1), c: serviceId }
|
||||
@publish(msg)
|
||||
.on 'error', (err) =>
|
||||
console.error('Error on container logs', err, err.stack)
|
||||
@attached[containerId] = false
|
||||
.on 'end', =>
|
||||
@attached[containerId] = false
|
||||
|
||||
objectNameForLogs: (obj = {}) ->
|
||||
if obj.service?.serviceName? and obj.service?.image?
|
||||
return "#{obj.service.serviceName} #{obj.service.image}"
|
||||
else if obj.image?
|
||||
return obj.image.name
|
||||
else if obj.network?.name?
|
||||
return obj.network.name
|
||||
else if obj.volume?.name?
|
||||
return obj.volume.name
|
||||
else
|
||||
return null
|
||||
|
||||
logSystemEvent: (logType, obj = {}) ->
|
||||
message = "#{logType.humanName}"
|
||||
objName = @objectNameForLogs(obj)
|
||||
message += " '#{objName}'" if objName?
|
||||
if obj.error?
|
||||
# Report the message from the original cause to the user.
|
||||
errMessage = obj.error.message
|
||||
if _.isEmpty(errMessage)
|
||||
errMessage = obj.error.json
|
||||
if _.isEmpty(errMessage)
|
||||
errMessage = obj.error.reason
|
||||
if _.isEmpty(errMessage)
|
||||
errMessage = 'Unknown cause'
|
||||
message += " due to '#{errMessage}'"
|
||||
@logSystemMessage(message, obj, logType.eventName)
|
||||
return
|
||||
|
||||
logConfigChange: (config, { success = false, err = null } = {}) ->
|
||||
obj = { config }
|
||||
if success
|
||||
msg = "Applied configuration change #{JSON.stringify(config)}"
|
||||
eventName = 'Apply config change success'
|
||||
else if err?
|
||||
msg = "Error applying configuration change: #{err}"
|
||||
eventName = 'Apply config change error'
|
||||
obj.error = err
|
||||
else
|
||||
msg = "Applying configuration change #{JSON.stringify(config)}"
|
||||
eventName = 'Apply config change in progress'
|
||||
@logSystemMessage(msg, obj, eventName)
|
Loading…
Reference in New Issue
Block a user