logstruct 0.0.1 → 0.0.2.pre.rc2

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -2
  3. data/LICENSE +21 -0
  4. data/README.md +67 -0
  5. data/lib/log_struct/concerns/configuration.rb +93 -0
  6. data/lib/log_struct/concerns/error_handling.rb +94 -0
  7. data/lib/log_struct/concerns/logging.rb +45 -0
  8. data/lib/log_struct/config_struct/error_handling_modes.rb +25 -0
  9. data/lib/log_struct/config_struct/filters.rb +80 -0
  10. data/lib/log_struct/config_struct/integrations.rb +89 -0
  11. data/lib/log_struct/configuration.rb +59 -0
  12. data/lib/log_struct/enums/error_handling_mode.rb +22 -0
  13. data/lib/log_struct/enums/error_reporter.rb +14 -0
  14. data/lib/log_struct/enums/event.rb +48 -0
  15. data/lib/log_struct/enums/level.rb +66 -0
  16. data/lib/log_struct/enums/source.rb +26 -0
  17. data/lib/log_struct/enums.rb +9 -0
  18. data/lib/log_struct/formatter.rb +224 -0
  19. data/lib/log_struct/handlers.rb +27 -0
  20. data/lib/log_struct/hash_utils.rb +21 -0
  21. data/lib/log_struct/integrations/action_mailer/callbacks.rb +100 -0
  22. data/lib/log_struct/integrations/action_mailer/error_handling.rb +173 -0
  23. data/lib/log_struct/integrations/action_mailer/event_logging.rb +90 -0
  24. data/lib/log_struct/integrations/action_mailer/metadata_collection.rb +78 -0
  25. data/lib/log_struct/integrations/action_mailer.rb +50 -0
  26. data/lib/log_struct/integrations/active_job/log_subscriber.rb +104 -0
  27. data/lib/log_struct/integrations/active_job.rb +38 -0
  28. data/lib/log_struct/integrations/active_record.rb +258 -0
  29. data/lib/log_struct/integrations/active_storage.rb +94 -0
  30. data/lib/log_struct/integrations/carrierwave.rb +111 -0
  31. data/lib/log_struct/integrations/good_job/log_subscriber.rb +228 -0
  32. data/lib/log_struct/integrations/good_job/logger.rb +73 -0
  33. data/lib/log_struct/integrations/good_job.rb +111 -0
  34. data/lib/log_struct/integrations/host_authorization.rb +81 -0
  35. data/lib/log_struct/integrations/integration_interface.rb +21 -0
  36. data/lib/log_struct/integrations/lograge.rb +114 -0
  37. data/lib/log_struct/integrations/rack.rb +31 -0
  38. data/lib/log_struct/integrations/rack_error_handler/middleware.rb +146 -0
  39. data/lib/log_struct/integrations/rack_error_handler.rb +32 -0
  40. data/lib/log_struct/integrations/shrine.rb +75 -0
  41. data/lib/log_struct/integrations/sidekiq/logger.rb +43 -0
  42. data/lib/log_struct/integrations/sidekiq.rb +39 -0
  43. data/lib/log_struct/integrations/sorbet.rb +49 -0
  44. data/lib/log_struct/integrations.rb +41 -0
  45. data/lib/log_struct/log/action_mailer.rb +55 -0
  46. data/lib/log_struct/log/active_job.rb +64 -0
  47. data/lib/log_struct/log/active_storage.rb +78 -0
  48. data/lib/log_struct/log/carrierwave.rb +82 -0
  49. data/lib/log_struct/log/error.rb +76 -0
  50. data/lib/log_struct/log/good_job.rb +151 -0
  51. data/lib/log_struct/log/interfaces/additional_data_field.rb +20 -0
  52. data/lib/log_struct/log/interfaces/common_fields.rb +42 -0
  53. data/lib/log_struct/log/interfaces/message_field.rb +20 -0
  54. data/lib/log_struct/log/interfaces/request_fields.rb +36 -0
  55. data/lib/log_struct/log/plain.rb +53 -0
  56. data/lib/log_struct/log/request.rb +76 -0
  57. data/lib/log_struct/log/security.rb +80 -0
  58. data/lib/log_struct/log/shared/add_request_fields.rb +29 -0
  59. data/lib/log_struct/log/shared/merge_additional_data_fields.rb +28 -0
  60. data/lib/log_struct/log/shared/serialize_common.rb +36 -0
  61. data/lib/log_struct/log/shrine.rb +70 -0
  62. data/lib/log_struct/log/sidekiq.rb +50 -0
  63. data/lib/log_struct/log/sql.rb +126 -0
  64. data/lib/log_struct/log.rb +43 -0
  65. data/lib/log_struct/log_keys.rb +102 -0
  66. data/lib/log_struct/monkey_patches/active_support/tagged_logging/formatter.rb +36 -0
  67. data/lib/log_struct/multi_error_reporter.rb +149 -0
  68. data/lib/log_struct/param_filters.rb +89 -0
  69. data/lib/log_struct/railtie.rb +31 -0
  70. data/lib/log_struct/semantic_logger/color_formatter.rb +209 -0
  71. data/lib/log_struct/semantic_logger/formatter.rb +94 -0
  72. data/lib/log_struct/semantic_logger/logger.rb +129 -0
  73. data/lib/log_struct/semantic_logger/setup.rb +219 -0
  74. data/lib/log_struct/sorbet/serialize_symbol_keys.rb +23 -0
  75. data/lib/log_struct/sorbet.rb +13 -0
  76. data/lib/log_struct/string_scrubber.rb +84 -0
  77. data/lib/log_struct/version.rb +6 -0
  78. data/lib/log_struct.rb +37 -0
  79. data/lib/logstruct.rb +2 -6
  80. data/logstruct.gemspec +52 -0
  81. metadata +221 -5
  82. data/Rakefile +0 -5
