Class: Rails::Pretty::Logger::PrettyLogger

Inherits:
Object
  • Object
show all
Defined in:
lib/rails/pretty/logger.rb

Defined Under Namespace

Classes: FileTooLarge, InvalidLogFile

Constant Summary collapse

SEVERITIES =
%w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze
LINE_INDEX_CACHE_LIMIT =
32
TAIL_READ_CHUNK_SIZE =
64 * 1024
STRUCTURED_TIMESTAMP_KEYS =
%w[@timestamp timestamp time datetime created_at].freeze
STRUCTURED_SEVERITY_KEYS =
%w[severity level log_level].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params) ⇒ PrettyLogger

Returns a new instance of PrettyLogger.



34
35
36
37
# File 'lib/rails/pretty/logger.rb', line 34

def initialize(params)
  @filter_params = params
  @log_file = self.class.resolve_log_file(params[:log_file])
end

Instance Attribute Details

#log_fileObject (readonly)

Returns the value of attribute log_file.



32
33
34
# File 'lib/rails/pretty/logger.rb', line 32

def log_file
  @log_file
end

Class Method Details

.clear_line_index_cache!Object



161
162
163
164
# File 'lib/rails/pretty/logger.rb', line 161

def self.clear_line_index_cache!
  clear_line_index_memory_cache!
  FileUtils.rm_rf(line_index_cache_root)
end

.clear_line_index_cache_for!(log_file) ⇒ Object



173
174
175
176
177
178
179
# File 'lib/rails/pretty/logger.rb', line 173

def self.clear_line_index_cache_for!(log_file)
  line_index_cache_mutex.synchronize do
    line_index_cache.delete_if { |(cache_key, _signature), _offsets| cache_key.first == log_file || cache_key[1] == log_file }
    line_index_cache_order.delete_if { |cache_key, _signature| cache_key.first == log_file || cache_key[1] == log_file }
  end
  FileUtils.rm_rf(line_index_cache_root)
end

.clear_line_index_memory_cache!Object



166
167
168
169
170
171
# File 'lib/rails/pretty/logger.rb', line 166

def self.clear_line_index_memory_cache!
  line_index_cache_mutex.synchronize do
    line_index_cache.clear
    line_index_cache_order.clear
  end
end

.custom_log_metadata(line) ⇒ Object



118
119
120
121
122
123
124
# File 'lib/rails/pretty/logger.rb', line 118

def self.(line)
  parser = Rails::Pretty::Logger.configuration.log_line_parser
  return {} unless parser.respond_to?(:call)

   = parser.call(line)
  .is_a?(Hash) ?  : {}
end

.ensure_file_size_within_limit!(log_file) ⇒ Object

Raises:



100
101
102
103
104
105
# File 'lib/rails/pretty/logger.rb', line 100

def self.ensure_file_size_within_limit!(log_file)
  max_file_size = Rails::Pretty::Logger.configuration.max_file_size
  return if max_file_size.blank?

  raise FileTooLarge if File.size(log_file) > max_file_size.to_i
end

.fetch_line_index_cache(cache_key, signature) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/rails/pretty/logger.rb', line 126

def self.fetch_line_index_cache(cache_key, signature)
  entry_key = [cache_key, signature]

  line_index_cache_mutex.synchronize do
    if line_index_cache.key?(entry_key)
      line_index_cache_order.delete(entry_key)
      line_index_cache_order << entry_key
      return line_index_cache.fetch(entry_key)
    end
  end

  if (offsets = read_persistent_line_index(cache_key, signature))
    store_line_index_cache(entry_key, offsets)
    return offsets
  end

  offsets = yield.freeze
  write_persistent_line_index(cache_key, signature, offsets)
  store_line_index_cache(entry_key, offsets)

  offsets
end

.file_size(log_file) ⇒ Object



47
48
49
# File 'lib/rails/pretty/logger.rb', line 47

