familia 2.0.0.pre17 → 2.0.0.pre18
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.rst +60 -0
- data/CLAUDE.md +9 -2
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/bin/irb +1 -1
- data/docs/guides/core-field-system.md +48 -26
- data/docs/migrating/v2.0.0-pre18.md +58 -0
- data/docs/qodo-merge-compliance.md +96 -0
- data/lib/familia/base.rb +0 -2
- data/lib/familia/connection/middleware.rb +58 -4
- data/lib/familia/connection.rb +1 -1
- data/lib/familia/data_type/{commands.rb → database_commands.rb} +2 -2
- data/lib/familia/data_type/serialization.rb +5 -5
- data/lib/familia/data_type.rb +2 -2
- data/lib/familia/encryption/encrypted_data.rb +12 -2
- data/lib/familia/encryption/manager.rb +11 -4
- data/lib/familia/features/autoloader.rb +3 -1
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +9 -9
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +41 -27
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/horreum/database_commands.rb +1 -1
- data/lib/familia/horreum/definition.rb +6 -37
- data/lib/familia/horreum/management.rb +17 -12
- data/lib/familia/horreum/persistence.rb +1 -1
- data/lib/familia/horreum/serialization.rb +91 -73
- data/lib/familia/horreum.rb +10 -6
- data/lib/familia/identifier_extractor.rb +60 -0
- data/lib/familia/logging.rb +271 -112
- data/lib/familia/refinements.rb +0 -1
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -2
- data/lib/middleware/{database_middleware.rb → database_logger.rb} +47 -14
- data/pr_agent.toml +31 -0
- data/pr_compliance_checklist.yaml +45 -0
- data/try/edge_cases/empty_identifiers_try.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +31 -31
- data/try/edge_cases/json_serialization_try.rb +2 -2
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
- data/try/edge_cases/race_conditions_try.rb +1 -1
- data/try/edge_cases/reserved_keywords_try.rb +1 -1
- data/try/edge_cases/string_coercion_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
- data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
- data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
- data/try/features/encrypted_fields/memory_security_try.rb +1 -1
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
- data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
- data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
- data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
- data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
- data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
- data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
- data/try/features/expiration/expiration_try.rb +1 -1
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +1 -1
- data/try/features/feature_improvements_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_try.rb +1 -1
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/real_feature_integration_try.rb +17 -14
- data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
- data/try/features/relationships/indexing_try.rb +6 -1
- data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
- data/try/features/relationships/participation_commands_verification_try.rb +4 -4
- data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
- data/try/features/relationships/participation_reverse_index_try.rb +1 -1
- data/try/features/relationships/relationships_api_changes_try.rb +1 -1
- data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
- data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
- data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
- data/try/features/relationships/relationships_performance_try.rb +1 -1
- data/try/features/relationships/relationships_performance_working_try.rb +1 -1
- data/try/features/relationships/relationships_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_try.rb +1 -1
- data/try/features/transient_fields/redacted_string_try.rb +1 -1
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +1 -1
- data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
- data/try/{core → integration/connection}/isolated_dbclient_try.rb +1 -1
- data/try/integration/connection/middleware_reconnect_try.rb +87 -0
- data/try/{connection → integration/connection}/operation_mode_guards_try.rb +1 -1
- data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +1 -1
- data/try/{core → integration/connection}/pools_try.rb +1 -1
- data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
- data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
- data/try/{core → integration}/create_method_try.rb +1 -1
- data/try/integration/cross_component_try.rb +1 -1
- data/try/{core → integration}/database_consistency_try.rb +11 -8
- data/try/{core → integration}/familia_extended_try.rb +1 -1
- data/try/{core → integration}/familia_members_methods_try.rb +1 -1
- data/try/{models → integration/models}/customer_safe_dump_try.rb +1 -1
- data/try/{models → integration/models}/customer_try.rb +1 -1
- data/try/{models → integration/models}/datatype_base_try.rb +1 -1
- data/try/{models → integration/models}/familia_object_try.rb +1 -1
- data/try/{core → integration}/persistence_operations_try.rb +1 -1
- data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
- data/try/{configuration → integration}/scenarios_try.rb +1 -1
- data/try/{core → integration}/secure_identifier_try.rb +1 -1
- data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
- data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
- data/try/{helpers → support/helpers}/test_helpers.rb +12 -3
- data/try/{core → unit/core}/autoloader_try.rb +1 -1
- data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
- data/try/{core → unit/core}/connection_try.rb +1 -1
- data/try/{core → unit/core}/errors_try.rb +1 -1
- data/try/{core → unit/core}/extensions_try.rb +1 -1
- data/try/unit/core/familia_logger_try.rb +110 -0
- data/try/{core → unit/core}/familia_try.rb +1 -1
- data/try/{core → unit/core}/middleware_try.rb +41 -1
- data/try/{core → unit/core}/settings_try.rb +1 -1
- data/try/{core → unit/core}/time_utils_try.rb +1 -1
- data/try/{core → unit/core}/tools_try.rb +1 -1
- data/try/{core → unit/core}/utils_try.rb +17 -14
- data/try/{data_types → unit/data_types}/boolean_try.rb +1 -1
- data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
- data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
- data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
- data/try/{data_types → unit/data_types}/list_try.rb +1 -1
- data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_zadd_options_try.rb +1 -1
- data/try/{data_types → unit/data_types}/string_try.rb +1 -1
- data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
- data/try/{horreum → unit/horreum}/auto_indexing_on_save_try.rb +1 -1
- data/try/{horreum → unit/horreum}/base_try.rb +3 -3
- data/try/{horreum → unit/horreum}/class_methods_try.rb +1 -1
- data/try/{horreum → unit/horreum}/commands_try.rb +1 -1
- data/try/{horreum → unit/horreum}/defensive_initialization_try.rb +1 -1
- data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +1 -1
- data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
- data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
- data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
- data/try/{horreum → unit/horreum}/initialization_try.rb +2 -2
- data/try/unit/horreum/json_type_preservation_try.rb +248 -0
- data/try/{horreum → unit/horreum}/relations_try.rb +1 -1
- data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
- data/try/{horreum → unit/horreum}/serialization_try.rb +4 -4
- data/try/{horreum → unit/horreum}/settings_try.rb +1 -1
- data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
- metadata +134 -125
- data/lib/familia/distinguisher.rb +0 -85
- data/lib/familia/refinements/logger_trace.rb +0 -60
- data/try/refinements/logger_trace_methods_try.rb +0 -44
- /data/try/{debugging → support/debugging}/README.md +0 -0
- /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
- /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
- /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
- /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +0 -0
- /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
- /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
data/lib/familia/logging.rb
CHANGED
@@ -3,151 +3,310 @@
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'logger'
|
5
5
|
|
6
|
+
# Familia - Logbook
|
7
|
+
#
|
6
8
|
module Familia
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
9
|
+
# Custom Logger subclass with TRACE level support.
|
10
|
+
#
|
11
|
+
# FamiliaLogger extends Ruby's standard Logger with a TRACE level for
|
12
|
+
# extremely detailed debugging output. The TRACE level is numerically
|
13
|
+
# equal to DEBUG (0) but distinguishes itself via a thread-local marker
|
14
|
+
# that the LogFormatter uses to output 'T' instead of 'D'.
|
15
|
+
#
|
16
|
+
# @example Basic usage
|
17
|
+
# logger = Familia::FamiliaLogger.new($stderr)
|
18
|
+
# logger.level = Familia::FamiliaLogger::TRACE
|
19
|
+
# logger.trace "Detailed trace message"
|
20
|
+
# # => T, 10-05 20:43:09.843 pid:123 [456/789]: Detailed trace message
|
21
|
+
#
|
22
|
+
# @example With progname
|
23
|
+
# logger.trace("MyApp") { "Trace with progname" }
|
24
|
+
#
|
25
|
+
# @see Familia::LogFormatter
|
26
|
+
#
|
27
|
+
class FamiliaLogger < Logger
|
28
|
+
# TRACE severity level (numerically equal to DEBUG=0).
|
29
|
+
#
|
30
|
+
# Uses the same numeric level as DEBUG but signals via thread-local
|
31
|
+
# marker to output 'T' prefix instead of 'D'. This approach works
|
32
|
+
# around Logger's limitation with negative severity values.
|
33
|
+
#
|
34
|
+
# Standard Logger levels: DEBUG=0, INFO=1, WARN=2, ERROR=3, FATAL=4, UNKNOWN=5
|
35
|
+
TRACE = 0
|
36
|
+
|
37
|
+
# Log a TRACE level message.
|
38
|
+
#
|
39
|
+
# This method behaves like the standard Logger methods (debug, info, etc.)
|
40
|
+
# but outputs with a 'T' severity letter when used with LogFormatter.
|
41
|
+
#
|
42
|
+
# @param progname [String, nil] Program name to include in log output
|
43
|
+
# @yield Block that returns the message to log (lazy evaluation)
|
44
|
+
# @return [true] Always returns true
|
45
|
+
#
|
46
|
+
# @example Simple message
|
47
|
+
# logger.trace("Entering complex calculation")
|
48
|
+
#
|
49
|
+
# @example With block for lazy evaluation
|
50
|
+
# logger.trace { "Expensive: #{expensive_debug_info}" }
|
51
|
+
#
|
52
|
+
# @example With progname
|
53
|
+
# logger.trace("MyApp") { "Application trace" }
|
54
|
+
#
|
55
|
+
# @note Sets Fiber[:familia_trace_mode] during execution to
|
56
|
+
# signal LogFormatter to output 'T' instead of 'D'
|
57
|
+
#
|
58
|
+
def trace(progname = nil, &)
|
59
|
+
# Store marker in thread-local to signal this is TRACE not DEBUG
|
60
|
+
# Track whether we set the flag to avoid clearing it in nested calls
|
61
|
+
was_already_tracing = Fiber[:familia_trace_mode]
|
62
|
+
Fiber[:familia_trace_mode] = true
|
63
|
+
add(TRACE, nil, progname, &)
|
64
|
+
ensure
|
65
|
+
# Only clear the flag if we set it (not already tracing)
|
66
|
+
Fiber[:familia_trace_mode] = false unless was_already_tracing
|
67
|
+
end
|
26
68
|
end
|
27
69
|
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
70
|
+
# Custom formatter for Familia logger output.
|
71
|
+
#
|
72
|
+
# LogFormatter produces structured log output with severity letters,
|
73
|
+
# timestamps, process/thread/fiber IDs, and the log message.
|
31
74
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# A custom log level for trace messages, typically used for very detailed
|
35
|
-
# debugging information.
|
75
|
+
# Output format:
|
76
|
+
# SEVERITY, MM-DD HH:MM:SS.mmm pid:PID [THREAD_ID/FIBER_ID]: MESSAGE
|
36
77
|
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# Familia::Refinements::LoggerTrace is used.
|
78
|
+
# @example Output
|
79
|
+
# I, 10-05 20:43:09.843 pid:12345 [67890/54321]: Connection established
|
80
|
+
# T, 10-05 20:43:10.123 pid:12345 [67890/54321]: [LOAD] redis -> user:123
|
41
81
|
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
82
|
+
# Severity letters:
|
83
|
+
# T = TRACE (when Fiber[:familia_trace_mode] is set, or level 0 when not using FamiliaLogger)
|
84
|
+
# D = DEBUG
|
85
|
+
# I = INFO
|
86
|
+
# W = WARN
|
87
|
+
# E = ERROR
|
88
|
+
# F = FATAL
|
89
|
+
# U = UNKNOWN
|
45
90
|
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
91
|
+
# @example Use with FamiliaLogger for TRACE support
|
92
|
+
# logger = Familia::FamiliaLogger.new($stderr)
|
93
|
+
# logger.formatter = Familia::LogFormatter.new
|
94
|
+
# logger.trace("Trace message") # => T, ...
|
95
|
+
# logger.debug("Debug message") # => D, ...
|
49
96
|
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
97
|
+
# @example Use with standard Logger (level 0 becomes 'T')
|
98
|
+
# logger = Logger.new($stderr)
|
99
|
+
# logger.formatter = Familia::LogFormatter.new
|
100
|
+
# logger.debug("Debug message") # => T, ... (because DEBUG=0)
|
53
101
|
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
102
|
+
# @note When used with FamiliaLogger, checks Fiber[:familia_trace_mode] to
|
103
|
+
# distinguish TRACE from DEBUG. When used with standard Logger, treats
|
104
|
+
# level 0 as TRACE since DEBUG and TRACE share the same numeric level.
|
57
105
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
|
106
|
+
# @see FamiliaLogger#trace
|
107
|
+
#
|
108
|
+
class LogFormatter < Logger::Formatter
|
109
|
+
# Severity string to letter mapping.
|
110
|
+
#
|
111
|
+
# Maps severity string labels to single-letter codes for compact output.
|
112
|
+
# Note: TRACE is handled via Fiber check in #call for FamiliaLogger.
|
113
|
+
SEVERITY_LETTERS = {
|
114
|
+
'DEBUG' => 'D',
|
115
|
+
'INFO' => 'I',
|
116
|
+
'WARN' => 'W',
|
117
|
+
'ERROR' => 'E',
|
118
|
+
'FATAL' => 'F',
|
119
|
+
'UNKNOWN' => 'U',
|
120
|
+
'ANY' => 'T' # ANY is Logger's label for severity < 0, treat as TRACE
|
121
|
+
}.freeze
|
122
|
+
|
123
|
+
# Format a log message with severity, timestamp, and context.
|
124
|
+
#
|
125
|
+
# @param severity [String] Severity label (e.g., "INFO", "DEBUG", "UNKNOWN")
|
126
|
+
# @param datetime [Time] Timestamp of the log message
|
127
|
+
# @param _progname [String] Program name (unused, kept for Logger compatibility)
|
128
|
+
# @param msg [String] The log message
|
129
|
+
# @return [String] Formatted log line with newline
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
# formatter = Familia::LogFormatter.new
|
133
|
+
# formatter.call("INFO", Time.now, nil, "Test message")
|
134
|
+
# # => "I, 10-05 20:43:09.843 pid:12345 [67890/54321]: Test message\n"
|
135
|
+
#
|
136
|
+
def call(severity, datetime, _progname, msg)
|
137
|
+
# Check if we're in trace mode (TRACE uses same level as DEBUG but marks itself)
|
138
|
+
# FamiliaLogger sets Fiber[:familia_trace_mode] when trace() is called
|
139
|
+
severity_letter = if Fiber[:familia_trace_mode]
|
140
|
+
'T'
|
141
|
+
else
|
142
|
+
SEVERITY_LETTERS.fetch(severity, severity[0])
|
143
|
+
end
|
144
|
+
|
145
|
+
utc_datetime = datetime.utc.strftime('%m-%d %H:%M:%S.%3N')
|
146
|
+
pid = Process.pid
|
147
|
+
thread_id = Thread.current.object_id
|
148
|
+
fiber_id = Fiber.current.object_id
|
149
|
+
|
150
|
+
"#{severity_letter}, #{utc_datetime} pid:#{pid} [#{thread_id}/#{fiber_id}]: #{msg}\n"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# The Logging module provides logging capabilities for Familia.
|
155
|
+
#
|
156
|
+
# Familia uses a custom FamiliaLogger that extends the standard Ruby Logger
|
157
|
+
# with a TRACE level for detailed debugging output.
|
158
|
+
#
|
159
|
+
# == Log Levels (from most to least verbose):
|
160
|
+
# - TRACE: Extremely detailed debugging (controlled by FAMILIA_TRACE env var)
|
161
|
+
# - DEBUG: Detailed debugging information
|
162
|
+
# - INFO: General informational messages
|
163
|
+
# - WARN: Warning messages
|
164
|
+
# - ERROR: Error messages
|
165
|
+
# - FATAL: Fatal errors that cause termination
|
61
166
|
#
|
62
167
|
# == Usage:
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# TRACE = 0
|
73
|
-
#
|
74
|
-
# def trace(progname = nil, &block)
|
75
|
-
# add(TRACE, nil, progname, &block)
|
76
|
-
# end
|
77
|
-
# end
|
78
|
-
# end
|
79
|
-
#
|
80
|
-
# using Familia::Refinements::LoggerTrace
|
81
|
-
#
|
82
|
-
# logger = Logger.new(STDOUT)
|
83
|
-
# logger.trace("This is a trace message")
|
84
|
-
# logger.debug("This is a debug message")
|
85
|
-
# logger.info("This is an info message")
|
86
|
-
# logger.warn("This is a warning message")
|
87
|
-
# logger.error("This is an error message")
|
88
|
-
# logger.fatal("This is a fatal message")
|
89
|
-
#
|
90
|
-
# In this example, the Familia::Refinements::LoggerTrace module is defined with a refinement
|
91
|
-
# for the Logger class. The TRACE constant and trace method are added to the Logger
|
92
|
-
# class within the refinement. The `using` keyword is used to apply the refinement
|
93
|
-
# in the scope where it's needed.
|
94
|
-
#
|
95
|
-
# == Conditions:
|
96
|
-
# The trace method and TRACE log level are only available if the Familia::Refinements::LoggerTrace
|
97
|
-
# module is used with the `using` keyword. Without this, the Logger class will not
|
98
|
-
# have the trace method or the TRACE log level.
|
99
|
-
#
|
100
|
-
# == Minimum Ruby Version:
|
101
|
-
# This module requires Ruby 2.0.0 or later to use refinements.
|
168
|
+
# # Use default logger
|
169
|
+
# Familia.info "Connection established"
|
170
|
+
# Familia.warn "Cache miss"
|
171
|
+
#
|
172
|
+
# # Set custom logger
|
173
|
+
# Familia.logger = Logger.new('familia.log')
|
174
|
+
#
|
175
|
+
# # Trace-level debugging (requires FAMILIA_TRACE=true)
|
176
|
+
# Familia.trace :LOAD, redis_client, "user:123", "from cache"
|
102
177
|
#
|
103
178
|
module Logging
|
104
|
-
|
105
|
-
|
106
|
-
#
|
107
|
-
|
179
|
+
# Get the logger instance, initializing with defaults if not yet set
|
180
|
+
#
|
181
|
+
# @return [FamiliaLogger] the logger instance
|
182
|
+
#
|
183
|
+
# @example Set a custom logger
|
184
|
+
# Familia.logger = Logger.new('familia.log')
|
185
|
+
#
|
186
|
+
# @example Use the default logger
|
187
|
+
# Familia.logger.info "Connection established"
|
188
|
+
#
|
189
|
+
def logger
|
190
|
+
@logger ||= FamiliaLogger.new($stderr).tap do |log|
|
191
|
+
log.progname = name
|
192
|
+
log.formatter = LogFormatter.new
|
193
|
+
end
|
194
|
+
end
|
108
195
|
|
109
|
-
|
110
|
-
|
196
|
+
# Set a custom logger instance.
|
197
|
+
#
|
198
|
+
# Allows replacing the default FamiliaLogger with any Logger-compatible
|
199
|
+
# object. Useful for integrating with application logging frameworks.
|
200
|
+
#
|
201
|
+
# @param new_logger [Logger] The logger to use
|
202
|
+
# @return [Logger] The logger that was set
|
203
|
+
#
|
204
|
+
# @example Use Rails logger
|
205
|
+
# Familia.logger = Rails.logger
|
206
|
+
#
|
207
|
+
# @example Custom file logger
|
208
|
+
# Familia.logger = Logger.new('familia.log').tap do |log|
|
209
|
+
# log.level = Logger::INFO
|
210
|
+
# end
|
211
|
+
#
|
212
|
+
def logger=(new_logger)
|
213
|
+
@logger = new_logger
|
111
214
|
end
|
112
215
|
|
113
|
-
|
114
|
-
|
216
|
+
# Log an informational message.
|
217
|
+
#
|
218
|
+
# @param msg [String] The message to log
|
219
|
+
# @return [true]
|
220
|
+
#
|
221
|
+
# @example
|
222
|
+
# Familia.info "Redis connection established"
|
223
|
+
#
|
224
|
+
def info(msg)
|
225
|
+
logger.info(msg)
|
115
226
|
end
|
116
227
|
|
117
|
-
|
118
|
-
|
228
|
+
# Log a warning message.
|
229
|
+
#
|
230
|
+
# @param msg [String] The message to log
|
231
|
+
# @return [true]
|
232
|
+
#
|
233
|
+
# @example
|
234
|
+
# Familia.warn "Cache miss for key: user:123"
|
235
|
+
#
|
236
|
+
def warn(msg)
|
237
|
+
logger.warn(msg)
|
238
|
+
end
|
119
239
|
|
120
|
-
|
240
|
+
# Log a debug message (only when Familia.debug? is true).
|
241
|
+
#
|
242
|
+
# Short for "log debug". Only outputs when FAMILIA_DEBUG environment
|
243
|
+
# variable is set to '1' or 'true'.
|
244
|
+
#
|
245
|
+
# @param msg [String] The message to log
|
246
|
+
# @return [true, nil] Returns true if logged, nil if debug disabled
|
247
|
+
#
|
248
|
+
# @example
|
249
|
+
# Familia.ld "Cache lookup for user:123"
|
250
|
+
# # Only outputs when FAMILIA_DEBUG=true
|
251
|
+
#
|
252
|
+
def ld(msg)
|
253
|
+
logger.debug(msg) if Familia.debug?
|
121
254
|
end
|
122
255
|
|
123
|
-
|
124
|
-
|
256
|
+
# Log an error message.
|
257
|
+
#
|
258
|
+
# Short for "log error".
|
259
|
+
#
|
260
|
+
# @param msg [String] The message to log
|
261
|
+
# @return [true]
|
262
|
+
#
|
263
|
+
# @example
|
264
|
+
# Familia.le "Failed to deserialize value: #{e.message}"
|
265
|
+
#
|
266
|
+
def le(msg)
|
267
|
+
logger.error(msg)
|
125
268
|
end
|
126
269
|
|
127
|
-
# Logs a trace message for debugging
|
270
|
+
# Logs a structured trace message for debugging Familia operations.
|
271
|
+
#
|
272
|
+
# This method only executes when both FAMILIA_TRACE and FAMILIA_DEBUG
|
273
|
+
# environment variables are enabled.
|
128
274
|
#
|
129
275
|
# @param label [Symbol] A label for the trace message (e.g., :EXPAND,
|
130
276
|
# :FROMREDIS, :LOAD, :EXISTS).
|
131
|
-
# @param instance_id
|
132
|
-
# @param ident [String] An identifier or key related to the operation being
|
133
|
-
#
|
134
|
-
# @param extra_context [Array<String>, String, nil] Any extra details to include.
|
135
|
-
#
|
136
|
-
# @example Familia.trace :LOAD, Familia.dbclient(uri), objkey if Familia.debug?
|
277
|
+
# @param instance_id [Object] The object instance being traced (e.g., Redis client)
|
278
|
+
# @param ident [String] An identifier or key related to the operation being traced
|
279
|
+
# @param extra_context [String, nil] Any extra details to include
|
137
280
|
#
|
138
281
|
# @return [nil]
|
139
282
|
#
|
140
|
-
# @
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
283
|
+
# @example
|
284
|
+
# Familia.trace :LOAD, redis_client, "user:123", "from cache"
|
285
|
+
# # Output: T, 10-05 20:43:09.843 pid:123 [456/789]: [LOAD] #<Redis> -> user:123 <-from cache
|
286
|
+
#
|
287
|
+
# @note Controlled by FAMILIA_TRACE environment variable (set to '1', 'true', or 'yes')
|
288
|
+
# @note The instance_id can be a Redis client, Redis::Future, or nil
|
144
289
|
#
|
145
290
|
def trace(label, instance_id = nil, ident = nil, extra_context = nil)
|
146
|
-
return unless Familia
|
291
|
+
return unless trace_enabled? && Familia.debug?
|
292
|
+
|
293
|
+
ident_str = ident.nil? ? '<nil>' : ident.to_s
|
294
|
+
logger.trace format('[%s] %s -> %s <-%s', label, instance_id, ident_str, extra_context)
|
295
|
+
end
|
147
296
|
|
148
|
-
|
149
|
-
|
150
|
-
|
297
|
+
private
|
298
|
+
|
299
|
+
# Check if trace logging is enabled via FAMILIA_TRACE environment variable.
|
300
|
+
#
|
301
|
+
# Trace logging is enabled when FAMILIA_TRACE is set to '1', 'true',
|
302
|
+
# or 'yes' (case-insensitive). Checks the environment variable on every
|
303
|
+
# call to support dynamic changes in test environments.
|
304
|
+
#
|
305
|
+
# @return [Boolean] true if trace logging is enabled
|
306
|
+
# @api private
|
307
|
+
#
|
308
|
+
def trace_enabled?
|
309
|
+
%w[1 true yes].include?(ENV.fetch('FAMILIA_TRACE', 'false').downcase)
|
151
310
|
end
|
152
311
|
end
|
153
312
|
end
|
data/lib/familia/refinements.rb
CHANGED
data/lib/familia/version.rb
CHANGED
data/lib/familia.rb
CHANGED
@@ -39,7 +39,7 @@ module Familia
|
|
39
39
|
using Refinements::StylizeWords
|
40
40
|
|
41
41
|
class << self
|
42
|
-
attr_accessor :debug
|
42
|
+
attr_accessor :debug
|
43
43
|
attr_reader :members
|
44
44
|
|
45
45
|
def included(member)
|
@@ -139,7 +139,7 @@ module Familia
|
|
139
139
|
require_relative 'familia/connection'
|
140
140
|
require_relative 'familia/settings'
|
141
141
|
require_relative 'familia/utils'
|
142
|
-
require_relative 'familia/
|
142
|
+
require_relative 'familia/identifier_extractor'
|
143
143
|
require_relative 'familia/json_serializer'
|
144
144
|
|
145
145
|
extend SecureIdentifier
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# lib/middleware/
|
1
|
+
# lib/middleware/database_logger.rb
|
2
2
|
|
3
3
|
require 'concurrent-ruby'
|
4
4
|
|
@@ -29,21 +29,34 @@ require 'concurrent-ruby'
|
|
29
29
|
# often outweigh the slight performance cost when enabled.
|
30
30
|
module DatabaseLogger
|
31
31
|
@logger = nil
|
32
|
-
@commands =
|
32
|
+
@commands = Concurrent::Array.new
|
33
|
+
@max_commands = 10_000
|
34
|
+
@process_start = Time.now.to_f.freeze
|
35
|
+
|
36
|
+
CommandMessage = Data.define(:command, :μs, :timeline)
|
33
37
|
|
34
38
|
class << self
|
35
39
|
# Gets/sets the logger instance used by DatabaseLogger.
|
36
40
|
# @return [Logger, nil] The current logger instance or nil if not set.
|
37
41
|
attr_accessor :logger
|
38
42
|
|
43
|
+
# Gets/sets the maximum number of commands to capture.
|
44
|
+
# @return [Integer] The maximum number of commands to capture.
|
45
|
+
attr_accessor :max_commands
|
46
|
+
|
39
47
|
# Gets the captured commands for testing purposes.
|
40
|
-
# @return [Array] Array of command hashes with :command, :duration, :
|
48
|
+
# @return [Array] Array of command hashes with :command, :duration, :timeline
|
41
49
|
attr_reader :commands
|
42
50
|
|
51
|
+
# Gets the timestamp when DatabaseLogger was loaded.
|
52
|
+
# @return [Float] The timestamp when DatabaseLogger was loaded.
|
53
|
+
attr_reader :process_start
|
54
|
+
|
43
55
|
# Clears the captured commands array.
|
44
56
|
# @return [Array] Empty array
|
45
57
|
def clear_commands
|
46
|
-
@commands
|
58
|
+
@commands.clear
|
59
|
+
nil
|
47
60
|
end
|
48
61
|
|
49
62
|
# Captures commands in a block and returns them.
|
@@ -62,8 +75,28 @@ module DatabaseLogger
|
|
62
75
|
def capture_commands
|
63
76
|
clear_commands
|
64
77
|
yield
|
65
|
-
@commands.
|
78
|
+
@commands.to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
# Thread-safe append with bounded size
|
82
|
+
#
|
83
|
+
# @param message [String] The message to append.
|
84
|
+
# @return [Array] The updated array of commands.
|
85
|
+
def append_command(message)
|
86
|
+
@commands.shift if @commands.size >= @max_commands
|
87
|
+
@commands << message
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the current time in microseconds.
|
91
|
+
# This is used to measure the duration of Database commands.
|
92
|
+
#
|
93
|
+
# Alias: now_in_microseconds
|
94
|
+
#
|
95
|
+
# @return [Integer] The current time in microseconds.
|
96
|
+
def now_in_μs
|
97
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
66
98
|
end
|
99
|
+
alias now_in_microseconds now_in_μs
|
67
100
|
end
|
68
101
|
|
69
102
|
# Logs the Database command and its execution time.
|
@@ -79,19 +112,19 @@ module DatabaseLogger
|
|
79
112
|
# @note Commands are always captured with minimal overhead for testing purposes.
|
80
113
|
# Logging only occurs when DatabaseLogger.logger is set.
|
81
114
|
def call(command, _config)
|
82
|
-
|
115
|
+
block_start = DatabaseLogger.now_in_μs
|
83
116
|
result = yield
|
84
|
-
|
117
|
+
block_duration = DatabaseLogger.now_in_μs - block_start
|
118
|
+
lifetime_duration = (Time.now.to_f - DatabaseLogger.process_start).round(6)
|
85
119
|
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
}
|
120
|
+
# We intentionally use two different codepaths for getting the
|
121
|
+
# time, although they will almost always be so similar that the
|
122
|
+
# difference is negligible.
|
123
|
+
message = CommandMessage.new(command, block_duration, lifetime_duration)
|
124
|
+
DatabaseLogger.append_command(message)
|
92
125
|
|
93
126
|
# Log if logger is set
|
94
|
-
DatabaseLogger.logger&.debug(
|
127
|
+
DatabaseLogger.logger&.debug(Oj.dump(message.to_h, mode: :strict))
|
95
128
|
|
96
129
|
result
|
97
130
|
end
|
data/pr_agent.toml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Qodo Merge Configuration
|
2
|
+
# Documentation: https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/
|
3
|
+
|
4
|
+
[config]
|
5
|
+
# Ensure consistent review language across all PRs
|
6
|
+
response_language = "en"
|
7
|
+
|
8
|
+
[rag_arguments]
|
9
|
+
# Enable RAG context enrichment for codebase duplication compliance checks
|
10
|
+
enable_rag = true
|
11
|
+
# Include related repositories for comprehensive context
|
12
|
+
rag_repo_list = ['delano/familia', 'delano/tryouts', 'delano/otto']
|
13
|
+
|
14
|
+
[compliance]
|
15
|
+
# Reference custom compliance checklist for project-specific rules
|
16
|
+
custom_compliance_path = "pr_compliance_checklist.yaml"
|
17
|
+
|
18
|
+
[ignore]
|
19
|
+
# Reduce noise by excluding generated files and build artifacts
|
20
|
+
glob = [
|
21
|
+
"*.lock", # Lock files (Gemfile.lock, etc.)
|
22
|
+
"*.gem", # Built gem files
|
23
|
+
"vendor/**", # Vendored dependencies
|
24
|
+
"tmp/**", # Temporary files
|
25
|
+
"log/**", # Log files
|
26
|
+
"data/**", # Data directories
|
27
|
+
"public/**", # Public assets
|
28
|
+
".yardoc/**", # YARD documentation cache
|
29
|
+
"dump.rdb", # Redis database dumps
|
30
|
+
"appendonlydir/**", # Redis append-only file directory
|
31
|
+
]
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Custom Compliance Checklist for Familia
|
2
|
+
# Documentation: https://qodo-merge-docs.qodo.ai/tools/compliance/
|
3
|
+
|
4
|
+
pr_compliances:
|
5
|
+
- title: "ErrorHandling"
|
6
|
+
compliance_label: true
|
7
|
+
objective: "All external API calls and database operations must have proper error handling"
|
8
|
+
success_criteria: "Try-catch blocks around external calls with appropriate logging or error handling mechanisms"
|
9
|
+
failure_criteria: "External API calls, database operations, or network requests without error handling"
|
10
|
+
|
11
|
+
- title: "TestCoverage"
|
12
|
+
compliance_label: true
|
13
|
+
objective: "New features must include corresponding tests using the Tryouts framework"
|
14
|
+
success_criteria: "Test files present in try/ directory for new functionality following *_try.rb or *.try.rb naming convention"
|
15
|
+
failure_criteria: "New code without test coverage or tests not following Tryouts framework conventions"
|
16
|
+
|
17
|
+
- title: "ChangelogFragment"
|
18
|
+
compliance_label: true
|
19
|
+
objective: "User-facing changes must include a changelog"
|
20
|
+
success_criteria: "New fragment file in changelog.d/ directory following the naming convention and RST format, or updates to CHANGELOG.rst in root directory, or explicit justification for omission"
|
21
|
+
failure_criteria: "User-facing changes without changelog fragment, CHANGELOG.rst updates, or documentation updates"
|
22
|
+
|
23
|
+
- title: "DocumentationUpdates"
|
24
|
+
compliance_label: true
|
25
|
+
objective: "API changes must be reflected in documentation"
|
26
|
+
success_criteria: "YARD documentation comments for new public methods, or updates to docs/ for significant changes"
|
27
|
+
failure_criteria: "New public APIs or significant behavior changes without documentation updates"
|
28
|
+
|
29
|
+
- title: "BackwardCompatibility"
|
30
|
+
compliance_label: true
|
31
|
+
objective: "Changes must maintain backward compatibility or document breaking changes"
|
32
|
+
success_criteria: "No breaking changes to public APIs, or breaking changes clearly documented in migration guides"
|
33
|
+
failure_criteria: "Breaking changes without migration documentation or deprecation warnings"
|
34
|
+
|
35
|
+
- title: "ThreadSafety"
|
36
|
+
compliance_label: true
|
37
|
+
objective: "Code handling shared state must be thread-safe"
|
38
|
+
success_criteria: "Proper synchronization for shared mutable state, or clear documentation of thread-safety assumptions"
|
39
|
+
failure_criteria: "Shared mutable state accessed without synchronization in concurrent contexts"
|
40
|
+
|
41
|
+
- title: "DatabaseKeyNaming"
|
42
|
+
compliance_label: true
|
43
|
+
objective: "Database key generation must follow Familia conventions"
|
44
|
+
success_criteria: "Keys use delim separator, avoid reserved keywords (ttl, db, valkey, redis), and handle empty identifiers"
|
45
|
+
failure_criteria: "Keys using reserved keywords, empty identifiers, or non-standard separators"
|