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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -1
  3. data/README.md +23 -3
  4. data/lib/log_struct/boot_buffer.rb +28 -0
  5. data/lib/log_struct/builders/active_job.rb +84 -0
  6. data/lib/log_struct/concerns/configuration.rb +178 -15
  7. data/lib/log_struct/concerns/error_handling.rb +3 -7
  8. data/lib/log_struct/config_struct/filters.rb +18 -0
  9. data/lib/log_struct/config_struct/integrations.rb +8 -12
  10. data/lib/log_struct/configuration.rb +13 -0
  11. data/lib/log_struct/enums/event.rb +13 -0
  12. data/lib/log_struct/enums/log_field.rb +154 -0
  13. data/lib/log_struct/enums/source.rb +4 -1
  14. data/lib/log_struct/formatter.rb +29 -17
  15. data/lib/log_struct/integrations/action_mailer/error_handling.rb +3 -11
  16. data/lib/log_struct/integrations/action_mailer/event_logging.rb +22 -12
  17. data/lib/log_struct/integrations/active_job/log_subscriber.rb +52 -48
  18. data/lib/log_struct/integrations/active_model_serializers.rb +8 -14
  19. data/lib/log_struct/integrations/active_record.rb +35 -5
  20. data/lib/log_struct/integrations/active_storage.rb +59 -20
  21. data/lib/log_struct/integrations/ahoy.rb +2 -1
  22. data/lib/log_struct/integrations/carrierwave.rb +13 -16
  23. data/lib/log_struct/integrations/dotenv.rb +278 -0
  24. data/lib/log_struct/integrations/good_job/log_subscriber.rb +86 -136
  25. data/lib/log_struct/integrations/good_job/logger.rb +8 -10
  26. data/lib/log_struct/integrations/good_job.rb +5 -7
  27. data/lib/log_struct/integrations/host_authorization.rb +25 -4
  28. data/lib/log_struct/integrations/lograge.rb +20 -14
  29. data/lib/log_struct/integrations/puma.rb +477 -0
  30. data/lib/log_struct/integrations/rack_error_handler/middleware.rb +11 -18
  31. data/lib/log_struct/integrations/shrine.rb +44 -19
  32. data/lib/log_struct/integrations/sorbet.rb +48 -0
  33. data/lib/log_struct/integrations.rb +21 -0
  34. data/lib/log_struct/log/action_mailer/delivered.rb +99 -0
  35. data/lib/log_struct/log/action_mailer/delivery.rb +99 -0
  36. data/lib/log_struct/log/action_mailer.rb +30 -45
  37. data/lib/log_struct/log/active_job/enqueue.rb +125 -0
  38. data/lib/log_struct/log/active_job/finish.rb +130 -0
  39. data/lib/log_struct/log/active_job/schedule.rb +125 -0
  40. data/lib/log_struct/log/active_job/start.rb +130 -0
  41. data/lib/log_struct/log/active_job.rb +41 -54
  42. data/lib/log_struct/log/active_model_serializers.rb +72 -33
  43. data/lib/log_struct/log/active_storage/delete.rb +87 -0
  44. data/lib/log_struct/log/active_storage/download.rb +103 -0
  45. data/lib/log_struct/log/active_storage/exist.rb +93 -0
  46. data/lib/log_struct/log/active_storage/metadata.rb +93 -0
  47. data/lib/log_struct/log/active_storage/stream.rb +93 -0
  48. data/lib/log_struct/log/active_storage/upload.rb +118 -0
  49. data/lib/log_struct/log/active_storage/url.rb +93 -0
  50. data/lib/log_struct/log/active_storage.rb +32 -68
  51. data/lib/log_struct/log/ahoy.rb +67 -33
  52. data/lib/log_struct/log/carrierwave/delete.rb +115 -0
  53. data/lib/log_struct/log/carrierwave/download.rb +131 -0
  54. data/lib/log_struct/log/carrierwave/upload.rb +141 -0
  55. data/lib/log_struct/log/carrierwave.rb +37 -72
  56. data/lib/log_struct/log/dotenv/load.rb +76 -0
  57. data/lib/log_struct/log/dotenv/restore.rb +76 -0
  58. data/lib/log_struct/log/dotenv/save.rb +76 -0
  59. data/lib/log_struct/log/dotenv/update.rb +76 -0
  60. data/lib/log_struct/log/dotenv.rb +12 -0
  61. data/lib/log_struct/log/error.rb +58 -47
  62. data/lib/log_struct/log/good_job/enqueue.rb +126 -0
  63. data/lib/log_struct/log/good_job/error.rb +151 -0
  64. data/lib/log_struct/log/good_job/finish.rb +136 -0
  65. data/lib/log_struct/log/good_job/log.rb +131 -0
  66. data/lib/log_struct/log/good_job/schedule.rb +136 -0
  67. data/lib/log_struct/log/good_job/start.rb +136 -0
  68. data/lib/log_struct/log/good_job.rb +40 -141
  69. data/lib/log_struct/log/interfaces/additional_data_field.rb +1 -17
  70. data/lib/log_struct/log/interfaces/common_fields.rb +1 -39
  71. data/lib/log_struct/log/interfaces/public_common_fields.rb +1 -28
  72. data/lib/log_struct/log/interfaces/request_fields.rb +1 -33
  73. data/lib/log_struct/log/plain.rb +59 -34
  74. data/lib/log_struct/log/puma/shutdown.rb +80 -0
  75. data/lib/log_struct/log/puma/start.rb +120 -0
  76. data/lib/log_struct/log/puma.rb +10 -0
  77. data/lib/log_struct/log/request.rb +132 -48
  78. data/lib/log_struct/log/security/blocked_host.rb +141 -0
  79. data/lib/log_struct/log/security/csrf_violation.rb +131 -0
  80. data/lib/log_struct/log/security/ip_spoof.rb +141 -0
  81. data/lib/log_struct/log/security.rb +40 -70
  82. data/lib/log_struct/log/shared/add_request_fields.rb +1 -26
  83. data/lib/log_struct/log/shared/merge_additional_data_fields.rb +1 -22
  84. data/lib/log_struct/log/shared/serialize_common.rb +1 -33
  85. data/lib/log_struct/log/shared/serialize_common_public.rb +9 -9
  86. data/lib/log_struct/log/shrine/delete.rb +85 -0
  87. data/lib/log_struct/log/shrine/download.rb +90 -0
  88. data/lib/log_struct/log/shrine/exist.rb +90 -0
  89. data/lib/log_struct/log/shrine/metadata.rb +90 -0
  90. data/lib/log_struct/log/shrine/upload.rb +105 -0
  91. data/lib/log_struct/log/shrine.rb +10 -67
  92. data/lib/log_struct/log/sidekiq.rb +65 -26
  93. data/lib/log_struct/log/sql.rb +113 -106
  94. data/lib/log_struct/log.rb +29 -36
  95. data/lib/log_struct/multi_error_reporter.rb +80 -22
  96. data/lib/log_struct/param_filters.rb +50 -7
  97. data/lib/log_struct/rails_boot_banner_silencer.rb +116 -0
  98. data/lib/log_struct/railtie.rb +67 -0
  99. data/lib/log_struct/semantic_logger/formatter.rb +4 -2
  100. data/lib/log_struct/semantic_logger/setup.rb +34 -18
  101. data/lib/log_struct/shared/interfaces/additional_data_field.rb +22 -0
  102. data/lib/log_struct/shared/interfaces/common_fields.rb +39 -0
  103. data/lib/log_struct/shared/interfaces/public_common_fields.rb +29 -0
  104. data/lib/log_struct/shared/interfaces/request_fields.rb +39 -0
  105. data/lib/log_struct/shared/shared/add_request_fields.rb +28 -0
  106. data/lib/log_struct/shared/shared/merge_additional_data_fields.rb +27 -0
  107. data/lib/log_struct/shared/shared/serialize_common.rb +58 -0
  108. data/lib/log_struct/version.rb +1 -1
  109. data/lib/log_struct.rb +36 -4
  110. data/logstruct.gemspec +2 -1
  111. metadata +78 -9
  112. data/lib/log_struct/log/interfaces/message_field.rb +0 -20
  113. data/lib/log_struct/log_keys.rb +0 -102