def self.file_size(log_file)
  File.size?("#{log_file}").to_f / 2**20
end

.get_hourly_log_file_listObject



85
86
87
88
# File 'lib/rails/pretty/logger.rb', line 85

def self.get_hourly_log_file_list
  log_files = Dir[File.join(log_root, "hourly", "**", "*")].select { |file| File.file?(file) }.sort
  logs_atr(log_files)
end

.get_log_file_listObject



80
81
82
83
# File 'lib/rails/pretty/logger.rb', line 80

def self.get_log_file_list
  log_files = Dir[File.join(log_root, "*")].select { |file| File.file?(file) }
  logs_atr(log_files)
end

.highlight(log) ⇒ Object



43
44
45
# File 'lib/rails/pretty/logger.rb', line 43

def self.highlight(log)
  self.logger.tagged('HIGHLIGHT') { logger.info log }
end

.line_index_cacheObject



181
182
183
# File 'lib/rails/pretty/logger.rb', line 181

def self.line_index_cache
  @line_index_cache ||= {}
end

.line_index_cache_mutexObject



189
190
191
# File 'lib/rails/pretty/logger.rb', line 189

def self.line_index_cache_mutex
  @line_index_cache_mutex ||= Mutex.new
end

.line_index_cache_orderObject



185
186
187
# File 'lib/rails/pretty/logger.rb', line 185

def self.line_index_cache_order
  @line_index_cache_order ||= []
end

.line_index_cache_path(cache_key) ⇒ Object



212
213
214
# File 'lib/rails/pretty/logger.rb', line 212

def self.line_index_cache_path(cache_key)
  line_index_cache_root.join("#{Digest::SHA256.hexdigest(Marshal.dump(cache_key))}.marshal")
end

.line_index_cache_rootObject



216
217
218
# File 'lib/rails/pretty/logger.rb', line 216

def self.line_index_cache_root
  Rails.root.join("tmp", "cache", "rails_pretty_logger", "line_indexes")
end

.log_rootObject



51
52
53
# File 'lib/rails/pretty/logger.rb', line 51

def self.log_root
  Rails.root.join("log")
end

.loggerObject



39
40
41
# File 'lib/rails/pretty/logger.rb', line 39

def self.logger
  Rails.logger
end

.logs_atr(log_files) ⇒ Object



90
91
92
93
94
95
96
97
98
# File 'lib/rails/pretty/logger.rb', line 90

def self.logs_atr(log_files)
  log = {}
  log_files.each_with_index do |log_file,index|
    log[index] = {}
    log[index][:file_name] =  log_file
    log[index][:file_size] = self.file_size(log_file).round(4)
  end
  log
end

.read_persistent_line_index(cache_key, signature) ⇒ Object



193
194
195
196
197
198
199
200
# File 'lib/rails/pretty/logger.rb', line 193

def self.read_persistent_line_index(cache_key, signature)
  payload = Marshal.load(File.binread(line_index_cache_path(cache_key)))
  return unless payload[:signature] == signature

  payload[:offsets].freeze
rescue Errno::ENOENT, EOFError, TypeError, ArgumentError
  nil
end

.real_log_rootObject



75
76
77
78
# File 'lib/rails/pretty/logger.rb', line 75

def self.real_log_root
  FileUtils.mkdir_p(log_root)
  log_root.realpath
end

.resolve_log_file(log_file) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/rails/pretty/logger.rb', line 55

def self.resolve_log_file(log_file)
  raise InvalidLogFile if log_file.blank?

  candidate = Pathname.new(log_file.to_s)
  candidate = log_root.join(candidate) unless candidate.absolute?

  root_path = real_log_root
  real_path = candidate.realpath

  unless real_path.to_s == root_path.to_s || real_path.to_s.start_with?("#{root_path}/")
    raise InvalidLogFile
  end

  raise InvalidLogFile unless real_path.file?

  real_path.to_s
