console_kit 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e392416a4b0f32756b184aefa7431d71ce45a733479e477f9c54f9e14affcd6
4
- data.tar.gz: 3b15e7006400dc93794f84e7e14828f573639c67452c0ccb4922ece8fcab9c89
3
+ metadata.gz: ee9784d78350655db23efa1080d49d5e2527e1b6530ef7a1c2772974e57a89ec
4
+ data.tar.gz: 2edbd66f104e2a2e549d11991fbbdbd1f77118227dfad56122bb58baefd4ee72
5
5
  SHA512:
6
- metadata.gz: c6f6b2d49d2c2e2d776af957764804dfbebb397a7fbb52449013a77bed92d1ff817672154d546a304bbc92c9c385f14e5402bee250046e8b001564169f52881d
7
- data.tar.gz: e9c24b447c674eb693a05b9552484e293a483b547e04620e78145f9c865010c27b45f487ab16b453bce8bd9a7bb8c4c180c9464abef3cda20f7e5678fdc932bd
6
+ metadata.gz: 92d30809eba5bb236130e2e472cb2ae8fc34f9ea0bd69b3a5c67334199cc34a9894666797ebe66c52489f8a2c22a403d1f65e097bfafdba89e3d3d9d5ec8d5b9
7
+ data.tar.gz: db949d61fdcff82557532b163dd1df0487e6e3fb6c3f0901eb172fa83b6adc87f70b191b59dca6405b76c90fad67c3b2c58aaff21161e441ae295e3dbdea116b
@@ -3,20 +3,23 @@
3
3
  module ConsoleKit
4
4
  # Stores ConsoleKit configurations such as tenant map and context behavior
5
5
  class Configuration
6
- attr_accessor :pretty_output, :tenants, :sql_base_class
7
- attr_writer :context_class
6
+ # Value object for storing configuration settings
7
+ Settings = Struct.new(:pretty_output, :tenants, :context_class, :sql_base_class, :show_dashboard)
8
8
 
9
9
  def initialize
10
- @pretty_output = true
11
- @tenants = nil
12
- @context_class = nil
13
- @sql_base_class = 'ApplicationRecord'
10
+ @settings = Settings.new(true, nil, nil, 'ApplicationRecord', false)
11
+ end
12
+
13
+ %i[pretty_output tenants context_class sql_base_class show_dashboard].each do |attr|
14
+ define_method(attr) { @settings.send(attr) }
15
+ define_method("#{attr}=") { |val| @settings.send("#{attr}=", val) }
14
16
  end
15
17
 
16
18
  def context_class
17
- case @context_class
18
- when String, Symbol then resolve_context_class
19
- else @context_class
19
+ val = @settings.context_class
20
+ case val
21
+ when String, Symbol then resolve_context_class(val)
22
+ else val
20
23
  end
21
24
  end
22
25
 
@@ -28,17 +31,17 @@ module ConsoleKit
28
31
  end
29
32
 
30
33
  def validate!
31
- raise Error, 'ConsoleKit: `tenants` is not configured.' if @tenants.blank?
32
- raise Error, 'ConsoleKit: `tenants` must be a Hash.' unless @tenants.is_a?(Hash)
33
- raise Error, 'ConsoleKit: `context_class` is not configured.' if @context_class.blank?
34
+ raise Error, 'ConsoleKit: `tenants` is not configured.' if tenants.blank?
35
+ raise Error, 'ConsoleKit: `tenants` must be a Hash.' unless tenants.is_a?(Hash)
36
+ raise Error, 'ConsoleKit: `context_class` is not configured.' if @settings.context_class.blank?
34
37
  end
35
38
 
36
39
  private
37
40
 
38
- def resolve_context_class
39
- @context_class.to_s.constantize
41
+ def resolve_context_class(val)
42
+ val.to_s.constantize
40
43
  rescue NameError
41
- raise Error, "ConsoleKit: context_class '#{@context_class}' could not be found. " \
44
+ raise Error, "ConsoleKit: context_class '#{val}' could not be found. " \
42
45
  'Ensure the class is defined before configuration is accessed.'
