Class: Logster::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/logster/message.rb

Constant Summary collapse

LOGSTER_ENV =
"_logster_env".freeze
ALLOWED_ENV =
%w[
  HTTP_HOST
  REQUEST_URI
  REQUEST_METHOD
  HTTP_USER_AGENT
  HTTP_ACCEPT
  HTTP_REFERER
  HTTP_X_FORWARDED_FOR
  HTTP_X_REAL_IP
  hostname
  process_id
  application_version
  time
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(severity, progname, message, timestamp = nil, key = nil, count: 1) ⇒ Message

Returns a new instance of Message.



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/logster/message.rb', line 35

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

Instance Attribute Details

#backtraceObject

Returns the value of attribute backtrace.



24
25
26
# File 'lib/logster/message.rb', line 24

def backtrace
  @backtrace
end

#countObject

Returns the value of attribute count.



24
25
26
# File 'lib/logster/message.rb', line 24

def count
  @count
end

#envObject

Returns the value of attribute env.



33
34
35
# File 'lib/logster/message.rb', line 33

def env
  @env
end

#env_bufferObject

Returns the value of attribute env_buffer.



24
25
26
# File 'lib/logster/message.rb', line 24

def env_buffer
  @env_buffer
end

#first_timestampObject

Returns the value of attribute first_timestamp.



24
25
26
# File 'lib/logster/message.rb', line 24

def first_timestamp
  @first_timestamp
end

#keyObject

Returns the value of attribute key.



24
25
26
# File 'lib/logster/message.rb', line 24

def key
  @key
end

#messageObject (readonly)

Returns the value of attribute message.



33
34
35
# File 'lib/logster/message.rb', line 33

def message
  @message
end

#prognameObject

Returns the value of attribute progname.



24
25
26
# File 'lib/logster/message.rb', line 24

def progname
  @progname
end

#protectedObject

Returns the value of attribute protected.



24
25
26
# File 'lib/logster/message.rb', line 24

def protected
  @protected
end

#severityObject

Returns the value of attribute severity.



24
25
26
# File 'lib/logster/message.rb', line 24

def severity
  @severity
end

#timestampObject

Returns the value of attribute timestamp.



24
25
26
# File 'lib/logster/message.rb', line 24

def timestamp
  @timestamp
end

Class Method Details

.default_envObject



122
123
124
125
126
127
128
# File 'lib/logster/message.rb', line 122

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) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/logster/message.rb', line 71

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

.hostnameObject



93
94
95
96
97
98
99
100
101
102
# File 'lib/logster/message.rb', line 93

def self.hostname
  @hostname ||=
    begin
      command = Logster.config.use_full_hostname ? `hostname -f` : `hostname`
      command.strip!
      command
    rescue StandardError
      "<unknown>"
    end
end

.populate_env_helper(env) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/logster/message.rb', line 183

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) ⇒ Object



175
176
177
178
179
180
181
# File 'lib/logster/message.rb', line 175

def self.populate_from_env(env)
  if Array === env
    env.map { |single_env| self.populate_env_helper(single_env) }
  else
    self.populate_env_helper(env)
  end
end

.scrub_params(params) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/logster/message.rb', line 229

def self.scrub_params(params)
  if Array === params
    params.map! { |p| scrub_params(p) }
    params
  elsif Hash === params
    params.each { |k, v| params[k] = scrub_params(v) }
    params
  elsif String === params
    scrubbed = params.scrub if !params.valid_encoding?
    scrubbed || params
  else
    params
  end
end

Instance Method Details

#<=>(other) ⇒ Object



207
208
209
210
211
212
# File 'lib/logster/message.rb', line 207

def <=>(other)
  time = self.timestamp <=> other.timestamp
  return time if time && time != 0

  self.key <=> other.key
end

#=~(pattern) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/logster/message.rb', line 214

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) ⇒ Object



248
249
250
251
252
253
254
# File 'lib/logster/message.rb', line 248

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) ⇒ Object



256
257
258
259
260
261
262
263
264
265
266
# File 'lib/logster/message.rb', line 256

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) ⇒ Object



244
245
246
# File 'lib/logster/message.rb', line 244

def drop_redundant_envs(limit)
  env.slice!(limit..-1) if Array === env
end

#grouping_hashObject

in its own method so it can be overridden



131
132
133
# File 'lib/logster/message.rb', line 131

def grouping_hash
  { message: message, severity: self.severity, backtrace: self.backtrace }
end

#grouping_keyObject

todo - memoize?



136
137
138
# File 'lib/logster/message.rb', line 136

def grouping_key
  Digest::SHA1.hexdigest JSON.fast_generate grouping_hash
end

#has_env_buffer?Boolean

Returns:

  • (Boolean)


171
172
173
# File 'lib/logster/message.rb', line 171

def has_env_buffer?
  env_buffer.size > 0
end

#merge_similar_message(other) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/logster/message.rb', line 154

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) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/logster/message.rb', line 104

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)
    env["time"] = @timestamp || get_timestamp if !env.key?("time") && !env.key?(:time)
  end
  self.env = Message.populate_from_env(env)
end

#solved_keysObject

todo - memoize?



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/logster/message.rb', line 141

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 { |version| Digest::SHA1.hexdigest "#{version} #{backtrace}" }
  end
end

#to_h(exclude_env: false) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/logster/message.rb', line 48

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) ⇒ Object



66
67
68
69
# File 'lib/logster/message.rb', line 66

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) ⇒ Object



268
269
270
271
# File 'lib/logster/message.rb', line 268

def truncate_backtrace(bytes_limit)
  @backtrace = @backtrace.byteslice(0...bytes_limit)
  @backtrace.slice!(-1) while !@backtrace[-1].valid_encoding? && @backtrace.size > 1
end