Skip to content
This repository
Browse code

* ActiveSupport::BufferedLogger#silence is deprecated. If you want to…

… squelch

    logs for a certain block, change the log level for that block.

*   ActiveSupport::BufferedLogger#open_log is deprecated.  This method should
    not have been public in the first place.

*   ActiveSupport::BufferedLogger's behavior of automatically creating the
    directory for your log file is deprecated.  Please make sure to create the
    directory for your log file before instantiating.

*   ActiveSupport::BufferedLogger#auto_flushing is deprecated.  Either set the
    sync level on the underlying file handle like this:

        f = File.open('foo.log', 'w')
        f.sync = true
        ActiveSupport::BufferedLogger.new f

    Or tune your filesystem.  The FS cache is now what controls flushing.

*   ActiveSupport::BufferedLogger#flush is deprecated.  Set sync on your
    filehandle, or tune your filesystem.
  • Loading branch information...
commit 04ef93dae6d9cec616973c1110a33894ad4ba6ed 1 parent f79b257
Aaron Patterson authored December 09, 2011
22  activesupport/CHANGELOG.md
Source Rendered
@@ -47,6 +47,28 @@
47 47
 *   ActiveSupport::OrderedHash now has different behavior for #each and
48 48
     \#each_pair when given a block accepting its parameters with a splat. *Andrew Radev*
49 49
 
  50
+*   ActiveSupport::BufferedLogger#silence is deprecated.  If you want to squelch
  51
+    logs for a certain block, change the log level for that block.
  52
+
  53
+*   ActiveSupport::BufferedLogger#open_log is deprecated.  This method should
  54
+    not have been public in the first place.
  55
+
  56
+*   ActiveSupport::BufferedLogger's behavior of automatically creating the
  57
+    directory for your log file is deprecated.  Please make sure to create the
  58
+    directory for your log file before instantiating.
  59
+
  60
+*   ActiveSupport::BufferedLogger#auto_flushing is deprecated.  Either set the
  61
+    sync level on the underlying file handle like this:
  62
+
  63
+        f = File.open('foo.log', 'w')
  64
+        f.sync = true
  65
+        ActiveSupport::BufferedLogger.new f
  66
+
  67
+    Or tune your filesystem.  The FS cache is now what controls flushing.
  68
+
  69
+*   ActiveSupport::BufferedLogger#flush is deprecated.  Set sync on your
  70
+    filehandle, or tune your filesystem.
  71
+
50 72
 ## Rails 3.1.0 (August 30, 2011) ##
51 73
 
52 74
 *   ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now
112  activesupport/lib/active_support/buffered_logger.rb
... ...
@@ -1,5 +1,8 @@
1 1
 require 'thread'
  2
+require 'logger'
2 3
 require 'active_support/core_ext/class/attribute_accessors'
  4
+require 'active_support/deprecation'
  5
+require 'fileutils'
3 6
 
4 7
 module ActiveSupport
5 8
   # Inspired by the buffered logger idea by Ezra
@@ -25,40 +28,35 @@ module Severity
25 28
     # Silences the logger for the duration of the block.
26 29
     def silence(temporary_level = ERROR)
27 30
       if silencer
28  
-        old_logger_level = @tmp_levels[Thread.current]
29 31
         begin
30  
-          @tmp_levels[Thread.current] = temporary_level
31  
-          yield self
  32
+          logger = self.class.new @log_dest, temporary_level
  33
+          yield logger
32 34
         ensure
33  
-          if old_logger_level
34  
-            @tmp_levels[Thread.current] = old_logger_level
35  
-          else
36  
-            @tmp_levels.delete(Thread.current)
37  
-          end
  35
+          logger.close
38 36
         end
39 37
       else
40 38
         yield self
41 39
       end
42 40
     end
  41
+    deprecate :silence
43 42
 
44  
-    attr_writer :level
45 43
     attr_reader :auto_flushing
  44
+    deprecate :auto_flushing
46 45
 
47 46
     def initialize(log, level = DEBUG)
48 47
       @level         = level
49  
-      @tmp_levels    = {}
50  
-      @buffer        = Hash.new { |h,k| h[k] = [] }
51  
-      @auto_flushing = 1
52  
-      @guard = Mutex.new
53  
-
54  
-      if log.respond_to?(:write)
55  
-        @log = log
56  
-      elsif File.exist?(log)
57  
-        @log = open_log(log, (File::WRONLY | File::APPEND))
58  
-      else
59  
-        FileUtils.mkdir_p(File.dirname(log))
60  
-        @log = open_log(log, (File::WRONLY | File::APPEND | File::CREAT))
  48