rescue Errno::ENOENT, Errno::EACCES, ArgumentError
  raise InvalidLogFile
end

.store_line_index_cache(entry_key, offsets) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rails/pretty/logger.rb', line 149

def self.store_line_index_cache(entry_key, offsets)
  line_index_cache_mutex.synchronize do
    line_index_cache[entry_key] = offsets
    line_index_cache_order.delete(entry_key)
    line_index_cache_order << entry_key

    while line_index_cache_order.length > LINE_INDEX_CACHE_LIMIT
      line_index_cache.delete(line_index_cache_order.shift)
    end
  end
end

.structured_log_payload(line) ⇒ Object



111
112
113
114
115
116
# File 'lib/rails/pretty/logger.rb', line 111

def self.structured_log_payload(line)
  payload = JSON.parse(line)
  payload if payload.is_a?(Hash)
rescue JSON::ParserError, TypeError
  nil
end

.tail_linesObject



107
108
109
# File 'lib/rails/pretty/logger.rb', line 107

def self.tail_lines
  Rails::Pretty::Logger.configuration.tail_lines.to_i.positive? ? Rails::Pretty::Logger.configuration.tail_lines.to_i : 500
end

.write_persistent_line_index(cache_key, signature, offsets) ⇒ Object



202
203
204
205
206
207
208
209
210
# File 'lib/rails/pretty/logger.rb', line 202

def self.write_persistent_line_index(cache_key, signature, offsets)
  FileUtils.mkdir_p(line_index_cache_root)
  path = line_index_cache_path(cache_key)
  temp_path = "#{path}.#{$$}.tmp"
  File.binwrite(temp_path, Marshal.dump(signature: signature, offsets: offsets))
  FileUtils.mv(temp_path, path)
rescue SystemCallError, TypeError, ArgumentError
  FileUtils.rm_f(temp_path) if temp_path
end

Instance Method Details

#build_log_line_offsetsObject



423
424
425
426
427
428
429
430
431
# File 'lib/rails/pretty/logger.rb', line 423

def build_log_line_offsets
  offsets = []

  each_log_line_with_offset(@log_file) do |line, offset|
    offsets << offset if line_matches_filters?(line)
  end

  offsets
end

#build_request_group_indexObject



497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# File 'lib/rails/pretty/logger.rb', line 497

def build_request_group_index
  groups = []
  current_group = nil

  each_log_line_with_offset(@log_file) do |line, offset|
    if ( = (line))
      groups << current_group if current_group
      current_group = .merge(line_offsets: [offset])
    elsif current_group
      current_group[:line_offsets] << offset
      current_group.merge!((line) || {})
    else
      current_group = { type: :ungrouped, line_offsets: [offset] }
    end
  end

  groups << current_group if current_group
  groups
end

#cached_log_line_offsetsObject



417
418
419
420
421
# File 'lib/rails/pretty/logger.rb', line 417

def cached_log_line_offsets
  self.class.fetch_line_index_cache(log_line_index_cache_key, log_file_signature) do
    build_log_line_offsets
  end
end

#cached_request_group_indexObject



491
492
493
494
495
# File 'lib/rails/pretty/logger.rb', line 491

def cached_request_group_index
  self.class.fetch_line_index_cache(request_group_index_cache_key, log_file_signature) do
    build_request_group_index
  end
end

#clear_logsObject



220
221
222
223
# File 'lib/rails/pretty/logger.rb', line 220

def clear_logs
  File.open(@log_file, File::TRUNC) {}
  self.class.clear_line_index_cache_for!(@log_file)
end

#custom_log_metadata(line) ⇒ Object



647
648
649
# File 'lib/rails/pretty/logger.rb', line 647

def (line)
  self.class.(line)
end

#custom_log_severity(line) ⇒ Object



642
643
644
645
# File 'lib/rails/pretty/logger.rb', line 642

