Class: Llv::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/llv/parser.rb

Overview

Turns a raw log line into a structured Event. Stateful per-stream so that untagged HTTP lines (when the host app hasn’t configured ‘config.log_tags = [:request_id]`) can still be grouped via Started/Completed bracketing.

Defined Under Namespace

Classes: Event

Constant Summary collapse

TAG_RE =

Rails::TaggedLogging emits “[tag] ” (single space) for each tag, so we only consume one space after each bracket — anything more is the message’s own indentation and is meaningful for classification.

/\A(\[[^\]]*\] )+/
UUID_RE =
/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/
CLASSIFIERS =
[
  [
    :request_started,
    # The optional bracketed segment matches `[WebSocket]` for ActionCable
    # upgrade lines and similar transport markers Rails sometimes emits.
    /\AStarted\s+(?<method>[A-Z]+)\s+"(?<path>[^"]+)"(?:\s+\[[^\]]+\])?\s+for\s+(?<ip>\S+)\s+at\s+(?<at>.+)\z/
  ],
  [
    :processing,
    /\AProcessing by\s+(?<controller>\S+)\s+as\s+(?<format>\S+)\z/
  ],
  [
    :parameters,
    /\A\s*Parameters:\s*(?<params>.+)\z/
  ],
  [
    :sql_source,
    /\A\s*↳\s+(?<source>.+)\z/
  ],
  [
    :sql,
    /\A\s+(?<label>[A-Z][A-Za-z0-9_:]*\s+(?:Load|Count|Exists\??|Pluck|Update|Insert|Create|Destroy|Delete|Maximum|Minimum|Sum|Average))\s+\((?<duration>[\d.]+)(?<unit>ms|s)\)\s+(?<query>.+)\z/
  ],
  [
    :sql,
    /\A\s+(?<label>SQL|TRANSACTION|SCHEMA|CACHE)\s+\((?<duration>[\d.]+)(?<unit>ms|s)\)\s+(?<query>.+)\z/
  ],
  [
    :render,
    /\A\s*Rendered\s+(?<template>.+)\z/
  ],
  [
    :cache,
    /\ACACHE\s+(?<result>HIT|MISS)\s+(?<key>.+)\z/
  ],
  [
    :request_completed,
    /\ACompleted\s+(?<status>\d+)\s+(?<status_text>[A-Za-z ]+?)\s+in\s+(?<duration_ms>[\d.]+)ms(?:\s+\((?<details>[^)]+)\))?/
  ],
  [
    :job_enqueued,
    /\AEnqueued\s+(?<job>\S+)\s+\(Job ID:\s+(?<job_id>[\w-]+)\)\s+to\s+(?<adapter>[^(]+)\((?<queue>[^)]+)\)/
  ],
  [
    :job_performing,
    /\APerforming\s+(?<job>\S+)\s+\(Job ID:\s+(?<job_id>[\w-]+)\)\s+from\s+(?<adapter>[^(]+)\((?<queue>[^)]+)\)/
  ],
  [
    :job_performed,
    /\APerformed\s+(?<job>\S+)\s+\(Job ID:\s+(?<job_id>[\w-]+)\)\s+from\s+(?<adapter>[^(]+)\((?<queue>[^)]+)\)\s+in\s+(?<duration_ms>[\d.]+)ms/
  ],
  [
    :job_retry_stopped,
    /\AStopped retrying\s+(?<job>\S+)\s+\(Job ID:\s+(?<job_id>[\w-]+)\)/
  ]
].freeze

Instance Method Summary collapse

Constructor Details

#initializeParser

Returns a new instance of Parser.



85
86
87
88
# File 'lib/llv/parser.rb', line 85

def initialize
  @untagged_seq = 0
  @open_untagged_http = nil
end

Instance Method Details

#parse(raw_line) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/llv/parser.rb', line 90

def parse(raw_line)
  raw = raw_line.chomp
  plain = Ansi.strip(raw)
  tag_match = plain.match(TAG_RE)
  tag_prefix_len = tag_match ? tag_match[0].length : 0
  tags = tag_match ? tag_match[0].scan(/\[([^\]]*)\]/).flatten : []

  raw_body = raw[tag_prefix_len..] || ""
  plain_body = plain[tag_prefix_len..] || ""

  group_id, group_kind, title = identify_group(tags)
  type, payload = classify(plain_body)

  group_id, group_kind, title = adjust_for_untagged(group_id, group_kind, title, type, payload)

  title ||= derive_title(type, payload)

  # An HTTP request to /cable is ActionCable. Treat it as its own kind so
  # the UI can filter it out — these connections are long-lived and noisy.
  if group_kind == :http && cable_path?(title, payload)
    group_kind = :cable
  end

  Event.new(
    group_id: group_id,
    group_kind: group_kind,
    group_title: title,
    type: type,
    payload: payload,
    raw: raw_body,
    plain: plain_body
  )
end