43
46
  end
44
47
  end
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/class/subclasses'
4
+ require 'active_support/core_ext/string/inflections'
5
+ require 'active_support/core_ext/string/filters'
6
+ require_relative 'diagnostic_helpers'
4
7
 
5
8
  module ConsoleKit
6
9
  module Connections
7
10
  # Parent class for connection handlers
8
11
  class BaseConnectionHandler
12
+ include DiagnosticHelpers
13
+
9
14
  class << self
10
15
  def registry = descendants
11
16
  end
@@ -14,7 +19,48 @@ module ConsoleKit
14
19
 
15
20
  def initialize(context) = @context = context
16
21
  def connect = raise NotImplementedError, "#{self.class} must implement #connect"
17
- def available? = false
22
+ def available? = raise NotImplementedError, "#{self.class} must implement #available?"
23
+ def diagnostics = raise NotImplementedError, "#{self.class} must implement #diagnostics"
24
+
25
+ def safe_diagnostics(timeout: 2)
26
+ handler_name = self.class.name.demodulize.delete_suffix('ConnectionHandler')
27
+ thread, result_wrapper = spawn_diagnostic_thread(handler_name)
28
+
29
+ if thread.join(timeout)
30
+ result_wrapper[:value] || error_diagnostics(handler_name, StandardError.new('Unknown error'))
31
+ else
32
+ thread.kill
33
+ timeout_diagnostics(handler_name, timeout)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def spawn_diagnostic_thread(handler_name)
40
+ wrapper = { value: nil }
41
+ thread = Thread.new { wrapper[:value] = run_diagnostics_safely(handler_name) }
42
+ [thread, wrapper]
43
+ end
44
+
45
+ def run_diagnostics_safely(name)
46
+ diagnostics
47
+ rescue StandardError => e
48
+ error_diagnostics(name, e)
49
+ end
50
+
51
+ def context_attribute(name)
52
+ @context.respond_to?(name, true) ? @context.send(name) : nil
53
+ end
54
+
55
+ def measure_latency
56
+ start = clock_time
57
+ yield
58
+ ((clock_time - start) * 1000).round(1)
59
+ end
60
+
61
+ def unavailable_diagnostics(name)
62
+ { name: name, status: :unavailable, latency_ms: nil, details: {} }
63
+ end
18
64
  end
19
65
  end
20
66
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative 'sql_connection_handler'
4
4
  require_relative 'mongo_connection_handler'
5
+ require_relative 'redis_connection_handler'
6
+ require_relative 'elasticsearch_connection_handler'
5
7
 
6
8
  module ConsoleKit
7
9
  module Connections
@@ -12,6 +14,8 @@ module ConsoleKit
12
14
  handler_classes.filter_map do |klass|
13
15
  handler = klass.new(context)
14
16
  handler if handler.available?
17
+ rescue NotImplementedError
18
+ nil
15
19
  end
16
20
  end
