Class: Hyperion::Logger
- Inherits:
-
Object
- Object
- Hyperion::Logger
- Defined in:
- lib/hyperion/logger.rb
Overview
Structured logger.
Usage:
logger = Hyperion::Logger.new
logger.info { { message: 'listening', host: '127.0.0.1', port: 9292 } }
logger.warn { { message: 'parse error', error: e., error_class: e.class.name } }
logger.error 'plain string also works for legacy callers'
Level is set from:
1. The `level:` constructor kwarg (highest precedence).
2. ENV['HYPERION_LOG_LEVEL'] if set.
3. Defaults to :info.
Format is :text (key=value), :json (JSONL), or :auto (default — picks the right one based on the runtime environment, see #pick_format below).
Each log line is prefixed with timestamp + level + a ‘hyperion’ tag so operators can grep multi-process worker output. When the resolved format is :text and the underlying IO is a TTY, level names are ANSI-coloured for readability.
Constant Summary collapse
- LEVELS =
{ debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }.freeze
- LEVEL_COLORS =
{ debug: "\e[90m", # bright black / grey info: "\e[32m", # green warn: "\e[33m", # yellow error: "\e[31m", # red fatal: "\e[35m" # magenta }.freeze
- COLOR_RESET =
magenta
"\e[0m"- PRODUCTION_ENVS =
%w[production staging].freeze
- ERROR_LEVELS =
Levels at WARN or higher are routed to the error stream (stderr by default). info / debug go to the regular stream (stdout by default). 12-factor: app logs to stdout, errors to stderr.
%i[warn error fatal].freeze
- ACCESS_FLUSH_BYTES =
Per-thread access-log buffer flush threshold. ~32 average-size lines per write(2) call, well under PIPE_BUF (4096) so writes stay atomic. Larger = fewer syscalls but higher latency-to-disk (up to ~32 reqs of delay before the line shows up in the log file). 4 KiB is a good balance: a 16-thread fleet at 24k r/s flushes ~750 buffers/sec total vs ~24 000 syscalls/sec without buffering.
4096
Instance Attribute Summary collapse
-
#format ⇒ Object
readonly
Returns the value of attribute format.
-
#level ⇒ Object
Returns the value of attribute level.
Instance Method Summary collapse
-
#access(method, path, query, status, duration_ms, remote_addr, http_version) ⇒ Object
Hot-path access-log emitter — bypasses the generic format_text / format_json kvs.join + hash#map allocations.
-
#flush_access_buffer ⇒ Object
Flush this thread’s buffered access-log lines.
-
#initialize(out: $stdout, err: $stderr, io: nil, level: nil, format: nil) ⇒ Logger
constructor
A new instance of Logger.
-
#io_for(lvl) ⇒ Object
Pick the destination IO for a given level.
Constructor Details
#initialize(out: $stdout, err: $stderr, io: nil, level: nil, format: nil) ⇒ Logger
Returns a new instance of Logger.
49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/hyperion/logger.rb', line 49 def initialize(out: $stdout, err: $stderr, io: nil, level: nil, format: nil) # `io:` is a back-compat alias for tests / single-IO use cases — it # routes both streams to the same target (e.g. a StringIO in specs). @out = io || out @err = io || err @level = parse_level(level || ENV.fetch('HYPERION_LOG_LEVEL', 'info')) requested = format || ENV['HYPERION_LOG_FORMAT'] @format = pick_format(requested) # Colorize when format is text AND the destination is a TTY. We only # check the regular stream here — colored text is for humans. @colorize = @format == :text && tty?(@out) end |
Instance Attribute Details
#format ⇒ Object (readonly)
Returns the value of attribute format.
42 43 44 |
# File 'lib/hyperion/logger.rb', line 42 def format @format end |
#level ⇒ Object
Returns the value of attribute level.
42 43 44 |
# File 'lib/hyperion/logger.rb', line 42 def level @level end |
Instance Method Details
#access(method, path, query, status, duration_ms, remote_addr, http_version) ⇒ Object
Hot-path access-log emitter — bypasses the generic format_text / format_json kvs.join + hash#map allocations. The whole line is built via a single interpolation, the timestamp is cached per-thread per millisecond, and we batch lines into a per-thread buffer that flushes when full (lock-free emit; POSIX write(2) is atomic for writes <= PIPE_BUF / 4096 bytes).
Returns silently on any IO error — logging must never crash the server.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/hyperion/logger.rb', line 98 def access(method, path, query, status, duration_ms, remote_addr, http_version) return unless emit?(:info) ts = line = if @format == :json build_access_json(ts, method, path, query, status, duration_ms, remote_addr, http_version) else build_access_text(ts, method, path, query, status, duration_ms, remote_addr, http_version) end buf = (Thread.current[:__hyperion_access_buf__] ||= +'') buf << line return if buf.bytesize < ACCESS_FLUSH_BYTES @out.write(buf) buf.clear rescue StandardError # Swallow logger failures — never let logging crash the server. end |
#flush_access_buffer ⇒ Object
Flush this thread’s buffered access-log lines. Called by the connection loop when a connection closes (so log lines from a closing keep-alive session don’t get stuck behind the buffer until the next connection).
121 122 123 124 125 126 127 128 129 |
# File 'lib/hyperion/logger.rb', line 121 def flush_access_buffer buf = Thread.current[:__hyperion_access_buf__] return if buf.nil? || buf.empty? @out.write(buf) buf.clear rescue StandardError # Swallow logger failures — never let logging crash the server. end |
#io_for(lvl) ⇒ Object
Pick the destination IO for a given level. warn / error / fatal → @err (stderr default). debug / info → @out (stdout default).
74 75 76 |
# File 'lib/hyperion/logger.rb', line 74 def io_for(lvl) ERROR_LEVELS.include?(lvl) ? @err : @out end |