def custom_log_severity(line)
  severity = ((line), :severity, :level, :log_level).to_s.upcase
  severity if SEVERITIES.include?(severity)
end

#custom_log_timestamp(line) ⇒ Object



638
639
640
# File 'lib/rails/pretty/logger.rb', line 638

def custom_log_timestamp(line)
  ((line), :timestamp, :time, :datetime, :created_at)
end

#date_filtered_log?Boolean

Returns:

  • (Boolean)


458
459
460
# File 'lib/rails/pretty/logger.rb', line 458

def date_filtered_log?
  !test_log?(@log_file) && !hourly_log?(@log_file)
end

#date_from_log_line(line) ⇒ Object



596
597
598
599
600
601
# File 'lib/rails/pretty/logger.rb', line 596

def date_from_log_line(line)
  timestamp = custom_log_timestamp(line) || request_timestamp(line) || structured_log_timestamp(line)
  timestamp&.to_date&.strftime("%Y-%m-%d")
rescue Date::Error, NoMethodError
  nil
end

#each_filtered_log_line(file) ⇒ Object



237
238
239
240
241
# File 'lib/rails/pretty/logger.rb', line 237

def each_filtered_log_line(file)
  return enum_for(:each_filtered_log_line, file) unless block_given?

  each_filtered_log_line_with_offset(file) { |line, _offset| yield line }
end

#each_filtered_log_line_with_offset(file) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/rails/pretty/logger.rb', line 243

def each_filtered_log_line_with_offset(file)
  return enum_for(:each_filtered_log_line_with_offset, file) unless block_given?

  start = false

  each_raw_log_line_with_offset(file) do |line, offset|
    if get_date_from_log_line(line)
      start = true
      yield line, offset
    elsif start && !(line_include_date?(line))
      yield line, offset
    else
      start = false
    end
  end
end

#each_log_line(file) ⇒ Object



268
269
270
271
272
# File 'lib/rails/pretty/logger.rb', line 268

def each_log_line(file)
  return enum_for(:each_log_line, file) unless block_given?

  each_log_line_with_offset(file) { |line, _offset| yield line }
end

#each_log_line_with_offset(file) ⇒ Object



274
275
276
277
278
279
280
281
282
# File 'lib/rails/pretty/logger.rb', line 274

def each_log_line_with_offset(file)
  return enum_for(:each_log_line_with_offset, file) unless block_given?

  if test_log?(file) || hourly_log?(file)
    each_raw_log_line_with_offset(file) { |line, offset| yield line, offset }
  else
    each_filtered_log_line_with_offset(file) { |line, offset| yield line, offset }
  end
end

#each_raw_log_line_with_offset(file) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/rails/pretty/logger.rb', line 284

def each_raw_log_line_with_offset(file)
  return enum_for(:each_raw_log_line_with_offset, file) unless block_given?

  File.open(file, "r") do |io|
    until io.eof?
      offset = io.pos
      line = io.gets
      yield line, offset if line
    end
  end
end

#each_request_group(file) ⇒ Object



483
484
485
486
487
488
489
# File 'lib/rails/pretty/logger.rb', line 483

def each_request_group(file)
  return enum_for(:each_request_group, file) unless block_given?

  cached_request_group_index.each do |group|
    yield request_group_from_index(group)
  end
end

#end_dateObject



229
230
231
# File 'lib/rails/pretty/logger.rb', line 229

def end_date
  @filter_params.dig(:date_range, :end) || Time.now.strftime("%Y-%m-%d")
end

#filter_logs_with_date(file) ⇒ Object



233
234
235
# File 'lib/rails/pretty/logger.rb', line 233

def filter_logs_with_date(file)
  each_filtered_log_line(file).to_a
end

#filtered_request_groups?Boolean

Returns:

  • (Boolean)


530
531
532
# File 'lib/rails/pretty/logger.rb', line 530

def filtered_request_groups?
  @filter_params[:query].to_s.strip.present? || normalized_severity_filter.present?
