Subclassing Logging

Tim,

I am trying to subclass the logging module to have a consistent setup for my classes. They all log to the same rolling file. I seem to get two different errors.

Error one:
require 'fileutils'
require 'logging'

module MyLogger
class Log < Logging::Logger
def initialize(config) # config not used in test but will be.
   super("reader")
   Logging.appenders.stdout(:level => :debug)
   file_path = "./reader.log"
   Logging.appenders.rolling_file(
   "readerlog",
   :filename => file_path,
   :age => :daily,
   :layout => Logging.layouts.json)
   add_appenders('stdout','readerlog')
end
end
end

Take a look at this example logging configuration. It walks though the concept of logger hierarchies in the logging framework.

This, I believe, is what you are trying to accomplish. You should not have to subclass the Logging:Logger. Take a look at the other example files in that "examples" folder; they walk through in code some of the concepts behind the logging framework.

Logging.appenders.stdout(
  'stdout',
  :layout => Logging.layouts.pattern
)

Logging.appenders.rolling_file(
  'readerlog',
  :filename => './reader.log',
  :age => :daily,
  :layout => Logging.layouts.json
)

logger = Logging.logger['YourModule']
logger.appenders = ['stdout', 'readerlog']
logger.level = :info

In the above snippet of code, I created two appenders. The first is named "stdout" and will use a pattern based layout to write log messages to STDOUT. The second is named "readerlog" and will use a JSON layout to write log messages to a file called "reader.log".

I then created a new logger instance, added the two appenders by name, and then set the log level to 'info'.

Now you can create nested loggers that will use the same appenders and have the same default log level.

another_logger = Logging.logger['YourModule::Another']
yet_another_logger = Logging.logger['YourModule::Another::Yet']

with this in a larger program or by it self I get:

NoMethodError: undefined method `synchronize' for nil:NilClass
       from /usr/local/ruby/lib/ruby/gems/1.8/gems/logging-1.4.1/lib/logging/logger.rb:51:in `new'
       from (irb):2
irb command is l = MyLogger::Log.new('test')

If I remove the parameter from the initialize, I can make it work standalone.

I tried using rspec in the larger app and just the test for log fails the same with the parameter. I take it off and the init passes but the actual log does not return.

The Logging framework has built in support for rspec. It can be configured to log all messages to a StringIO instance during tests. You can then read out your log messages from the StringIO and make sure the right messages are making it into the log.

In your spec_helper file ....

require 'spec/logging_helper'

Spec::Runner.configure do |config|
  include Spec::LoggingHelper
  config.capture_log_messages
end

And in your actual specs ...

it 'logs a message on error' do
  @foo.error_method
  @log_output.readline.should == 'This is your error message'
end

I hope all this helps.

Blessings,
TwP

PS I cc'd the ruby-talk mailing list so this information can be found by others.

···

On Apr 30, 2010, at 11:23 PM, dhf@dhfrench.com wrote:

describe MyLogger::Log do
it "should initialize" do
   l = MyLogger::Log.new()
   l.class.should == MyLogger::Log
end
end

describe MyLogger::Log do
before :each do
   @log = MyLogger::Log.new
end

it "should log a debug" do
   @log.info "Rspec debug".should == true
end

end

the @log.info fails with:
'MyLogger::Log should log a debug' FAILED
expected: true,
    got: "Rspec debug" (using ==)

I can use any help you can give.

Don French

> Tim,

> I am trying to subclass the logging module to have a consistent setup for my classes. They all log to the same rolling file. I seem to get two different errors.

> Error one:
> require 'fileutils'
> require 'logging'

> module MyLogger
> class Log < Logging::Logger
> def initialize(config) # config not used in test but will be.
> super("reader")
> Logging.appenders.stdout(:level => :debug)
> file_path = "./reader.log"
> Logging.appenders.rolling_file(
> "readerlog",
> :filename => file_path,
> :age => :daily,
> :layout => Logging.layouts.json)
> add_appenders('stdout','readerlog')
> end
> end
> end

