1212
1313RSpec . describe Html2rss ::Web ::LogSanitizer do
1414 let ( :io ) { StringIO . new }
15- let ( :logger ) { Logger . new ( io ) . tap { |log | log . formatter = Html2rss ::Web ::AppLogger . send ( :method , :format_entry ) } }
16- let ( :sanitized_url ) do
15+ let ( :test_logger ) { Logger . new ( io ) . tap { |log | log . formatter = Html2rss ::Web ::AppLogger . send ( :method , :format_entry ) } }
16+ let ( :expected_news_url ) do
1717 {
1818 host : 'news.ycombinator.com' ,
1919 scheme : 'https' ,
3636 Html2rss ::Web ::RequestContext . set! ( context )
3737 Html2rss ::Web ::AppLogger . reset_logger!
3838 Html2rss ::Web ::SecurityLogger . reset_logger!
39- allow ( Html2rss :: Web :: AppLogger ) . to receive ( :logger ) . and_return ( logger )
39+ allow ( Logger ) . to receive ( :new ) . and_return ( test_logger )
4040 end
4141
4242 after do
5252 end
5353
5454 it 'replaces logged urls with hashed host metadata' do
55- expect ( described_class . sanitize_details ( url : 'https://news.ycombinator.com' ) ) . to eq ( url : sanitized_url )
55+ expect ( described_class . sanitize_details ( url : 'https://news.ycombinator.com' ) ) . to eq ( url : expected_news_url )
5656 end
5757
5858 it 'falls back to a hash for malformed urls' do
7676 expect ( payload . slice ( :path , :details ) ) . to eq (
7777 path : '/api/v1/feeds/[REDACTED]' ,
7878 details : {
79- url : sanitized_url ,
79+ url : expected_news_url ,
8080 token_hash : Digest ::SHA256 . hexdigest ( 'very-secret-token' ) [ 0 ..7 ] ,
8181 success : true
8282 }
8383 )
8484 end
8585
86+ it 'sanitizes security logger fallback output when structured logging fails' do
87+ allow ( Html2rss ::Web ::LogEvent ) . to receive ( :emit ) . and_raise ( JSON ::GeneratorError , 'boom' )
88+ allow ( Kernel ) . to receive ( :warn )
89+ emit_failing_token_usage_log
90+
91+ expect ( Kernel ) . to have_received ( :warn ) . with ( 'Structured logging fallback: JSON::GeneratorError: boom' )
92+ expect ( fallback_warning_line ) . to eq ( expected_fallback_warning_line )
93+ end
94+
8695 it 'sanitizes observability details' do
8796 Html2rss ::Web ::Observability . emit (
8897 event_name : 'feed.render' ,
93102 lines = io . string . lines . map { |line | JSON . parse ( line , symbolize_names : true ) }
94103 observability_payload = lines . first
95104
96- expect ( observability_payload . dig ( :details , :url ) ) . to eq ( sanitized_url )
105+ expect ( observability_payload . dig ( :details , :url ) ) . to eq ( expected_news_url )
97106 end
98107
99108 it 'formats rack-timeout logfmt as json' do
100- logger . info ( 'source=rack-timeout id=req-123 timeout=15000ms state=completed' )
109+ Html2rss :: Web :: AppLogger . logger . info ( 'source=rack-timeout id=req-123 timeout=15000ms state=completed' )
101110
102111 payload = JSON . parse ( io . string . lines . last , symbolize_names : true )
103112 expect ( payload ) . to include (
@@ -122,20 +131,20 @@ def nested_url_payload
122131 # @return [Hash{Symbol=>Object}]
123132 def expected_nested_url_payload
124133 {
125- url : sanitized_url ( 'news.ycombinator.com' , 'https://news.ycombinator.com' ) ,
134+ url : expected_sanitized_url ( 'news.ycombinator.com' , 'https://news.ycombinator.com' ) ,
126135 related_urls : [
127- sanitized_url ( 'example.com' , 'https://example.com/feed.xml' )
136+ expected_sanitized_url ( 'example.com' , 'https://example.com/feed.xml' )
128137 ] ,
129138 details : {
130- url : sanitized_url ( 'lobste.rs' , 'https://lobste.rs/s/test' )
139+ url : expected_sanitized_url ( 'lobste.rs' , 'https://lobste.rs/s/test' )
131140 }
132141 }
133142 end
134143
135144 # @param host [String]
136145 # @param url [String]
137146 # @return [Hash{Symbol=>String}]
138- def sanitized_url ( host , url )
147+ def expected_sanitized_url ( host , url )
139148 { host :, scheme : 'https' , hash : url_hash ( url ) }
140149 end
141150
@@ -144,4 +153,30 @@ def sanitized_url(host, url)
144153 def url_hash ( url )
145154 Digest ::SHA256 . hexdigest ( url ) [ 0 ..11 ]
146155 end
156+
157+ # @return [void]
158+ def emit_failing_token_usage_log
159+ Html2rss ::Web ::SecurityLogger . log_token_usage (
160+ 'very-secret-token' ,
161+ 'https://news.ycombinator.com/private/feed' ,
162+ true
163+ )
164+ end
165+
166+ # @return [String]
167+ def fallback_warning_line
168+ warning_messages = RSpec ::Mocks . space . proxy_for ( Kernel ) . messages_arg_list . map ( &:first )
169+ warning_messages . find { |message | message . include? ( 'component=security_logger' ) }
170+ end
171+
172+ # @return [Hash{Symbol=>String}]
173+ def sanitized_fallback_url
174+ expected_sanitized_url ( 'news.ycombinator.com' , 'https://news.ycombinator.com/private/feed' )
175+ end
176+
177+ # @return [String]
178+ def expected_fallback_warning_line
179+ 'component=security_logger security_event=token_usage ' \
180+ "details={success: true, url: #{ sanitized_fallback_url . inspect } , token_hash: \" 01cadf39\" }"
181+ end
147182end
0 commit comments