end

#get_date_from_log_line(line) ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/rails/pretty/logger.rb', line 296

def get_date_from_log_line(line)
  date = date_from_log_line(line)
  if date.present?
    start_date = @filter_params.dig(:date_range, :start)
    end_date = @filter_params.dig(:date_range, :end)
    if start_date.present? && end_date.present?
      date.between?(start_date, end_date)
    else
      date.between?(Time.now.strftime("%Y-%m-%d"), Time.now.strftime("%Y-%m-%d"))
    end
  end
end

#get_logs_from_file(file) ⇒ Object



264
265
266
# File 'lib/rails/pretty/logger.rb', line 264

def get_logs_from_file(file)
  each_log_line(file).to_a
end

#get_test_logs(file) ⇒ Object



260
261
262
# File 'lib/rails/pretty/logger.rb', line 260

def get_test_logs(file)
  IO.foreach(file).to_a
end

#group_matches_filters?(group) ⇒ Boolean

Returns:

  • (Boolean)


534
535
536
# File 'lib/rails/pretty/logger.rb', line 534

def group_matches_filters?(group)
  group.fetch(:lines).any? { |line| line_matches_filters?(line) }
end

#grouped_log_data(error, divider) ⇒ Object



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/rails/pretty/logger.rb', line 467

def grouped_log_data(error, divider)
  self.class.ensure_file_size_within_limit!(@log_file)

  page_start = @filter_params[:page].to_i * divider
  page_end = page_start + divider
  matching_groups = matching_request_group_index

  paginated_groups = (matching_groups[page_start...page_end] || []).map { |group| request_group_from_index(group) }

  {
    logs_count: (matching_groups.length.to_f / divider).ceil,
    paginated_logs: paginated_groups,
    error: error
  }
end

#hourly_log?(file) ⇒ Boolean

Returns:

  • (Boolean)


371
372
373
# File 'lib/rails/pretty/logger.rb', line 371

def hourly_log?(file)
  file.include?("#{File::SEPARATOR}hourly#{File::SEPARATOR}")
end

#line_include_date?(line) ⇒ Boolean

Returns:

  • (Boolean)


309
310
311
# File 'lib/rails/pretty/logger.rb', line 309

def line_include_date?(line)
  date_from_log_line(line).present?
end

#line_matches_filters?(line) ⇒ Boolean

Returns:

  • (Boolean)


375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/rails/pretty/logger.rb', line 375