Take a look at this example logging configuration. It walks though the concept of logger hierarchies in the logging framework.

logging/examples/hierarchies.rb at master · TwP/logging · GitHub

This, I believe, is what you are trying to accomplish. You should not have to subclass the Logging:Logger. Take a look at the other example files in that "examples" folder; they walk through in code some of the concepts behind the logging framework.

Logging.appenders.stdout(
'stdout',
:layout => Logging.layouts.pattern
)

Logging.appenders.rolling_file(
'readerlog',
:filename => './reader.log',
:age => :daily,
:layout => Logging.layouts.json
)

logger = Logging.logger['YourModule']
logger.appenders = ['stdout', 'readerlog']
logger.level = :info

In the above snippet of code, I created two appenders. The first is named "stdout" and will use a pattern based layout to write log messages to STDOUT. The second is named "readerlog" and will use a JSON layout to write log messages to a file called "reader.log".

I then created a new logger instance, added the two appenders by name, and then set the log level to 'info'.

Now you can create nested loggers that will use the same appenders and have the same default log level.

another_logger = Logging.logger['YourModule::Another']
yet_another_logger = Logging.logger['YourModule::Another::Yet']

> with this in a larger program or by it self I get:

> NoMethodError: undefined method `synchronize' for nil:NilClass
> from /usr/local/ruby/lib/ruby/gems/1.8/gems/logging-1.4.1/lib/logging/logger.rb:51:in `new'
> from (irb):2
> irb command is l = MyLogger::Log.new('test')

> If I remove the parameter from the initialize, I can make it work standalone.

> I tried using rspec in the larger app and just the test for log fails the same with the parameter. I take it off and the init passes but the actual log does not return.

The Logging framework has built in support for rspec. It can be configured to log all messages to a StringIO instance during tests. You can then read out your log messages from the StringIO and make sure the right messages are making it into the log.

In your spec_helper file ....

require 'spec/logging_helper'

Spec::Runner.configure do |config|
include Spec::LoggingHelper
config.capture_log_messages
end

And in your actual specs ...

it 'logs a message on error' do
@foo.error_method
@log_output.readline.should == 'This is your error message'
end

I hope all this helps.

Blessings,
TwP

PS I cc'd the ruby-talk mailing list so this information can be found by others.

> describe MyLogger::Log do
> it "should initialize" do
> l = MyLogger::Log.new()
> l.class.should == MyLogger::Log
> end
> end

> describe MyLogger::Log do
> before :each do
> @log = MyLogger::Log.new
> end

> it "should log a debug" do
> @log.info "Rspec debug".should == true
> end

> end

> the @log.info fails with:
> 'MyLogger::Log should log a debug' FAILED
> expected: true,
> got: "Rspec debug" (using ==)

> I can use any help you can give.

> Don French

Thanks for the information.

This is the structure. The live processor (could be several) are
instantiated my main and need to log what happens in their own
processor. Same thing with the test processor. Can the two "reader"
logger ids be configured different? In testing it seems that all write
to all the log files. If I change the top level name, how does the
lower level objects know what the top level is? I know I can pass to
every object the mode, but really do not like doing that. They do not
care other than for logging.

main
-- live_processor Logger['reader'] Logs into a file called
live_tag.log tag changes for each live processor instantiated.
   -- processor class Logger['reader::processor']
  -- class_1 Logger['reader::processor::class_1'] or
can I just do 'reader::processor'
      > -- class_2
      > >
           > >
      > -- class_x
  -- class_y
-- test_processor Logger['reader'] Logs into a file called test.log
  -- class_1 Logger['reader::processor']
      > -- class_2 Logger['reader::processor::class_1'] or can I just
do 'reader::processor'
      > >
           > >
      > -- class_x
  -- class_y

Thanks

Don

···

On May 1, 11:29 am, Tim Pease <tim.pe...@gmail.com> wrote:

On Apr 30, 2010, at 11:23 PM, d...@dhfrench.com wrote: