Module: Legion::Logging::Builder

Included in:
Legion::Logging, Logger
Defined in:
lib/legion/logging/builder.rb

Instance Method Summary collapse

Instance Method Details

#async?Boolean

Returns:

  • (Boolean)


174
175
176
# File 'lib/legion/logging/builder.rb', line 174

def async?
  (@async == true && @async_writer&.alive?) || false
end

#build_context_kv_pairsObject



88
89
90
91
92
93
94
95
96
97
# File 'lib/legion/logging/builder.rb', line 88

def build_context_kv_pairs
  pairs = []
  request_id = Thread.current[:legion_log_request_id]
  pairs << "request_id=#{request_id}" if request_id.is_a?(String) && !request_id.empty?
  exchange_id = Thread.current[:legion_log_exchange_id]
  pairs << "exchange_id=#{exchange_id}" if exchange_id.is_a?(String) && !exchange_id.empty?
  chain_id = Thread.current[:legion_log_chain_id]
  pairs << "chain_id=#{chain_id}" if chain_id.is_a?(String) && !chain_id.empty?
  pairs.join(' ')
end

#build_runner_trace(loc = caller_locations(6, 1)&.first) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/legion/logging/builder.rb', line 99

def build_runner_trace(loc = caller_locations(6, 1)&.first)
  return unless loc

  path = loc.to_s.split('/').last(2)
  {
    type:        path[0],
    file:        File.basename(loc.path, '.*'),
    function:    loc.base_label,
    line_number: loc.lineno
  }
end

#json?Boolean

Returns:

  • (Boolean)


17
18
19
# File 'lib/legion/logging/builder.rb', line 17

def json?
  @format == :json
end

#json_format(include_pid: false) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/legion/logging/builder.rb', line 21