+      @log_dest      = log
  49
+
  50
+      unless log.respond_to?(:write)
  51
+        unless File.exist?(File.dirname(log))
  52
+          ActiveSupport::Deprecation.warn(<<-eowarn)
  53
+Automatic directory creation for '#{log}' is deprecated.  Please make sure the directory for your log file exists before creating the logger.
  54
+          eowarn
  55
+          FileUtils.mkdir_p(File.dirname(log))
  56
+        end
61 57
       end
  58
+
  59
+      @log = open_logfile log
62 60
     end
63 61
 
64 62
     def open_log(log, mode)
@@ -67,20 +65,18 @@ def open_log(log, mode)
67 65
         open_log.sync = true
68 66
       end
69 67
     end
  68
+    deprecate :open_log
70 69
 
71 70
     def level
72  
-      @tmp_levels[Thread.current] || @level
  71
+      @log.level
  72
+    end
  73
+
  74
+    def level=(l)
  75
+      @log.level = l
73 76
     end
74 77
 
75 78
     def add(severity, message = nil, progname = nil, &block)
76  
-      return if level > severity
77  
-      message = (message || (block && block.call) || progname).to_s
78  
-      # If a newline is necessary then create a new message ending with a newline.
79  
-      # Ensures that the original message is not mutated.
80  
-      message = "#{message}\n" unless message[-1] == ?\n
81  
-      buffer << message
82  
-      auto_flush
83  
-      message
  79
+      @log.add(severity, message, progname, &block)
84 80
     end
85 81
 
86 82
     # Dynamically add methods such as:
@@ -104,62 +100,20 @@ def #{severity.downcase}?                                       # def debug?
104 100
     # never auto-flush. If you turn auto-flushing off, be sure to regularly
105 101
     # flush the log yourself -- it will eat up memory until you do.
106 102
     def auto_flushing=(period)
107  
-      @auto_flushing =
108  
-        case period
109  
-        when true;                1
110  
-        when false, nil, 0;       MAX_BUFFER_SIZE
111  
-        when Integer;             period
112  
-        else raise ArgumentError, "Unrecognized auto_flushing period: #{period.inspect}"
113  
-        end
114 103
     end
  104
+    deprecate :auto_flushing=
115 105
 
116 106
     def flush
117  
-      @guard.synchronize do
118  
-        write_buffer(buffer)
119  
-
120  
-        # Important to do this even if buffer was empty or else @buffer will
121  
-        # accumulate empty arrays for each request where nothing was logged.
122  
-        clear_buffer
123  
-
124  
-        # Clear buffers associated with dead threads or else spawned threads
125  
-        # that don't call flush will result in a memory leak.
126  
-        flush_dead_buffers
127  
-      end
128 107
     end
  108
+    deprecate :flush
129 109
 
130 110
     def close
131  
-      flush
132  
-      @log.close if @log.respond_to?(:close)
133  
-      @log = nil
  111
+      @log.close
134 112
     end
135 113
 
136  
-    protected
137  
-      def auto_flush
138  
-        flush if buffer.size >= @auto_flushing
139  
-      end
140  
-
141  
-      def buffer
142  
-        @buffer[Thread.current]
143  
-      end
144  
-
145  
-      def clear_buffer
146  
-        @buffer.delete(Thread.current)
147  
-      end
148  
-
149  
-      # Find buffers created by threads that are no longer alive and flush them to the log
150  
-      # in order to prevent memory leaks from spawned threads.
151  
-      def flush_dead_buffers #:nodoc:
152  
-        @buffer.keys.reject{|thread| thread.alive?}.each do |thread|
153  
-          buffer = @buffer[thread]
154  
-          write_buffer(buffer)
155  
-          @buffer.delete(thread)
156  
-        end
157  
-      end
158  
-
159  
-      def write_buffer(buffer)
160  
-        buffer.each do |content|
161  
-          @log.write(content)
162  
-        end
163  
-      end
  114
+    private
  115
+    def open_logfile(log)
  116
+      Logger.new log
  117
+    end
164 118
   end
165 119
 end
3  activesupport/lib/active_support/tagged_logging.rb
@@ -38,9 +38,8 @@ def #{severity}(progname = nil, &block)
38 38
       EOM
39 39
     end
40 40
 
41  
-    def flush(*args)
  41
+    def flush
42 42
       @tags.delete(Thread.current)