def line_matches_filters?(line)
  query = @filter_params[:query].to_s.strip
  return false if query.present? && !line.downcase.include?(query.downcase)

  severity = @filter_params[:severity].to_s.upcase
  return true unless SEVERITIES.include?(severity)

  structured_severity = structured_log_severity(line)
  return structured_severity == severity if structured_severity.present?

  line.match?(/\b#{Regexp.escape(severity)}\b/i)
end

#log_dataObject



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/rails/pretty/logger.rb', line 325

def log_data
  error = validate_date
  divider = set_divider_value
  return grouped_log_data(error, divider) if request_grouping?

  page_start = @filter_params[:page].to_i * divider
  page_end = page_start + divider

  self.class.ensure_file_size_within_limit!(@log_file)

  line_offsets = cached_log_line_offsets
  paginated_logs = read_log_lines_at_offsets(@log_file, line_offsets[page_start...page_end] || [])

  data = {}
  data[:logs_count] = (line_offsets.length.to_f / divider).ceil
  data[:paginated_logs] = paginated_logs
  data[:error] = error
  return data
end

#log_file_signatureObject



453
454
455
456
# File 'lib/rails/pretty/logger.rb', line 453

def log_file_signature
  stat = File.stat(@log_file)
  [stat.size, stat.mtime.to_f, stat.ctime.to_f]
end

#log_line_index_cache_keyObject



442
443
444
445
446
447
448
449
450
451
# File 'lib/rails/pretty/logger.rb', line 442

def log_line_index_cache_key
  [
    @log_file,
    date_filtered_log? ? start_date : nil,
    date_filtered_log? ? end_date : nil,
    @filter_params[:query].to_s.strip.downcase,
    normalized_severity_filter,
    Rails::Pretty::Logger.configuration.log_line_parser&.object_id
  ]
end

#matching_request_group_indexObject



517
518
519
520
521
522
# File 'lib/rails/pretty/logger.rb', line 517

def matching_request_group_index
  groups = cached_request_group_index
  return groups unless filtered_request_groups?

  groups.select { |group| group_matches_filters?(request_group_from_index(group)) }
end

#metadata_value(metadata, *keys) ⇒ Object



651
652
653
654
655
656
657
658
659
# File 'lib/rails/pretty/logger.rb', line 651

def (, *keys)
  keys.each do |key|
    string_key = key.to_s
    return [string_key] if .key?(string_key)
    return [key] if .key?(key)
  end

  nil
end

#normalized_severity_filterObject



462
463
464
465
# File 'lib/rails/pretty/logger.rb', line 462

def normalized_severity_filter
  severity = @filter_params[:severity].to_s.upcase
  SEVERITIES.include?(severity) ? severity : nil
end

#read_log_lines_at_offsets(file, offsets) ⇒ Object



433
434
435
436
437
438
439
440
# File 'lib/rails/pretty/logger.rb', line 433

def read_log_lines_at_offsets(file, offsets)
  File.open(file, "r") do |io|
    offsets.map do |offset|
      io.seek(offset)
      io.gets
    end.compact
  end
end

#request_completion_metadata(line) ⇒ Object



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/rails/pretty/logger.rb', line 575

def (line)
   = (line)
  response_status = (, :response_status, :status)
  duration = (, :duration, :request_duration)

  if response_status.present? || duration.present?
    return {
      status: response_status.to_s,
      duration: duration.to_s
    }
  end

  match = line.strip.match(/\ACompleted\s+(?<status>\d{3}).*?\sin\s+(?<duration>[\d.]+ms)/)
  return unless match

  {
    status: match[:status],
    duration: match[:duration]
  }
end

#request_group_from_index(group) ⇒ Object



524
525
526
527
528
# File 'lib/rails/pretty/logger.rb', line 524

def request_group_from_index(group)
  group.merge(lines: read_log_lines_at_offsets(@log_file, group.fetch(:line_offsets))).tap do |request_group|
    request_group.delete(:line_offsets)
  end
end

#request_group_index_cache_keyObject



538
539
540
541
542
543
544
545
546
# File 'lib/rails/pretty/logger.rb', line 538

def request_group_index_cache_key
  [
    :request_groups,
    @log_file,
    date_filtered_log? ? start_date : nil,
    date_filtered_log? ? end_date : nil,
    Rails::Pretty::Logger.configuration.log_line_parser&.object_id
  ]
end

#request_grouping?Boolean

Returns:

  • (Boolean)


413
414
415
# File 'lib/rails/pretty/logger.rb', line 413

def request_grouping?
  @filter_params[:group].to_s == "request"
end

#request_start_metadata(line) ⇒ Object



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/rails/pretty/logger.rb', line 548

def (line)
   = (line)
  request_method = (, :request_method, :method)
  request_path = (, :request_path, :path)

  if request_method.present? && request_path.present?
    return {
      type: :request,
      method: request_method.to_s,
      path: request_path.to_s,
      ip: (, :request_ip, :ip),
      started_at: (, :request_started_at, :started_at, :timestamp, :time).to_s
    }
  end

  match = line.strip.match(/\AStarted\s+(?<method>[A-Z]+)\s+"(?<path>[^"]+)"(?:\s+for\s+(?<ip>\S+))?\s+at\s+(?<timestamp>.+)\z/)
  return unless match

  {
    type: :request,
    method: match[:method],
    path: match[:path],
    ip: match[:ip],
    started_at: match[:timestamp].strip
  }
