Module: LogStruct::Integrations::Shrine
- Extended by:
- IntegrationInterface, T::Sig
- Defined in:
- lib/log_struct/integrations/shrine.rb
Overview
Shrine integration for structured logging
Defined Under Namespace
Modules: SkipLoggedEvents
Constant Summary collapse
- SHRINE_EVENTS =
T.let(%i[upload exists download delete metadata open].freeze, T::Array[Symbol])
- DISPOSITION_FILENAME_REGEX =
The file name in a Content-Disposition (attachment; filename="..."; filename*=UTF-8''...). A quoted name can hold a semicolon, and whatever follows a quote (malformed or not) goes too.
/(\bfilename\*?\s*=\s*)(?:"(?:[^"\\]|\\.)*"?)?[^;]*/i
Class Method Summary collapse
-
.setup(config) ⇒ Boolean?
Set up Shrine structured logging.
-
.without_file_names(options) ⇒ T.untyped
Upload options with the file names taken out of a Content-Disposition (upload_options: ..., which the upload_options plugin often sets): they can name a person.
Methods included from IntegrationInterface
Class Method Details
.setup(config) ⇒ Boolean?
Set up Shrine structured logging
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/log_struct/integrations/shrine.rb', line 25 def self.setup(config) return nil unless defined?(::Shrine) return nil unless config.enabled return nil unless config.integrations.enable_shrine # Create a structured log subscriber for Shrine # ActiveSupport::Notifications::Event has name, time, end, transaction_id, payload, and duration shrine_log_subscriber = lambda do |event| payload = event.payload.except(:io, :name).dup # Map event name to Event type event_type = case event.name when :upload then Event::Upload when :download then Event::Download when :open then Event::Download when :delete then Event::Delete when :exists then Event::Exist else Event::Metadata end # Create structured log data # Ensure storage is always a symbol storage_sym = payload[:storage].to_sym log_data = case event_type when Event::Upload = payload[:metadata].is_a?(Hash) ? payload[:metadata] : {} size = ["size"] Log::Shrine::Upload.new( storage: storage_sym, location: payload[:location], mime_type: ["mime_type"]&.to_s, size: size.is_a?(Integer) ? size : nil, uploader: payload[:uploader]&.to_s, upload_options: without_file_names(payload[:upload_options]), options: payload[:options], duration_ms: event.duration.to_f ) when Event::Download Log::Shrine::Download.new( storage: storage_sym, location: payload[:location], download_options: payload[:download_options] ) when Event::Delete Log::Shrine::Delete.new( storage: storage_sym, location: payload[:location] ) when Event::Exist Log::Shrine::Exist.new( storage: storage_sym, location: payload[:location] ) else # metadata, the only other event in SHRINE_EVENTS Log::Shrine::Metadata.new(storage: storage_sym, duration_ms: event.duration.to_f) end # Log directly through SemanticLogger, NOT through Shrine.logger # Shrine.logger is a basic Logger that would just call .to_s on the struct ::SemanticLogger[::Shrine].info(log_data) end # Load the instrumentation plugin without Shrine's default log subscriber, which logs # each event as a line of text (a no-op if the app has loaded it already). Shrine # ignores a subscriber that's already subscribed. ::Shrine.plugin :instrumentation, log_subscriber: nil SHRINE_EVENTS.each do |event_name| ::Shrine.subscribe(event_name, &shrine_log_subscriber) end # The app may load the plugin with the default log subscriber too, before or after this ::Shrine::Plugins::Instrumentation::LogSubscriber.singleton_class.prepend(SkipLoggedEvents) # Other plugins' log subscribers and Shrine's warnings write text to Shrine.logger ::Shrine.logger = ::SemanticLogger[::Shrine] true end |
.without_file_names(options) ⇒ T.untyped
Upload options with the file names taken out of a Content-Disposition (upload_options: ..., which the upload_options plugin often sets): they can name a person
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/log_struct/integrations/shrine.rb', line 109 def self.without_file_names() return unless .is_a?(Hash) .to_h do |key, value| if value.is_a?(String) && KeyWords.normalize(key).include?("disposition") [key, value.gsub(DISPOSITION_FILENAME_REGEX, '\1[FILENAME]')] else [key, value] end end end |