vector_mcp 0.3.1 → 0.3.2
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 +68 -0
- data/lib/vector_mcp/errors.rb +6 -3
- data/lib/vector_mcp/handlers/core.rb +199 -45
- data/lib/vector_mcp/logger.rb +148 -0
- data/lib/vector_mcp/middleware/base.rb +177 -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 +191 -0
- data/lib/vector_mcp/middleware.rb +43 -0
- data/lib/vector_mcp/security/strategies/jwt_token.rb +6 -3
- data/lib/vector_mcp/server.rb +61 -17
- data/lib/vector_mcp/session.rb +37 -4
- data/lib/vector_mcp/version.rb +1 -1
- data/lib/vector_mcp.rb +10 -35
- metadata +17 -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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edd8f7804cea74064b13b6432daa19149d4b2d6c34efbd7ccfbae393870b648a
|
4
|
+
data.tar.gz: bd04d07372ef947498c39021adfba055d8030b94bf76b567fc8b5cb73aae994b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a47a3ad028e39de9bd8d4973779040fd0c571067e4c3588ad406d26dd138cdfad06921d497a3922d1eb2406df3ede02379df7f24620539cb1170ac6fdb1bed5
|
7
|
+
data.tar.gz: ad1d3d147c3b4ef8624651aeabb0809f689bbfb5c4ae702dcc0c5fa458cc500ff7ffd54e46a4ea02ae995d908af0122f5a88b7ab895eeaab8c1d780e6eec8043
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,71 @@
|
|
1
|
+
## [0.3.2] – 2025-07-02
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
* **Comprehensive Middleware System**: Pluggable hook system for custom behavior around all MCP operations
|
6
|
+
* **Hook Points**: Support for all major operations including tools, resources, prompts, sampling, transport, and authentication
|
7
|
+
* **Priority-Based Execution**: Control middleware execution order with configurable priorities
|
8
|
+
* **Conditional Execution**: Run middleware only for specific operations, users, or conditions
|
9
|
+
* **Context Management**: Rich execution context with operation metadata and session information
|
10
|
+
* **Error Handling**: Graceful error recovery with middleware-specific error hooks
|
11
|
+
* **Built-in Middleware**: PII redaction, request retry, rate limiting, and enhanced logging examples
|
12
|
+
|
13
|
+
* **Enhanced Examples Organization**: Comprehensive example reorganization for better developer experience
|
14
|
+
* **Getting Started Examples**: `examples/getting_started/` with basic server implementations
|
15
|
+
* **Core Features Examples**: `examples/core_features/` demonstrating key capabilities
|
16
|
+
* **Use Cases Examples**: `examples/use_cases/` with real-world application scenarios
|
17
|
+
* **Logging Examples**: `examples/logging/` showcasing structured logging capabilities
|
18
|
+
* **Middleware Examples**: `examples/middleware_examples.rb` and `examples/simple_middleware_demo.rb`
|
19
|
+
|
20
|
+
* **Refactored Logging System**: Enhanced logging architecture with better performance and flexibility
|
21
|
+
* **Simplified API**: Streamlined `VectorMCP.logger_for(component)` interface
|
22
|
+
* **Performance Improvements**: Optimized log formatting and output handling
|
23
|
+
* **Better Component Organization**: Hierarchical logger management with cleaner separation
|
24
|
+
|
25
|
+
### Changed
|
26
|
+
|
27
|
+
* **Middleware Integration**: Core server architecture enhanced to support middleware hooks
|
28
|
+
* **Server Methods**: New `use_middleware`, `middleware_stats`, `remove_middleware`, and `clear_middleware` methods
|
29
|
+
* **Handler Integration**: All core handlers now support middleware execution around operations
|
30
|
+
* **Session Context**: Enhanced session context with middleware metadata and execution tracking
|
31
|
+
|
32
|
+
* **Example Structure**: Major reorganization of examples for better discoverability
|
33
|
+
* **Categorized Examples**: Logical grouping by functionality and use case
|
34
|
+
* **Enhanced Documentation**: Each example category includes detailed README files
|
35
|
+
* **Use Case Focus**: Real-world scenarios like data analysis, file operations, and web scraping
|
36
|
+
|
37
|
+
* **Backward Compatibility**: All middleware features are opt-in with zero impact on existing servers
|
38
|
+
* **Default Behavior**: Servers without middleware continue working exactly as before
|
39
|
+
* **Optional Integration**: Middleware can be added incrementally to existing applications
|
40
|
+
|
41
|
+
### Fixed
|
42
|
+
|
43
|
+
* **Ruby Version Compatibility**: Enhanced support for older Ruby versions
|
44
|
+
* **Code Quality**: Multiple bug fixes and improvements identified through expanded test coverage
|
45
|
+
* **Performance**: Optimized middleware execution path for minimal overhead when no middleware is registered
|
46
|
+
|
47
|
+
### Security
|
48
|
+
|
49
|
+
* **Middleware Security**: Security-aware middleware execution
|
50
|
+
* **Session Context Integration**: Middleware has access to authentication and authorization context
|
51
|
+
* **Secure Error Handling**: Middleware errors handled securely without information leakage
|
52
|
+
* **Permission-Aware Hooks**: Middleware can respect user permissions and security policies
|
53
|
+
|
54
|
+
### Testing
|
55
|
+
|
56
|
+
* **Comprehensive Middleware Tests**: 50+ tests covering all middleware functionality
|
57
|
+
* **Hook Execution Tests**: Verification of all hook types and execution order
|
58
|
+
* **Priority and Condition Tests**: Complex scenario testing for middleware orchestration
|
59
|
+
* **Integration Tests**: End-to-end testing with real server operations
|
60
|
+
* **Performance Tests**: Overhead measurement and resource usage validation
|
61
|
+
|
62
|
+
### Technical Details
|
63
|
+
|
64
|
+
* **API Compatibility**: All middleware features maintain full backward compatibility
|
65
|
+
* **Performance**: Minimal overhead when middleware is not used, efficient execution when enabled
|
66
|
+
* **Memory Management**: Proper cleanup and resource management for long-running servers
|
67
|
+
* **Thread Safety**: Concurrent middleware execution with proper synchronization
|
68
|
+
|
1
69
|
## [0.3.1] – 2025-06-25
|
2
70
|
|
3
71
|
### Added
|
data/lib/vector_mcp/errors.rb
CHANGED
@@ -28,7 +28,8 @@ module VectorMCP
|
|
28
28
|
# @param details [Hash, nil] Additional details for the error (optional).
|
29
29
|
# @param request_id [String, Integer, nil] The ID of the originating request.
|
30
30
|
def initialize(message, code: -32_600, details: nil, request_id: nil)
|
31
|
-
VectorMCP.
|
31
|
+
logger = VectorMCP.logger_for("errors")
|
32
|
+
logger.debug("Initializing ProtocolError with code: #{code}")
|
32
33
|
@code = code
|
33
34
|
@message = message
|
34
35
|
@details = details # NOTE: `data` in JSON-RPC is often used for this purpose.
|
@@ -104,7 +105,8 @@ module VectorMCP
|
|
104
105
|
# @param details [Hash, nil] Additional details for the error (optional).
|
105
106
|
# @param request_id [String, Integer, nil] The ID of the originating request.
|
106
107
|
def initialize(message = "Server error", code: -32_000, details: nil, request_id: nil)
|
107
|
-
VectorMCP.
|
108
|
+
logger = VectorMCP.logger_for("errors")
|
109
|
+
logger.debug("Initializing ServerError with code: #{code}")
|
108
110
|
unless (-32_099..-32_000).cover?(code)
|
109
111
|
warn "Server error code #{code} is outside of the reserved range (-32099 to -32000). Using -32000 instead."
|
110
112
|
code = -32_000
|
@@ -131,7 +133,8 @@ module VectorMCP
|
|
131
133
|
# @param details [Hash, nil] Additional details for the error (optional).
|
132
134
|
# @param request_id [String, Integer, nil] The ID of the originating request.
|
133
135
|
def initialize(message = "Not Found", details: nil, request_id: nil)
|
134
|
-
VectorMCP.
|
136
|
+
logger = VectorMCP.logger_for("errors")
|
137
|
+
logger.debug("Initializing NotFoundError with code: -32001")
|
135
138
|
super(message, code: -32_001, details: details, request_id: request_id)
|
136
139
|
end
|
137
140
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "json"
|
4
4
|
require "uri"
|
5
5
|
require "json-schema"
|
6
|
+
require_relative "../middleware"
|
6
7
|
|
7
8
|
module VectorMCP
|
8
9
|
module Handlers
|
@@ -21,7 +22,8 @@ module VectorMCP
|
|
21
22
|
# @param _server [VectorMCP::Server] The server instance (ignored).
|
22
23
|
# @return [Hash] An empty hash, as per MCP spec for ping.
|
23
24
|
def self.ping(_params, _session, _server)
|
24
|
-
VectorMCP.
|
25
|
+
logger = VectorMCP.logger_for("handlers.core")
|
26
|
+
logger.debug("Handling ping request")
|
25
27
|
{}
|
26
28
|
end
|
27
29
|
|
@@ -54,27 +56,23 @@ module VectorMCP
|
|
54
56
|
tool_name = params["name"]
|
55
57
|
arguments = params["arguments"] || {}
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
+
context = create_tool_context(tool_name, params, session, server)
|
60
|
+
context = server.middleware_manager.execute_hooks(:before_tool_call, context)
|
61
|
+
return handle_middleware_error(context) if context.error?
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
+
begin
|
64
|
+
tool = find_tool!(tool_name, server)
|
65
|
+
security_result = validate_tool_security!(session, tool, server)
|
66
|
+
validate_input_arguments!(tool_name, tool, arguments)
|
63
67
|
|
64
|
-
|
65
|
-
|
68
|
+
result = execute_tool_handler(tool, arguments, security_result)
|
69
|
+
context.result = build_tool_result(result)
|
66
70
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
tool.handler.call(arguments, security_result[:session_context])
|
73
|
-
end
|
74
|
-
{
|
75
|
-
isError: false,
|
76
|
-
content: VectorMCP::Util.convert_to_mcp_content(result)
|
77
|
-
}
|
71
|
+
context = server.middleware_manager.execute_hooks(:after_tool_call, context)
|
72
|
+
context.result
|
73
|
+
rescue StandardError => e
|
74
|
+
handle_tool_error(e, context, server)
|
75
|
+
end
|
78
76
|
end
|
79
77
|
|
80
78
|
# Handles the `resources/list` request.
|
@@ -103,27 +101,24 @@ module VectorMCP
|
|
103
101
|
# @raise [VectorMCP::ForbiddenError] if authorization fails.
|
104
102
|
def self.read_resource(params, session, server)
|
105
103
|
uri_s = params["uri"]
|
106
|
-
raise VectorMCP::NotFoundError.new("Not Found", details: "Resource not found: #{uri_s}") unless server.resources[uri_s]
|
107
104
|
|
108
|
-
|
105
|
+
context = create_resource_context(uri_s, params, session, server)
|
106
|
+
context = server.middleware_manager.execute_hooks(:before_resource_read, context)
|
107
|
+
return handle_middleware_error(context) if context.error?
|
109
108
|
|
110
|
-
|
111
|
-
|
112
|
-
|
109
|
+
begin
|
110
|
+
resource = find_resource!(uri_s, server)
|
111
|
+
security_result = validate_resource_security!(session, resource, server)
|
113
112
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
contents.each do |item|
|
123
|
-
# Add URI to each content item if not already present
|
124
|
-
item[:uri] ||= uri_s
|
113
|
+
content_raw = execute_resource_handler(resource, params, security_result)
|
114
|
+
contents = process_resource_content(content_raw, resource, uri_s)
|
115
|
+
|
116
|
+
context.result = { contents: contents }
|
117
|
+
context = server.middleware_manager.execute_hooks(:after_resource_read, context)
|
118
|
+
context.result
|
119
|
+
rescue StandardError => e
|
120
|
+
handle_resource_error(e, context, server)
|
125
121
|
end
|
126
|
-
{ contents: contents }
|
127
122
|
end
|
128
123
|
|
129
124
|
# Handles the `prompts/list` request.
|
@@ -184,20 +179,51 @@ module VectorMCP
|
|
184
179
|
# @raise [VectorMCP::NotFoundError] if the prompt name is not found.
|
185
180
|
# @raise [VectorMCP::InvalidParamsError] if arguments are invalid.
|
186
181
|
# @raise [VectorMCP::InternalError] if the prompt handler returns an invalid data structure.
|
187
|
-
def self.get_prompt(params,
|
182
|
+
def self.get_prompt(params, session, server)
|
188
183
|
prompt_name = params["name"]
|
189
|
-
prompt = fetch_prompt(prompt_name, server)
|
190
184
|
|
191
|
-
|
192
|
-
|
185
|
+
# Create middleware context
|
186
|
+
context = VectorMCP::Middleware::Context.new(
|
187
|
+
operation_type: :prompt_get,
|
188
|
+
operation_name: prompt_name,
|
189
|
+
params: params,
|
190
|
+
session: session,
|
191
|
+
server: server,
|
192
|
+
metadata: { start_time: Time.now }
|
193
|
+
)
|
194
|
+
|
195
|
+
# Execute before_prompt_get hooks
|
196
|
+
context = server.middleware_manager.execute_hooks(:before_prompt_get, context)
|
197
|
+
return handle_middleware_error(context) if context.error?
|
198
|
+
|
199
|
+
begin
|
200
|
+
prompt = fetch_prompt(prompt_name, server)
|
193
201
|
|
194
|
-
|
195
|
-
|
202
|
+
arguments = params["arguments"] || {}
|
203
|
+
validate_arguments!(prompt_name, prompt, arguments)
|
196
204
|
|
197
|
-
|
205
|
+
# Call the registered handler after arguments were validated
|
206
|
+
result_data = prompt.handler.call(arguments)
|
198
207
|
|
199
|
-
|
200
|
-
|
208
|
+
validate_prompt_response!(prompt_name, result_data, server)
|
209
|
+
|
210
|
+
# Set result in context
|
211
|
+
context.result = result_data
|
212
|
+
|
213
|
+
# Execute after_prompt_get hooks
|
214
|
+
context = server.middleware_manager.execute_hooks(:after_prompt_get, context)
|
215
|
+
|
216
|
+
context.result
|
217
|
+
rescue StandardError => e
|
218
|
+
# Set error in context and execute error hooks
|
219
|
+
context.error = e
|
220
|
+
context = server.middleware_manager.execute_hooks(:on_prompt_error, context)
|
221
|
+
|
222
|
+
# Re-raise unless middleware handled the error
|
223
|
+
raise e unless context.result
|
224
|
+
|
225
|
+
context.result
|
226
|
+
end
|
201
227
|
end
|
202
228
|
|
203
229
|
# --- Notification Handlers ---
|
@@ -427,6 +453,134 @@ module VectorMCP
|
|
427
453
|
end
|
428
454
|
end
|
429
455
|
private_class_method :handle_security_failure
|
456
|
+
|
457
|
+
# Handle middleware error by returning appropriate response or raising error
|
458
|
+
# @api private
|
459
|
+
# @param context [VectorMCP::Middleware::Context] The middleware context with error
|
460
|
+
# @return [Hash, nil] Response hash if middleware provided one
|
461
|
+
# @raise [StandardError] Re-raises the original error if not handled
|
462
|
+
def self.handle_middleware_error(context)
|
463
|
+
# If middleware provided a result, return it
|
464
|
+
return context.result if context.result
|
465
|
+
|
466
|
+
# Otherwise, re-raise the middleware error
|
467
|
+
raise context.error
|
468
|
+
end
|
469
|
+
|
470
|
+
# Tool helper methods
|
471
|
+
|
472
|
+
# Create middleware context for tool operations
|
473
|
+
def self.create_tool_context(tool_name, params, session, server)
|
474
|
+
VectorMCP::Middleware::Context.new(
|
475
|
+
operation_type: :tool_call,
|
476
|
+
operation_name: tool_name,
|
477
|
+
params: params,
|
478
|
+
session: session,
|
479
|
+
server: server,
|
480
|
+
metadata: { start_time: Time.now }
|
481
|
+
)
|
482
|
+
end
|
483
|
+
|
484
|
+
# Find and validate tool exists
|
485
|
+
def self.find_tool!(tool_name, server)
|
486
|
+
tool = server.tools[tool_name]
|
487
|
+
raise VectorMCP::NotFoundError.new("Not Found", details: "Tool not found: #{tool_name}") unless tool
|
488
|
+
|
489
|
+
tool
|
490
|
+
end
|
491
|
+
|
492
|
+
# Validate tool security
|
493
|
+
def self.validate_tool_security!(session, tool, server)
|
494
|
+
security_result = check_tool_security(session, tool, server)
|
495
|
+
handle_security_failure(security_result) unless security_result[:success]
|
496
|
+
security_result
|
497
|
+
end
|
498
|
+
|
499
|
+
# Execute tool handler with proper arity handling
|
500
|
+
def self.execute_tool_handler(tool, arguments, security_result)
|
501
|
+
if [1, -1].include?(tool.handler.arity)
|
502
|
+
tool.handler.call(arguments)
|
503
|
+
else
|
504
|
+
tool.handler.call(arguments, security_result[:session_context])
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# Build tool result response
|
509
|
+
def self.build_tool_result(result)
|
510
|
+
{
|
511
|
+
isError: false,
|
512
|
+
content: VectorMCP::Util.convert_to_mcp_content(result)
|
513
|
+
}
|
514
|
+
end
|
515
|
+
|
516
|
+
# Handle tool execution errors
|
517
|
+
def self.handle_tool_error(error, context, server)
|
518
|
+
context.error = error
|
519
|
+
context = server.middleware_manager.execute_hooks(:on_tool_error, context)
|
520
|
+
raise error unless context.result
|
521
|
+
|
522
|
+
context.result
|
523
|
+
end
|
524
|
+
|
525
|
+
# Resource helper methods
|
526
|
+
|
527
|
+
# Create middleware context for resource operations
|
528
|
+
def self.create_resource_context(uri_s, params, session, server)
|
529
|
+
VectorMCP::Middleware::Context.new(
|
530
|
+
operation_type: :resource_read,
|
531
|
+
operation_name: uri_s,
|
532
|
+
params: params,
|
533
|
+
session: session,
|
534
|
+
server: server,
|
535
|
+
metadata: { start_time: Time.now }
|
536
|
+
)
|
537
|
+
end
|
538
|
+
|
539
|
+
# Find and validate resource exists
|
540
|
+
def self.find_resource!(uri_s, server)
|
541
|
+
raise VectorMCP::NotFoundError.new("Not Found", details: "Resource not found: #{uri_s}") unless server.resources[uri_s]
|
542
|
+
|
543
|
+
server.resources[uri_s]
|
544
|
+
end
|
545
|
+
|
546
|
+
# Validate resource security
|
547
|
+
def self.validate_resource_security!(session, resource, server)
|
548
|
+
security_result = check_resource_security(session, resource, server)
|
549
|
+
handle_security_failure(security_result) unless security_result[:success]
|
550
|
+
security_result
|
551
|
+
end
|
552
|
+
|
553
|
+
# Execute resource handler with proper arity handling
|
554
|
+
def self.execute_resource_handler(resource, params, security_result)
|
555
|
+
if [1, -1].include?(resource.handler.arity)
|
556
|
+
resource.handler.call(params)
|
557
|
+
else
|
558
|
+
resource.handler.call(params, security_result[:session_context])
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
# Process resource content and add URI
|
563
|
+
def self.process_resource_content(content_raw, resource, uri_s)
|
564
|
+
contents = VectorMCP::Util.convert_to_mcp_content(content_raw, mime_type: resource.mime_type)
|
565
|
+
contents.each do |item|
|
566
|
+
item[:uri] ||= uri_s
|
567
|
+
end
|
568
|
+
contents
|
569
|
+
end
|
570
|
+
|
571
|
+
# Handle resource execution errors
|
572
|
+
def self.handle_resource_error(error, context, server)
|
573
|
+
context.error = error
|
574
|
+
context = server.middleware_manager.execute_hooks(:on_resource_error, context)
|
575
|
+
raise error unless context.result
|
576
|
+
|
577
|
+
context.result
|
578
|
+
end
|
579
|
+
|
580
|
+
private_class_method :handle_middleware_error, :create_tool_context, :find_tool!, :validate_tool_security!,
|
581
|
+
:execute_tool_handler, :build_tool_result, :handle_tool_error, :create_resource_context,
|
582
|
+
:find_resource!, :validate_resource_security!, :execute_resource_handler,
|
583
|
+
:process_resource_content, :handle_resource_error
|
430
584
|
end
|
431
585
|
end
|
432
586
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module VectorMCP
|
7
|
+
# Simple, environment-driven logger for VectorMCP
|
8
|
+
# Supports JSON and text formats with component-based identification
|
9
|
+
class Logger
|
10
|
+
LEVELS = {
|
11
|
+
"TRACE" => ::Logger::DEBUG,
|
12
|
+
"DEBUG" => ::Logger::DEBUG,
|
13
|
+
"INFO" => ::Logger::INFO,
|
14
|
+
"WARN" => ::Logger::WARN,
|
15
|
+
"ERROR" => ::Logger::ERROR,
|
16
|
+
"FATAL" => ::Logger::FATAL
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
attr_reader :component, :ruby_logger
|
20
|
+
|
21
|
+
def initialize(component = "vectormcp")
|
22
|
+
@component = component.to_s
|
23
|
+
@ruby_logger = create_ruby_logger
|
24
|
+
@format = ENV.fetch("VECTORMCP_LOG_FORMAT", "text").downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.for(component)
|
28
|
+
new(component)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Log methods with context support and block evaluation
|
32
|
+
def debug(message = nil, **context, &block)
|
33
|
+
log(:debug, message || block&.call, context)
|
34
|
+
end
|
35
|
+
|
36
|
+
def info(message = nil, **context, &block)
|
37
|
+
log(:info, message || block&.call, context)
|
38
|
+
end
|
39
|
+
|
40
|
+
def warn(message = nil, **context, &block)
|
41
|
+
log(:warn, message || block&.call, context)
|
42
|
+
end
|
43
|
+
|
44
|
+
def error(message = nil, **context, &block)
|
45
|
+
log(:error, message || block&.call, context)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fatal(message = nil, **context, &block)
|
49
|
+
log(:fatal, message || block&.call, context)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Security-specific logging
|
53
|
+
def security(message, **context)
|
54
|
+
log(:error, "[SECURITY] #{message}", context.merge(security_event: true))
|
55
|
+
end
|
56
|
+
|
57
|
+
# Performance measurement
|
58
|
+
def measure(description, **context)
|
59
|
+
start_time = Time.now
|
60
|
+
result = yield
|
61
|
+
duration = Time.now - start_time
|
62
|
+
|
63
|
+
info("#{description} completed", **context, duration_ms: (duration * 1000).round(2),
|
64
|
+
success: true)
|
65
|
+
|
66
|
+
result
|
67
|
+
rescue StandardError => e
|
68
|
+
duration = Time.now - start_time
|
69
|
+
error("#{description} failed", **context, duration_ms: (duration * 1000).round(2),
|
70
|
+
success: false,
|
71
|
+
error: e.class.name,
|
72
|
+
error_message: e.message)
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def log(level, message, context)
|
79
|
+
return unless @ruby_logger.send("#{level}?")
|
80
|
+
|
81
|
+
if @format == "json"
|
82
|
+
log_json(level, message, context)
|
83
|
+
else
|
84
|
+
log_text(level, message, context)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def log_json(level, message, context)
|
89
|
+
entry = {
|
90
|
+
timestamp: Time.now.iso8601(3),
|
91
|
+
level: level.to_s.upcase,
|
92
|
+
component: @component,
|
93
|
+
message: message,
|
94
|
+
thread_id: Thread.current.object_id
|
95
|
+
}
|
96
|
+
entry.merge!(context) unless context.empty?
|
97
|
+
|
98
|
+
@ruby_logger.send(level, entry.to_json)
|
99
|
+
end
|
100
|
+
|
101
|
+
def log_text(level, message, context)
|
102
|
+
formatted_message = if context.empty?
|
103
|
+
"[#{@component}] #{message}"
|
104
|
+
else
|
105
|
+
context_str = context.map { |k, v| "#{k}=#{v}" }.join(" ")
|
106
|
+
"[#{@component}] #{message} (#{context_str})"
|
107
|
+
end
|
108
|
+
|
109
|
+
@ruby_logger.send(level, formatted_message)
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_ruby_logger
|
113
|
+
output = determine_output
|
114
|
+
logger = ::Logger.new(output)
|
115
|
+
logger.level = determine_level
|
116
|
+
logger.formatter = method(:format_log_entry)
|
117
|
+
logger
|
118
|
+
end
|
119
|
+
|
120
|
+
def determine_output
|
121
|
+
case ENV.fetch("VECTORMCP_LOG_OUTPUT", "stderr").downcase
|
122
|
+
when "stdout"
|
123
|
+
$stdout
|
124
|
+
when "file"
|
125
|
+
file_path = ENV.fetch("VECTORMCP_LOG_FILE", "./vectormcp.log")
|
126
|
+
File.open(file_path, "a")
|
127
|
+
else
|
128
|
+
$stderr
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def determine_level
|
133
|
+
level_name = ENV.fetch("VECTORMCP_LOG_LEVEL", "INFO").upcase
|
134
|
+
LEVELS.fetch(level_name, ::Logger::INFO)
|
135
|
+
end
|
136
|
+
|
137
|
+
def format_log_entry(severity, datetime, _progname, msg)
|
138
|
+
if @format == "json"
|
139
|
+
# JSON messages are already formatted
|
140
|
+
"#{msg}\n"
|
141
|
+
else
|
142
|
+
# Text format with timestamp
|
143
|
+
timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
144
|
+
"#{timestamp} [#{severity}] #{msg}\n"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|