3

I'm using a small Groovy application with Spring Social to consume a stream from Twitter. I now want to log every Tweet I receive, including some detail.

If the Tweet class would be inside my own code, i can easily do this to get a nice log output:

import groovy.transform.ToString

@ToString
class Tweet {
    // fields...
}

But since the class I want to log is inside Spring Social, I can't do that. Is there a "Groovy" way of overriding the toString method?

Adding a toString Closure to the metaClass does not really help, since I don't want to call toString explicitly at every log output.

org.springframework.social.twitter.api.Tweet.metaClass.toString = {
    "${delegate.field1}, ${delegate.field2}"
}

// still prints "received tweet [org.springframework.social.twitter.api.Tweet@637c1991]"
log.info("received tweet [{}]", tweet) 

// prints "received tweet [field1 field]"
log.info("received tweet [{}]", tweet.toString())
Patrick
  • 915
  • 1
  • 8
  • 17
  • 2
    `Tweet.metaClass.toString = { "$delegate.field1 $delegate.field2" }`? – tim_yates Jul 22 '17 at 15:40
  • In general I'd avoid doing this though as it's hard to find, hard to get in the right place, and will not work if the code is invoked from Java. You must have something that contains one or more `Tweet` instances? Why not just do a toString method there? – tim_yates Jul 22 '17 at 15:42
  • @tim_yates that's true, if I had a choice I would go for a decorator, it is definitely way cleaner solution. – Mateusz Chrzaszcz Jul 22 '17 at 15:47
  • Adding a toString closure to the metaClass does not help, since I explicitly have to call `toString` at every log output – Patrick Jul 22 '17 at 16:01
  • What about `log.info("received tweet [$tweet]")` – tim_yates Jul 22 '17 at 16:10
  • @tim_yates even if this log.info change works, it will mess with logger performance as it will turn off "parametrized messages" feature. – Mateusz Chrzaszcz Jul 22 '17 at 16:16
  • You turn off info logging? – tim_yates Jul 22 '17 at 16:19
  • No no, it will still be logging but it might impact performance, have a look at https://www.slf4j.org/faq.html , under "Better yet, use parameterized messages". – Mateusz Chrzaszcz Jul 22 '17 at 16:22
  • Looks like there is no ideal way to achieve the wanted behaviour, so if anyone wants to write an answer with these possibilities and the potential pitfalls feel free to do so and I will accept it. – Patrick Jul 24 '17 at 09:43

1 Answers1

2

Yes, you can do that. Groovy has got the capacity to inject new method to existing class using metaClass capabilities. When you call a method on a class the so called metaclass registry is inspected to see if a metaClass extension method is present for that class instance.

Simply define:

Tweet.metaClass.toString = {"${delegate.field}, ${delegate.anotherField}"}

Mateusz Chrzaszcz
  • 1,240
  • 14
  • 32
  • That does not work for log-output, I updated the question – Patrick Jul 22 '17 at 16:06
  • I am afraid that if Tweet is a Java not Groovy class, this is as far as you can go. Have a look at groovy docs or this topic: https://stackoverflow.com/questions/1927796/using-groovy-metaclass-to-overwrite-methods#2404388 – Mateusz Chrzaszcz Jul 22 '17 at 16:10
  • 3
    Not strictly true. It will work if called from groovy, but not if called from Java. So as long as you're logging in a groovy class, that's fine. If a Java class is logging, it won't work – tim_yates Jul 22 '17 at 16:14
  • @tim_yates thanks for sharing, I didn't know that :) – Mateusz Chrzaszcz Jul 22 '17 at 16:17