43  
-      @logger.flush(*args) if @logger.respond_to?(:flush)
44 43
     end
45 44
 
46 45
     def method_missing(method, *args)
47  activesupport/test/benchmarkable_test.rb
@@ -3,8 +3,23 @@
3 3
 class BenchmarkableTest < ActiveSupport::TestCase
4 4
   include ActiveSupport::Benchmarkable
5 5
 
6  
-  def teardown
7  
-    logger.send(:clear_buffer)
  6
+  attr_reader :buffer, :logger
  7
+
  8
+  class Buffer
  9
+    include Enumerable
  10
+
  11
+    def initialize; @lines = []; end
  12
+    def each(&block); @lines.each(&block); end
  13
+    def write(x); @lines << x; end
  14
+    def close; end
  15
+    def last; @lines.last; end
  16
+    def size; @lines.size; end
  17
+    def empty?; @lines.empty?; end
  18
+  end
  19
+
  20
+  def setup
  21
+    @buffer = Buffer.new
  22
+    @logger = ActiveSupport::BufferedLogger.new(@buffer)
8 23
   end
9 24
 
10 25
   def test_without_block
@@ -40,35 +55,7 @@ def test_outside_level
40 55
     logger.level = ActiveSupport::BufferedLogger::DEBUG
41 56
   end
42 57
 
43  
-  def test_without_silencing
44  
-    benchmark('debug_run', :silence => false) do
45  
-      logger.info "not silenced!"
46  
-    end
47  
-
48  
-    assert_equal 2, buffer.size
49  
-  end
50  
-
51  
-  def test_with_silencing
52  
-    benchmark('debug_run', :silence => true) do
53  
-      logger.info "silenced!"
54  
-    end
55  
-
56  
-    assert_equal 1, buffer.size
57  
-  end
58  
-
59 58
   private
60  
-    def logger
61  
-      @logger ||= begin
62  
-        logger = ActiveSupport::BufferedLogger.new(StringIO.new)
63  
-        logger.auto_flushing = false
64  
-        logger
65  
-      end
66  
-    end
67  
-
68  
-    def buffer
69  
-      logger.send(:buffer)
70  
-    end
71  
-
72 59
     def assert_last_logged(message = 'Benchmarking')
