class LogStash::Filters::UserAgent

Parse user agent strings into structured data based on BrowserScope data

UserAgent filter, adds information about user agent like family, operating system, version, and device

Logstash releases ship with the regexes.yaml database made available from ua-parser with an Apache 2.0 license. For more details on ua-parser, see <github.com/tobie/ua-parser/>.

Constants

LOOKUP_CACHE

Public Instance Methods

filter(event) click to toggle source
# File lib/logstash/filters/useragent.rb, line 95
def filter(event)
  useragent = event.get(@source)
  useragent = useragent.first if useragent.is_a?(Array)

  return if useragent.nil? || useragent.empty?

  begin
    ua_data = lookup_useragent(useragent)
  rescue StandardError => e
    @logger.error("Uknown error while parsing user agent data", :exception => e, :field => @source, :event => event)
    return
  end

  return unless ua_data

  event.remove(@source) if @target == @source
  set_fields(event, ua_data)

  filter_matched(event)
end
lookup_useragent(useragent) click to toggle source

should be private but need to stay public for specs TODO: (colin) the related specs should be refactored to not rely on private methods.

# File lib/logstash/filters/useragent.rb, line 118
def lookup_useragent(useragent)
  return unless useragent

  cached = LOOKUP_CACHE[useragent]
  return cached if cached

  # the UserAgentParser::Parser class is not thread safe, indications are that it is probably
  # caused by the underlying JRuby regex code that is not thread safe.
  # see https://github.com/logstash-plugins/logstash-filter-useragent/issues/25
  ua_data = @parser.parse(useragent)

  LOOKUP_CACHE[useragent] = ua_data
  ua_data
end
register() click to toggle source
# File lib/logstash/filters/useragent.rb, line 58
def register
  require 'user_agent_parser'

  if @regexes.nil?
    begin
      @parser = UserAgentParser::Parser.new
    rescue Exception => e
      begin
        path = ::File.expand_path('../../../vendor/regexes.yaml', ::File.dirname(__FILE__))
        @parser = UserAgentParser::Parser.new(:patterns_path => path)
      rescue => ex
        raise("Failed to cache, due to: #{ex}\n")
      end
    end
  else
    @logger.debug("Using user agent regexes", :regexes => @regexes)
    @parser = UserAgentParser::Parser.new(:patterns_path => @regexes)
  end

  LOOKUP_CACHE.max_size = @lru_cache_size

  # make @target in the format [field name] if defined, i.e. surrounded by brakets
  normalized_target = (@target && @target !~ /^\[[^\[\]]+\]$/) ? "[#{@target}]" : ""

  # predefine prefixed field names
  @prefixed_name = "#{normalized_target}[#{@prefix}name]"
  @prefixed_os = "#{normalized_target}[#{@prefix}os]"
  @prefixed_os_name = "#{normalized_target}[#{@prefix}os_name]"
  @prefixed_os_major = "#{normalized_target}[#{@prefix}os_major]"
  @prefixed_os_minor = "#{normalized_target}[#{@prefix}os_minor]"
  @prefixed_device = "#{normalized_target}[#{@prefix}device]"
  @prefixed_major = "#{normalized_target}[#{@prefix}major]"
  @prefixed_minor = "#{normalized_target}[#{@prefix}minor]"
  @prefixed_patch = "#{normalized_target}[#{@prefix}patch]"
  @prefixed_build = "#{normalized_target}[#{@prefix}build]"
end

Private Instance Methods

set_fields(event, ua_data) click to toggle source
# File lib/logstash/filters/useragent.rb, line 135
def set_fields(event, ua_data)
  # UserAgentParser outputs as US-ASCII.

  event.set(@prefixed_name, ua_data.name.dup.force_encoding(Encoding::UTF_8))

  #OSX, Android and maybe iOS parse correctly, ua-agent parsing for Windows does not provide this level of detail

  # Calls in here use #dup because there's potential for later filters to modify these values
  # and corrupt the cache. See uap source here for details https://github.com/ua-parser/uap-ruby/tree/master/lib/user_agent_parser
  if (os = ua_data.os)
    # The OS is a rich object
    event.set(@prefixed_os, ua_data.os.to_s.dup.force_encoding(Encoding::UTF_8))
    event.set(@prefixed_os_name, os.name.dup.force_encoding(Encoding::UTF_8)) if os.name

    # These are all strings
    if (os_version = os.version)
      event.set(@prefixed_os_major, os_version.major.dup.force_encoding(Encoding::UTF_8)) if os_version.major
      event.set(@prefixed_os_minor, os_version.minor.dup.force_encoding(Encoding::UTF_8)) if os_version.minor
    end
  end

  event.set(@prefixed_device, ua_data.device.to_s.dup.force_encoding(Encoding::UTF_8)) if ua_data.device

  if (ua_version = ua_data.version)
    event.set(@prefixed_major, ua_version.major.dup.force_encoding(Encoding::UTF_8)) if ua_version.major
    event.set(@prefixed_minor, ua_version.minor.dup.force_encoding(Encoding::UTF_8)) if ua_version.minor
    event.set(@prefixed_patch, ua_version.patch.dup.force_encoding(Encoding::UTF_8)) if ua_version.patch
    event.set(@prefixed_build, ua_version.patch_minor.dup.force_encoding(Encoding::UTF_8)) if ua_version.patch_minor
  end
end