@@ -0,0 +1,64 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "interfaces/common_fields"
5
+ require_relative "interfaces/additional_data_field"
6
+ require_relative "shared/serialize_common"
7
+ require_relative "shared/merge_additional_data_fields"
8
+ require_relative "../enums/source"
9
+ require_relative "../enums/event"
10
+ require_relative "../enums/level"
11
+ require_relative "../log_keys"
12
+
13
+ module LogStruct
14
+ module Log
15
+ # ActiveJob log entry for structured logging
16
+ class ActiveJob < T::Struct
17
+ extend T::Sig
18
+
19
+ include Interfaces::CommonFields
20
+ include Interfaces::AdditionalDataField
21
+ include SerializeCommon
22
+ include MergeAdditionalDataFields
23
+
24
+ ActiveJobEvent = T.type_alias {
25
+ T.any(
26
+ Event::Enqueue,
27
+ Event::Schedule,
28
+ Event::Start,
29
+ Event::Finish
30
+ )
31
+ }
32
+
33
+ # Common fields
34
+ const :source, Source::Job, default: T.let(Source::Job, Source::Job)
35
+ const :event, ActiveJobEvent
36
+ const :timestamp, Time, factory: -> { Time.now }
37
+ const :level, Level, default: T.let(Level::Info, Level)
38
+
39
+ # Job-specific fields
40
+ const :job_id, T.nilable(String), default: nil
41
+ const :job_class, T.nilable(String), default: nil
42
+ const :queue_name, T.nilable(String), default: nil
43
+ const :arguments, T.nilable(T::Array[T.untyped]), default: nil
44
+ const :duration, T.nilable(Float), default: nil
45
+ const :additional_data, T::Hash[Symbol, T.untyped], default: {}
46
+
47
+ # Convert the log entry to a hash for serialization
48
+ sig { override.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
49
+ def serialize(strict = true)
50
+ hash = serialize_common(strict)
51
+ merge_additional_data_fields(hash)
52
+
53
+ # Add job-specific fields if they're present
54
+ hash[LOG_KEYS.fetch(:job_id)] = job_id if job_id
55
+ hash[LOG_KEYS.fetch(:job_class)] = job_class if job_class
56
+ hash[LOG_KEYS.fetch(:queue_name)] = queue_name if queue_name
57
+ hash[LOG_KEYS.fetch(:arguments)] = arguments if arguments
58
+ hash[LOG_KEYS.fetch(:duration)] = duration if duration
59
+
60
+ hash
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,78 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "interfaces/common_fields"
5
+ require_relative "shared/serialize_common"
6
+ require_relative "../enums/source"
7
+ require_relative "../enums/event"
8
+ require_relative "../enums/level"
9
+
10
+ module LogStruct
11
+ module Log
12
+ # ActiveStorage log entry for structured logging
13
+ class ActiveStorage < T::Struct
14
+ extend T::Sig
15
+
16
+ include Interfaces::CommonFields
17
+ include SerializeCommon
18
+
19
+ # Define valid event types for ActiveStorage
20
+ ActiveStorageEvent = T.type_alias {
21
+ T.any(
22
+ Event::Upload,
23
+ Event::Download,
24
+ Event::Delete,
25
+ Event::Metadata,
26
+ Event::Exist,
27
+ Event::Stream,
28
+ Event::Url,
29
+ Event::Unknown
30
+ )
31
+ }
32
+
33
+ # Common fields
34
+ const :source, Source::Storage, default: T.let(Source::Storage, Source::Storage)
35
+ const :event, ActiveStorageEvent
36
+ const :timestamp, Time, factory: -> { Time.now }
37
+ const :level, Level, default: T.let(Level::Info, Level)
38
+
39
+ # ActiveStorage-specific fields
40
+ const :operation, T.nilable(Symbol), default: nil
41
+ const :storage, T.nilable(String), default: nil
42
+ const :file_id, T.nilable(String), default: nil
43
+ const :filename, T.nilable(String), default: nil
44
+ const :mime_type, T.nilable(String), default: nil
45
+ const :size, T.nilable(Integer), default: nil
46
+ const :metadata, T.nilable(T::Hash[String, T.untyped]), default: nil
47
+ const :duration, T.nilable(Float), default: nil
48
+ const :checksum, T.nilable(String), default: nil
49
+ const :exist, T.nilable(T::Boolean), default: nil
50
+ const :url, T.nilable(String), default: nil
51
+ const :prefix, T.nilable(String), default: nil
52
+ const :range, T.nilable(String), default: nil
53
+
54
+ # Convert the log entry to a hash for serialization
55
+ sig { override.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
56
+ def serialize(strict = true)
57
+ hash = serialize_common(strict)
58
+
59
+ # Add ActiveStorage-specific fields - only include non-nil values
60
+ hash[LOG_KEYS.fetch(:operation)] = operation if operation
61
+ hash[LOG_KEYS.fetch(:storage)] = storage if storage
62
+ hash[LOG_KEYS.fetch(:file_id)] = file_id if file_id
63
+ hash[LOG_KEYS.fetch(:filename)] = filename if filename
64
+ hash[LOG_KEYS.fetch(:mime_type)] = mime_type if mime_type
65
+ hash[LOG_KEYS.fetch(:size)] = size if size
66
+ hash[LOG_KEYS.fetch(:metadata)] = metadata if metadata
67
+ hash[LOG_KEYS.fetch(:duration)] = duration if duration
68
+ hash[LOG_KEYS.fetch(:checksum)] = checksum if checksum
69
+ hash[LOG_KEYS.fetch(:exist)] = exist if !exist.nil?
70
+ hash[LOG_KEYS.fetch(:url)] = url if url
71
+ hash[LOG_KEYS.fetch(:prefix)] = prefix if prefix
72
+ hash[LOG_KEYS.fetch(:range)] = range if range
73
+
74
+ hash
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,82 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "interfaces/common_fields"
5
+ require_relative "interfaces/additional_data_field"
6
+ require_relative "shared/serialize_common"
7
+ require_relative "shared/merge_additional_data_fields"
8
+ require_relative "../enums/source"
9
+ require_relative "../enums/event"
10
+ require_relative "../enums/level"
11
+ require_relative "../log_keys"
12
+
13
+ module LogStruct
14
+ module Log
15
+ # CarrierWave log entry for structured logging
16
+ class CarrierWave < T::Struct
17
+ extend T::Sig
18
+
19
+ include Interfaces::CommonFields
20
+ include Interfaces::AdditionalDataField
21
+ include SerializeCommon
22
+ include MergeAdditionalDataFields
23
+
24
+ CarrierWaveEvent = T.type_alias {
25
+ T.any(
26
+ Event::Upload,
27
+ Event::Download,
28
+ Event::Delete,
29
+ Event::Metadata,
30
+ Event::Exist,
31
+ Event::Unknown
32
+ )
33
+ }
34
+
35
+ # Common fields
36
+ const :source, Source::CarrierWave, default: T.let(Source::CarrierWave, Source::CarrierWave)
37
+ const :event, CarrierWaveEvent
38
+ const :timestamp, Time, factory: -> { Time.now }
39
+ const :level, Level, default: T.let(Level::Info, Level)
40
+
41
+ # File-specific fields
42
+ const :operation, T.nilable(Symbol), default: nil
43
+ const :storage, T.nilable(String), default: nil
44
+ const :file_id, T.nilable(String), default: nil
45
+ const :filename, T.nilable(String), default: nil
46
+ const :mime_type, T.nilable(String), default: nil
47
+ const :size, T.nilable(Integer), default: nil
48
+ const :metadata, T.nilable(T::Hash[String, T.untyped]), default: nil
49
+ const :duration, T.nilable(Float), default: nil
50
+
51
+ # CarrierWave-specific fields
52
+ const :uploader, T.nilable(String), default: nil
53
+ const :model, T.nilable(String), default: nil
54
+ const :mount_point, T.nilable(String), default: nil
55
+ const :additional_data, T::Hash[Symbol, T.untyped], default: {}
56
+
57
+ # Convert the log entry to a hash for serialization
58
+ sig { override.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
59
+ def serialize(strict = true)
60
+ hash = serialize_common(strict)
61
+ merge_additional_data_fields(hash)
62
+
63
+ # Add file-specific fields if they're present
64
+ hash[LOG_KEYS.fetch(:storage)] = storage if storage
65
+ hash[LOG_KEYS.fetch(:operation)] = operation if operation
66
+ hash[LOG_KEYS.fetch(:file_id)] = file_id if file_id
67
+ hash[LOG_KEYS.fetch(:filename)] = filename if filename
68
+ hash[LOG_KEYS.fetch(:mime_type)] = mime_type if mime_type
69
+ hash[LOG_KEYS.fetch(:size)] = size if size
70
+ hash[LOG_KEYS.fetch(:metadata)] = metadata if metadata
71
+ hash[LOG_KEYS.fetch(:duration)] = duration if duration
72
+
73
+ # Add CarrierWave-specific fields if they're present
74
+ hash[LOG_KEYS.fetch(:uploader)] = uploader if uploader
75
+ hash[LOG_KEYS.fetch(:model)] = model if model
76
+ hash[LOG_KEYS.fetch(:mount_point)] = mount_point if mount_point
77
+
78
+ hash
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,76 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "interfaces/common_fields"
5
+ require_relative "interfaces/additional_data_field"
6
+ require_relative "interfaces/message_field"
7
+ require_relative "shared/serialize_common"
8
+ require_relative "shared/merge_additional_data_fields"
9
+ require_relative "../enums/source"
10
+ require_relative "../enums/event"
11
+ require_relative "../enums/level"
12
+ require_relative "../log_keys"
13
+
14
+ module LogStruct
15
+ module Log
16
+ # Exception log entry for Ruby exceptions with class, message, and backtrace
17
+ class Error < T::Struct
18
+ extend T::Sig
19
+
20
+ include Interfaces::CommonFields
21
+ include Interfaces::AdditionalDataField
22
+ include Interfaces::MessageField
23
+ include MergeAdditionalDataFields
24
+
25
+ ErrorEvent = T.type_alias {
26
+ Event::Error
27
+ }
28
+
29
+ # Common fields
30
+ const :source, Source # Used by all sources, should not have a default.
31
+ const :event, ErrorEvent, default: T.let(Event::Error, ErrorEvent)
32
+ const :timestamp, Time, factory: -> { Time.now }
33
+ const :level, Level, default: T.let(Level::Error, Level)
34
+
35
+ # Exception-specific fields
36
+ const :err_class, T.class_of(StandardError)
37
+ const :message, String
38
+ const :backtrace, T.nilable(T::Array[String]), default: nil
39
+ const :additional_data, T::Hash[Symbol, T.untyped], default: {}
40
+
41
+ # Convert the log entry to a hash for serialization
42
+ sig { override.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
43
+ def serialize(strict = true)
44
+ hash = serialize_common(strict)
45
+ merge_additional_data_fields(hash)
46
+
47
+ # Add exception-specific fields
48
+ hash[LOG_KEYS.fetch(:err_class)] = err_class.name
49
+ hash[LOG_KEYS.fetch(:message)] = message
50
+ if backtrace.is_a?(Array) && backtrace&.any?
51
+ hash[LOG_KEYS.fetch(:backtrace)] = backtrace&.first(10)
52
+ end
53
+
54
+ hash
55
+ end
56
+
57
+ # Create an Error log from a Ruby StandardError
58
+ sig {
59
+ params(
60
+ source: Source,
61
+ ex: StandardError,
62
+ additional_data: T::Hash[Symbol, T.untyped]
63
+ ).returns(Log::Error)
64
+ }
65
+ def self.from_exception(source, ex, additional_data = {})
66
+ new(
67
+ source: source,
68
+ message: ex.message,
69
+ err_class: ex.class,
70
+ backtrace: ex.backtrace,
71
+ additional_data: additional_data
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,151 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "interfaces/common_fields"
5
+ require_relative "interfaces/additional_data_field"
6
+ require_relative "shared/serialize_common"
7
+ require_relative "shared/merge_additional_data_fields"
8
+ require_relative "../enums/source"
9
+ require_relative "../enums/event"
10
+ require_relative "../enums/level"
11
+ require_relative "../log_keys"
12
+
13
+ module LogStruct
14
+ module Log
15
+ # GoodJob log entry for structured logging
16
+ #
17
+ # GoodJob is a PostgreSQL-based ActiveJob backend that provides reliable,
18
+ # scalable job processing for Rails applications. This log class captures
19
+ # GoodJob-specific events including job execution, database operations,
20
+ # error handling, and performance metrics.
21
+ #
22
+ # ## Key Features Logged:
23
+ # - Job execution lifecycle (enqueue, start, finish, retry)
24
+ # - Database-backed job persistence events
25
+ # - Error handling and retry logic
26
+ # - Job batching and bulk operations
27
+ # - Performance metrics and timing data
28
+ # - Thread and process information
29
+ #
30
+ # ## Usage Examples:
31
+ #
32
+ # ```ruby
33
+ # # Job execution logging
34
+ # LogStruct::Log::GoodJob.new(
35
+ # event: Event::Start,
36
+ # job_id: "job_123",
37
+ # job_class: "UserNotificationJob",
38
+ # queue_name: "default",
39
+ # execution_time: 1.5
40
+ # )
41
+ #
42
+ # # Error logging
43
+ # LogStruct::Log::GoodJob.new(
44
+ # event: Event::Error,
45
+ # job_id: "job_123",
46
+ # error_class: "StandardError",
47
+ # error_message: "Connection failed"
48
+ # )
49
+ # ```
50
+ class GoodJob < T::Struct
51
+ extend T::Sig
52
+
53
+ include Interfaces::CommonFields
54
+ include Interfaces::AdditionalDataField
55
+ include SerializeCommon
56
+ include MergeAdditionalDataFields
57
+
58
+ # Valid event types for GoodJob operations
59
+ GoodJobEvent = T.type_alias {
60
+ T.any(
61
+ Event::Log, # General logging
62
+ Event::Enqueue, # Job queued
63
+ Event::Start, # Job execution started
64
+ Event::Finish, # Job completed successfully
65
+ Event::Error, # Job failed with error
66
+ Event::Schedule # Job scheduled for future execution
67
+ )
68
+ }
69
+
70
+ # Common fields
71
+ const :source, Source::Job, default: T.let(Source::Job, Source::Job)
72
+ const :event, GoodJobEvent
73
+ const :timestamp, Time, factory: -> { Time.now }
74
+ const :level, Level, default: T.let(Level::Info, Level)
75
+
76
+ # Job identification fields
77
+ const :job_id, T.nilable(String), default: nil
78
+ const :job_class, T.nilable(String), default: nil
79
+ const :queue_name, T.nilable(String), default: nil
80
+ const :batch_id, T.nilable(String), default: nil
81
+ const :job_label, T.nilable(String), default: nil
82
+
83
+ # Job execution context
84
+ const :arguments, T.nilable(T::Array[T.untyped]), default: nil
85
+ const :executions, T.nilable(Integer), default: nil
86
+ const :exception_executions, T.nilable(Integer), default: nil
87
+ const :execution_time, T.nilable(Float), default: nil
88
+ const :scheduled_at, T.nilable(Time), default: nil
89
+
90
+ # Error information
91
+ const :error_class, T.nilable(String), default: nil
92
+ const :error_message, T.nilable(String), default: nil
93
+ const :error_backtrace, T.nilable(T::Array[String]), default: nil
94
+
95
+ # GoodJob-specific metadata
96
+ const :process_id, T.nilable(Integer), default: nil
97
+ const :thread_id, T.nilable(String), default: nil
98
+ const :priority, T.nilable(Integer), default: nil
99
+ const :cron_key, T.nilable(String), default: nil
100
+ const :database_connection_name, T.nilable(String), default: nil
101
+
102
+ # Performance and metrics
103
+ const :wait_time, T.nilable(Float), default: nil
104
+ const :run_time, T.nilable(Float), default: nil
105
+ const :finished_at, T.nilable(Time), default: nil
106
+
107
+ # Additional contextual data
108
+ const :additional_data, T::Hash[Symbol, T.untyped], default: {}
109
+
110
+ # Convert the log entry to a hash for serialization
111
+ sig { override.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
112
+ def serialize(strict = true)
113
+ hash = serialize_common(strict)
114
+ merge_additional_data_fields(hash)
115
+
116
+ # Add job identification fields
117
+ hash[LOG_KEYS.fetch(:job_id)] = job_id if job_id
118
+ hash[LOG_KEYS.fetch(:job_class)] = job_class if job_class
119
+ hash[LOG_KEYS.fetch(:queue_name)] = queue_name if queue_name
120
+ hash[:batch_id] = batch_id if batch_id
121
+ hash[:job_label] = job_label if job_label
122
+
123
+ # Add execution context
124
+ hash[LOG_KEYS.fetch(:arguments)] = arguments if arguments
125
+ hash[:executions] = executions if executions
126
+ hash[:exception_executions] = exception_executions if exception_executions
127
+ hash[:execution_time] = execution_time if execution_time
128
+ hash[:scheduled_at] = scheduled_at&.iso8601 if scheduled_at
129
+
130
+ # Add error information
131
+ hash[LOG_KEYS.fetch(:err_class)] = error_class if error_class
132
+ hash[:error_message] = error_message if error_message
133
+ hash[LOG_KEYS.fetch(:backtrace)] = error_backtrace if error_backtrace
134
+
135
+ # Add GoodJob-specific metadata
136
+ hash[LOG_KEYS.fetch(:process_id)] = process_id if process_id
137
+ hash[LOG_KEYS.fetch(:thread_id)] = thread_id if thread_id
138
+ hash[:priority] = priority if priority
139
+ hash[:cron_key] = cron_key if cron_key
140
+ hash[:database_connection_name] = database_connection_name if database_connection_name
141
+
142
+ # Add performance metrics
143
+ hash[:wait_time] = wait_time if wait_time
144
+ hash[:run_time] = run_time if run_time
145
+ hash[:finished_at] = finished_at&.iso8601 if finished_at
146
+
147
+ hash
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LogStruct
5
+ module Log
6
+ module Interfaces
7
+ # Common interface for logs that include an additional_data field
8
+ module AdditionalDataField
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ interface!
13
+
14
+ # Additional data field for extra context
15
+ sig { abstract.returns(T::Hash[Symbol, T.untyped]) }
16
+ def additional_data; end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../../enums/source"
5
+ require_relative "../../enums/event"
6
+ require_relative "../../enums/level"
7
+
8
+ module LogStruct
9
+ module Log
10
+ module Interfaces
11
+ # Common interface that all log entry types must implement
12
+ module CommonFields
13
+ extend T::Sig
14
+ extend T::Helpers
15
+
16
+ interface!
17
+
18
+ # The source of the log entry (JSON property: src)
19
+ sig { abstract.returns(Source) }
20
+ def source; end
21
+
22
+ # The event type of the log entry (JSON property: evt)
23
+ sig { abstract.returns(Event) }
24
+ def event; end
25
+
26
+ # The log level (JSON property: lvl)
27
+ sig { abstract.returns(Level) }
28
+ def level; end
29
+
30
+ # The timestamp of the log entry (JSON property: ts)
31
+ sig { abstract.returns(Time) }
32
+ def timestamp; end
33
+
34
+ # All logs must define a custom serialize method
35
+ # If the class is a T::Struct that responds to serialize then we can be sure
36
+ # we're getting symbols as keys and don't need to call #serialize.deep_symbolize_keys
37
+ sig { abstract.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
38
+ def serialize(strict = true); end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LogStruct
5
+ module Log
6
+ module Interfaces
7
+ # Common interface for logs that include a message field
8
+ module MessageField
9
+ extend T::Sig
10
+ extend T::Helpers
11
+
12
+ interface!
13
+
14
+ # Message field
15
+ sig { abstract.returns(T.nilable(String)) }
16
+ def message; end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LogStruct
5
+ module Log
6
+ module Interfaces
7
+ # Common interface for request-related fields
8
+ # Used by both Request and Security logs
9
+ module RequestFields
10
+ extend T::Sig
11
+ extend T::Helpers
12
+
13
+ interface!
14
+
15
+ # Common request fields
16
+ sig { abstract.returns(T.nilable(String)) }
17
+ def path; end
18
+
19
+ sig { abstract.returns(T.nilable(String)) }
20
+ def http_method; end
21
+
22
+ sig { abstract.returns(T.nilable(String)) }
23
+ def source_ip; end
24
+
25
+ sig { abstract.returns(T.nilable(String)) }
26
+ def user_agent; end
27
+
28
+ sig { abstract.returns(T.nilable(String)) }
29
+ def referer; end
30
+
31
+ sig { abstract.returns(T.nilable(String)) }
32
+ def request_id; end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "interfaces/common_fields"
5
+ require_relative "interfaces/additional_data_field"
6
+ require_relative "shared/serialize_common"
7
+ require_relative "shared/merge_additional_data_fields"
8
+ require_relative "../enums/source"
9
+ require_relative "../enums/event"
10
+ require_relative "../enums/level"
11
+ require_relative "../log_keys"
12
+
13
+ module LogStruct
14
+ module Log
15
+ # Plain log entry for structured logging
16
+ class Plain < T::Struct
17
+ extend T::Sig
18
+
19
+ include Interfaces::CommonFields
20
+ include Interfaces::AdditionalDataField
21
+ include SerializeCommon
22
+ include MergeAdditionalDataFields
23
+
24
+ PlainEvent = T.type_alias {
25
+ Event::Log
26
+ }
27
+
28
+ # Common fields
29
+ const :source, Source, default: T.let(Source::App, Source)
30
+ const :event, PlainEvent, default: T.let(Event::Log, PlainEvent)
31
+ const :level, Level, default: T.let(Level::Info, Level)
32
+ const :timestamp, Time, factory: -> { Time.now }
33
+
34
+ # Plain log messages can be any type (String, Number, Array, Hash, etc.)
35
+ # Developers might do something like Rails.logger.info(123) or Rails.logger.info(@variable)
36
+ # when debugging, or gems might send all kinds of random stuff to the logger.
37
+ # We don't want to crash with a type error in any of these cases.
38
+ const :message, T.untyped # rubocop:disable Sorbet/ForbidUntypedStructProps
39
+
40
+ # Allow people to submit additional data
41
+ const :additional_data, T::Hash[Symbol, T.untyped], default: {}
42
+
43
+ # Convert the log entry to a hash for serialization
44
+ sig { override.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
45
+ def serialize(strict = true)
46
+ hash = serialize_common(strict)
47
+ merge_additional_data_fields(hash)
48
+ hash[LOG_KEYS.fetch(:message)] = message
49
+ hash
50
+ end
51
+ end
52
+ end
53
+ end