vector_mcp 0.3.1 → 0.3.3
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 +4 -4
- data/CHANGELOG.md +122 -0
- data/lib/vector_mcp/definitions.rb +25 -9
- data/lib/vector_mcp/errors.rb +2 -3
- data/lib/vector_mcp/handlers/core.rb +206 -50
- data/lib/vector_mcp/logger.rb +148 -0
- data/lib/vector_mcp/middleware/base.rb +171 -0
- data/lib/vector_mcp/middleware/context.rb +76 -0
- data/lib/vector_mcp/middleware/hook.rb +169 -0
- data/lib/vector_mcp/middleware/manager.rb +179 -0
- data/lib/vector_mcp/middleware.rb +43 -0
- data/lib/vector_mcp/request_context.rb +182 -0
- data/lib/vector_mcp/sampling/result.rb +11 -1
- data/lib/vector_mcp/security/middleware.rb +2 -28
- data/lib/vector_mcp/security/strategies/api_key.rb +2 -24
- data/lib/vector_mcp/security/strategies/jwt_token.rb +6 -3
- data/lib/vector_mcp/server/capabilities.rb +5 -7
- data/lib/vector_mcp/server/message_handling.rb +11 -5
- data/lib/vector_mcp/server.rb +74 -20
- data/lib/vector_mcp/session.rb +131 -8
- data/lib/vector_mcp/transport/base_session_manager.rb +320 -0
- data/lib/vector_mcp/transport/http_stream/event_store.rb +151 -0
- data/lib/vector_mcp/transport/http_stream/session_manager.rb +189 -0
- data/lib/vector_mcp/transport/http_stream/stream_handler.rb +269 -0
- data/lib/vector_mcp/transport/http_stream.rb +779 -0
- data/lib/vector_mcp/transport/sse.rb +74 -19
- data/lib/vector_mcp/transport/sse_session_manager.rb +188 -0
- data/lib/vector_mcp/transport/stdio.rb +70 -13
- data/lib/vector_mcp/transport/stdio_session_manager.rb +181 -0
- data/lib/vector_mcp/util.rb +39 -1
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +10 -35
- metadata +25 -24
- data/lib/vector_mcp/logging/component.rb +0 -131
- data/lib/vector_mcp/logging/configuration.rb +0 -156
- data/lib/vector_mcp/logging/constants.rb +0 -21
- data/lib/vector_mcp/logging/core.rb +0 -175
- data/lib/vector_mcp/logging/filters/component.rb +0 -69
- data/lib/vector_mcp/logging/filters/level.rb +0 -23
- data/lib/vector_mcp/logging/formatters/base.rb +0 -52
- data/lib/vector_mcp/logging/formatters/json.rb +0 -83
- data/lib/vector_mcp/logging/formatters/text.rb +0 -72
- data/lib/vector_mcp/logging/outputs/base.rb +0 -64
- data/lib/vector_mcp/logging/outputs/console.rb +0 -35
- data/lib/vector_mcp/logging/outputs/file.rb +0 -157
- data/lib/vector_mcp/logging.rb +0 -71
@@ -1,156 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "yaml"
|
4
|
-
|
5
|
-
module VectorMCP
|
6
|
-
module Logging
|
7
|
-
class Configuration
|
8
|
-
DEFAULT_CONFIG = {
|
9
|
-
level: "INFO",
|
10
|
-
format: "text",
|
11
|
-
output: "console",
|
12
|
-
components: {},
|
13
|
-
console: {
|
14
|
-
colorize: true,
|
15
|
-
include_timestamp: true,
|
16
|
-
include_thread: false
|
17
|
-
},
|
18
|
-
file: {
|
19
|
-
path: nil,
|
20
|
-
rotation: "daily",
|
21
|
-
max_size: "100MB",
|
22
|
-
max_files: 7
|
23
|
-
}
|
24
|
-
}.freeze
|
25
|
-
|
26
|
-
attr_reader :config
|
27
|
-
|
28
|
-
def initialize(config = {})
|
29
|
-
@config = deep_merge(DEFAULT_CONFIG, normalize_config(config))
|
30
|
-
validate_config!
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.from_file(path)
|
34
|
-
config = YAML.load_file(path)
|
35
|
-
new(config["logging"] || config)
|
36
|
-
rescue StandardError => e
|
37
|
-
raise ConfigurationError, "Failed to load configuration from #{path}: #{e.message}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.from_env
|
41
|
-
config = {}
|
42
|
-
|
43
|
-
config[:level] = ENV["VECTORMCP_LOG_LEVEL"] if ENV["VECTORMCP_LOG_LEVEL"]
|
44
|
-
config[:format] = ENV["VECTORMCP_LOG_FORMAT"] if ENV["VECTORMCP_LOG_FORMAT"]
|
45
|
-
config[:output] = ENV["VECTORMCP_LOG_OUTPUT"] if ENV["VECTORMCP_LOG_OUTPUT"]
|
46
|
-
|
47
|
-
config[:file] = { path: ENV["VECTORMCP_LOG_FILE_PATH"] } if ENV["VECTORMCP_LOG_FILE_PATH"]
|
48
|
-
|
49
|
-
new(config)
|
50
|
-
end
|
51
|
-
|
52
|
-
def level_for(component)
|
53
|
-
component_level = @config[:components][component.to_s]
|
54
|
-
level_value = component_level || @config[:level]
|
55
|
-
Logging.level_value(level_value)
|
56
|
-
end
|
57
|
-
|
58
|
-
def set_component_level(component, level)
|
59
|
-
@config[:components][component.to_s] = if level.is_a?(Integer)
|
60
|
-
Logging.level_name(level)
|
61
|
-
else
|
62
|
-
level.to_s.upcase
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def component_config(component_name)
|
67
|
-
@config[:components][component_name.to_s] || {}
|
68
|
-
end
|
69
|
-
|
70
|
-
def console_config
|
71
|
-
@config[:console]
|
72
|
-
end
|
73
|
-
|
74
|
-
def file_config
|
75
|
-
@config[:file]
|
76
|
-
end
|
77
|
-
|
78
|
-
def format
|
79
|
-
@config[:format]
|
80
|
-
end
|
81
|
-
|
82
|
-
def output
|
83
|
-
@config[:output]
|
84
|
-
end
|
85
|
-
|
86
|
-
def configure(&)
|
87
|
-
instance_eval(&) if block_given?
|
88
|
-
validate_config!
|
89
|
-
end
|
90
|
-
|
91
|
-
def level(new_level)
|
92
|
-
@config[:level] = new_level.to_s.upcase
|
93
|
-
end
|
94
|
-
|
95
|
-
def component(name, level:)
|
96
|
-
@config[:components][name.to_s] = level.to_s.upcase
|
97
|
-
end
|
98
|
-
|
99
|
-
def console(options = {})
|
100
|
-
@config[:console].merge!(options)
|
101
|
-
end
|
102
|
-
|
103
|
-
def file(options = {})
|
104
|
-
@config[:file].merge!(options)
|
105
|
-
end
|
106
|
-
|
107
|
-
def to_h
|
108
|
-
@config.dup
|
109
|
-
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
def normalize_config(config)
|
114
|
-
case config
|
115
|
-
when Hash
|
116
|
-
config.transform_keys(&:to_sym)
|
117
|
-
when String
|
118
|
-
{ level: config }
|
119
|
-
else
|
120
|
-
{}
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def deep_merge(hash1, hash2)
|
125
|
-
result = hash1.dup
|
126
|
-
hash2.each do |key, value|
|
127
|
-
result[key] = if result[key].is_a?(Hash) && value.is_a?(Hash)
|
128
|
-
deep_merge(result[key], value)
|
129
|
-
else
|
130
|
-
value
|
131
|
-
end
|
132
|
-
end
|
133
|
-
result
|
134
|
-
end
|
135
|
-
|
136
|
-
def validate_config!
|
137
|
-
validate_level!(@config[:level])
|
138
|
-
@config[:components].each_value do |level|
|
139
|
-
validate_level!(level)
|
140
|
-
end
|
141
|
-
|
142
|
-
raise ConfigurationError, "Invalid format: #{@config[:format]}" unless %w[text json].include?(@config[:format])
|
143
|
-
|
144
|
-
return if %w[console file both].include?(@config[:output])
|
145
|
-
|
146
|
-
raise ConfigurationError, "Invalid output: #{@config[:output]}"
|
147
|
-
end
|
148
|
-
|
149
|
-
def validate_level!(level)
|
150
|
-
return if Logging::LEVELS.key?(level.to_s.upcase.to_sym)
|
151
|
-
|
152
|
-
raise ConfigurationError, "Invalid log level: #{level}"
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module VectorMCP
|
4
|
-
module Logging
|
5
|
-
module Constants
|
6
|
-
# JSON serialization limits
|
7
|
-
MAX_SERIALIZATION_DEPTH = 5
|
8
|
-
MAX_ARRAY_SERIALIZATION_DEPTH = 3
|
9
|
-
MAX_ARRAY_ELEMENTS_TO_SERIALIZE = 10
|
10
|
-
|
11
|
-
# Text formatting limits
|
12
|
-
DEFAULT_MAX_MESSAGE_LENGTH = 1000
|
13
|
-
DEFAULT_COMPONENT_WIDTH = 20
|
14
|
-
DEFAULT_LEVEL_WIDTH = 8
|
15
|
-
TRUNCATION_SUFFIX_LENGTH = 4 # for "..."
|
16
|
-
|
17
|
-
# ISO timestamp precision
|
18
|
-
TIMESTAMP_PRECISION = 3 # milliseconds
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,175 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "logger"
|
4
|
-
|
5
|
-
module VectorMCP
|
6
|
-
module Logging
|
7
|
-
class Core
|
8
|
-
attr_reader :configuration, :components, :outputs
|
9
|
-
|
10
|
-
def initialize(configuration = nil)
|
11
|
-
@configuration = configuration || Configuration.new
|
12
|
-
@components = {}
|
13
|
-
@outputs = []
|
14
|
-
@mutex = Mutex.new
|
15
|
-
@legacy_logger = nil
|
16
|
-
|
17
|
-
setup_default_output
|
18
|
-
end
|
19
|
-
|
20
|
-
def logger_for(component_name)
|
21
|
-
@mutex.synchronize do
|
22
|
-
@components[component_name] ||= Component.new(
|
23
|
-
component_name,
|
24
|
-
self,
|
25
|
-
@configuration.component_config(component_name)
|
26
|
-
)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def legacy_logger
|
31
|
-
@legacy_logger ||= LegacyAdapter.new(self)
|
32
|
-
end
|
33
|
-
|
34
|
-
def log(level, component, message, context = {})
|
35
|
-
return unless should_log?(level, component)
|
36
|
-
|
37
|
-
log_entry = Logging::LogEntry.new({
|
38
|
-
timestamp: Time.now,
|
39
|
-
level: level,
|
40
|
-
component: component,
|
41
|
-
message: message,
|
42
|
-
context: context,
|
43
|
-
thread_id: Thread.current.object_id
|
44
|
-
})
|
45
|
-
|
46
|
-
@outputs.each do |output|
|
47
|
-
output.write(log_entry)
|
48
|
-
rescue StandardError => e
|
49
|
-
warn "Failed to write to output #{output.class}: #{e.message}"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def add_output(output)
|
54
|
-
@mutex.synchronize do
|
55
|
-
@outputs << output unless @outputs.include?(output)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def remove_output(output)
|
60
|
-
@mutex.synchronize do
|
61
|
-
@outputs.delete(output)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def configure(&)
|
66
|
-
@configuration.configure(&)
|
67
|
-
reconfigure_outputs
|
68
|
-
end
|
69
|
-
|
70
|
-
def shutdown
|
71
|
-
@outputs.each(&:close)
|
72
|
-
@outputs.clear
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def should_log?(level, component)
|
78
|
-
min_level = @configuration.level_for(component)
|
79
|
-
level >= min_level
|
80
|
-
end
|
81
|
-
|
82
|
-
def setup_default_output
|
83
|
-
console_output = Outputs::Console.new(@configuration.console_config)
|
84
|
-
add_output(console_output)
|
85
|
-
end
|
86
|
-
|
87
|
-
def reconfigure_outputs
|
88
|
-
@outputs.each(&:reconfigure)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
class LegacyAdapter
|
93
|
-
def initialize(core)
|
94
|
-
@core = core
|
95
|
-
@component = "legacy"
|
96
|
-
@progname = "VectorMCP"
|
97
|
-
end
|
98
|
-
|
99
|
-
def debug(message = nil, &)
|
100
|
-
log_with_block(Logging::LEVELS[:DEBUG], message, &)
|
101
|
-
end
|
102
|
-
|
103
|
-
def info(message = nil, &)
|
104
|
-
log_with_block(Logging::LEVELS[:INFO], message, &)
|
105
|
-
end
|
106
|
-
|
107
|
-
def warn(message = nil, &)
|
108
|
-
log_with_block(Logging::LEVELS[:WARN], message, &)
|
109
|
-
end
|
110
|
-
|
111
|
-
def error(message = nil, &)
|
112
|
-
log_with_block(Logging::LEVELS[:ERROR], message, &)
|
113
|
-
end
|
114
|
-
|
115
|
-
def fatal(message = nil, &)
|
116
|
-
log_with_block(Logging::LEVELS[:FATAL], message, &)
|
117
|
-
end
|
118
|
-
|
119
|
-
def level
|
120
|
-
@core.configuration.level_for(@component)
|
121
|
-
end
|
122
|
-
|
123
|
-
def level=(new_level)
|
124
|
-
@core.configuration.set_component_level(@component, new_level)
|
125
|
-
end
|
126
|
-
|
127
|
-
attr_accessor :progname
|
128
|
-
|
129
|
-
def add(severity, message = nil, progname = nil, &block)
|
130
|
-
actual_message = message || block&.call || progname
|
131
|
-
@core.log(severity, @component, actual_message)
|
132
|
-
end
|
133
|
-
|
134
|
-
# For backward compatibility with Logger interface checks
|
135
|
-
def is_a?(klass)
|
136
|
-
return true if klass == Logger
|
137
|
-
|
138
|
-
super
|
139
|
-
end
|
140
|
-
|
141
|
-
def kind_of?(klass)
|
142
|
-
return true if klass == Logger
|
143
|
-
|
144
|
-
super
|
145
|
-
end
|
146
|
-
|
147
|
-
# Simulate Logger's logdev for compatibility
|
148
|
-
def instance_variable_get(var_name)
|
149
|
-
if var_name == :@logdev
|
150
|
-
# Return a mock object that simulates Logger's logdev
|
151
|
-
MockLogdev.new
|
152
|
-
else
|
153
|
-
super
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
private
|
158
|
-
|
159
|
-
def log_with_block(level, message, &block)
|
160
|
-
if block_given?
|
161
|
-
return unless @core.configuration.level_for(@component) <= level
|
162
|
-
|
163
|
-
message = block.call
|
164
|
-
end
|
165
|
-
@core.log(level, @component, message)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
class MockLogdev
|
170
|
-
def dev
|
171
|
-
$stderr
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "set"
|
4
|
-
|
5
|
-
module VectorMCP
|
6
|
-
module Logging
|
7
|
-
module Filters
|
8
|
-
class Component
|
9
|
-
def initialize(allowed_components = nil, blocked_components = nil)
|
10
|
-
@allowed_components = normalize_components(allowed_components)
|
11
|
-
@blocked_components = normalize_components(blocked_components)
|
12
|
-
end
|
13
|
-
|
14
|
-
def accept?(log_entry)
|
15
|
-
return false if blocked?(log_entry.component)
|
16
|
-
return true if @allowed_components.nil?
|
17
|
-
|
18
|
-
allowed?(log_entry.component)
|
19
|
-
end
|
20
|
-
|
21
|
-
def allow_component(component)
|
22
|
-
@allowed_components ||= Set.new
|
23
|
-
@allowed_components.add(component.to_s)
|
24
|
-
end
|
25
|
-
|
26
|
-
def block_component(component)
|
27
|
-
@blocked_components ||= Set.new
|
28
|
-
@blocked_components.add(component.to_s)
|
29
|
-
end
|
30
|
-
|
31
|
-
def remove_component_filter(component)
|
32
|
-
@allowed_components&.delete(component.to_s)
|
33
|
-
@blocked_components&.delete(component.to_s)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def allowed?(component)
|
39
|
-
return true if @allowed_components.nil?
|
40
|
-
|
41
|
-
@allowed_components.include?(component) ||
|
42
|
-
@allowed_components.any? { |pattern| component.start_with?(pattern) }
|
43
|
-
end
|
44
|
-
|
45
|
-
def blocked?(component)
|
46
|
-
return false if @blocked_components.nil?
|
47
|
-
|
48
|
-
@blocked_components.include?(component) ||
|
49
|
-
@blocked_components.any? { |pattern| component.start_with?(pattern) }
|
50
|
-
end
|
51
|
-
|
52
|
-
def normalize_components(components)
|
53
|
-
case components
|
54
|
-
when nil
|
55
|
-
nil
|
56
|
-
when String
|
57
|
-
Set.new([components])
|
58
|
-
when Array
|
59
|
-
Set.new(components.map(&:to_s))
|
60
|
-
when Set
|
61
|
-
components
|
62
|
-
else
|
63
|
-
Set.new([components.to_s])
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module VectorMCP
|
4
|
-
module Logging
|
5
|
-
module Filters
|
6
|
-
class Level
|
7
|
-
def initialize(min_level)
|
8
|
-
@min_level = min_level.is_a?(Integer) ? min_level : Logging.level_value(min_level)
|
9
|
-
end
|
10
|
-
|
11
|
-
def accept?(log_entry)
|
12
|
-
log_entry.level >= @min_level
|
13
|
-
end
|
14
|
-
|
15
|
-
def min_level=(new_level)
|
16
|
-
@min_level = new_level.is_a?(Integer) ? new_level : Logging.level_value(new_level)
|
17
|
-
end
|
18
|
-
|
19
|
-
attr_reader :min_level
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../constants"
|
4
|
-
|
5
|
-
module VectorMCP
|
6
|
-
module Logging
|
7
|
-
module Formatters
|
8
|
-
class Base
|
9
|
-
def initialize(options = {})
|
10
|
-
@options = options
|
11
|
-
end
|
12
|
-
|
13
|
-
def format(log_entry)
|
14
|
-
raise NotImplementedError, "Subclasses must implement #format"
|
15
|
-
end
|
16
|
-
|
17
|
-
protected
|
18
|
-
|
19
|
-
def format_timestamp(timestamp)
|
20
|
-
timestamp.strftime("%Y-%m-%d %H:%M:%S.%#{Constants::TIMESTAMP_PRECISION}N")
|
21
|
-
end
|
22
|
-
|
23
|
-
def format_level(level_name, width = Constants::DEFAULT_LEVEL_WIDTH)
|
24
|
-
level_name.ljust(width)
|
25
|
-
end
|
26
|
-
|
27
|
-
def format_component(component, width = Constants::DEFAULT_COMPONENT_WIDTH)
|
28
|
-
if component.length > width
|
29
|
-
"#{component[0..(width - Constants::TRUNCATION_SUFFIX_LENGTH)]}..."
|
30
|
-
else
|
31
|
-
component.ljust(width)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def format_context(context)
|
36
|
-
return "" if context.empty?
|
37
|
-
|
38
|
-
pairs = context.map do |key, value|
|
39
|
-
"#{key}=#{value}"
|
40
|
-
end
|
41
|
-
" (#{pairs.join(", ")})"
|
42
|
-
end
|
43
|
-
|
44
|
-
def truncate_message(message, max_length = Constants::DEFAULT_MAX_MESSAGE_LENGTH)
|
45
|
-
return message if message.length <= max_length
|
46
|
-
|
47
|
-
"#{message[0..(max_length - Constants::TRUNCATION_SUFFIX_LENGTH)]}..."
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require_relative "../constants"
|
5
|
-
|
6
|
-
module VectorMCP
|
7
|
-
module Logging
|
8
|
-
module Formatters
|
9
|
-
class Json < Base
|
10
|
-
def initialize(options = {})
|
11
|
-
super
|
12
|
-
@pretty = options.fetch(:pretty, false)
|
13
|
-
@include_thread = options.fetch(:include_thread, false)
|
14
|
-
end
|
15
|
-
|
16
|
-
def format(log_entry)
|
17
|
-
data = {
|
18
|
-
timestamp: log_entry.timestamp.iso8601(Constants::TIMESTAMP_PRECISION),
|
19
|
-
level: log_entry.level_name,
|
20
|
-
component: log_entry.component,
|
21
|
-
message: log_entry.message
|
22
|
-
}
|
23
|
-
|
24
|
-
data[:context] = log_entry.context unless log_entry.context.empty?
|
25
|
-
data[:thread_id] = log_entry.thread_id if @include_thread
|
26
|
-
|
27
|
-
if @pretty
|
28
|
-
"#{JSON.pretty_generate(data)}\n"
|
29
|
-
else
|
30
|
-
"#{JSON.generate(data)}\n"
|
31
|
-
end
|
32
|
-
rescue JSON::GeneratorError, JSON::NestingError => e
|
33
|
-
fallback_format(log_entry, e)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def fallback_format(log_entry, error)
|
39
|
-
safe_data = {
|
40
|
-
timestamp: log_entry.timestamp.iso8601(Constants::TIMESTAMP_PRECISION),
|
41
|
-
level: log_entry.level_name,
|
42
|
-
component: log_entry.component,
|
43
|
-
message: "JSON serialization failed: #{error.message}",
|
44
|
-
original_message: log_entry.message.to_s,
|
45
|
-
context: sanitize_context(log_entry.context)
|
46
|
-
}
|
47
|
-
|
48
|
-
"#{JSON.generate(safe_data)}\n"
|
49
|
-
end
|
50
|
-
|
51
|
-
def sanitize_context(context, depth = 0)
|
52
|
-
return "<max_depth_reached>" if depth > Constants::MAX_SERIALIZATION_DEPTH
|
53
|
-
return {} unless context.is_a?(Hash)
|
54
|
-
|
55
|
-
context.each_with_object({}) do |(key, value), sanitized|
|
56
|
-
sanitized[key.to_s] = sanitize_value(value, depth + 1)
|
57
|
-
end
|
58
|
-
rescue StandardError
|
59
|
-
{ "<sanitization_error>" => true }
|
60
|
-
end
|
61
|
-
|
62
|
-
def sanitize_value(value, depth = 0)
|
63
|
-
return "<max_depth_reached>" if depth > Constants::MAX_SERIALIZATION_DEPTH
|
64
|
-
|
65
|
-
case value
|
66
|
-
when String, Numeric, TrueClass, FalseClass, NilClass
|
67
|
-
value
|
68
|
-
when Array
|
69
|
-
return "<complex_array>" if depth > Constants::MAX_ARRAY_SERIALIZATION_DEPTH
|
70
|
-
|
71
|
-
value.first(Constants::MAX_ARRAY_ELEMENTS_TO_SERIALIZE).map { |v| sanitize_value(v, depth + 1) }
|
72
|
-
when Hash
|
73
|
-
sanitize_context(value, depth)
|
74
|
-
else
|
75
|
-
value.to_s
|
76
|
-
end
|
77
|
-
rescue StandardError
|
78
|
-
"<serialization_error>"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../constants"
|
4
|
-
|
5
|
-
module VectorMCP
|
6
|
-
module Logging
|
7
|
-
module Formatters
|
8
|
-
class Text < Base
|
9
|
-
COLORS = {
|
10
|
-
TRACE: "\e[90m", # gray
|
11
|
-
DEBUG: "\e[36m", # cyan
|
12
|
-
INFO: "\e[32m", # green
|
13
|
-
WARN: "\e[33m", # yellow
|
14
|
-
ERROR: "\e[31m", # red
|
15
|
-
FATAL: "\e[35m", # magenta
|
16
|
-
SECURITY: "\e[1;31m" # bold red
|
17
|
-
}.freeze
|
18
|
-
|
19
|
-
RESET_COLOR = "\e[0m"
|
20
|
-
|
21
|
-
def initialize(options = {})
|
22
|
-
super
|
23
|
-
@colorize = options.fetch(:colorize, true)
|
24
|
-
@include_timestamp = options.fetch(:include_timestamp, true)
|
25
|
-
@include_thread = options.fetch(:include_thread, false)
|
26
|
-
@include_component = options.fetch(:include_component, true)
|
27
|
-
@max_message_length = options.fetch(:max_message_length, Constants::DEFAULT_MAX_MESSAGE_LENGTH)
|
28
|
-
end
|
29
|
-
|
30
|
-
def format(log_entry)
|
31
|
-
parts = []
|
32
|
-
|
33
|
-
parts << format_timestamp_part(log_entry.timestamp) if @include_timestamp
|
34
|
-
|
35
|
-
parts << format_level_part(log_entry.level_name)
|
36
|
-
|
37
|
-
parts << format_component_part(log_entry.component) if @include_component
|
38
|
-
|
39
|
-
parts << format_thread_part(log_entry.thread_id) if @include_thread
|
40
|
-
|
41
|
-
message = truncate_message(log_entry.message, @max_message_length)
|
42
|
-
context_str = format_context(log_entry.context)
|
43
|
-
|
44
|
-
"#{parts.join(" ")} #{message}#{context_str}\n"
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def format_timestamp_part(timestamp)
|
50
|
-
"[#{format_timestamp(timestamp)}]"
|
51
|
-
end
|
52
|
-
|
53
|
-
def format_level_part(level_name)
|
54
|
-
level_str = format_level(level_name.to_s, Constants::DEFAULT_LEVEL_WIDTH)
|
55
|
-
if @colorize && COLORS[level_name.to_sym]
|
56
|
-
"#{COLORS[level_name.to_sym]}#{level_str}#{RESET_COLOR}"
|
57
|
-
else
|
58
|
-
level_str
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def format_component_part(component)
|
63
|
-
format_component(component, Constants::DEFAULT_COMPONENT_WIDTH)
|
64
|
-
end
|
65
|
-
|
66
|
-
def format_thread_part(thread_id)
|
67
|
-
"[#{thread_id}]"
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|