UNPKG

pimatic-log-reader

Version:

Provides predicates for log entries in log files of other programs.

226 lines (190 loc) 7.33 kB
module.exports = (env) -> # ##Dependencies # * from node.js util = require 'util' # * pimatic imports. Promise = env.require 'bluebird' assert = env.require 'cassert' _ = env.require 'lodash' M = env.matcher Tail = env.Tail or require('tail').Tail t = env.require('decl-api').types LineByLineReader = require("line-by-line") # ##The LogReaderPlugin class LogReaderPlugin extends env.plugins.Plugin init: (app, @framework, @config) -> @framework.ruleManager.addPredicateProvider new LogWatcherPredicateProvider(@framework) deviceConfigDef = require("./device-config-schema") @framework.deviceManager.registerDeviceClass("LogWatcher", { configDef: deviceConfigDef.LogWatcher, createCallback: (config, lastState) => return new LogWatcher(config, lastState) }) plugin = new LogReaderPlugin # ##LogWatcher Sensor class LogWatcher extends env.devices.Sensor constructor: (@config, lastState) -> @id = @config.id @name = @config.name if @config.template? @template = @config.template @attributeValue = {} @changedAttributeValue = {} @attributes = {} # initialise all attributes for attr, i in @config.attributes do (attr) => # legacy support if typeof attr is "string" attr = { name: attr type: "string" } @config.attributes[i] = attr name = attr.name assert attr.name? assert attr.type? lastValue = lastState?[name]?.value unless typeof lastValue is attr.type lastValue = null switch attr.type when "string" # that the value to 'unknown' @attributeValue[name] = lastValue # Get all possible values possibleValues = _.map(_.filter(@config.lines, (l) => l[name]?), (l) => l[name]) # Add attribute definition @attributes[name] = description: name type: t.string enum: possibleValues when "number" @attributeValue[name] = lastValue @attributes[name] = description: name type: t.number @attributes[name].unit = attr.unit if attr.unit? when "boolean" @attributeValue[name] = lastValue @attributes[name] = description: name type: t.boolean else throw new Error("Illegal type: #{attr.type} for attributes #{name} in LogWatcher.") for property in ['label', 'acronym', 'discrete'] @attributes[name][property] = attr[property] if attr[property]? if _.isArray attr.labels @attributes[name].labels = attr.labels # Create a getter for this attribute @_createGetter name, ( => Promise.resolve @attributeValue[name] ) onLine = (data) => # check all lines in config for line in @config.lines # for a match. matches = new RegExp(line.match).exec(data) if matches? # If a match occures then emit a "match"-event. @emit 'match', line, data, matches return @_tailing = no onMatch = (line, data, matches) => # then check for each prop in the config for attr in @config.attributes # if the attr is registered for the log line. if attr.name of line # When a value for the attr is defined, then set the value # and emit the event. valueToSet = line[attr.name] value = null if attr.type is "boolean" value = line[attr.name] else matchesRegexValue = valueToSet.match(/\$(\d+)/) if matchesRegexValue? value = matches[parseInt(matchesRegexValue[1], 10)] else value = line[attr.name] if attr.type is "number" then value = parseFloat(value) if @_tailing @attributeValue[attr.name] = value @emit(attr.name, value) else if @attributeValue[attr.name] isnt value @attributeValue[attr.name] = value @changedAttributeValue[attr.name] = value return # When a match event occurs @on 'match', onMatch # read the file to get initial values: retryTimeout = 0 reader = () => retryTimeout += 10000 if retryTimeout <= 60000 @lr.removeAllListeners() if @lr? @lr = new LineByLineReader(@config.file) @lr.on "error", (err) -> env.logger.error err.message env.logger.debug err.stack setTimeout reader, retryTimeout @lr.on "open", => retryTimeout = 0 @lr.on "line", onLine @lr.on "end", => @_tailing = yes for attrName, value of @changedAttributeValue @emit(attrName, value) @changedAttributeValue = {} # If we have read the full file then tail the file @tail = new Tail(@config.file) # On every new line in the log file @tail.on 'line', onLine @tail.on 'error', (errMessage) => # Tail passes an error message string errMessage = errMessage.replace ': undefined', '' if @tail? @tail.unwatch() @tail.removeAllListeners() @lr.emit "error", new Error(errMessage) reader() super() destroy: () -> @removeAllListeners 'match' @lr.close() if @lr? @tail.unwatch() if @tail? super() class LogWatcherPredicateProvider extends env.predicates.PredicateProvider listener: [] constructor: (@framework) -> parsePredicate: (input, context) -> for id, d of @framework.deviceManager.devices if d instanceof LogWatcher info = @_getLineWithPredicate d.config, input, context if info? return { token: info.token nextInput: input.substring(info.token.length) predicateHandler: new LogWatcherPredicateHandler(this, d, info.line) } return null _getLineWithPredicate: (config, input, context) -> for line in config.lines if line.predicate? m = M(input, context).match(line.predicate) if m.hadMatch() match = m.getFullMatch() return {line, token: match, nextInput: input.substring(match.length)} return null class LogWatcherPredicateHandler extends env.predicates.PredicateHandler constructor: (@provider, @device, @line) -> setup: -> @deviceListener = (line, data) => if @device._tailing and line.match is @line.match then @emit('change', 'event') @device.addListener 'match', @deviceListener super() getValue: -> Promise.resolve(false) destroy: -> @device.removeListener 'match', @deviceListener super() getType: -> 'event' # For testing... @LogReaderPlugin = LogReaderPlugin @LogWatcherPredicateProvider = LogWatcherPredicateProvider # Export the plugin. return plugin