17
21
 
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'table_renderer'
4
+
5
+ module ConsoleKit
6
+ module Connections
7
+ # Displays connection diagnostics as a Unicode table
8
+ module Dashboard
9
+ class << self
10
+ def display
11
+ rows = fetch_diagnostics
12
+ return Output.print_warning('No connections available') if rows.empty?
13
+
14
+ Output.print_header('Connection Dashboard')
15
+ Output.print_raw(TableRenderer.render(rows))
16
+ end
17
+
18
+ private
19
+
20
+ def fetch_diagnostics
21
+ ctx = ConsoleKit.configuration.context_class
22
+ handlers = ConnectionManager.available_handlers(ctx)
23
+ handlers.map(&:safe_diagnostics)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ module Connections
5
+ # Shared helper methods for connection diagnostics
6
+ module DiagnosticHelpers
7
+ module_function
8
+
9
+ def clock_time
10
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
11
+ end
12
+
13
+ def error_diagnostics(name, error)
14
+ { name: name, status: :error, latency_ms: nil, details: { error: error.message.truncate(60) } }
15
+ end
16
+
17
+ def timeout_diagnostics(name, timeout)
18
+ { name: name, status: :timeout, latency_ms: nil, details: { error: "Timed out after #{timeout}s" } }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_connection_handler'
4
+
5
+ module ConsoleKit
6
+ module Connections
7
+ # Handles Elasticsearch connections
8
+ class ElasticsearchConnectionHandler < BaseConnectionHandler
9
+ def connect
10
+ prefix = context_attribute(:tenant_elasticsearch_prefix).presence
11
+ Output.print_info(switch_message(prefix))
12
+ Thread.current[:console_kit_elasticsearch_prefix] = prefix
13
+ apply_model_index_prefix(prefix)
14
+ end
15
+
16
+ def available?
17
+ !!(defined?(Elasticsearch::Model) && Elasticsearch::Model.respond_to?(:client))
18
+ end
19
+
20
+ def diagnostics
21
+ name = 'Elasticsearch'
22
+ return unavailable_diagnostics(name) unless available?
23
+
24
+ client = Elasticsearch::Model.client
25
+ latency = measure_latency { client.ping }
26
+ build_elasticsearch_diagnostics(client, latency)
27
+ rescue StandardError => e
28
+ error_diagnostics(name, e)
29
+ end
30
+
31
+ private
32
+
33
+ def build_elasticsearch_diagnostics(client, latency)
34
+ {
35
+ name: 'Elasticsearch',
36
+ status: :connected,
37
+ latency_ms: latency,
38
+ details: elasticsearch_details(client.cluster.health)
39
+ }
40
+ end
41
+
42
+ def elasticsearch_details(health)
43
+ {
44
+ prefix: context_attribute(:tenant_elasticsearch_prefix),
45
+ cluster: health['cluster_name'],
46
+ health: health['status']
47
+ }
48
+ end
49
+
50
+ def apply_model_index_prefix(prefix)
51
+ return unless defined?(Elasticsearch::Model)
52
+ return unless Elasticsearch::Model.respond_to?(:index_name_prefix=)
53
+
54
+ Elasticsearch::Model.index_name_prefix = prefix
55
+ end
56
+
57
+ def switch_message(prefix)
58
+ prefix ? "Setting Elasticsearch index prefix: #{prefix}" : 'Resetting Elasticsearch index prefix to default'
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,28 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
3
  require_relative 'base_connection_handler'
5
4
 
6
5
  module ConsoleKit
7
6
  module Connections
8
7
  # Handles MongoDB connections
9
8
  class MongoConnectionHandler < BaseConnectionHandler
10
- extend Forwardable
11
-
12
- def_delegator :@context, :tenant_mongo_db
13
-
14
9
  def connect
15
- db = tenant_mongo_db.presence
10
+ db = context_attribute(:tenant_mongo_db).presence
16
11
  Output.print_info(switch_message(db))
17
- Mongoid.override_client(db)
12
+ Mongoid.override_database(db)
18
13
  rescue NoMethodError
19
- Output.print_warning('Mongoid.override_client is not defined.')
14
+ Output.print_warning('Mongoid.override_database is not available in this version of Mongoid.')
20
15
  end
21
16
 
22
17
  def available? = defined?(Mongoid)
23
18
 
19
+ def diagnostics
20
+ name = 'MongoDB'
21
+ return unavailable_diagnostics(name) unless available?
22
+
23
+ db = tenant_database
24
+ latency = measure_latency { db.command(ping: 1) }
25
+ build_mongo_diagnostics(db, latency)
26
+ rescue StandardError => e
27
+ error_diagnostics(name, e)
28
+ end
29
+
24
30
  private
25
31
 
32
+ def tenant_database
33
+ override = context_attribute(:tenant_mongo_db).presence
34
+ client = Mongoid.default_client
35
+ return client.database unless override
36
+
37
+ client.use(override).database
38
+ end
39
+
40
+ def build_mongo_diagnostics(database, latency)
41
+ build_info = database.command(buildInfo: 1).first
42
+ {
43
+ name: 'MongoDB',
44
+ status: :connected,
45
+ latency_ms: latency,
46
+ details: mongo_details(database.name, build_info['version'])
47
+ }
48
+ end
49
+
50
+ def mongo_details(name, version)
51
+ { database: name, version: version }
52
+ end
53
+
26
54
  def switch_message(db)
27
55
  db ? "Switching to MongoDB client: #{db}" : 'Resetting MongoDB client to default'
28
56
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_connection_handler'
4
+
5
+ module ConsoleKit
6
+ module Connections
7
+ # Handles Redis connections
8
+ class RedisConnectionHandler < BaseConnectionHandler
9
+ DEFAULT_REDIS_DB = 0
10
+
11
+ def connect
12
+ db = context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB
13
+ Output.print_info(switch_message(db))
14
+ select_redis_db(db)
15
+ end
16
+
17
+ def available? = defined?(Redis)
18
+
19
+ def diagnostics
20
+ name = 'Redis'
21
+ return unavailable_diagnostics(name) unless available?
22
+
23
+ redis = fetch_redis_client
24
+ return unavailable_diagnostics(name) unless redis
25
+
26
+ latency = measure_latency { redis.ping }
27
+ build_redis_diagnostics(redis.info, latency)
28
+ rescue StandardError => e
29
+ error_diagnostics(name, e)
30
+ end
31
+
32
+ private
33
+
34
+ def fetch_redis_client
35
+ Redis.respond_to?(:current) && Redis.current
36
+ end
37
+
38
+ def build_redis_diagnostics(info, latency)
39
+ {
40
+ name: 'Redis',
41
+ status: :connected,
42
+ latency_ms: latency,
43
+ details: redis_details(info)
44
+ }
45
+ end
46
+
47
+ def redis_details(info)
48
+ {
49
+ db: context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB,
50
+ version: info['redis_version'],
51
+ memory: info['used_memory_human']
52
+ }
53
+ end
54
+
55
+ def select_redis_db(db)
56
+ redis = fetch_redis_client
57
+ if redis
58
+ redis.select(db)
59
+ elsif defined?(RedisClient) && db != DEFAULT_REDIS_DB
60
+ warn_about_redis_client(db)
61
+ end
62
+ rescue NoMethodError
63
+ Output.print_warning('Redis.current is not available (deprecated in Redis v5+).')
64
+ end
65
+
66
+ def warn_about_redis_client(db)
67
+ Output.print_warning("Redis DB #{db} configured but auto-select not supported with RedisClient. " \
68
+ 'Ensure your Redis configuration sets the correct DB.')
69
+ end
70
+
71
+ def switch_message(db)
72
+ db ? "Switching to Redis DB: #{db}" : 'Resetting Redis connection to default'
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,28 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
3
  require_relative 'base_connection_handler'
5
4
 
6
5
  module ConsoleKit
7
6
  module Connections
8
7
  # Handles SQL connections
9
8
  class SqlConnectionHandler < BaseConnectionHandler
10
- extend Forwardable
11
-
12
- def_delegator :@context, :tenant_shard
13
-
14
9
  def connect
15
- shard = tenant_shard.presence&.to_sym
16
- msg = shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
17
-
18
- Output.print_info("#{msg} via #{base_class}")
19
- base_class.establish_connection(shard)
10
+ shard = context_attribute(:tenant_shard).presence&.to_sym
11
+ Output.print_info("#{connection_message(shard)} via #{base_class}")
12
+ disconnect_existing_pool
13
+ shard ? base_class.establish_connection(shard) : base_class.establish_connection
20
14
  end
21
15
 
22
16
  def available? = sql_base_class_name.to_s.safe_constantize.present?
23
17
 
18
+ def diagnostics
19
+ name = 'SQL'
20
+ return unavailable_diagnostics(name) unless available?
21
+
22
+ conn = base_class.connection
23
+ latency = measure_latency { conn.execute('SELECT 1') }
24
+ build_sql_diagnostics(conn, latency)
25
+ rescue StandardError => e
26
+ error_diagnostics(name, e)
27
+ end
28
+
24
29
  private
25
30
 