@@ -38,168 +38,118 @@ module LogStruct
38
38
  extend T::Sig
39
39
 
40
40
  # Job enqueued event
41
- sig { params(event: T.untyped).void }
41
+ sig { params(event: ::ActiveSupport::Notifications::Event).void }
42
42
  def enqueue(event)
43
- job_data = extract_job_data(event)
44
-
45
- log_entry = LogStruct::Log::GoodJob.new(
46
- event: Event::Enqueue,
47
- level: Level::Info,
48
- job_id: job_data[:job_id],
49
- job_class: job_data[:job_class],
50
- queue_name: job_data[:queue_name],
51
- arguments: job_data[:arguments],
52
- scheduled_at: job_data[:scheduled_at],
53
- priority: job_data[:priority],
54
- execution_time: event.duration,
55
- additional_data: {
56
- enqueue_caller: job_data[:caller_location]
57
- }
58
- )
59
-
60
- logger.info(log_entry)
43
+ payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
44
+ job = payload[:job]
45
+ base_fields = build_base_fields(job, payload)
46
+ ts = event.time ? Time.at(event.time) : Time.now
47
+
48
+ logger.info(Log::GoodJob::Enqueue.new(
49
+ **base_fields.to_kwargs,
50
+ scheduled_at: (job&.scheduled_at ? Time.at(job.scheduled_at.to_i) : nil),
51
+ duration_ms: event.duration.to_f,
52
+ additional_data: {enqueue_caller: job&.enqueue_caller_location},
53
+ timestamp: ts
54
+ ))
61
55
  end
