class Fluent::SlackOutput

Constants

Field

Attributes

localtime[R]

for test

mrkdwn_in[R]

for test

post_message_opts[R]

for test

slack[R]

for test

time_format[R]

for test

timef[R]

for test

Public Class Methods

desc(description) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 13
def desc(description)
end
new() click to toggle source
Calls superclass method
# File lib/fluent/plugin/out_buffered_slack.rb, line 127
def initialize
  super
  require 'uri'
end

Public Instance Methods

configure(conf) click to toggle source
Calls superclass method
# File lib/fluent/plugin/out_buffered_slack.rb, line 132
def configure(conf)
  conf['time_format'] ||= '%H:%M:%S' # old version compatiblity
  conf['localtime'] ||= true unless conf['utc']

  super

  if @channel
    @channel = URI.unescape(@channel) # old version compatibility
    if !@channel.start_with?('#') and !@channel.start_with?('@')
      @channel = '#' + @channel # Add # since `#` is handled as a comment in fluentd conf
    end
  end

  if @webhook_url
    if @webhook_url.empty?
      raise Fluent::ConfigError.new("`webhook_url` is an empty string")
    end
    unless @as_user.nil?
      log.warn "out_slack: `as_user` parameter are not available for Incoming Webhook"
    end
    @slack = Fluent::SlackClient::IncomingWebhook.new(@webhook_url)
  elsif @slackbot_url
    if @slackbot_url.empty?
      raise Fluent::ConfigError.new("`slackbot_url` is an empty string")
    end
    if @channel.nil?
      raise Fluent::ConfigError.new("`channel` parameter required for Slackbot Remote Control")
    end

    if @username or @color or @icon_emoji or @icon_url
      log.warn "out_slack: `username`, `color`, `icon_emoji`, `icon_url` parameters are not available for Slackbot Remote Control"
    end
    unless @as_user.nil?
      log.warn "out_slack: `as_user` parameter are not available for Slackbot Remote Control"
    end
    @slack = Fluent::SlackClient::Slackbot.new(@slackbot_url)
  elsif @token
    if @token.empty?
      raise Fluent::ConfigError.new("`token` is an empty string")
    end
    if @channel.nil?
      raise Fluent::ConfigError.new("`channel` parameter required for Slack WebApi")
    end

    @slack = Fluent::SlackClient::WebApi.new
  else
    raise Fluent::ConfigError.new("One of `webhook_url` or `slackbot_url`, or `token` is required")
  end
  @slack.log = log
  @slack.debug_dev = log.out if log.level <= Fluent::Log::LEVEL_TRACE

  if @https_proxy
    @slack.https_proxy = @https_proxy
  end

  @message      ||= '%s'
  @message_keys ||= %w[message]
  begin
    @message % (['1'] * @message_keys.length)
  rescue ArgumentError
    raise Fluent::ConfigError, "string specifier '%s' for `message`  and `message_keys` specification mismatch"
  end
  if @title and @title_keys
    begin
      @title % (['1'] * @title_keys.length)
    rescue ArgumentError
      raise Fluent::ConfigError, "string specifier '%s' for `title` and `title_keys` specification mismatch"
    end
  end
  if @channel && @channel_keys
    begin
      @channel % (['1'] * @channel_keys.length)
    rescue ArgumentError
      raise Fluent::ConfigError, "string specifier '%s' for `channel` and `channel_keys` specification mismatch"
    end
  end

  if @icon_emoji and @icon_url
    raise Fluent::ConfigError, "either of `icon_emoji` or `icon_url` can be specified"
  end

  if @as_user and (@icon_emoji or @icon_url or @username)
    raise Fluent::ConfigError, "`username`, `icon_emoji` and `icon_url` cannot be specified when `as_user` is set to true"
  end

  if @mrkdwn
    # Enable markdown for attachments. See https://api.slack.com/docs/formatting
    @mrkdwn_in = %w[text fields]
  end

  if @parse and !%w[none full].include?(@parse)
    raise Fluent::ConfigError, "`parse` must be either of `none` or `full`"
  end

  @post_message_opts = {}
  if @auto_channels_create
    raise Fluent::ConfigError, "`token` parameter is required to use `auto_channels_create`" unless @token
    @post_message_opts = {auto_channels_create: true}
  end
end
format(tag, time, record) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 233
def format(tag, time, record)
  [tag, time, record].to_msgpack