31
+ def disconnect_existing_pool
32
+ return unless base_class.respond_to?(:connection_pool)
33
+
34
+ pool = base_class.connection_pool
35
+ pool&.disconnect!
36
+ end
37
+
38
+ def build_sql_diagnostics(conn, latency)
39
+ {
40
+ name: 'SQL',
41
+ status: :connected,
42
+ latency_ms: latency,
43
+ details: {
44
+ adapter: conn.adapter_name,
45
+ pool_size: base_class.connection_pool.size,
46
+ version: fetch_sql_version(conn).to_s.truncate(50)
47
+ }
48
+ }
49
+ end
50
+
51
+ def fetch_sql_version(conn)
52
+ conn.select_value('SELECT version()')
53
+ rescue StandardError
54
+ nil
55
+ end
56
+
26
57
  def base_class
27
58
  klass = sql_base_class_name.to_s.safe_constantize
28
59
  return klass if klass
@@ -30,6 +61,10 @@ module ConsoleKit
30
61
  raise Error, "ConsoleKit: sql_base_class '#{sql_base_class_name}' could not be found."
31
62
  end
32
63
 
64
+ def connection_message(shard)
65
+ shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
66
+ end
67
+
33
68
  def sql_base_class_name = ConsoleKit.configuration.sql_base_class
34
69
  end
35
70
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ module Connections
5
+ # Formats raw diagnostic data for table display
6
+ module TableFormatter
7
+ class << self
8
+ def format_row(diag)
9
+ [
10
+ diag[:name],
11
+ format_status(diag[:status]),
12
+ format_latency(diag[:latency_ms]),
13
+ format_details(diag[:details])
14
+ ]
15
+ end
16
+
17
+ def format_status(status)
18
+ return "\u2713 Connected" if status == :connected
19
+ return "\u2717 Error" if %i[error timeout].include?(status)
20
+ return "\u2014 N/A" if status == :unavailable
21
+
22
+ '? Unknown'
23
+ end
24
+
25
+ def format_latency(latency_ms)
26
+ latency_ms ? "#{latency_ms}ms" : "\u2014"
27
+ end
28
+
29
+ def format_details(details)
30
+ return '' unless details&.any?
31
+
32
+ details.compact.map { |key, value| "#{key}: #{value}" }.join(', ')
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'table_formatter'
4
+
5
+ module ConsoleKit
6
+ module Connections
7
+ # Renders diagnostic data into a Unicode box-drawing table
8
+ module TableRenderer
9
+ class << self
10
+ def render(rows)
11
+ headers = %w[Service Status Latency Details]
12
+ table_rows = rows.map { |row| TableFormatter.format_row(row) }
13
+ widths = calculate_widths(headers, table_rows)
14
+
15
+ build_table(headers, table_rows, widths)
16
+ end
17
+
18
+ private
19
+
20
+ def calculate_widths(headers, rows)
21
+ all_rows = [headers] + rows
22
+ headers.each_index.map do |index|
23
+ column_max_width(all_rows, index)
24
+ end
25
+ end
26
+
27
+ def column_max_width(rows, index)
28
+ rows.map { |row| row[index].length }.max
29
+ end
30
+
31
+ def build_table(headers, rows, widths)
32
+ lines = [table_top(widths), table_line(headers, widths), table_mid(widths)]
33
+ rows.each { |row| lines << table_line(row, widths) }
34
+ lines << table_bottom(widths)
35
+ lines.join("\n")
36
+ end
37
+
38
+ def table_top(widths) = "\u250C#{widths.map { |width| "\u2500" * (width + 2) }.join("\u252C")}\u2510"
39
+ def table_mid(widths) = "\u251C#{widths.map { |width| "\u2500" * (width + 2) }.join("\u253C")}\u2524"
40
+ def table_bottom(widths) = "\u2514#{widths.map { |width| "\u2500" * (width + 2) }.join("\u2534")}\u2518"
41
+
42
+ def table_line(cells, widths)
43
+ content = cells.each_with_index.map { |cell, index| " #{cell.ljust(widths[index])} " }.join("\u2502")
44
+ "\u2502#{content}\u2502"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleKit
4
+ # Helper methods available in the Rails console
5
+ module ConsoleHelpers
6
+ def switch_tenant = ConsoleKit.reset_current_tenant
7
+
8
+ def tenant_info
9
+ tenant = ConsoleKit::Setup.current_tenant
10
+ unless tenant
11
+ ConsoleKit::Output.print_warning('No tenant is currently configured.')
12
+ return
13
+ end
14
+
15
+ constants = ConsoleKit.configuration.tenants[tenant]&.[](:constants) || {}
16
+ print_tenant_details(tenant, constants)
17
+ end
18
+
19
+ def dashboard = ConsoleKit::Connections::Dashboard.display
20
+
21
+ def tenants
22
+ names = ConsoleKit.configuration.tenants&.keys || []
23
+ ConsoleKit::Output.print_list(names, header: 'Available Tenants')
24
+ names
25
+ end
26
+
27
+ DETAIL_LABELS = {
28
+ 'Partner' => :partner_code, 'Shard' => :shard, 'Mongo DB' => :mongo_db,
29
+ 'Redis DB' => :redis_db, 'ES Prefix' => :elasticsearch_prefix, 'Environment' => :environment
30
+ }.freeze
31
+
32
+ private
33
+
34
+ def print_tenant_details(tenant, constants)
35
+ ConsoleKit::Output.print_header("Tenant: #{tenant}")
36
+ DETAIL_LABELS.each do |label, key|
37
+ constants.fetch(key, :missing).then do |val|
38
+ case val
39
+ when :missing then next
40
+ else ConsoleKit::Output.print_info(" #{label.ljust(13)}#{val}")
41
+ end
42
+ end
43
+ end
44
+ nil
45
+ end
46
+ end
47
+ end
@@ -34,7 +34,7 @@ module ConsoleKit
34
34
  return if silent