62
56
 
63
57
  # Job execution started event
64
- sig { params(event: T.untyped).void }
58
+ sig { params(event: ::ActiveSupport::Notifications::Event).void }
65
59
  def start(event)
66
- job_data = extract_job_data(event)
67
-
68
- log_entry = LogStruct::Log::GoodJob.new(
69
- event: Event::Start,
70
- level: Level::Info,
71
- job_id: job_data[:job_id],
72
- job_class: job_data[:job_class],
73
- queue_name: job_data[:queue_name],
74
- arguments: job_data[:arguments],
75
- executions: job_data[:executions],
76
- wait_time: job_data[:wait_time],
77
- scheduled_at: job_data[:scheduled_at],
60
+ payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
61
+ job = payload[:job]
62
+ execution = payload[:execution] || payload[:good_job_execution]
63
+ base_fields = build_base_fields(job, payload)
64
+ ts = event.time ? Time.at(event.time) : Time.now
65
+
66
+ logger.info(Log::GoodJob::Start.new(
67
+ **base_fields.to_kwargs,
68
+ wait_ms: begin
69
+ wt = execution&.wait_time || calculate_wait_time(execution)
70
+ wt ? (wt.to_f * 1000.0) : nil
71
+ end,
72
+ scheduled_at: (job&.scheduled_at ? Time.at(job.scheduled_at.to_i) : nil),
78
73
  process_id: ::Process.pid,
79
- thread_id: Thread.current.object_id.to_s(36)
80
- )
81
-
82
- logger.info(log_entry)
74
+ thread_id: Thread.current.object_id.to_s(36),
75
+ timestamp: ts
76
+ ))
83
77
  end
84
78
 
85
79
  # Job completed successfully event
86
- sig { params(event: T.untyped).void }
80
+ sig { params(event: ::ActiveSupport::Notifications::Event).void }
87
81
  def finish(event)
88
- job_data = extract_job_data(event)
89
-
90
- log_entry = LogStruct::Log::GoodJob.new(
91
- event: Event::Finish,
92
- level: Level::Info,
93
- job_id: job_data[:job_id],
94
- job_class: job_data[:job_class],
95
- queue_name: job_data[:queue_name],
96
- executions: job_data[:executions],
97
- run_time: event.duration,
98
- finished_at: Time.now,
82
+ payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
83
+ job = payload[:job]
84
+ base_fields = build_base_fields(job, payload)
85
+ start_ts = event.time ? Time.at(event.time) : Time.now
86
+ end_ts = event.end ? Time.at(event.end) : Time.now
87
+
88
+ logger.info(Log::GoodJob::Finish.new(
89
+ **base_fields.to_kwargs,
90
+ duration_ms: event.duration.to_f,
91
+ finished_at: end_ts,
99
92
  process_id: ::Process.pid,
100
93
  thread_id: Thread.current.object_id.to_s(36),
101
- additional_data: {
102
- result: job_data[:result]
103
- }
104
- )
105
-
106
- logger.info(log_entry)
94
+ additional_data: {result: payload[:result]},
95
+ timestamp: start_ts
96
+ ))
107
97
  end
