class Logster::Message

Constants

ALLOWED_ENV
LOGSTER_ENV

Attributes

backtrace[RW]
count[RW]
env[R]
env_buffer[RW]
first_timestamp[RW]
key[RW]
message[R]
progname[RW]
protected[RW]
severity[RW]
timestamp[RW]

Public Class Methods

default_env() click to toggle source
# File lib/logster/message.rb, line 106
def self.default_env
  env = {
    "hostname" => hostname,
    "process_id" => Process.pid
  }
  env["application_version"] = Logster.config.application_version if Logster.config.application_version
  env
end
from_json(json) click to toggle source
# File lib/logster/message.rb, line 63
def self.from_json(json)
  parsed = ::JSON.parse(json)
  msg = new(parsed["severity"],
        parsed["progname"],
        parsed["message"],
        parsed["timestamp"],
        parsed["key"])
  msg.backtrace = parsed["backtrace"]
  msg.env = parsed["env"]
  msg.count = parsed["count"]
  msg.protected = parsed["protected"]
  msg.first_timestamp = parsed["first_timestamp"]
  msg
end
hostname() click to toggle source
# File lib/logster/message.rb, line 82
def self.hostname
  command = Logster.config.use_full_hostname ? `hostname -f` : `hostname`
  @hostname ||= command.strip! rescue "<unknown>"
end
new(severity, progname, message, timestamp = nil, key = nil, count: 1) click to toggle source
# File lib/logster/message.rb, line 27
def initialize(severity, progname, message, timestamp = nil, key = nil, count: 1)
  @timestamp = timestamp || get_timestamp
  @severity = severity
  @progname = progname
  @message = message
  @key = key || SecureRandom.hex
  @backtrace = nil
  @count = count || 1
  @protected = false
  @first_timestamp = nil
  @env_buffer = []
end
populate_env_helper(env) click to toggle source
# File lib/logster/message.rb, line 173
def self.populate_env_helper(env)
  env[LOGSTER_ENV] ||= begin
    unless env.include? "rack.input"
      # Not a web request
      return env
    end
    scrubbed = default_env
    request = Rack::Request.new(env)
    params = {}
    request.params.each do |k, v|
      if k.include? "password"
        params[k] = "[redacted]"
      elsif Array === v
        params[k] = v[0..20]
      else
        params[k] = v && v[0..100]
      end
    end
    scrubbed["params"] = params if params.length > 0
    ALLOWED_ENV.map { |k|
      scrubbed[k] = env[k] if env[k]
    }
    scrubbed
  end
end
populate_from_env(env) click to toggle source
# File lib/logster/message.rb, line 163
def self.populate_from_env(env)
  if Array === env
    env.map do |single_env|
      self.populate_env_helper(single_env)
    end
  else
    self.populate_env_helper(env)
  end
end
scrub_params(params) click to toggle source
# File lib/logster/message.rb, line 221
def self.scrub_params(params)
  if Array === params
    params.map! { |p| scrub_params(p) }
    params
  elsif Hash === params
    params.each do |k, v|
      params[k] = scrub_params(v)
    end
    params
  elsif String === params
    scrubbed = params.scrub if !params.valid_encoding?
    scrubbed || params
  else
    params
  end
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/logster/message.rb, line 199
def <=>(other)
  time = self.timestamp <=> other.timestamp
  return time if time && time != 0

  self.key <=> other.key
end
=~(pattern) click to toggle source
# File lib/logster/message.rb, line 206
def =~(pattern)
  case pattern
  when Hash
    IgnorePattern.new(nil, pattern).matches? self
  when String
    IgnorePattern.new(pattern, nil).matches? self
  when Regexp
    IgnorePattern.new(pattern, nil).matches? self
  when IgnorePattern
    pattern.matches? self
    else
    nil
  end
end
apply_env_size_limit(size_limit) click to toggle source
# File lib/logster/message.rb, line 244
def apply_env_size_limit(size_limit)
  if Array === env
    env.each { |e| truncate_env(e, size_limit) }
  elsif Hash === env
    truncate_env(env, size_limit)
  end
end
apply_message_size_limit(limit, gems_dir: nil) click to toggle source
# File lib/logster/message.rb, line 252
def apply_message_size_limit(limit, gems_dir: nil)
  size = self.to_json(exclude_env: true).bytesize
  if size > limit && @backtrace
    @backtrace.gsub!(gems_dir, "") if gems_dir
    @backtrace.strip!
    size = self.to_json(exclude_env: true).bytesize
    backtrace_limit = limit - (size - @backtrace.bytesize)
    return if backtrace_limit <= 0 || size <= limit
    truncate_backtrace(backtrace_limit)
  end
