Gandi/hubot-pager-v2

View on GitHub
lib/pagerv2.coffee

Summary

Maintainability
Test Coverage
# Description:
#   handles communication with PagerDuty API v2
#
# Dependencies:
#
# Configuration:
#  PAGERV2_API_KEY
#  PAGERV2_SCHEDULE_ID       # the schedule used for oncall and overrides
#  PAGERV2_OVERRIDERS        # list of user_id that can be targets of overrides
#  PAGERV2_SERVICES          # list of services that are concerned by massive maintenance
#  PAGERV2_DEFAULT_RESOLVER  # name of the default user for resolution (ex. nagios)
#  PAGERV2_LOG_PATH          # dir where are saved error logs
#  PAGERV2_CUSTOM_ACTION     # listen for custom action (see README.md)
#
#
# Author:
#   mose
#   kolo

fs = require 'fs'
path = require 'path'
https = require 'https'
moment = require 'moment'
Promise = require 'bluebird'
querystring = require 'querystring'

class Pagerv2

  constructor: (@robot) ->
    @robot.brain.data.pagerv2 ?= {
      users: { },
      services: { },
      custom: { },
      custom_name: { }
    }
    @robot.brain.data.pagerv2.custom ?= { }
    @robot.brain.data.pagerv2.custom_name ?= { }
    @robot.brain.data.pagerv2.services ?= { }
    @robot.brain.data.pagerv2.schedules ?= { }
    @robot.brain.data.pagerv2.users ?= { }
    @pagerServices = [ ]
    if process.env.PAGERV2_SERVICES?
      for service in process.env.PAGERV2_SERVICES.split(',')
        @pagerServices.push(service)
    @logger = @robot.logger
    if process.env.PAGERV2_CUSTOM_ACTION_FILE?
      content = fs.readFileSync(process.env.PAGERV2_CUSTOM_ACTION_FILE)
      @robot.brain.data.pagerv2.custom = JSON.parse(content)
      @robot.brain.data.pagerv2.custom_name = { }
      for _, value of @robot.brain.data.pagerv2.custom
        if value.name?
          @robot.brain.data.pagerv2.custom_name[value.name] = value
    @logger.debug 'Pagerv2 Loaded'
    if process.env.PAGERV2_LOG_PATH?
      @errorlog = path.join process.env.PAGERV2_LOG_PATH, 'pagerv2-error.log'

  getPermission: (user, group) =>
    return new Promise (res, err) =>
      isAuthorized = @robot.auth?.hasRole(user, [group, 'pageradmin']) or
                     @robot.auth?.isAdmin(user)
      if process.env.PAGERV2_NEED_GROUP_AUTH? and
         process.env.PAGERV2_NEED_GROUP_AUTH isnt '0' and
         @robot.auth? and
         not(isAuthorized)
        err "You don't have permission to do that."
      else
        res()

  request: (method, endpoint, query, from = 'noc@gandi.net', retry_count) ->
    return new Promise (res, err) =>
      if process.env.PAGERV2_API_KEY?
        auth = "Token token=#{process.env.PAGERV2_API_KEY}"
        body = JSON.stringify(query)
        if method is 'GET'
          qs = querystring.stringify(query)
          if qs isnt ''
            endpoint += "?#{qs}"
        options = {
          hostname: 'api.pagerduty.com'
          port: 443
          method: method
          path: endpoint
          headers: {
            Authorization: "#{auth}",
            Accept: 'application/vnd.pagerduty+json;version=2',
            'Content-Type': 'application/json'
          }
        }
        if from?
          options.headers.From = from
        req = https.request options, (response) =>
          data = []
          response.on 'data', (chunk) ->
            data.push chunk
          response.on 'end', =>
            if data.length > 0
              try
                json_data = JSON.parse(data.join(''))
                if json_data.error?
                  err "#{json_data.error.code} #{json_data.error.message}"
                else
                  res json_data
              catch e
                @robot.logger.error 'unable to parse answer'
                @robot.logger.error "query was : #{method} #{req.path}"
                if response.statusCode >= 429 and retry_count > 0 # 429 as "too many requests"
                  retry_count--
                  @request(method, endpoint, query, from, retry_count)
                  .then (rdata) ->
                    res rdata
                  .catch (rdata) ->
                    err rdata
                else
                  err 'Unable to read request output'
            else
              res { }
        req.on 'error', (error) ->
          err "#{error.code} #{error.message}"
        if method is 'PUT' or method is 'POST'
          req.write body
        req.end()
      else
        err 'PAGERV2_API_KEY is not set in your environment.'

  getUser: (from, user) =>
    return new Promise (res, err) =>
      unless user.id?
        user.id = user.name
      if @robot.brain.data.pagerv2.users[user.id]?.pagerid?
        res @robot.brain.data.pagerv2.users[user.id].pagerid
      else
        @robot.brain.data.pagerv2.users[user.id] ?= {
          name: user.name,
          id: user.id
        }
        email = @robot.brain.data.pagerv2.users[user.id].email or user.email_address
        unless email
          err @_ask_for_email(from, user)
        else
          user = @robot.brain.data.pagerv2.users[user.id]
          query = { 'query': email }
          @request('GET', '/users', query)
          .then (body) =>
            if body.users[0]?
              @robot.brain.data.pagerv2.users[user.id].pagerid = body.users[0].id
              res body.users[0].id
            else
              err "Sorry, I cannot find #{email}"

  getUserEmail: (from, user) ->
    return new Promise (res, err) =>
      unless user.id?
        user.id = user.name
      email = @robot.brain.data.pagerv2.users[user.id]?.email or user.email_address
      if email?
        res email
      else
        err @_ask_for_email(from, user)

  setUser: (user, email) =>
    return new Promise (res, err) =>
      unless user.id?
        user.id = user.name
      @robot.brain.data.pagerv2.users[user.id] ?= {
        name: user.name,
        email: email,
        id: user.id
      }
      user = @robot.brain.data.pagerv2.users[user.id]
      query = { 'query': email }
      @request('GET', '/users', query)
      .then (body) =>
        if body.users[0]?
          @robot.brain.data.pagerv2.users[user.id].pagerid = body.users[0].id
          @robot.brain.data.pagerv2.users[user.id].email = email
          res body.users[0].id
        else
          err "Sorry, I cannot find #{email}"
      .catch (e) ->
        err e

  _ask_for_email: (from, user) ->
    if from.name is user.name
      "Sorry, I can't figure out your email address :( " +
      'Can you tell me with `.pager me as <email>`?'
    else
      if @robot.auth? and (@robot.auth.hasRole(from, ['pageradmin']) or
         @robot.auth.isAdmin(from))
        "Sorry, I can't figure #{user.name} email address. " +
        "Can you help me with `.pager #{user.name} as <email>`?"
      else
        "Sorry, I can't figure #{user.name} email address. " +
        'Can you ask them to `.pager me as <email>`?'
 
  getSchedules: ->
    return @request('GET', '/schedules')

  getScheduleIdByName: (name) ->
    new Promise (res, err) =>
      if @robot.brain.data.pagerv2.schedules[name]?
        res @robot.brain.data.pagerv2.schedules[name]
      else
        @request('GET', '/schedules')
        .then (body) =>
          for schedule in body.schedules
            @robot.brain.data.pagerv2.schedules[schedule.name] = schedule.id
            if schedule.name is name
              res schedule.id
              return
          throw new Error("no matching \"#{name}\" schedule found")
        .catch (e) ->
          err "#{e}"
  
  getSchedule: (schedule_id = process.env.PAGERV2_SCHEDULE_ID) ->
    @request('GET', "/schedules/#{schedule_id}")
    .then (body) ->
      body.schedule

  getOverride: (schedule_id = process.env.PAGERV2_SCHEDULE_ID) ->
    query = {
      since: moment().format(),
      until: moment().add(1, 'minutes').format(),
      editable: 'true',
      overflow: 'true'
    }
    @request('GET', "/schedules/#{schedule_id}/overrides", query)
    .then (body) ->
      body.overrides
      
  getFirstOncall: (fromtime = null, schedule_id = process.env.PAGERV2_SCHEDULE_ID) ->
    @getOncall(fromtime, schedule_id)
    .then (data) ->
      return data[0]

  printOncall: (oncall, schedule = false) ->
    nowDate = moment().utc()
    endDate = moment(oncall.end).utc()
    if nowDate.isSame(endDate, 'day')
      endDate = endDate.format('HH:mm')
    else
      endDate = endDate.format('dddd HH:mm')
    if schedule
      in_schedule = " in #{oncall.schedule.summary}."
    else
      in_schedule = '.'
    return "#{oncall.user.summary} is on call until #{endDate} (utc)#{in_schedule}"

  getOncall: (fromtime = null, schedule_id = process.env.PAGERV2_SCHEDULE_ID) ->
    query = {
      time_zone: 'UTC',
      'schedule_ids[]': schedule_id,
      earliest: 'true'
    }
    if fromtime?
      query['since'] = moment(fromtime).utc().add(1, 'minutes').format()
      query['until'] = moment(fromtime).utc().add(2, 'minutes').format()
    @request('GET', '/oncalls', query)
    .then (body) ->
      body.oncalls

  setOverride: (from,
  who,
  duration = null,
  start = null,
  schedule_id = process.env.PAGERV2_SCHEDULE_ID) ->
    return new Promise (res, err) =>
      if duration? and duration > 1440
        err 'Sorry you cannot set an override of more than 1 day.'
      else
        if not who? or not who.name? or who.name is 'me'
          who = { name: from.name }
        if who?
          @getUser(from, who)
          .bind({ id: null })
          .then (id) =>
            @id = id
            @getFirstOncall(start)
          .then (data) =>
            query = { override: { } }
            if @id is data.user.id
              err "Sorry, you can't override yourself"
            else
              if start?
                momentStart = moment(start).utc()
                data.start = start
              else
                momentStart = moment().utc()
              query.override.start = momentStart.format()
              if not start and duration?
                duration = parseInt duration
                query.override.end = momentStart.add(duration, 'minutes').format()
              else
                query.override.end = moment(data.end).utc().format()
              query.override.user = {
                'id': "#{@id}",
                'type': 'user_reference'
              }
              @request('POST', "/schedules/#{schedule_id}/overrides", query)
              .then (body) ->
                body.override.over = {
                  name: who.name or who,
                  from: data.user.summary,
                  start: data.start,
                  end: data.end
                }
                res body.override
          .catch (error) ->
            err error

  dropOverride: (from, who) ->
    return new Promise (res, err) =>
      schedule_id = process.env.PAGERV2_SCHEDULE_ID
      if not who? or not who.name? or who.name is 'me'
        who = { name: from.name }
      if who?
        @getUser(from, who)
        .bind({ id: null })
        .then (id) =>
          @id = id
          @getOverride()
        .then (data) =>
          if data
            todo = null
            for over in data
              if over.user.id is @id
                todo = over.id
            if todo?
              @request('DELETE', "/schedules/#{schedule_id}/overrides/#{todo}")
              .then (data) ->
                res data
              .catch (e) ->
                err e
            else
              res null
          else
            res null

  getIncidentWithAlerts: (incident) =>
    @request('GET', "/incidents/#{incident.id}/alerts")
    .then (data) ->
      incident.alerts = data.alerts
      return incident
    .catch (e) =>
      @robot.logger.debug(e)
      incident.alerts = []
      return incident

  getIncident: (incident) ->
    @request('GET', "/incidents/#{incident}")

  listIncidents: (
    incidents = '',
    statuses = 'triggered,acknowledged',
    date_since = null,
    date_until = null,
    limit = 100
  ) ->
    if incidents isnt ''
      new Promise (res, err) ->
        res {
          incidents: incidents.split(/[, ]+/).map (inc) ->
            { id: inc }
          }
    else
      query = {
        time_zone: 'UTC',
        'urgencies[]': 'high',
        sort_by: 'created_at'
      }
      if date_since?
        unless date_until?
          date_until = moment().utc()
        query['since'] = moment(date_since).utc().format()
        query['until'] = moment(date_until).utc().format()
      else
        query['date_range'] = 'all'
      if statuses?
        query['statuses[]'] = statuses.split /,/
      query['limit'] = limit
      query['total'] = 'true'
      @request('GET', '/incidents', query)
      .then (data) =>
        if data.total > 100
          pages = Math.floor(data.total / 100)
          Promise.each [1..pages], (offset) =>
            query['offset'] =  offset * 100
            @request('GET', '/incidents', query)
            .then (page) ->
              data.incidents = data.incidents.concat(page.incidents)
          .then ->
            data
        else
          data

  completeIncidentWithNotes: (incident) =>
    @listNotes(incident.id)
    .then (payload) ->
      incident.notes = payload.notes
      return incident

  listIncidentsWithNotes: (
    incidents = '',
    statuses = 'triggered,acknowledged',
    date_since = null,
    date_until = null,
    limit = 100
  ) ->
    @listIncidents(incidents, statuses, date_since, date_until, limit)
    .then (data) =>
      alldata = Promise.map(data.incidents, @completeIncidentWithNotes, { concurrency: 2 })
      Promise.all(alldata)
      .then (incidents) ->
        { incidents: incidents }

  upagerateIncidents: (user, incidents = '', which = 'triggered', status = 'acknowledged') ->
    @getUserEmail(user, user)
    .bind({ from: null })
    .then (email) =>
      @from = email
      @listIncidents incidents, which
    .then (data) =>
      if data.incidents.length > 0
        payload = {
          incidents: []
        }
        for inc in data.incidents
          payload.incidents.push {
            id: inc.id,
            type: 'incident_reference',
            status: status
          }
        @request('PUT', '/incidents', payload, @from)
      else
        throw { message: "There is no #{which} incidents at the moment." }

  assignIncidents: (user, who, incidents = '') ->
    @getUserEmail(user, user)
    .bind({ from: null })
    .bind({ assignees: null })
    .then (email) =>
      @from = email
      assigneesDone = Promise.map who.split(/, ?/), (assignee) =>
        @getUser(user, { name: assignee })
      Promise.all assigneesDone
    .then (assignees) =>
      @assignees = assignees
      @listIncidents incidents
    .then (data) =>
      if data.incidents.length > 0
        payload = {
          incidents: []
        }
        for inc in data.incidents
          assignments = []
          for a in @assignees
            assignments.push {
              assignee: {
                id: a,
                type: 'user_reference'
              }
            }
          payload.incidents.push {
            id: inc.id,
            type: 'incident_reference',
            assignments: assignments
          }
        @request('PUT', '/incidents', payload, @from)
      else
        throw { message: 'There is no incidents at the moment.' }

  snoozeIncidents: (user, incidents = '', duration = 120) ->
    @getUserEmail(user, user)
    .bind({ from: null })
    .then (email) =>
      @from = email
      @listIncidents incidents
    .then (data) =>
      if data.incidents.length > 0
        incidentsDone = Promise.map data.incidents, (inc) =>
          payload = {
            duration: +duration * 60
          }
          @request('POST', "/incidents/#{inc.id}/snooze", payload, @from)
        Promise.all incidentsDone
      else
        throw { message: 'There is no open incidents at the moment.' }

  addNote: (user, incident, note) ->
    @getUserEmail(user, user)
    .then (email) =>
      payload = {
        note: {
          content: note
        }
      }
      @request('POST', "/incidents/#{incident}/notes", payload, email)

  listNotes: (incident) ->
    @request('GET', "/incidents/#{incident}/notes", undefined, undefined, 1)

  listMaintenances: ->
    query = {
      filter: 'ongoing'
    }
    @request('GET', '/maintenance_windows', query)

  addMaintenance: (user, duration = 60, description, services = []) ->
    @getUserEmail(user, user)
    .bind(@email)
    .then (email) =>
      @email = email
      if services.length is 0
        services = @pagerServices
      service_ids = Promise.map services, (service) =>
        @serviceId(service)
      Promise.all(service_ids)
    .then (service_ids) =>
      payload = {
        maintenance_window: {
          type: 'maintenance_window',
          start_time: moment().format(),
          end_time: moment().add(duration, 'minutes').format(),
          description: description or 'Maintenance in progress.',
          services: [ ]
        }
      }
      for service in service_ids
        payload.maintenance_window.services.push {
          id: service,
          type: 'service_reference'
        }
      @request('POST', '/maintenance_windows', payload, @email)

  endMaintenance: (user, id) ->
    @request('DELETE', "/maintenance_windows/#{id}", { })

  coloring: {
    irc: (text, color) ->
      colors = require('irc-colors')
      if colors[color]
        colors[color](text)
      else
        text

    slack: (text, color) ->
      "*#{text}*"

    generic: (text, color) ->
      text
  }

  getService: (name) ->
    payload = {
      query: name
    }
    @request('GET', '/services', payload)

  serviceId: (name) ->
    new Promise (res, err) =>
      if @robot.brain.data.pagerv2.services[name]?
        res @robot.brain.data.pagerv2.services[name]
      else
        @getService(name)
        .then (payload) =>
          @robot.brain.data.pagerv2.services[name] = payload.services[0].id
          res @robot.brain.data.pagerv2.services[name]

  listExtensions: (name) ->
    if name?
      query = {
        query: name
      }
    else
      query = { }
    @request('GET', '/extensions', query)

  parseWebhook: (adapter, messages) =>
    return new Promise (res, err) =>
      res messages.map (message) =>
        try
          incident = if message.type? then message.data.incident else message.incident
          type = if message.type? then message.type else message.event
          level = type.substring(type.indexOf('.') + 1)
          if level is 'custom'
            return @launchActionById(message.webhook.id)
          else
            return @printIncident(incident, type, adapter)
        catch e
          @robot.logger.error 'unable to parse message'
          @robot.logger.error message
          @robot.logger.error e
          return 'Message parsing failed'

  parseWebhookv3: (adapter, messages) =>
    return new Promise (res, err) =>
      res Array.from(messages).map (message) =>
        try
          incident = if message.event.data? then message.event # data + agent
          type = if message.event.event_type? then message.event.event_type
          level = type.substring(type.indexOf('.') + 1)
          return @printIncidentv3(incident, type, adapter)
        catch e
          @robot.logger.error 'unable to parse message v3'
          @robot.logger.error message
          @robot.logger.error e
          return 'Message v3 parsing failed'

  launchActionById: (action_id) =>
    if @robot.brain.data.pagerv2.custom?[action_id]?
      custom_action = @robot.brain.data.pagerv2.custom[action_id]
      return @robot.emit(custom_action.action, custom_action.args)
    else
      return "Unknown action for id #{action_id}"

  launchActionByName: (name) =>
    if @robot.brain.data.pagerv2.custom_name?[name]?
      custom_action = @robot.brain.data.pagerv2.custom_name[name]
      @robot.emit(custom_action.action, custom_action.args)
      "Action \"#{name}\" sent"
    else
      "Unknown action for name #{name}"

  listActions: (name) =>
    actions = new Array()
    if @robot.brain.data.pagerv2.custom_name?[name]?
      custom_action = @robot.brain.data.pagerv2.custom_name[name]
      details = if custom_action.summary? then custom_action.summary else custom_action.action
      actions.push("[#{name}] : #{details}")
    else
      if @robot.brain.data.pagerv2.custom_name?
        for key, value of @robot.brain.data.pagerv2.custom_name
          custom_action = @robot.brain.data.pagerv2.custom_name[key]
          details = if custom_action.summary? then custom_action.summary else custom_action.action
          actions.push("[#{key}] : #{details}")

    if actions.length is 0
      return ['No named action available']
    else
      return actions

  requestResponderIncidents: (user, who, incidents = '',
    message = process.env.PAGERV2_RESPONDER_MESSAGE) ->
    @getUser(user, user)
    .bind({ from: null })
    .then (user_id) =>
      @from = user_id
      respondersDone = Promise.map who.split(/,/), (responder) =>
        @getUser(user, { name: responder })
      Promise.all respondersDone
    .then (responders) =>
      @responders = responders
      @listIncidents(incidents)
      .then (data) =>
        if data.incidents.length > 0
          requestDone = Promise.map data.incidents, (inc) =>
            payload = {
              requester_id: @from
              responder_request_targets: []
              message: message
            }
            for r in @responders
              payload.responder_request_targets.push {
                responder_request_target: {
                  id: r,
                  type: 'user_reference'
                }
              }
            @request('POST', "/incidents/#{inc.id}/responder_requests", payload, @from)
          Promise.all requestDone
        else
          throw { message: 'There is no incidents at the moment.' }

  printIncidentv3: (incident, type, adapter) =>
    level = type.split('.')[type.split('.').length - 1]
    service = 'unknown'
    if type is 'incident.annotated'
      v3_id = incident.data['incident'].id
      description = incident.data['incident'].summary
      note = incident.data.content
      who = incident.agent.summary
      "#{who} added note: #{note} on: #{v3_id} - #{description}"
    else
      if incident.data.service? and incident.data.service.summary?
        service = incident.data.service.summary
      origin = @colorer(adapter, level, "[#{service}]")
      description = @getDescriptionFromIncident(incident.data)
      who = @get_assignee(incident, type)
      priority = ''

      if incident.data?.number?
        priority = @colorer(adapter, "#{level}", "#{incident.data.number}")
      "#{origin} #{incident.data.id} - [##{priority}] #{description} - #{level} (#{who})"

  printIncident: (incident, type, adapter) =>
    level = type.split('.')[type.split('.').length - 1]
    service = 'unknown'
    if incident.service.name?
      service = incident.service.name
    else if incident.service.summary?
      service = incident.service.summary
    origin = @colorer(adapter,
      level,
      "[#{service}]"
    )
    description = @getDescriptionFromIncident(incident)
    who = @get_assignee(incident, type)
    priority = ''
    if incident.priority?.name?
      priority = @colorer(adapter,
        incident.priority.name,
        "{#{incident.priority.name}} "
      )
    "#{origin} #{priority}#{incident.id} - #{description} - #{level} (#{who})"
  
  getDescriptionFromIncident: (incident) ->
    if incident.trigger_summary_data?
      if incident.trigger_summary_data.subject?
        description = incident.trigger_summary_data.subject.
                      replace(' (CRITICAL)', '')
      else if incident.trigger_summary_data.description?
        description = incident.trigger_summary_data.description
    # V3
    else if incident.title?
      description = incident.title

    if not description? and incident.summary?
      description = incident.summary
    if not description?
      description = '(no subject)'
    if incident.alerts?.length > 0
      alertsDescription = @getDetailsFromAlerts(incident.alerts)
      if alertsDescription.length > 0
        description = alertsDescription.join(' - ')
    return description

  getDetailsFromAlerts: (alerts) ->
    result = []
    for alert in alerts
      if alert.body?.details?.incident_parameters?.FAILED_LOCATIONS?
        tags = alert.body.details.incident_parameters.TAGS.join(',')
        url = alert.body.details.incident_parameters.MONITORURL
        status = alert.body.details.incident_parameters.STATUS
        reason = alert.body.details.incident_parameters.INCIDENT_REASON
        locations = alert.body.details.incident_parameters.FAILED_LOCATIONS
        result.push("|#{tags}| #{url} : #{status} #{reason} from #{locations}")
      else if alert.body?.details?.pd_description?
        result.push(alert.body.details.pd_description)
      else if alert.body?.details? and alert.body.cef_details?.description? and
              alert.body.cef_details.client_url?
        result.push(alert.body.cef_details.description)
        result.push(JSON.stringify(alert.body.details))
        result.push(alert.body.cef_details.client_url)
      else
        @robot.logger.warning 'no details found in the alerts'
        @robot.logger.warning alert.body
    return result


  get_assignee: (incident, type) =>
    # V1/2
    if type? and type is 'incident.resolve' and incident.resolved_by_user?
      who = incident.resolved_by_user.name
    else if incident.assigned_to_user?
      who = incident.assigned_to_user.name
    else if incident.assignments?
      who = []
      for assignment in incident.assignments
        who.push(assignment.assignee.summary)
      who = who.join(',')
    # V3
    else if type? and type is 'incident.resolved'
      if incident.agent?
        who = if incident.agent.summary? then incident.agent.summary
      else if incident.data.assignees?
        who = []
        for assignment in incident.data.assignees
          who.push(assignment.summary)
    else if incident.data.assignees?
      who = []
      for assignment in incident.data.assignees
        who.push(assignment.summary)
    # Fallback
    else
      who = process.env.PAGERV2_DEFAULT_RESOLVER or 'nagios'
      @robot.logger.warning("fallback parsing triggered for incident #{incident.id}")
      @robot.logger.debug(incident)

    return who

  colorer: (adapter, level, text) ->
    colors = {
      trigger: 'red',
      triggered: 'red',
      unacknowledge: 'red',
      unacknowledged: 'red',
      acknowledge: 'yellow',
      acknowledged: 'yellow',
      resolve: 'green',
      resolved: 'green',
      assign: 'blue',
      escalate: 'blue',
      Sev1: 'teal',
      Sev2: 'cyan',
      Sev3: 'royal',
      Sev4: 'grey'
    }
    if @coloring[adapter]?
      @coloring[adapter](text, colors[level])
    else
      @coloring.generic(text, colors[level])
 
  logError: (message, payload) ->
    if @errorlog?
      fs.appendFileSync @errorlog, '\n---------------------\n'
      fs.appendFileSync @errorlog, "#{moment().utc().format()} - #{message}\n\n"
      fs.appendFileSync @errorlog, JSON.stringify(payload, null, 2), 'utf-8'



module.exports = Pagerv2