Class: Logsy::JsonFormatter

Inherits:
Logger::Formatter
  • Object
show all
Defined in:
lib/logsy/json_formatter.rb

Overview

Logger formatter that emits one JSON object per log call.

Each line includes:

- ts:    UTC ISO-8601 timestamp with millis
- level: severity ('INFO', 'ERROR', ...)
- msg:   the log message (when message is a String or Exception)
- file:  "path:line" of the nearest app frame (when
       include_caller_location). Gem, stdlib, and ignored frames
       are skipped, so an ActiveRecord SQL line points at the
       controller/model code that ran the query — not at
       active_record/log_subscriber.rb. Omitted entirely for
       framework-only lines (e.g. "Started GET ..."), which have
       no app frame.
- any non-nil tags from Logsy[]

String messages are cleaned before emission: ANSI color codes (e.g. ActiveRecord's SQL highlighting) are stripped and surrounding whitespace removed, so log stores get plain searchable text.

When the message is a Hash, its keys are merged directly into the top-level payload (useful for "wide event" emissions like a request summary). Symbols and strings are accepted as keys.

Example output:

{"ts":"2026-05-02T10:00:00.123Z","level":"INFO","msg":"hello",
"file":"app/controllers/orders_controller.rb:34",
"request_id":"abc-123","user_id":"u-1"}

Constant Summary collapse

ANSI_ESCAPE =
/\e\[[0-9;]*m/
FRAME_BATCH_SIZE =
16

Instance Method Summary collapse

Instance Method Details

#call(severity, time, _progname, message) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/logsy/json_formatter.rb', line 39

def call(severity, time, _progname, message)
  payload = {
    ts: time.utc.iso8601(3),
    level: severity
  }

  # Hash messages are structured wide-event emissions from our own
  # ControllerHooks/JobHooks. They're logged from a logsy frame with no
  # app frame above to attribute, so the caller walk only ever scans the
  # full stack to emit no :file — skip it (output is identical either way).
  add_caller_location!(payload) if Logsy.configuration.include_caller_location && !message.is_a?(::Hash)
  merge_context!(payload)
  merge_message!(payload, message)

  "#{JSON.dump(payload)}\n"
rescue StandardError => e
  fallback_line(severity, time, e)
end