end

#request_timestamp(line) ⇒ Object



603
604
605
606
# File 'lib/rails/pretty/logger.rb', line 603

def request_timestamp(line)
  match = line.strip.match(/\AStarted\s+.*\sat\s+(?<timestamp>.+)\z/)
  match[:timestamp] if match
end

#set_divider_valueObject



356
357
358
359
360
361
362
363
364
365
# File 'lib/rails/pretty/logger.rb', line 356

def set_divider_value
  if @filter_params[:date_range].blank?
    100
  elsif @filter_params[:date_range][:divider].blank?
    100
  else
    divider = @filter_params[:date_range][:divider].to_i
    divider.positive? ? divider : 100
  end
end

#start_dateObject



225
226
227
# File 'lib/rails/pretty/logger.rb', line 225

def start_date
  @filter_params.dig(:date_range, :start) || Time.now.strftime("%Y-%m-%d")
end

#structured_log_severity(line) ⇒ Object



619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
# File 'lib/rails/pretty/logger.rb', line 619

def structured_log_severity(line)
  severity = custom_log_severity(line)
  return severity if severity.present?

  payload = self.class.structured_log_payload(line)
  return unless payload

  STRUCTURED_SEVERITY_KEYS.each do |key|
    severity = payload[key].to_s.upcase
    return severity if SEVERITIES.include?(severity)
  end

  nested_log = payload["log"]
  return unless nested_log.respond_to?(:[])

  nested_severity = nested_log["level"].to_s.upcase
  nested_severity if SEVERITIES.include?(nested_severity)
end

#structured_log_timestamp(line) ⇒ Object



608
609
610
611
612
613
614
615
616
617
# File 'lib/rails/pretty/logger.rb', line 608

def structured_log_timestamp(line)
  payload = self.class.structured_log_payload(line)
  return unless payload

  STRUCTURED_TIMESTAMP_KEYS.each do |key|
    return payload[key].to_s if payload[key].present?
  end

  nil
end

#tail_lines(file, count) ⇒ Object



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/rails/pretty/logger.rb', line 388

def tail_lines(file, count)
  count = count.to_i
  return [] unless count.positive?

  File.open(file, "rb") do |io|
    offset = io.size
    return [] if offset.zero?

    chunks = []
    newline_count = 0

    while offset.positive? && newline_count <= count
      chunk_size = [TAIL_READ_CHUNK_SIZE, offset].min
      offset -= chunk_size
      io.seek(offset)

      chunk = io.read(chunk_size)
      chunks.unshift(chunk)
      newline_count += chunk.count("\n")
    end

    chunks.join.lines.last(count).map { |line| line.force_encoding(Encoding.default_external) }
  end
end

#tail_log_dataObject



345
346
347
348
349
350
351
352
353
354
# File 'lib/rails/pretty/logger.rb', line 345

def tail_log_data
  self.class.ensure_file_size_within_limit!(@log_file)

  lines = tail_lines(@log_file, self.class.tail_lines).select { |line| line_matches_filters?(line) }
  {
    logs_count: lines.any? ? 1 : 0,
    paginated_logs: lines,
    error: nil
  }
end

#test_log?(file) ⇒ Boolean

Returns:

  • (Boolean)


367
368
369
# File 'lib/rails/pretty/logger.rb', line 367

def test_log?(file)
  File.basename(file).include?("test")
end

#validate_dateObject



313
314
315
316
317
318
319
320
321
322
323
# File 'lib/rails/pretty/logger.rb', line 313

def validate_date
  start_date = @filter_params.dig(:date_range, :start)
  end_date = @filter_params.dig(:date_range, :end)
  if (start_date.present? && end_date.present?)
    if (start_date > end_date)
      "End Date should not be less than Start Date."
    end
  else
    "Start and End Date must be given."
  end
end