logstruct 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +23 -3
- data/lib/log_struct/boot_buffer.rb +28 -0
- data/lib/log_struct/builders/active_job.rb +84 -0
- data/lib/log_struct/concerns/configuration.rb +178 -15
- data/lib/log_struct/concerns/error_handling.rb +3 -7
- data/lib/log_struct/config_struct/filters.rb +18 -0
- data/lib/log_struct/config_struct/integrations.rb +8 -12
- data/lib/log_struct/configuration.rb +13 -0
- data/lib/log_struct/enums/event.rb +13 -0
- data/lib/log_struct/enums/log_field.rb +154 -0
- data/lib/log_struct/enums/source.rb +4 -1
- data/lib/log_struct/formatter.rb +29 -17
- data/lib/log_struct/integrations/action_mailer/error_handling.rb +3 -11
- data/lib/log_struct/integrations/action_mailer/event_logging.rb +22 -12
- data/lib/log_struct/integrations/active_job/log_subscriber.rb +52 -48
- data/lib/log_struct/integrations/active_model_serializers.rb +8 -14
- data/lib/log_struct/integrations/active_record.rb +35 -5
- data/lib/log_struct/integrations/active_storage.rb +59 -20
- data/lib/log_struct/integrations/ahoy.rb +2 -1
- data/lib/log_struct/integrations/carrierwave.rb +13 -16
- data/lib/log_struct/integrations/dotenv.rb +278 -0
- data/lib/log_struct/integrations/good_job/log_subscriber.rb +86 -136
- data/lib/log_struct/integrations/good_job/logger.rb +8 -10
- data/lib/log_struct/integrations/good_job.rb +5 -7
- data/lib/log_struct/integrations/host_authorization.rb +25 -4
- data/lib/log_struct/integrations/lograge.rb +20 -14
- data/lib/log_struct/integrations/puma.rb +477 -0
- data/lib/log_struct/integrations/rack_error_handler/middleware.rb +11 -18
- data/lib/log_struct/integrations/shrine.rb +44 -19
- data/lib/log_struct/integrations/sorbet.rb +48 -0
- data/lib/log_struct/integrations.rb +21 -0
- data/lib/log_struct/log/action_mailer/delivered.rb +99 -0
- data/lib/log_struct/log/action_mailer/delivery.rb +99 -0
- data/lib/log_struct/log/action_mailer.rb +30 -45
- data/lib/log_struct/log/active_job/enqueue.rb +125 -0
- data/lib/log_struct/log/active_job/finish.rb +130 -0
- data/lib/log_struct/log/active_job/schedule.rb +125 -0
- data/lib/log_struct/log/active_job/start.rb +130 -0
- data/lib/log_struct/log/active_job.rb +41 -54
- data/lib/log_struct/log/active_model_serializers.rb +72 -33
- data/lib/log_struct/log/active_storage/delete.rb +87 -0
- data/lib/log_struct/log/active_storage/download.rb +103 -0
- data/lib/log_struct/log/active_storage/exist.rb +93 -0
- data/lib/log_struct/log/active_storage/metadata.rb +93 -0
- data/lib/log_struct/log/active_storage/stream.rb +93 -0
- data/lib/log_struct/log/active_storage/upload.rb +118 -0
- data/lib/log_struct/log/active_storage/url.rb +93 -0
- data/lib/log_struct/log/active_storage.rb +32 -68
- data/lib/log_struct/log/ahoy.rb +67 -33
- data/lib/log_struct/log/carrierwave/delete.rb +115 -0
- data/lib/log_struct/log/carrierwave/download.rb +131 -0
- data/lib/log_struct/log/carrierwave/upload.rb +141 -0
- data/lib/log_struct/log/carrierwave.rb +37 -72
- data/lib/log_struct/log/dotenv/load.rb +76 -0
- data/lib/log_struct/log/dotenv/restore.rb +76 -0
- data/lib/log_struct/log/dotenv/save.rb +76 -0
- data/lib/log_struct/log/dotenv/update.rb +76 -0
- data/lib/log_struct/log/dotenv.rb +12 -0
- data/lib/log_struct/log/error.rb +58 -47
- data/lib/log_struct/log/good_job/enqueue.rb +126 -0
- data/lib/log_struct/log/good_job/error.rb +151 -0
- data/lib/log_struct/log/good_job/finish.rb +136 -0
- data/lib/log_struct/log/good_job/log.rb +131 -0
- data/lib/log_struct/log/good_job/schedule.rb +136 -0
- data/lib/log_struct/log/good_job/start.rb +136 -0
- data/lib/log_struct/log/good_job.rb +40 -141
- data/lib/log_struct/log/interfaces/additional_data_field.rb +1 -17
- data/lib/log_struct/log/interfaces/common_fields.rb +1 -39
- data/lib/log_struct/log/interfaces/public_common_fields.rb +1 -28
- data/lib/log_struct/log/interfaces/request_fields.rb +1 -33
- data/lib/log_struct/log/plain.rb +59 -34
- data/lib/log_struct/log/puma/shutdown.rb +80 -0
- data/lib/log_struct/log/puma/start.rb +120 -0
- data/lib/log_struct/log/puma.rb +10 -0
- data/lib/log_struct/log/request.rb +132 -48
- data/lib/log_struct/log/security/blocked_host.rb +141 -0
- data/lib/log_struct/log/security/csrf_violation.rb +131 -0
- data/lib/log_struct/log/security/ip_spoof.rb +141 -0
- data/lib/log_struct/log/security.rb +40 -70
- data/lib/log_struct/log/shared/add_request_fields.rb +1 -26
- data/lib/log_struct/log/shared/merge_additional_data_fields.rb +1 -22
- data/lib/log_struct/log/shared/serialize_common.rb +1 -33
- data/lib/log_struct/log/shared/serialize_common_public.rb +9 -9
- data/lib/log_struct/log/shrine/delete.rb +85 -0
- data/lib/log_struct/log/shrine/download.rb +90 -0
- data/lib/log_struct/log/shrine/exist.rb +90 -0
- data/lib/log_struct/log/shrine/metadata.rb +90 -0
- data/lib/log_struct/log/shrine/upload.rb +105 -0
- data/lib/log_struct/log/shrine.rb +10 -67
- data/lib/log_struct/log/sidekiq.rb +65 -26
- data/lib/log_struct/log/sql.rb +113 -106
- data/lib/log_struct/log.rb +29 -36
- data/lib/log_struct/multi_error_reporter.rb +80 -22
- data/lib/log_struct/param_filters.rb +50 -7
- data/lib/log_struct/rails_boot_banner_silencer.rb +116 -0
- data/lib/log_struct/railtie.rb +67 -0
- data/lib/log_struct/semantic_logger/formatter.rb +4 -2
- data/lib/log_struct/semantic_logger/setup.rb +34 -18
- data/lib/log_struct/shared/interfaces/additional_data_field.rb +22 -0
- data/lib/log_struct/shared/interfaces/common_fields.rb +39 -0
- data/lib/log_struct/shared/interfaces/public_common_fields.rb +29 -0
- data/lib/log_struct/shared/interfaces/request_fields.rb +39 -0
- data/lib/log_struct/shared/shared/add_request_fields.rb +28 -0
- data/lib/log_struct/shared/shared/merge_additional_data_fields.rb +27 -0
- data/lib/log_struct/shared/shared/serialize_common.rb +58 -0
- data/lib/log_struct/version.rb +1 -1
- data/lib/log_struct.rb +36 -4
- data/logstruct.gemspec +2 -1
- metadata +78 -9
- data/lib/log_struct/log/interfaces/message_field.rb +0 -20
- data/lib/log_struct/log_keys.rb +0 -102
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require_relative "enums/error_reporter"
|
5
|
+
require_relative "handlers"
|
5
6
|
|
6
7
|
# Try to require all supported error reporting libraries
|
7
8
|
# Users may have multiple installed, so we should load all of them
|
@@ -19,33 +20,62 @@ module LogStruct
|
|
19
20
|
# but the operation should be allowed to continue (e.g. scrubbing log data.)
|
20
21
|
class MultiErrorReporter
|
21
22
|
# Class variable to store the selected reporter
|
22
|
-
|
23
|
+
class CallableReporterWrapper
|
24
|
+
extend T::Sig
|
25
|
+
|
26
|
+
sig { params(callable: T.untyped).void }
|
27
|
+
def initialize(callable)
|
28
|
+
@callable = callable
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { returns(T.untyped) }
|
32
|
+
attr_reader :callable
|
33
|
+
alias_method :original, :callable
|
34
|
+
|
35
|
+
sig { params(error: StandardError, context: T.nilable(T::Hash[Symbol, T.untyped]), source: Source).void }
|
36
|
+
def call(error, context, source)
|
37
|
+
case callable_arity
|
38
|
+
when 3
|
39
|
+
callable.call(error, context, source)
|
40
|
+
when 2
|
41
|
+
callable.call(error, context)
|
42
|
+
when 1
|
43
|
+
callable.call(error)
|
44
|
+
else
|
45
|
+
callable.call(error, context, source)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
sig { returns(Integer) }
|
52
|
+
def callable_arity
|
53
|
+
callable.respond_to?(:arity) ? callable.arity : -1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ReporterImpl = T.type_alias { T.any(ErrorReporter, CallableReporterWrapper) }
|
58
|
+
|
59
|
+
@reporter_impl = T.let(nil, T.nilable(ReporterImpl))
|
23
60
|
|
24
61
|
class << self
|
25
62
|
extend T::Sig
|
26
63
|
|
27
|
-
sig { returns(
|
64
|
+
sig { returns(ReporterImpl) }
|
28
65
|
def reporter
|
29
|
-
|
66
|
+
reporter_impl
|
30
67
|
end
|
31
68
|
|
32
69
|
# Set the reporter to use (user-friendly API that accepts symbols)
|
33
|
-
sig { params(reporter_type: T.any(ErrorReporter, Symbol)).returns(
|
70
|
+
sig { params(reporter_type: T.any(ErrorReporter, Symbol, Handlers::ErrorReporter)).returns(ReporterImpl) }
|
34
71
|
def reporter=(reporter_type)
|
35
|
-
@
|
72
|
+
@reporter_impl = case reporter_type
|
36
73
|
when ErrorReporter
|
37
74
|
reporter_type
|
38
75
|
when Symbol
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
when :rollbar then ErrorReporter::Rollbar
|
43
|
-
when :honeybadger then ErrorReporter::Honeybadger
|
44
|
-
when :rails_logger then ErrorReporter::RailsLogger
|
45
|
-
else
|
46
|
-
valid_types = ErrorReporter.values.map { |v| ":#{v.serialize}" }.join(", ")
|
47
|
-
raise ArgumentError, "Unknown reporter type: #{reporter_type}. Valid types are: #{valid_types}"
|
48
|
-
end
|
76
|
+
resolve_symbol_reporter(reporter_type)
|
77
|
+
else
|
78
|
+
wrap_callable_reporter(reporter_type)
|
49
79
|
end
|
50
80
|
end
|
51
81
|
|
@@ -69,7 +99,9 @@ module LogStruct
|
|
69
99
|
sig { params(error: StandardError, context: T::Hash[T.untyped, T.untyped]).void }
|
70
100
|
def report_error(error, context = {})
|
71
101
|
# Call the appropriate reporter method based on what's available
|
72
|
-
|
102
|
+
impl = reporter_impl
|
103
|
+
|
104
|
+
case impl
|
73
105
|
when ErrorReporter::Sentry
|
74
106
|
report_to_sentry(error, context)
|
75
107
|
when ErrorReporter::Bugsnag
|
@@ -78,13 +110,43 @@ module LogStruct
|
|
78
110
|
report_to_rollbar(error, context)
|
79
111
|
when ErrorReporter::Honeybadger
|
80
112
|
report_to_honeybadger(error, context)
|
81
|
-
|
113
|
+
when ErrorReporter::RailsLogger
|
82
114
|
fallback_logging(error, context)
|
115
|
+
when CallableReporterWrapper
|
116
|
+
impl.call(error, context, Source::Internal)
|
83
117
|
end
|
84
118
|
end
|
85
119
|
|
86
120
|
private
|
87
121
|
|
122
|
+
sig { returns(ReporterImpl) }
|
123
|
+
def reporter_impl
|
124
|
+
@reporter_impl ||= detect_reporter
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { params(symbol: Symbol).returns(ErrorReporter) }
|
128
|
+
def resolve_symbol_reporter(symbol)
|
129
|
+
case symbol
|
130
|
+
when :sentry then ErrorReporter::Sentry
|
131
|
+
when :bugsnag then ErrorReporter::Bugsnag
|
132
|
+
when :rollbar then ErrorReporter::Rollbar
|
133
|
+
when :honeybadger then ErrorReporter::Honeybadger
|
134
|
+
when :rails_logger then ErrorReporter::RailsLogger
|
135
|
+
else
|
136
|
+
valid_types = ErrorReporter.values.map { |v| ":#{v.serialize}" }.join(", ")
|
137
|
+
raise ArgumentError, "Unknown reporter type: #{symbol}. Valid types are: #{valid_types}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
sig { params(callable: T.untyped).returns(CallableReporterWrapper) }
|
142
|
+
def wrap_callable_reporter(callable)
|
143
|
+
unless callable.respond_to?(:call)
|
144
|
+
raise ArgumentError, "Reporter must respond to #call"
|
145
|
+
end
|
146
|
+
|
147
|
+
CallableReporterWrapper.new(callable)
|
148
|
+
end
|
149
|
+
|
88
150
|
# Report to Sentry
|
89
151
|
sig { params(error: StandardError, context: T::Hash[T.untyped, T.untyped]).void }
|
90
152
|
def report_to_sentry(error, context = {})
|
@@ -135,11 +197,7 @@ module LogStruct
|
|
135
197
|
return if error.nil?
|
136
198
|
|
137
199
|
# Create a proper error log entry
|
138
|
-
error_log = Log
|
139
|
-
Source::LogStruct,
|
140
|
-
error,
|
141
|
-
context
|
142
|
-
)
|
200
|
+
error_log = Log.from_exception(Source::Internal, error, context)
|
143
201
|
|
144
202
|
# Use LogStruct.error to properly log the error
|
145
203
|
LogStruct.error(error_log)
|
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
require "digest"
|
5
5
|
require_relative "hash_utils"
|
6
|
+
require_relative "config_struct/filters"
|
7
|
+
require_relative "enums/source"
|
6
8
|
|
7
9
|
module LogStruct
|
8
10
|
# This class contains methods for filtering sensitive data in logs
|
@@ -12,19 +14,30 @@ module LogStruct
|
|
12
14
|
extend T::Sig
|
13
15
|
|
14
16
|
# Check if a key should be filtered based on our defined sensitive keys
|
15
|
-
sig { params(key: T.
|
16
|
-
def should_filter_key?(key)
|
17
|
-
LogStruct.config.filters
|
17
|
+
sig { params(key: T.untyped, value: T.untyped).returns(T::Boolean) }
|
18
|
+
def should_filter_key?(key, value = nil)
|
19
|
+
filters = LogStruct.config.filters
|
20
|
+
normalized_key = key.to_s
|
21
|
+
normalized_symbol = normalized_key.downcase.to_sym
|
22
|
+
|
23
|
+
return true if filters.filter_keys.include?(normalized_symbol)
|
24
|
+
|
25
|
+
filters.filter_matchers.any? do |matcher|
|
26
|
+
matcher.matches?(normalized_key, value)
|
27
|
+
rescue => e
|
28
|
+
handle_filter_matcher_error(e, matcher, normalized_key)
|
29
|
+
false
|
30
|
+
end
|
18
31
|
end
|
19
32
|
|
20
33
|
# Check if a key should be hashed rather than completely filtered
|
21
|
-
sig { params(key: T.
|
34
|
+
sig { params(key: T.untyped).returns(T::Boolean) }
|
22
35
|
def should_include_string_hash?(key)
|
23
36
|
LogStruct.config.filters.filter_keys_with_hashes.include?(key.to_s.downcase.to_sym)
|
24
37
|
end
|
25
38
|
|
26
39
|
# Convert a value to a filtered summary hash (e.g. { _filtered: { class: "String", ... }})
|
27
|
-
sig { params(key: T.
|
40
|
+
sig { params(key: T.untyped, data: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
|
28
41
|
def summarize_json_attribute(key, data)
|
29
42
|
case data
|
30
43
|
when Hash
|
@@ -59,12 +72,18 @@ module LogStruct
|
|
59
72
|
return {_class: "Hash", _empty: true} if hash.empty?
|
60
73
|
|
61
74
|
# Don't include byte size if hash contains any filtered keys
|
62
|
-
has_sensitive_keys =
|
75
|
+
has_sensitive_keys = T.let(false, T::Boolean)
|
76
|
+
normalized_keys = []
|
77
|
+
|
78
|
+
hash.each do |key, value|
|
79
|
+
has_sensitive_keys ||= should_filter_key?(key, value)
|
80
|
+
normalized_keys << normalize_summary_key(key)
|
81
|
+
end
|
63
82
|
|
64
83
|
summary = {
|
65
84
|
_class: Hash,
|
66
85
|
_keys_count: hash.keys.size,
|
67
|
-
_keys:
|
86
|
+
_keys: normalized_keys.take(10)
|
68
87
|
}
|
69
88
|
|
70
89
|
# Only add byte size if no sensitive keys are present
|
@@ -84,6 +103,30 @@ module LogStruct
|
|
84
103
|
_bytes: array.to_json.bytesize
|
85
104
|
}
|
86
105
|
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
sig { params(key: T.any(String, Symbol, Integer, T.untyped)).returns(T.any(Symbol, String)) }
|
110
|
+
def normalize_summary_key(key)
|
111
|
+
if key.is_a?(Symbol)
|
112
|
+
key
|
113
|
+
elsif key.respond_to?(:to_sym)
|
114
|
+
key.to_sym
|
115
|
+
else
|
116
|
+
key.to_s
|
117
|
+
end
|
118
|
+
rescue
|
119
|
+
key.to_s
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { params(error: StandardError, matcher: ConfigStruct::FilterMatcher, key: String).void }
|
123
|
+
def handle_filter_matcher_error(error, matcher, key)
|
124
|
+
context = {
|
125
|
+
matcher: matcher.label,
|
126
|
+
key: key
|
127
|
+
}
|
128
|
+
LogStruct.handle_exception(error, source: Source::Internal, context: context)
|
129
|
+
end
|
87
130
|
end
|
88
131
|
end
|
89
132
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
6
|
+
module LogStruct
|
7
|
+
module RailsBootBannerSilencer
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
@installed = T.let(false, T::Boolean)
|
11
|
+
|
12
|
+
sig { void }
|
13
|
+
def self.install!
|
14
|
+
return if @installed
|
15
|
+
@installed = true
|
16
|
+
|
17
|
+
return unless ARGV.include?("server")
|
18
|
+
patch!
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { returns(T::Boolean) }
|
22
|
+
def self.patch!
|
23
|
+
begin
|
24
|
+
require "rails/command"
|
25
|
+
require "rails/commands/server/server_command"
|
26
|
+
rescue LoadError
|
27
|
+
# Best-effort – if Rails isn't available yet we'll try again later
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
server_command = T.let(nil, T.untyped)
|
32
|
+
# rubocop:disable Sorbet/ConstantsFromStrings
|
33
|
+
begin
|
34
|
+
server_command = ::Object.const_get("Rails::Command::ServerCommand")
|
35
|
+
rescue NameError
|
36
|
+
server_command = nil
|
37
|
+
end
|
38
|
+
# rubocop:enable Sorbet/ConstantsFromStrings
|
39
|
+
return false unless server_command
|
40
|
+
|
41
|
+
patch_server_command(server_command)
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(server_command: T.untyped).void }
|
46
|
+
def self.patch_server_command(server_command)
|
47
|
+
return if server_command <= ServerCommandSilencer
|
48
|
+
|
49
|
+
server_command.prepend(ServerCommandSilencer)
|
50
|
+
end
|
51
|
+
|
52
|
+
module ServerCommandSilencer
|
53
|
+
extend T::Sig
|
54
|
+
|
55
|
+
sig { params(args: T.untyped, block: T.nilable(T.proc.returns(T.untyped))).returns(T.untyped) }
|
56
|
+
def perform(*args, &block)
|
57
|
+
::LogStruct.server_mode = true
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
sig { params(server: T.untyped, url: T.nilable(String)).void }
|
62
|
+
def print_boot_information(server, url)
|
63
|
+
::LogStruct.server_mode = true
|
64
|
+
consume_boot_banner(server, url)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
sig { params(server: T.untyped, url: T.nilable(String)).void }
|
70
|
+
def consume_boot_banner(server, url)
|
71
|
+
return unless defined?(::LogStruct::Integrations::Puma)
|
72
|
+
|
73
|
+
begin
|
74
|
+
::LogStruct::Integrations::Puma.emit_boot_if_needed!
|
75
|
+
rescue => e
|
76
|
+
::LogStruct::Integrations::Puma.handle_integration_error(e)
|
77
|
+
end
|
78
|
+
|
79
|
+
begin
|
80
|
+
model = ::ActiveSupport::Inflector.demodulize(server)
|
81
|
+
rescue
|
82
|
+
model = "Puma"
|
83
|
+
end
|
84
|
+
|
85
|
+
lines = [
|
86
|
+
"=> Booting #{model}",
|
87
|
+
build_rails_banner_line(url),
|
88
|
+
"=> Run `#{lookup_executable} --help` for more startup options"
|
89
|
+
]
|
90
|
+
|
91
|
+
lines.each do |line|
|
92
|
+
::LogStruct::Integrations::Puma.process_line(line)
|
93
|
+
rescue => e
|
94
|
+
::LogStruct::Integrations::Puma.handle_integration_error(e)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { params(url: T.nilable(String)).returns(String) }
|
99
|
+
def build_rails_banner_line(url)
|
100
|
+
suffix = url ? " #{url}" : ""
|
101
|
+
"=> Rails #{::Rails.version} application starting in #{::Rails.env}#{suffix}"
|
102
|
+
rescue
|
103
|
+
"=> Rails application starting"
|
104
|
+
end
|
105
|
+
|
106
|
+
sig { returns(String) }
|
107
|
+
def lookup_executable
|
108
|
+
return "rails" unless T.unsafe(self).respond_to?(:executable, true)
|
109
|
+
|
110
|
+
T.cast(T.unsafe(self).send(:executable), String)
|
111
|
+
rescue
|
112
|
+
"rails"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/log_struct/railtie.rb
CHANGED
@@ -5,14 +5,37 @@ require "rails"
|
|
5
5
|
require "semantic_logger"
|
6
6
|
require_relative "formatter"
|
7
7
|
require_relative "semantic_logger/setup"
|
8
|
+
require_relative "integrations"
|
8
9
|
|
9
10
|
module LogStruct
|
10
11
|
# Railtie to integrate with Rails
|
11
12
|
class Railtie < ::Rails::Railtie
|
13
|
+
# Ensure test hosts are allowed early enough for middleware build
|
14
|
+
initializer "logstruct.allow_test_hosts", before: :build_middleware_stack do |app|
|
15
|
+
if ::Rails.env.test? && app.config.respond_to?(:hosts)
|
16
|
+
begin
|
17
|
+
app.config.hosts << /.*\z/
|
18
|
+
rescue
|
19
|
+
# best-effort
|
20
|
+
end
|
21
|
+
begin
|
22
|
+
app.config.middleware.delete(::ActionDispatch::HostAuthorization)
|
23
|
+
rescue
|
24
|
+
# best-effort
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# After ActionDispatch is configured, remove HostAuthorization in test to prevent 403s
|
30
|
+
# (No late deletion needed; handled above before middleware stack is built)
|
31
|
+
|
12
32
|
# Configure early, right after logger initialization
|
13
33
|
initializer "logstruct.configure_logger", after: :initialize_logger do |app|
|
14
34
|
next unless LogStruct.enabled?
|
15
35
|
|
36
|
+
# Apply TaggedLogging monkey patch only when enabled
|
37
|
+
require_relative "monkey_patches/active_support/tagged_logging/formatter"
|
38
|
+
|
16
39
|
# Use SemanticLogger for powerful logging features
|
17
40
|
LogStruct::SemanticLogger::Setup.configure_semantic_logger(app)
|
18
41
|
end
|
@@ -26,6 +49,50 @@ module LogStruct
|
|
26
49
|
|
27
50
|
# Set up all integrations
|
28
51
|
Integrations.setup_integrations
|
52
|
+
|
53
|
+
# Note: Host allowances are managed by the test app itself.
|
29
54
|
end
|
55
|
+
|
56
|
+
# Emit Puma lifecycle logs when running `rails server`
|
57
|
+
initializer "logstruct.puma_lifecycle", after: "logstruct.configure_logger" do
|
58
|
+
is_server = ::LogStruct.server_mode?
|
59
|
+
next unless is_server
|
60
|
+
begin
|
61
|
+
require "log_struct/log/puma"
|
62
|
+
port = T.let(nil, T.nilable(String))
|
63
|
+
ARGV.each_with_index do |arg, idx|
|
64
|
+
if arg == "-p" || arg == "--port"
|
65
|
+
port = ARGV[idx + 1]
|
66
|
+
break
|
67
|
+
elsif arg.start_with?("--port=")
|
68
|
+
port = arg.split("=", 2)[1]
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
started = LogStruct::Log::Puma::Start.new(
|
73
|
+
mode: "single",
|
74
|
+
environment: (defined?(::Rails) && ::Rails.respond_to?(:env)) ? ::Rails.env : nil,
|
75
|
+
process_id: Process.pid,
|
76
|
+
listening_addresses: port ? ["tcp://127.0.0.1:#{port}"] : nil
|
77
|
+
)
|
78
|
+
begin
|
79
|
+
warn("[logstruct] puma lifecycle init")
|
80
|
+
rescue
|
81
|
+
end
|
82
|
+
LogStruct.info(started)
|
83
|
+
|
84
|
+
at_exit do
|
85
|
+
shutdown = LogStruct::Log::Puma::Shutdown.new(
|
86
|
+
process_id: Process.pid
|
87
|
+
)
|
88
|
+
LogStruct.info(shutdown)
|
89
|
+
end
|
90
|
+
rescue
|
91
|
+
# best-effort
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Delegate integration initializers to Integrations module
|
96
|
+
LogStruct::Integrations.setup_initializers(self)
|
30
97
|
end
|
31
98
|
end
|
@@ -62,7 +62,7 @@ module LogStruct
|
|
62
62
|
sig { params(log: ::SemanticLogger::Log, logger: T.untyped).returns(String) }
|
63
63
|
def call(log, logger)
|
64
64
|
# Handle LogStruct types specially - they get wrapped in payload hash by SemanticLogger
|
65
|
-
if log.payload.is_a?(Hash) && log.payload[:payload].is_a?(LogStruct::Log::Interfaces::CommonFields)
|
65
|
+
json = if log.payload.is_a?(Hash) && log.payload[:payload].is_a?(LogStruct::Log::Interfaces::CommonFields)
|
66
66
|
# Use our formatter to process LogStruct types
|
67
67
|
@logstruct_formatter.call(log.level, log.time, log.name, log.payload[:payload])
|
68
68
|
elsif log.payload.is_a?(LogStruct::Log::Interfaces::CommonFields)
|
@@ -77,12 +77,14 @@ module LogStruct
|
|
77
77
|
else
|
78
78
|
# For plain messages, create a Plain log entry
|
79
79
|
message_data = log.payload || log.message
|
80
|
-
plain_log = LogStruct::Log::Plain.new(
|
80
|
+
plain_log = ::LogStruct::Log::Plain.new(
|
81
81
|
message: message_data,
|
82
82
|
timestamp: log.time
|
83
83
|
)
|
84
84
|
@logstruct_formatter.call(log.level, log.time, log.name, plain_log)
|
85
85
|
end
|
86
|
+
# SemanticLogger appenders typically add their own newline. Avoid double newlines by stripping ours.
|
87
|
+
json.end_with?("\n") ? json.chomp : json
|
86
88
|
end
|
87
89
|
|
88
90
|
private
|
@@ -106,7 +106,7 @@ module LogStruct
|
|
106
106
|
elsif Rails.env.production?
|
107
107
|
:info
|
108
108
|
elsif Rails.env.test?
|
109
|
-
:
|
109
|
+
:debug
|
110
110
|
else
|
111
111
|
:debug
|
112
112
|
end
|
@@ -119,17 +119,32 @@ module LogStruct
|
|
119
119
|
# Determine output destination
|
120
120
|
io = determine_output(app)
|
121
121
|
|
122
|
-
if Rails.env.development?
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
122
|
+
if Rails.env.development?
|
123
|
+
if config.prefer_json_in_development
|
124
|
+
# Default to production-style JSON in development when enabled
|
125
|
+
::SemanticLogger.add_appender(
|
126
|
+
io: io,
|
127
|
+
formatter: LogStruct::SemanticLogger::Formatter.new,
|
128
|
+
filter: determine_filter
|
129
|
+
)
|
130
|
+
elsif config.enable_color_output
|
131
|
+
# Opt-in colorful human formatter in development
|
132
|
+
::SemanticLogger.add_appender(
|
133
|
+
io: io,
|
134
|
+
formatter: LogStruct::SemanticLogger::ColorFormatter.new(
|
135
|
+
color_map: config.color_map
|
136
|
+
),
|
137
|
+
filter: determine_filter
|
138
|
+
)
|
139
|
+
else
|
140
|
+
::SemanticLogger.add_appender(
|
141
|
+
io: io,
|
142
|
+
formatter: LogStruct::SemanticLogger::Formatter.new,
|
143
|
+
filter: determine_filter
|
144
|
+
)
|
145
|
+
end
|
131
146
|
else
|
132
|
-
# Use our custom JSON formatter
|
147
|
+
# Use our custom JSON formatter in non-development environments
|
133
148
|
::SemanticLogger.add_appender(
|
134
149
|
io: io,
|
135
150
|
formatter: LogStruct::SemanticLogger::Formatter.new,
|
@@ -147,15 +162,16 @@ module LogStruct
|
|
147
162
|
end
|
148
163
|
end
|
149
164
|
|
150
|
-
sig { params(app: T.untyped).returns(T.
|
165
|
+
sig { params(app: T.untyped).returns(T.untyped) }
|
151
166
|
def self.determine_output(app)
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
167
|
+
# Always honor explicit STDOUT directive, even in test, or when LogStruct is enabled via env
|
168
|
+
return $stdout if ENV["RAILS_LOG_TO_STDOUT"].present? || ENV["LOGSTRUCT_ENABLED"].to_s.strip.downcase == "true"
|
169
|
+
|
170
|
+
if Rails.env.test?
|
171
|
+
# Default to StringIO to keep test output clean unless STDOUT is requested
|
156
172
|
StringIO.new
|
157
173
|
else
|
158
|
-
#
|
174
|
+
# Default to STDOUT for app logs
|
159
175
|
$stdout
|
160
176
|
end
|
161
177
|
end
|
@@ -164,7 +180,7 @@ module LogStruct
|
|
164
180
|
def self.determine_filter
|
165
181
|
# Filter out noisy loggers if configured
|
166
182
|
config = LogStruct.config
|
167
|
-
return nil unless config.
|
183
|
+
return nil unless config.filter_noisy_loggers
|
168
184
|
|
169
185
|
# Common noisy loggers to filter
|
170
186
|
/\A(ActionView|ActionController::RoutingError|ActiveRecord::SchemaMigration)/
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Moved from lib/log_struct/log/interfaces/additional_data_field.rb
|
5
|
+
module LogStruct
|
6
|
+
module Log
|
7
|
+
module Interfaces
|
8
|
+
module AdditionalDataField
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
|
12
|
+
interface!
|
13
|
+
|
14
|
+
requires_ancestor { T::Struct }
|
15
|
+
|
16
|
+
sig { abstract.returns(T.nilable(T::Hash[T.any(String, Symbol), T.untyped])) }
|
17
|
+
def additional_data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
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
|
+
module CommonFields
|
12
|
+
extend T::Sig
|
13
|
+
extend T::Helpers
|
14
|
+
|
15
|
+
interface!
|
16
|
+
|
17
|
+
sig { abstract.returns(Source) }
|
18
|
+
def source
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { abstract.returns(Event) }
|
22
|
+
def event
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { abstract.returns(Level) }
|
26
|
+
def level
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { abstract.returns(Time) }
|
30
|
+
def timestamp
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { abstract.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
|
34
|
+
def serialize(strict = true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../../enums/level"
|
5
|
+
|
6
|
+
module LogStruct
|
7
|
+
module Log
|
8
|
+
module Interfaces
|
9
|
+
module PublicCommonFields
|
10
|
+
extend T::Sig
|
11
|
+
extend T::Helpers
|
12
|
+
|
13
|
+
interface!
|
14
|
+
|
15
|
+
sig { abstract.returns(Level) }
|
16
|
+
def level
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { abstract.returns(Time) }
|
20
|
+
def timestamp
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { abstract.params(strict: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
|
24
|
+
def serialize(strict = true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module LogStruct
|
5
|
+
module Log
|
6
|
+
module Interfaces
|
7
|
+
module RequestFields
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
|
11
|
+
interface!
|
12
|
+
|
13
|
+
sig { abstract.returns(T.nilable(String)) }
|
14
|
+
def path
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { abstract.returns(T.nilable(String)) }
|
18
|
+
def http_method
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { abstract.returns(T.nilable(String)) }
|
22
|
+
def source_ip
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { abstract.returns(T.nilable(String)) }
|
26
|
+
def user_agent
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { abstract.returns(T.nilable(String)) }
|
30
|
+
def referer
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { abstract.returns(T.nilable(String)) }
|
34
|
+
def request_id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|