108
98
 
109
99
  # Job failed with error event
110
- sig { params(event: T.untyped).void }
100
+ sig { params(event: ::ActiveSupport::Notifications::Event).void }
111
101
  def error(event)
112
- job_data = extract_job_data(event)
113
-
114
- log_entry = LogStruct::Log::GoodJob.new(
115
- event: Event::Error,
116
- level: Level::Error,
117
- job_id: job_data[:job_id],
118
- job_class: job_data[:job_class],
119
- queue_name: job_data[:queue_name],
120
- executions: job_data[:executions],
121
- exception_executions: job_data[:exception_executions],
122
- error_class: job_data[:error_class],
123
- error_message: job_data[:error_message],
124
- error_backtrace: job_data[:error_backtrace],
125
- run_time: event.duration,
102
+ payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
103
+ job = payload[:job]
104
+ execution = payload[:execution] || payload[:good_job_execution]
105
+ exception = payload[:exception] || payload[:error]
106
+ ts = event.time ? Time.at(event.time) : Time.now
107
+ base_fields = build_base_fields(job, payload)
108
+
109
+ logger.error(Log::GoodJob::Error.new(
110
+ **base_fields.to_kwargs,
111
+ exception_executions: execution&.exception_executions,
112
+ err_class: exception&.class&.name,
113
+ error_message: exception&.message,
114
+ backtrace: exception&.backtrace&.first(20),
115
+ duration_ms: event.duration.to_f,
126
116
  process_id: ::Process.pid,
127
- thread_id: Thread.current.object_id.to_s(36)
128
- )
129
-
130
- logger.error(log_entry)
117
+ thread_id: Thread.current.object_id.to_s(36),
118
+ timestamp: ts
119
+ ))
131
120
  end
132
121
 
133
122
  # Job scheduled for future execution event
134
- sig { params(event: T.untyped).void }
123
+ sig { params(event: ::ActiveSupport::Notifications::Event).void }
135
124
  def schedule(event)
136
- job_data = extract_job_data(event)
137
-
138
- log_entry = LogStruct::Log::GoodJob.new(
139
- event: Event::Schedule,
140
- level: Level::Info,
141
- job_id: job_data[:job_id],
142
- job_class: job_data[:job_class],
143
- queue_name: job_data[:queue_name],
144
- arguments: job_data[:arguments],
145
- scheduled_at: job_data[:scheduled_at],
146
- priority: job_data[:priority],
147
- cron_key: job_data[:cron_key],
148
- execution_time: event.duration
149
- )
150
-
151
- logger.info(log_entry)
125
+ payload = T.let(event.payload, T::Hash[Symbol, T.untyped])
126
+ job = payload[:job]
127
+ base_fields = build_base_fields(job, payload)
128
+ ts = event.time ? Time.at(event.time) : Time.now
129
+
130
+ logger.info(Log::GoodJob::Schedule.new(
131
+ **base_fields.to_kwargs,
132
+ scheduled_at: (job&.scheduled_at ? Time.at(job.scheduled_at.to_i) : nil),
133
+ priority: job&.priority,
134
+ cron_key: job&.cron_key,
135
+ duration_ms: event.duration.to_f,
136
+ timestamp: ts
137
+ ))
152
138
  end
153
139
 
154
140
  private
155
141
 