def json_format(include_pid: false)
  log.formatter = proc do |severity, datetime, _progname, msg|
    entry = {
      timestamp: datetime.utc.iso8601(3),
      level:     severity.downcase,
      message:   msg.is_a?(String) ? msg.gsub(/\e\[[0-9;]*m/, '') : msg.to_s,
      thread:    Thread.current.object_id
    }
    entry[:pid] = ::Process.pid if include_pid
    segments = Thread.current[:legion_log_segments]
    entry[:segments] = segments if segments
    method_ctx = Thread.current[:legion_log_method]
    entry[:method] = method_ctx if method_ctx
    conv_id = Thread.current[:legion_log_conv_id]
    entry[:conversation_id] = conv_id if conv_id.is_a?(String) && !conv_id.empty?
    request_id = Thread.current[:legion_log_request_id]
    entry[:request_id] = request_id if request_id.is_a?(String) && !request_id.empty?
    exchange_id = Thread.current[:legion_log_exchange_id]
    entry[:exchange_id] = exchange_id if exchange_id.is_a?(String) && !exchange_id.empty?
    chain_id = Thread.current[:legion_log_chain_id]
    entry[:chain_id] = chain_id if chain_id.is_a?(String) && !chain_id.empty?
    "#{::JSON.generate(entry)}\n"
  rescue StandardError => e
    warn("Legion::Logging::Builder#json_format formatter failed: #{e.message}")
    "{\"timestamp\":\"#{datetime}\",\"level\":\"#{severity}\",\"message\":#{msg.to_s.dump}}\n"
  end
end

#levelObject



147
148
149
# File 'lib/legion/logging/builder.rb', line 147

def level
  log.level
end

#logObject



115
116
117
# File 'lib/legion/logging/builder.rb', line 115

def log
  @log ||= set_log
end

#log_format(format: :text, include_pid: false) ⇒ Object



8
9
10
11
12
13
14
15
# File 'lib/legion/logging/builder.rb', line 8

def log_format(format: :text, include_pid: false, **)
  @format = format.to_sym
  if @format == :json
    json_format(include_pid: include_pid)
  else
    text_format(include_pid: include_pid, **)
  end
end

#log_level(level = 'debug') ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/legion/logging/builder.rb', line 151

def log_level(level = 'debug')
  log.level = case level
              when 'trace', 'debug'
                ::Logger::DEBUG
              when 'info'
                ::Logger::INFO
              when 'warn'
                ::Logger::WARN
              when 'error'
                ::Logger::ERROR
              when 'fatal'
                ::Logger::FATAL
              when nil
                42
              else
                if level.is_a? Integer
                  level
                else
                  1
                end
              end
end

#output(**options) ⇒ Object



111
112
113
# File 'lib/legion/logging/builder.rb', line 111

def output(**options)
  set_log(logfile: options[:log_file], log_stdout: options[:log_stdout])
end

#prepare_log_path(path) ⇒ Object



141
142
143
144
145
# File 'lib/legion/logging/builder.rb', line 141

def prepare_log_path(path)
  expanded = File.expand_path(path)
  FileUtils.mkdir_p(File.dirname(expanded))
  expanded
end

#resolve_lex_tag(options) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/legion/logging/builder.rb', line 70

def resolve_lex_tag(options)
  segments = Thread.current[:legion_log_segments]
  tag = if segments
          segments.map { |s| "[#{s}]" }.join
        elsif options.key?(:lex_segments)
          options[:lex_segments].map { |s| "[#{s}]" }.join
        elsif options.key?(:lex) && !options[:lex].nil?
          "[#{options[:lex]}]"
        end

  method_ctx = Thread.current[:legion_log_method]
  tag = "#{tag}{#{method_ctx}}" if tag && method_ctx

  context_id = Thread.current[:legion_log_conv_id]
  tag = "#{tag}{#{context_id}}" if tag && context_id.is_a?(String) && !context_id.empty?
  tag
end

#set_log(logfile: nil, log_stdout: nil) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/legion/logging/builder.rb', line 119

def set_log(logfile: nil, log_stdout: nil, **)
  previous_log = @log

  if logfile && log_stdout != false
    path = prepare_log_path(logfile)
    require_relative 'multi_io'
    file = File.new(path, 'a')
    file.sync = true
    io = MultiIO.new($stdout, file)
    @log = ::Logger.new(io)
  elsif logfile
    file = File.new(prepare_log_path(logfile), 'a')
    file.sync = true
    @log = ::Logger.new(file)
  else
    @log = ::Logger.new($stdout)
  end

  close_replaced_log(previous_log)
  @log
end

#start_async_writer(buffer_size: 10_000) ⇒ Object

rubocop:disable Naming/PredicateMethod



179
180
181
182
183
184
185
186
187
# File 'lib/legion/logging/builder.rb', line 179

def start_async_writer(buffer_size: 10_000)
  require_relative 'async_writer'
  return false if @async_writer&.alive? && stop_async_writer == false

  @async_writer = AsyncWriter.new(log, buffer_size: buffer_size)
  @async_writer.start
  @async = true
  true
end

#stop_async_writerObject



189
190
191
192
193
194
195
196
197
198
# File 'lib/legion/logging/builder.rb', line 189

def stop_async_writer
  writer = @async_writer
  stopped = writer&.stop
  return false if stopped == false

  close_replaced_log(writer.logger) if writer.respond_to?(:logger)
  @async_writer = nil if @async_writer.equal?(writer)
  @async = false
  true
end

#text_format(include_pid: false, **options) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/legion/logging/builder.rb', line 49

def text_format(include_pid: false, **options)
  log.formatter = proc do |severity, datetime, _progname, msg|
    lex_name = resolve_lex_tag(options)
    runner_trace = Thread.current[:legion_log_caller] || build_runner_trace if lex_name

    string = "[#{datetime}]"
    string.concat("[#{::Process.pid}]") if include_pid
    string.concat(lex_name) if lex_name
    if runner_trace.is_a?(Hash) && (options[:extended] || severity == 'debug')
      string.concat("[#{runner_trace[:type]}:#{runner_trace[:file]}:#{runner_trace[:function]}:#{runner_trace[:line_number]}]")
    end
    ctx_pairs = build_context_kv_pairs
    if ctx_pairs.empty?
      string.concat(" #{severity} #{msg}\n")
    else
      string.concat(" #{severity} #{msg} #{ctx_pairs}\n")
    end
    string
  end
end