7

I must really be missing something obvious, but I'm having trouble with general use of Log4r in my Ruby application. I am able to log without issue, but the overhead seems clunky the way I have it setup. I'm basically passing the full path to a filename to log in each class in my application. The ruby script that is called pulls the log file from one of the arguments in ARGV which is then passed around and set in each class that I call in ruby. In each class I use the patternFormatter to insert the class/file name into the log statement.

Is there a better way to make this work? It feels like no matter what I think of will require something to be passed to each class in my ruby application. I could set the log file in a yaml configuration file instead, but then I would be passing around the configuration file to each class as well.

Any advice? If this doesn't make sense I could try and post some more specific code samples to further explain what I mean.

Thanks!

Spencer
  • 85
  • 1
  • 7

2 Answers2

14

I'm the maintainer of log4r,

For individual scripts (different .rb files), you could approach this in a few different ways (fitting, I know), first, be mindful that the features I'm covering here are available in >= 1.1.4.

One way would be to set a different PatternFormatter string per script (if you create a yaml or xml configuration file, you can specify different patterns on a per class name basis).

Another way would be to use one of GDC, NDC or MDC in a PatternFormatter.

GDC will set a "Global Diagnostic Context" which is to say, it's a value that is available from all threads running a script. You can use it by putting %g in the pattern and setting the value via GDC.set(String) for more detailed information, see: http://log4r.rubyforge.org/manual.html

NDC and MDC are Nested and Mapped Diagnostic Contexts respectively. The pattern for these is to use %x and %X{Symbol|Object}, and to set them via NDC.set(String) and MDC.put(Symbol|Object, Object)

Yet another way would be to use the Pattern %t, which prints out the filename and line number of where the call was made.

The trade off between each of these methods is that they are progressively more expensive in CPU resource use. I tend to first use GDC for the sort of thing you're asking for.

C G-K
  • 362
  • 2
  • 11
12

Hmm, any reason why you don't instantiate Log4r::Logger class at the beginning of your script and pass the instance around? You don't even have to pass it around, you can always get it by name from Logger class:

run.rb:

require 'log4r'
require 'class_a'

logger = Log4r::Logger.new('test')
logger.outputters << Log4r::Outputter.stdout
logger.outputters << Log4r::FileOutputter.new('logtest', :filename =>  'logtest.log')
logger.info('started script')
a = A.new
a.do_something
logger.info('finishing')

class_a.rb:

class A
  def do_something
    logger = Log4r::Logger['test']
    logger.info('in do_something')
    puts 'hi!'
  end
end

and when you run run.rb you get:

$ ruby run.rb 
 INFO test: started script
 INFO test: in do_something
hi!
 INFO test: finishing

and a log file named logtest.log on disk.

Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
  • I can't seem to make that work. The call to Logger['mylog'] in another .rb file returns nil. – Spencer Mar 18 '10 at 20:27
  • And how would I tell Logger what file to log out to? – Spencer Mar 18 '10 at 20:33
  • Take a look at the updated example above. What do you mean when you say "another .rb file"? – Mladen Jablanović Mar 18 '10 at 20:55
  • 2
    If you have many connected ruby scripts that do not run in the same instance (such as one script calling another externally, or a batch that runs 10 scripts sequentially) then you will need to instantiate the logger in each script separately. If you are discussing other scripts that you include via 'require', then you may need to create your Log4r instance before requiring these external files, if they log during the first pass through the file (rather than just when they are used later). – Myrddin Emrys Mar 19 '10 at 00:21
  • Eureka! Your code example above made it crystal clear Mladen. Now I will just need to figure out how I can change the log tag or whatever you call it that is displayed at the beginning of each line to display the ruby class or filename. But, now I'm off on the right track. Thanks! – Spencer Mar 19 '10 at 13:58
  • Well, I figure I may as well continue my logging saga with an add on question. Would there be a better way to get the executing filename into the log file over doing the following in class A's do_something method? logger.info("<#{File.basename(__FILE__)}> in do_something") Note, there are two underscores prefixing/postfixing the word FILE above. It was marked up as bold instead of being displayed. – Spencer Mar 19 '10 at 17:30
  • That sounds ok, if you want to log the filename once. If you'd want to print the file name as part of each log message, you'd probably better change the logger's formatter. – Mladen Jablanović Mar 19 '10 at 17:46
  • That's my real question, I do not see anything in PatternFormatter that will give me the filename or ruby class that is currently executing/logging. I do not like the prospects of adding '<#{File.basename(FILE)}>' each time I log. – Spencer Mar 19 '10 at 17:52
  • Perhaps you should open a new question... :) – Mladen Jablanović Mar 19 '10 at 18:19