73 60
       assert_match(/^#{message} \(.*\)$/, buffer.last)
74 61
     end
139  activesupport/test/buffered_logger_test.rb
@@ -7,6 +7,7 @@
7 7
 
8 8
 class BufferedLoggerTest < Test::Unit::TestCase
9 9
   include MultibyteTestHelpers
  10
+  include ActiveSupport::Testing::Deprecation
10 11
 
11 12
   Logger = ActiveSupport::BufferedLogger
12 13
 
@@ -23,7 +24,10 @@ def test_write_binary_data_to_existing_file
23 24
     t.write 'hi mom!'
24 25
     t.close
25 26
 
26  
-    logger = Logger.new t.path
  27
+    f = File.open(t.path, 'w')
  28
+    f.binmode
  29
+
  30
+    logger = Logger.new f
27 31
     logger.level = Logger::DEBUG
28 32
 
29 33
     str = "\x80"
@@ -32,7 +36,6 @@ def test_write_binary_data_to_existing_file
32 36
     end
33 37
 
34 38
     logger.add Logger::DEBUG, str
35  
-    logger.flush
36 39
   ensure
37 40
     logger.close
38 41
     t.close true
@@ -40,7 +43,10 @@ def test_write_binary_data_to_existing_file
40 43
 
41 44
   def test_write_binary_data_create_file
42 45
     fname = File.join Dir.tmpdir, 'lol', 'rofl.log'
43  
-    logger = Logger.new fname
  46
+    f = File.open(fname, 'w')
  47
+    f.binmode
  48
+
  49
+    logger = Logger.new f
44 50
     logger.level = Logger::DEBUG
45 51
 
46 52
     str = "\x80"
@@ -49,7 +55,6 @@ def test_write_binary_data_create_file
49 55
     end
50 56
 
51 57
     logger.add Logger::DEBUG, str
52  
-    logger.flush
53 58
   ensure
54 59
     logger.close
55 60
     File.unlink fname
@@ -104,34 +109,6 @@ def test_should_not_mutate_message
104 109
     assert_equal message_copy, @message
105 110
   end
106 111
 
107  
-
108  
-  [false, nil, 0].each do |disable|
109  
-    define_method "test_disabling_auto_flush_with_#{disable.inspect}_should_buffer_until_explicit_flush" do
110  
-      @logger.auto_flushing = disable
111  
-
112  
-      4.times do
113  
-        @logger.info 'wait for it..'
114  
-        assert @output.string.empty?, "@output.string should be empty but it is #{@output.string}"
115  
-      end
116  
-
117  
-      @logger.flush
118  
-      assert !@output.string.empty?, "@logger.send(:buffer).size.to_s should not be empty but it is empty"
119  
-    end
120  
-
121  
-    define_method "test_disabling_auto_flush_with_#{disable.inspect}_should_flush_at_max_buffer_size_as_failsafe" do
122  
-      @logger.auto_flushing = disable
123  
-      assert_equal Logger::MAX_BUFFER_SIZE, @logger.auto_flushing
124  
-
125  
-      (Logger::MAX_BUFFER_SIZE - 1).times do
126  
-        @logger.info 'wait for it..'
127  
-        assert @output.string.empty?, "@output.string should be empty but is #{@output.string}"
128  
-      end
129  
-
130  
-      @logger.info 'there it is.'
131  
-      assert !@output.string.empty?, "@logger.send(:buffer).size.to_s should not be empty but it is empty"
132  
-    end
133  
-  end
134  
-
135 112
   def test_should_know_if_its_loglevel_is_below_a_given_level
136 113
     Logger::Severity.constants.each do |level|
137 114
       @logger.level = Logger::Severity.const_get(level) - 1
@@ -139,56 +116,17 @@ def test_should_know_if_its_loglevel_is_below_a_given_level
139 116
     end
140 117
   end
141 118
 
142  
-  def test_should_auto_flush_every_n_messages
143  
-    @logger.auto_flushing = 5
144  
-
145  
-    4.times do
146  
-      @logger.info 'wait for it..'
147  
-      assert @output.string.empty?, "@output.string should be empty but it is #{@output.string}"
148  
-    end
149  
-
150  
-    @logger.info 'there it is.'
151  
-    assert !@output.string.empty?, "@output.string should not be empty but it is empty"
152  
-  end
153  
-
154 119
   def test_should_create_the_log_directory_if_it_doesnt_exist
155 120
     tmp_directory = File.join(File.dirname(__FILE__), "tmp")
156 121
     log_file = File.join(tmp_directory, "development.log")
157 122
     FileUtils.rm_rf(tmp_directory)
158  
-    @logger  = Logger.new(log_file)
159  
-    assert File.exist?(tmp_directory)
160  
-  end
161  
-
162  
-  def test_logger_should_maintain_separate_buffers_for_each_thread
163  
-    @logger.auto_flushing = false
164  
-
165  
-    a = Thread.new do
166  
-      @logger.info("a"); Thread.pass;
167  
-      @logger.info("b"); Thread.pass;
168  
-      @logger.info("c"); @logger.flush
169  
-    end
170  
-
171  
-    b = Thread.new do
172  
-      @logger.info("x"); Thread.pass;
173  
-      @logger.info("y"); Thread.pass;
174  
-      @logger.info("z"); @logger.flush
  123
+    assert_deprecated do
  124
+      @logger  = Logger.new(log_file)
175 125
     end
176  
-
177  
-    a.join
178  
-    b.join
179  
-
180  
-    assert @output.string.include?("a\nb\nc\n")
181  
-    assert @output.string.include?("x\ny\nz\n")
182  
-  end
183  
-
184  
-  def test_flush_should_remove_empty_buffers
185  
-    @logger.send :buffer
186  
-    @logger.expects :clear_buffer
187  
-    @logger.flush
  126
+    assert File.exist?(tmp_directory)
188 127
   end
189 128
 
190 129
   def test_buffer_multibyte
191  
-    @logger.auto_flushing = 2
192 130
     @logger.info(UNICODE_STRING)
193 131
     @logger.info(BYTE_STRING)
194 132
     assert @output.string.include?(UNICODE_STRING)
@@ -198,57 +136,4 @@ def test_buffer_multibyte
198 136
     end
199 137
     assert byte_string.include?(BYTE_STRING)
200 138
   end
201  
-
202  
-  def test_silence_only_current_thread
203  
-    @logger.auto_flushing = true
204  
-    run_thread_a = false
205  
-
206  
-    a = Thread.new do
207  
-      while !run_thread_a do
208  
-        sleep(0.001)
209  
-      end
210  
-      @logger.info("x")
211  
-      run_thread_a = false
212  
-    end
213  
-
214  
-    @logger.silence do
215  
-      run_thread_a = true
216  
-      @logger.info("a")
217  
-      while run_thread_a do
218  
-        sleep(0.001)
219  
-      end
220  
-    end
221  
-
222  
-    a.join
223  
-
224  
-    assert @output.string.include?("x")
225  
-    assert !@output.string.include?("a")
226  
-  end
227  
-
228  
-  def test_flush_dead_buffers
229  
-    @logger.auto_flushing = false
230  
-
231  
-    a = Thread.new do
232  
-      @logger.info("a")
233  
-    end
234  
-    
235  
-    keep_running = true
236  
-    Thread.new do
237  
-      @logger.info("b")
238  
-      while keep_running
239  
-        sleep(0.001)
240  
-      end
241  
-    end
242  
-
243  
-    @logger.info("x")
244  
-    a.join
245  
-    @logger.flush
246  
-
247  
-
248  
-    assert @output.string.include?("x")
249  
-    assert @output.string.include?("a")
250  
-    assert !@output.string.include?("b")
251  
-    
252  
-    keep_running = false
253  
-  end
254 139
 end
14  railties/lib/rails/application/bootstrap.rb
@@ -24,9 +24,18 @@ module Bootstrap
24 24
       initializer :initialize_logger, :group => :all do
25 25
         Rails.logger ||= config.logger || begin
26 26
           path = config.paths["log"].first
27  
-          logger = ActiveSupport::TaggedLogging.new(ActiveSupport::BufferedLogger.new(path))
  27
+          unless File.exist? File.dirname path
  28
+            FileUtils.mkdir_p File.dirname path
  29
+          end
  30
+
  31
+          f = File.open path, 'w'
  32
+          f.binmode
  33
+          f.sync = !Rails.env.production? # make sure every write flushes
  34
+
  35
+          logger = ActiveSupport::TaggedLogging.new(
  36
+            ActiveSupport::BufferedLogger.new(f)
  37
+          )
28 38
           logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase)