156
- # Extract job data from ActiveSupport event payload
157
- sig { params(event: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
158
- def extract_job_data(event)
159
- payload = event.payload || {}
160
- job = payload[:job]
142
+ # Build BaseFields from job + payload (execution)
143
+ sig { params(job: T.untyped, payload: T::Hash[Symbol, T.untyped]).returns(Log::GoodJob::BaseFields) }
144
+ def build_base_fields(job, payload)
161
145
  execution = payload[:execution] || payload[:good_job_execution]
162
- exception = payload[:exception] || payload[:error]
163
-
164
- data = {}
165
-
166
- # Basic job information
167
- if job
168
- data[:job_id] = job.job_id if job.respond_to?(:job_id)
169
- data[:job_class] = job.job_class if job.respond_to?(:job_class)
170
- data[:queue_name] = job.queue_name if job.respond_to?(:queue_name)
171
- data[:arguments] = job.arguments if job.respond_to?(:arguments)
172
- data[:priority] = job.priority if job.respond_to?(:priority)
173
- data[:scheduled_at] = job.scheduled_at if job.respond_to?(:scheduled_at)
174
- data[:cron_key] = job.cron_key if job.respond_to?(:cron_key)
175
- data[:caller_location] = job.enqueue_caller_location if job.respond_to?(:enqueue_caller_location)
176
- end
177
-
178
- # Execution-specific information
179
- if execution
180
- data[:executions] = execution.executions if execution.respond_to?(:executions)
181
- data[:exception_executions] = execution.exception_executions if execution.respond_to?(:exception_executions)
182
- # Use existing wait_time if available, otherwise calculate it
183
- if execution.respond_to?(:wait_time) && execution.wait_time
184
- data[:wait_time] = execution.wait_time
185
- elsif execution.respond_to?(:created_at)
186
- data[:wait_time] = calculate_wait_time(execution)
187
- end
188
- data[:batch_id] = execution.batch_id if execution.respond_to?(:batch_id)
189
- data[:cron_key] ||= execution.cron_key if execution.respond_to?(:cron_key)
190
- end
191
-
192
- # Error information
193
- if exception
194
- data[:error_class] = exception.class.name
195
- data[:error_message] = exception.message
196
- data[:error_backtrace] = exception.backtrace&.first(20) # Limit backtrace size
197
- end
198
-
199
- # Result information
200
- data[:result] = payload[:result] if payload.key?(:result)
201
-
202
- data
146
+ Log::GoodJob::BaseFields.new(
147
+ job_id: job&.job_id,
148
+ job_class: job&.job_class,
149
+ queue_name: job&.queue_name,
150
+ arguments: job&.arguments,
151
+ executions: execution&.executions
152
+ )
203
153
  end
204
154
 
205
155
  # Calculate wait time from job creation to execution start
@@ -46,24 +46,22 @@ module LogStruct
46
46
  end
47
47
  end
48
48
 
49
- # Create a GoodJob log struct with the context
50
- log_struct = Log::GoodJob.new(
51
- event: Event::Log,
52
- level: LogStruct::Level.from_severity(level.to_s.upcase),
49
+ # Emit a GoodJob::Log event with context and extra fields as additional_data
50
+ extras = {}
51
+ extras[:scheduled_at] = job_context[:scheduled_at] if job_context.key?(:scheduled_at)
52
+ extras[:priority] = job_context[:priority] if job_context.key?(:priority)
53
+
54
+ log_struct = Log::GoodJob::Log.new(
55
+ message: message || (block ? block.call : ""),
53
56
  process_id: ::Process.pid,
54
57
  thread_id: Thread.current.object_id.to_s(36),
55
58
  job_id: job_context[:job_id],
56
59
  job_class: job_context[:job_class],
57
60
  queue_name: job_context[:queue_name],
58
61
  executions: job_context[:executions],
59
- scheduled_at: job_context[:scheduled_at],
60
- priority: job_context[:priority],
61
- additional_data: {
62
- message: message || (block ? block.call : "")
63
- }
62
+ additional_data: extras
64
63
  )
65
64
 
66
- # Pass the struct to SemanticLogger
67
65
  super(log_struct, payload, &nil)
68
66
  end
69
67
  end
@@ -81,15 +81,13 @@ module LogStruct
81
81
  # Configure error handling for thread errors if GoodJob supports it
82
82
  if goodjob_module.respond_to?(:on_thread_error=)
83
83
  goodjob_module.on_thread_error = ->(exception) do
84
- # Log the error using our structured format
85
- log_entry = LogStruct::Log::GoodJob.new(
86
- event: Event::Error,
87
- level: Level::Error,
88
- error_class: exception.class.name,
84
+ log_entry = LogStruct::Log::GoodJob::Error.new(
85
+ err_class: exception.class.name,
89
86
  error_message: exception.message,
90
- error_backtrace: exception.backtrace
87
+ backtrace: exception.backtrace,
88
+ process_id: ::Process.pid,
89
+ thread_id: Thread.current.object_id.to_s(36)
91
90
  )
92
-
93
91
  goodjob_module.logger.error(log_entry)
94
92
  end
95
93
  end
@@ -34,6 +34,23 @@ module LogStruct
34
34
  return nil unless config.enabled
35
35
  return nil unless config.integrations.enable_host_authorization
36
36
 
37
+ # In test environment, ensure HostAuthorization does not block requests
38
+ # from the default integration test hosts. Allow all hosts explicitly.
39
+ if ::Rails.env.test? && ::Rails.application.config.respond_to?(:hosts)
40
+ begin
41
+ ::Rails.application.config.hosts << /.*\z/
42
+ rescue
43
+ # best-effort; ignore if hosts not configurable
44
+ end
45
+ # Additionally, exclude all requests from HostAuthorization in test
46
+ begin
47
+ ::Rails.application.config.host_authorization ||= {}
48
+ ::Rails.application.config.host_authorization[:exclude] = ->(_request) { true }
49
+ rescue
50
+ # best-effort
51
+ end
52
+ end
53
+
37
54
  # Define the response app as a separate variable to fix block alignment
38
55
  response_app = lambda do |env|
39
56
  request = ::ActionDispatch::Request.new(env)
@@ -69,10 +86,14 @@ module LogStruct
69
86
  [FORBIDDEN_STATUS, RESPONSE_HEADERS, [RESPONSE_HTML]]
70
87
  end
71
88
 
72
- # Replace the default HostAuthorization app with our custom app for logging
73
- Rails.application.config.host_authorization = {
74
- response_app: response_app
75
- }
89
+ # Merge our response_app into existing host_authorization config to preserve excludes
90
+ existing = Rails.application.config.host_authorization
91
+ unless existing.is_a?(Hash)
92
+ existing = {}
93
+ end
94
+ existing = existing.dup
95
+ existing[:response_app] = response_app
96
+ Rails.application.config.host_authorization = existing
76
97
 
77
98
  true
78
99
  end
@@ -38,21 +38,27 @@ module LogStruct
38
38
  # The struct is converted to JSON by our Formatter (after filtering, etc.)
39
39
  config.lograge.formatter = T.let(
40
40
  lambda do |data|
41
- # Convert the data hash to a Log::Request struct
41
+ # Coerce common fields to expected types
42
+ status = ((s = data[:status]) && s.respond_to?(:to_i)) ? s.to_i : s
43
+ duration_ms = ((d = data[:duration]) && d.respond_to?(:to_f)) ? d.to_f : d
44
+ view = ((v = data[:view]) && v.respond_to?(:to_f)) ? v.to_f : v
45
+ db = ((b = data[:db]) && b.respond_to?(:to_f)) ? b.to_f : b
46
+
47
+ params = data[:params]
48
+ params = params.deep_symbolize_keys if params&.respond_to?(:deep_symbolize_keys)
49
+
42
50
  Log::Request.new(
43
- source: Source::Rails,
44
- event: Event::Request,
45
- timestamp: Time.now,
46
- http_method: data[:method],
47
- path: data[:path],
48
- format: data[:format],
49
- controller: data[:controller],
50
- action: data[:action],
51
- status: data[:status],
52
- duration: data[:duration],
53
- view: data[:view],
54
- db: data[:db],
55
- params: data[:params]
51
+ http_method: data[:method]&.to_s,
52
+ path: data[:path]&.to_s,
53
+ format: data[:format]&.to_s,
54
+ controller: data[:controller]&.to_s,
55
+ action: data[:action]&.to_s,
56
+ status: status,
57
+ duration_ms: duration_ms,
58
+ view: view,
59
+ database: db,
60
+ params: params,
61
+ timestamp: Time.now
56
62
  )
57
63
  end,
58
64
  T.proc.params(hash: T::Hash[Symbol, T.untyped]).returns(Log::Request)