15

I'm author of the logging package on CRAN, I don't see myself as an R programmer, so I tried to make it as code-compatible with the Python standard logging package as I could, but now I have a question. and I hope it will give me the chance to learn some more R!

it's about hierarchical loggers. in Python I would create a logger and send it logging records:

l = logging.getLogger("some.lower.name")
l.debug("test")
l.info("some")
l.warn("say no")

In my R package instead you do not create a logger to which you send messages, you invoke a function where one of the arguments is the name of the logger. something like

logdebug("test", logger="some.lower.name")
loginfo("some", logger="some.lower.name")
logwarn("say no", logger="some.lower.name")

the problem is that you have to repeat the name of the logger each time you want to send it a logging message. I was thinking, I might create a partially applied function object and invoke that instead, something like

logdebug <- curry(logging::logdebug, logger="some.lower.logger")

but then I need doing so for all debugging functions...

how would you R users approach this?

mariotomo
  • 9,438
  • 8
  • 47
  • 66
  • guys, I received two interesting answers, both adding requirements to my small library. in the long run, I think I prefer the one based on `ReferenceClasses`, but in the short run `proto` allows me to use R2.11... – mariotomo Mar 04 '11 at 13:58

3 Answers3

29

Sounds like a job for a reference class ?setRefClass, ?ReferenceClasses

Logger <- setRefClass("Logger",
                  fields=list(name = "character"),
                  methods=list(
                    log = function(level, ...) 
                          { levellog(level, ..., logger=name) },
                    debug = function(...) { log("DEBUG", ...) },
                    info = function(...) { log("INFO", ...) },
                    warn = function(...) { log("WARN", ...) },
                    error = function(...) { log("ERROR", ...) }
                    ))

and then

> basicConfig()
> l <- Logger$new(name="hierarchic.logger.name")
> l$debug("oops")
> l$info("oops")
2011-02-11 11:54:05 NumericLevel(INFO):hierarchic.logger.name:oops
> l$warn("oops")
2011-02-11 11:54:11 NumericLevel(WARN):hierarchic.logger.name:oops
> 
mariotomo
  • 9,438
  • 8
  • 47
  • 66
Martin Morgan
  • 45,935
  • 7
  • 84
  • 112
  • I have a [question](http://stackoverflow.com/questions/5140447) that follows this answer... – mariotomo Feb 28 '11 at 09:58
  • I think using reference classes just for syntactic sugar is a bad idea because it makes code harder to reason about. You can imPlement the same functionality by returning a list of functions. – hadley Feb 28 '11 at 13:58
  • @hadley I saw this as aligning the concept of a 'logger' with reference semantics, rather than syntactic sugar. – Martin Morgan Mar 02 '11 at 04:20
  • But you're not actually modifying anything, which makes the use of reference semantics moot. – hadley Mar 02 '11 at 14:24
  • I would have expected the logger to be writing to (i.e., modifying) a connection, e.g., file on disk. – Martin Morgan Mar 02 '11 at 17:39
3

This could be done with the proto package. This supports older versions of R (its been around for years) so you would not have a problem of old vs. new versions of R.

library(proto)
library(logging)

Logger. <- proto(
        new = function(this, name)
            this$proto(name = name),
        log = function(this, ...) 
            levellog(..., logger = this$name),
        setLevel = function(this, newLevel) 
            logging::setLevel(newLevel, container = this$name),
        addHandler = function(this, ...)
            logging::addHandler(this, ..., logger = this$name), 
        warn = function(this, ...)
            this$log(loglevels["WARN"], ...),
        error = function(this, ...)
            this$log(loglevels["ERROR"], ...) 
)
basicConfig()
l <- Logger.$new(name = "hierarchic.logger.name")
l$warn("this may be bad")
l$error("this definitely is bad")

This gives the output:

> basicConfig()
> l <- Logger.$new(name = "hierarchic.logger.name")
> l$warn("this may be bad")
2011-02-28 10:17:54 WARNING:hierarchic.logger.name:this may be bad
> l$error("this definitely is bad")
2011-02-28 10:17:54 ERROR:hierarchic.logger.name:this definitely is bad

In the above we have merely layered proto on top of logging but it would be possible to turn each logging object into a proto object, i.e. it would be both, since both logging objects and proto objects are R environments. That would get rid of the extra layer.

See the http://r-proto.googlecode.com for more info.

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • Gabor, I'd like to remove the [distributing R package with optional dependencies](http://stackoverflow.com/questions/5140447) question, but there you attached an answer which is useful here. if you moved it from there to here, I'll remove the whole dRpwod question. I tried to do the move myself, but my edits were rejected. – mariotomo Mar 02 '11 at 08:49
  • There is a delete button but it does not appear to delete the post. It only asks me if I want to vote to delete it. I have moved the post and have replaced this one with a pointer to the new one. – G. Grothendieck Mar 02 '11 at 13:26
  • can you edit this answer and paste here the text of the other answer? I will then remove (vote for removal) of the other whole question. – mariotomo Mar 02 '11 at 21:34
  • I tried to add proto to the logging package, but I did not manage. reference classes were easier to use after all. – mariotomo Jul 20 '11 at 07:27
1

Why would you repeat the name? It would be more convenient to pass the log-object directly to the function, ie

logdebug("test",logger=l)
# or
logdebug("test",l)

A bit the way one would use connections in a number of functions. That seems more the R way of doing it I guess.

Joris Meys
  • 106,551
  • 31
  • 221
  • 263
  • Might be more R-ish to have the logger as the first parameter and then logdebug is a method for the logger class. – Spacedman Feb 10 '11 at 17:43
  • @Spacedman : I'd say so too, but I kept as close as possible to the OPs construct. It would be easier with S4 though. – Joris Meys Feb 10 '11 at 19:00
  • 5
    "It would be easier with S4" - famous last words :P – hadley Feb 10 '11 at 21:52
  • @Hadley : OK, you got me there. :-) (I meant that putting logger as first argument would make using S4 easier... or less painful...) – Joris Meys Feb 10 '11 at 21:56
  • also the other question is S4 based, but easier to follow for someone used to Python, Java and C++. anyhow, I'm following your hints and I'm going to read more about S4. thanks to all of you! – mariotomo Feb 11 '11 at 11:02
  • @mariotomo : reference classes are NOT S4 classes, there's a huge difference. And indeed, for OOP-minded programmers from other languages, reference classes seem more natural. I'll stick to the good ol' functional way. – Joris Meys Feb 11 '11 at 11:11