end
write(chunk) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 237
def write(chunk)
  begin
    payloads = build_payloads(chunk)
    payloads.each {|payload| @slack.post_message(payload, @post_message_opts) }
  rescue Timeout::Error => e
    log.warn "out_slack:", :error => e.to_s, :error_class => e.class.to_s
    raise e # let Fluentd retry
  rescue => e
    log.error "out_slack:", :error => e.to_s, :error_class => e.class.to_s
    log.warn_backtrace e.backtrace
    # discard. @todo: add more retriable errors
  end
end

Private Instance Methods

build_channel(record) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 361
def build_channel(record)
  return nil if @channel.nil?
  return @channel unless @channel_keys

  values = fetch_keys(record, @channel_keys)
  @channel % values
end
build_color_payloads(chunk) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 316
def build_color_payloads(chunk)
  messages = {}
  chunk.msgpack_each do |tag, time, record|
    channel = build_channel(record)
    messages[channel] ||= ''
    messages[channel] << "#{build_message(record)}\n"
  end
  messages.map do |channel, text|
    msg = {
      attachments: [{
        :fallback => text,
        :text     => text,
      }.merge(common_attachment)],
    }
    msg.merge!(channel: channel) if channel
    msg.merge!(common_payload)
  end
end
build_message(record) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 349
def build_message(record)
  values = fetch_keys(record, @message_keys)
  @message % values
end
build_payloads(chunk) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 253
def build_payloads(chunk)
  if @title
    build_title_payloads(chunk)
  elsif @color
    build_color_payloads(chunk)
  else
    build_plain_payloads(chunk)
  end
end
build_plain_payloads(chunk) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 335
def build_plain_payloads(chunk)
  messages = {}
  chunk.msgpack_each do |tag, time, record|
    channel = build_channel(record)
    messages[channel] ||= ''
    messages[channel] << "#{build_message(record)}\n"
  end
  messages.map do |channel, text|
    msg = {text: text}
    msg.merge!(channel: channel) if channel
    msg.merge!(common_payload)
  end
end
build_title(record) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 354
def build_title(record)
  return @title unless @title_keys

  values = fetch_keys(record, @title_keys)
  @title % values
end
build_title_payloads(chunk) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 289
def build_title_payloads(chunk)
  ch_fields = {}
  chunk.msgpack_each do |tag, time, record|
    channel = build_channel(record)
    per     = tag # title per tag
    ch_fields[channel]      ||= {}
    ch_fields[channel][per] ||= Field.new(build_title(record), '')
    ch_fields[channel][per].value << "#{build_message(record)}\n"
  end
  ch_fields.map do |channel, fields|
    fallback_text = if @verbose_fallback
                      fields.values.map { |f| "#{f.title} #{f.value}" }.join(' ')
                    else
                      fields.values.map(&:title).join(' ')
                    end

    msg = {
      attachments: [{
        :fallback => fallback_text, # fallback is the message shown on popup
        :fields   => fields.values.map(&:to_h)
      }.merge(common_attachment)],
    }
    msg.merge!(channel: channel) if channel
    msg.merge!(common_payload)
  end
end
common_attachment() click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 277
def common_attachment
  return @common_attachment if @common_attachment
  @common_attachment = {}
  @common_attachment[:color]     = @color     if @color
  @common_attachment[:mrkdwn_in] = @mrkdwn_in if @mrkdwn_in
  @common_attachment
end
common_payload() click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 263
def common_payload
  return @common_payload if @common_payload
  @common_payload = {}
  @common_payload[:as_user]    = @as_user    unless @as_user.nil?
  @common_payload[:username]   = @username   if @username
  @common_payload[:icon_emoji] = @icon_emoji if @icon_emoji
  @common_payload[:icon_url]   = @icon_url   if @icon_url
  @common_payload[:mrkdwn]     = @mrkdwn     if @mrkdwn
  @common_payload[:link_names] = @link_names if @link_names
  @common_payload[:parse]      = @parse      if @parse
  @common_payload[:token]      = @token      if @token
  @common_payload
end
fetch_keys(record, keys) click to toggle source
# File lib/fluent/plugin/out_buffered_slack.rb, line 369
def fetch_keys(record, keys)
  Array(keys).map do |key|
    begin
      accessor = record_accessor_create(key)
      accessor.call(record).to_s
      #record.fetch(key).to_s
    rescue KeyError
      log.warn "out_slack: the specified key '#{key}' not found in record. [#{record}]"
      ''
    end
  end
end