logstruct 0.0.1 → 0.0.2.pre.rc1
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 +26 -2
- data/LICENSE +21 -0
- data/README.md +67 -0
- data/lib/log_struct/concerns/configuration.rb +93 -0
- data/lib/log_struct/concerns/error_handling.rb +94 -0
- data/lib/log_struct/concerns/logging.rb +45 -0
- data/lib/log_struct/config_struct/error_handling_modes.rb +25 -0
- data/lib/log_struct/config_struct/filters.rb +80 -0
- data/lib/log_struct/config_struct/integrations.rb +89 -0
- data/lib/log_struct/configuration.rb +59 -0
- data/lib/log_struct/enums/error_handling_mode.rb +22 -0
- data/lib/log_struct/enums/error_reporter.rb +14 -0
- data/lib/log_struct/enums/event.rb +48 -0
- data/lib/log_struct/enums/level.rb +66 -0
- data/lib/log_struct/enums/source.rb +26 -0
- data/lib/log_struct/enums.rb +9 -0
- data/lib/log_struct/formatter.rb +224 -0
- data/lib/log_struct/handlers.rb +27 -0
- data/lib/log_struct/hash_utils.rb +21 -0
- data/lib/log_struct/integrations/action_mailer/callbacks.rb +100 -0
- data/lib/log_struct/integrations/action_mailer/error_handling.rb +173 -0
- data/lib/log_struct/integrations/action_mailer/event_logging.rb +90 -0
- data/lib/log_struct/integrations/action_mailer/metadata_collection.rb +78 -0
- data/lib/log_struct/integrations/action_mailer.rb +50 -0
- data/lib/log_struct/integrations/active_job/log_subscriber.rb +104 -0
- data/lib/log_struct/integrations/active_job.rb +38 -0
- data/lib/log_struct/integrations/active_record.rb +258 -0
- data/lib/log_struct/integrations/active_storage.rb +94 -0
- data/lib/log_struct/integrations/carrierwave.rb +111 -0
- data/lib/log_struct/integrations/good_job/log_subscriber.rb +228 -0
- data/lib/log_struct/integrations/good_job/logger.rb +73 -0
- data/lib/log_struct/integrations/good_job.rb +111 -0
- data/lib/log_struct/integrations/host_authorization.rb +81 -0
- data/lib/log_struct/integrations/integration_interface.rb +21 -0
- data/lib/log_struct/integrations/lograge.rb +114 -0
- data/lib/log_struct/integrations/rack.rb +31 -0
- data/lib/log_struct/integrations/rack_error_handler/middleware.rb +146 -0
- data/lib/log_struct/integrations/rack_error_handler.rb +32 -0
- data/lib/log_struct/integrations/shrine.rb +75 -0
- data/lib/log_struct/integrations/sidekiq/logger.rb +43 -0
- data/lib/log_struct/integrations/sidekiq.rb +39 -0
- data/lib/log_struct/integrations/sorbet.rb +49 -0
- data/lib/log_struct/integrations.rb +41 -0
- data/lib/log_struct/log/action_mailer.rb +55 -0
- data/lib/log_struct/log/active_job.rb +64 -0
- data/lib/log_struct/log/active_storage.rb +78 -0
- data/lib/log_struct/log/carrierwave.rb +82 -0
- data/lib/log_struct/log/error.rb +76 -0
- data/lib/log_struct/log/good_job.rb +151 -0
- data/lib/log_struct/log/interfaces/additional_data_field.rb +20 -0
- data/lib/log_struct/log/interfaces/common_fields.rb +42 -0
- data/lib/log_struct/log/interfaces/message_field.rb +20 -0
- data/lib/log_struct/log/interfaces/request_fields.rb +36 -0
- data/lib/log_struct/log/plain.rb +53 -0
- data/lib/log_struct/log/request.rb +76 -0
- data/lib/log_struct/log/security.rb +80 -0
- data/lib/log_struct/log/shared/add_request_fields.rb +29 -0
- data/lib/log_struct/log/shared/merge_additional_data_fields.rb +28 -0
- data/lib/log_struct/log/shared/serialize_common.rb +36 -0
- data/lib/log_struct/log/shrine.rb +70 -0
- data/lib/log_struct/log/sidekiq.rb +50 -0
- data/lib/log_struct/log/sql.rb +126 -0
- data/lib/log_struct/log.rb +43 -0
- data/lib/log_struct/log_keys.rb +102 -0
- data/lib/log_struct/monkey_patches/active_support/tagged_logging/formatter.rb +36 -0
- data/lib/log_struct/multi_error_reporter.rb +149 -0
- data/lib/log_struct/param_filters.rb +89 -0
- data/lib/log_struct/railtie.rb +31 -0
- data/lib/log_struct/semantic_logger/color_formatter.rb +209 -0
- data/lib/log_struct/semantic_logger/formatter.rb +94 -0
- data/lib/log_struct/semantic_logger/logger.rb +129 -0
- data/lib/log_struct/semantic_logger/setup.rb +219 -0
- data/lib/log_struct/sorbet/serialize_symbol_keys.rb +23 -0
- data/lib/log_struct/sorbet.rb +13 -0
- data/lib/log_struct/string_scrubber.rb +84 -0
- data/lib/log_struct/version.rb +6 -0
- data/lib/log_struct.rb +37 -0
- data/lib/logstruct.rb +2 -6
- data/logstruct.gemspec +52 -0
- metadata +221 -5
- 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
|