35
35
 
36
36
  formatted = (type == :header ? "\n--- #{text} ---" : text)
37
- print_with(type, formatted, timestamp, newline: newline)
37
+ print_with(type, formatted, timestamp: timestamp, newline: newline)
38
38
  end
39
39
  end
40
40
 
@@ -51,19 +51,24 @@ module ConsoleKit
51
51
  puts text
52
52
  end
53
53
 
54
- # Backtrace prints always with timestamp, no param
55
54
  def print_backtrace(exception)
56
55
  return if silent
57
56
 
58
- exception&.backtrace&.each { |line| print_with(:trace, " #{line}", true, newline: true) }
57
+ exception&.backtrace&.each do |line|
58
+ print_with(:trace, " #{line}", timestamp: true)
59
+ end
59
60
  end
60
61
 
61
62
  private
62
63
 
63
- def print_with(type, text, timestamp, newline: true)
64
+ def print_with(type, text, options = {})
65
+ opts = options.is_a?(Hash) ? options : { timestamp: options }
64
66
  meta = TYPES.fetch(type)
65
- message = build_message(text, meta[:symbol], timestamp)
66
- output(message, meta[:color], newline: newline)
67
+ message = build_message(text, meta[:symbol], opts[:timestamp])
68
+ color = meta[:color]
69
+ formatted = ConsoleKit.configuration.pretty_output && color ? "\e[#{color}m#{message}\e[0m" : message
70
+
71
+ opts.fetch(:newline, true) ? puts(formatted) : print(formatted)
67
72
  end
68
73
 
69
74
  def build_message(text, symbol, timestamp)
@@ -73,13 +78,6 @@ module ConsoleKit
73
78
  def prefix_for(value) = value ? yield(value) : ''
74
79
  def timestamp_prefix(timestamp) = prefix_for(timestamp) { Time.current.strftime('[%Y-%m-%d %H:%M:%S] ') }
75
80
  def symbol_prefix(symbol) = prefix_for(symbol) { |sym| "#{sym} " }
76
-
77
- def output(message, color, newline: true)
78
- method = newline ? :puts : :print
79
- return send(method, message) unless ConsoleKit.configuration.pretty_output && color
80
-
81
- send(method, "\e[#{color}m#{message}\e[0m")
82
- end
83
81
  end
84
82
  end
85
83
  end