29  
-          logger.auto_flushing = false if Rails.env.production?
30 39
           logger
31 40
         rescue StandardError
32 41
           logger = ActiveSupport::TaggedLogging.new(ActiveSupport::BufferedLogger.new(STDERR))
@@ -37,7 +46,6 @@ module Bootstrap
37 46
           )
38 47
           logger
39 48
         end
40  
-        at_exit { Rails.logger.flush if Rails.logger.respond_to?(:flush) }
41 49
       end
42 50
 
43 51
       # Initialize cache early in the stack so railties can make use of it.

8 notes on commit 04ef93d

Arun Agrawal
Collaborator

One test broken in ActiveSupport :-)

José Valim
Owner

Fixed in cd7fbcb.

Chris Griego

One of the benefits provided by BufferredLogger was that log messages from one request would not interleave with log messages for different requests. Is there a replacement for this functionality?

Aaron Patterson
Owner

It depends on what you want to achieve. If you just want to deal with production logs, then you should log the pid of your process. The default logger from stdlib will automatically log the PID for you, so you can use it instead:

log the pid

Maybe we should switch to use it by default.

Chris Griego

Logging the PID would at least provide a way of sorting it out. The stdlib Logger's default format is pretty verbose and kinda haphazard. A syslog style for production, like the hodel_3000_compliant_logger gem uses, would make a nice core default. :thumbsup:

Alexey Trofimenko

I still don't get rationale behind all this. BufferedLogging with flushing after every request was really, I mean really useful. Much more useful than a little performance boost. Logging pids doesn't make it much more readable.

So, @tenderlove , was it simplifying things the point of that commit, or were there other concerns? I'm thinking about extracting old buffered logger (with autoflushing middleware) into separate gem. Is there something wrong with that idea you can warn me about?

Olli Huotari

I guess there were some issues that if application would crash in the middle of the request you would lose all the logging.

With that in mind I would still appreciate a decent way of choosing if I want to flush log after every requetst.

Filesystem tuning didn't seem a simple thing.

I guess File.sync is now always true: https://github.com/rails/rails/blob/3-2-stable/railties/lib/rails/application/bootstrap.rb#L33

Couldn't BufferedLogger have something like def flush; log_dest.fsync; log_flushable_count = 0; end and also auto flushing after some log_count. The default could be with auto_flush = 0 but it would be easy to set some linecount like it used to be.

I'd rather have nicely outputted logs that I can use daily than the current way of having a mess that is useful with exception that happens once a year.

Hrvoje Simic

@codesnik @holli +1

What's the point of deprecating this?

Please sign in to comment.
Something went wrong with that request. Please try again.