end
drop_redundant_envs(limit) click to toggle source
# File lib/logster/message.rb, line 238
def drop_redundant_envs(limit)
  if Array === env
    env.slice!(limit..-1)
  end
end
env=(env) click to toggle source
# File lib/logster/message.rb, line 78
def env=(env)
  @env = self.class.scrub_params(env)
end
grouping_hash() click to toggle source

in its own method so it can be overridden

# File lib/logster/message.rb, line 116
def grouping_hash
  message = self.message.gsub(/[0-9a-f]+/i, "X")
  { message: message, severity: self.severity, backtrace: self.backtrace }
end
grouping_key() click to toggle source

todo - memoize?

# File lib/logster/message.rb, line 122
def grouping_key
  Digest::SHA1.hexdigest JSON.fast_generate grouping_hash
end
has_env_buffer?() click to toggle source
# File lib/logster/message.rb, line 159
def has_env_buffer?
  env_buffer.size > 0
end
merge_similar_message(other) click to toggle source
# File lib/logster/message.rb, line 142
def merge_similar_message(other)
  self.first_timestamp ||= self.timestamp
  self.timestamp = [self.timestamp, other.timestamp].max
  self.count += other.count || 1

  if Hash === other.env && !other.env.key?("time") && !other.env.key?(:time)
    other.env["time"] = other.timestamp
  end

  if Array === other.env
    env_buffer.unshift(*other.env)
  else
    env_buffer.unshift(other.env)
  end
  true
end
populate_from_env(env) click to toggle source
# File lib/logster/message.rb, line 87
def populate_from_env(env)
  env ||= {}
  if Array === env
    env = env.map do |single_env|
      single_env = self.class.default_env.merge(single_env)
      if !single_env.key?("time") && !single_env.key?(:time)
        single_env["time"] = @timestamp || get_timestamp
      end
      single_env
    end
  else
    env = self.class.default_env.merge(env)
    if !env.key?("time") && !env.key?(:time)
      env["time"] = @timestamp || get_timestamp
    end
  end
  self.env = Message.populate_from_env(env)
end
solved_keys() click to toggle source

todo - memoize?

# File lib/logster/message.rb, line 127
def solved_keys
  if Array === env
    versions = env.map { |single_env| single_env["application_version"] }
  else
    versions = [env["application_version"]]
  end
  versions.compact!

  if backtrace && backtrace.length > 0
    versions.map do |version|
      Digest::SHA1.hexdigest "#{version} #{backtrace}"
    end
  end
end
to_h(exclude_env: false) click to toggle source
# File lib/logster/message.rb, line 40
def to_h(exclude_env: false)
  h = {
    message: @message,
    progname: @progname,
    severity: @severity,
    timestamp: @timestamp,
    key: @key,
    backtrace: @backtrace,
    count: @count,
    protected: @protected
  }

  h[:first_timestamp] = @first_timestamp if @first_timestamp
  h[:env] = @env unless exclude_env

  h
end
to_json(opts = nil) click to toggle source
# File lib/logster/message.rb, line 58
def to_json(opts = nil)
  exclude_env = Hash === opts && opts.delete(:exclude_env)
  JSON.fast_generate(to_h(exclude_env: exclude_env), opts)
end
truncate_backtrace(bytes_limit) click to toggle source
# File lib/logster/message.rb, line 264
def truncate_backtrace(bytes_limit)
  @backtrace = @backtrace.byteslice(0...bytes_limit)
  while !@backtrace[-1].valid_encoding? && @backtrace.size > 1
    @backtrace.slice!(-1)
  end
end

Protected Instance Methods

get_timestamp() click to toggle source
# File lib/logster/message.rb, line 306
def get_timestamp
  (Time.now.to_f * 1000).to_i
end
truncate_env(env, limit) click to toggle source
# File lib/logster/message.rb, line 273
def truncate_env(env, limit)
  if JSON.fast_generate(env).bytesize > limit
    sizes = {}
    braces = '{}'.bytesize
    env.each do |k, v|
      sizes[k] = JSON.fast_generate(k => v).bytesize - braces
    end
    sorted = env.keys.sort { |a, b| sizes[a] <=> sizes[b] }

    kept_keys = []
    if env.key?(:time)
      kept_keys << :time
    elsif env.key?("time")
      kept_keys << "time"
    end

    sum = braces
    if time_key = kept_keys.first
      sum += sizes[time_key]
      sorted.delete(time_key)
    end
    comma = ','.bytesize

    sorted.each do |k|
      extra = kept_keys.size == 0 ? 0 : comma
      break if sum + sizes[k] + extra > limit
      kept_keys << k
      sum += sizes[k] + extra
    end
    env.select! { |k| kept_keys.include?(k) }
  end
end