1

It is not a duplication. The "object" expression is the keyword.

I am trying to integrate Timber Android Library with https://github.com/orhanobut/logger and I need a customLogStrategyForTimber for my needs.

I came up with this logic. But I am having difficulty on initializing the customLogStrategyForTimber

class App : Application() {
    // I CREATED THE lateinit VARIABLE
    lateinit var customLogStrategyForTimber: LogStrategy
    
    override fun onCreate() {
        super.onCreate()
        Timber.plant(object : Timber.DebugTree() {
            override fun log(
                priorityTimber: Int, tagTimber: String?, message: String, t: Throwable?
            ) {
                
                // USED THE lateinit HERE
                customLogStrategyForTimber = object : LogcatLogStrategy() {
                    override fun log(priority: Int, tag: String?, message: String) {
                        super.log(priorityTimber, tagTimber, message)
                    }
                }

 println("customLogStrategyForTimber: ${::customLogStrategyForTimber.isInitialized}") // PRINTS TRUE
            }
        })

        println("customLogStrategyForTimber OUTSIDE: ${::customLogStrategyForTimber.isInitialized}") // PRINTS FALSE - WHY??


            var formatStrategy1 = PrettyFormatStrategy.newBuilder()
                
                // TRYING TO CALL THE lateinit VARIABLE HERE
                // NOW, HERE THE lateinit IS NOT INITIALIZED.
                .logStrategy(customLogStrategyForTimber)
                .build()
            Logger.addLogAdapter(AndroidLogAdapter(formatStrategy1))
    }
}

The customLogStrategyForTimber is never initialized. Is there a better way to do this logic? Trying to add the entire formatStrategy code inside the first override fun log method results in unexpected behaviour when using Timber logging, so that does not seem to be the easy option.

Crash when running the App

  Caused by: kotlin.UninitializedPropertyAccessException: lateinit property customLogStrategyForTimber has not been initialized

Tried using isInitialized as well.

The code inside it never runs.

EDIT:

Created a sample project: https://github.com/shipsywor/demotimberlogger

EDIT 2:

I added println statements to the code above. You will see that ::customLogStrategyForTimber.isInitialized returns False at one point of code and True at another

NOTE: I cannot put formatStrategy code inside of Timber.plant {... }. It results in unexpected behavior.

dsf
  • 1,203
  • 1
  • 6
  • 11
  • Remove the val keyword from the object initialization. – Eyosiyas Jun 15 '21 at 07:46
  • I edited the post. It was unintentionally added. – dsf Jun 15 '21 at 08:03
  • Will this link help - https://stackoverflow.com/questions/40176083/lateinit-property-has-not-been-initialized ? – Amit Dash Jun 15 '21 at 08:33
  • @AmitDash unfortunately, I am unable to resolve issue by looking at the answers in the link. If you could, please notice that I have added `println` statements to the code in the post . At one point `customLogStrategyForTimber.isInitialized` returns True and at another, it returns False. This could give you a better understanding of the problem. Thanks – dsf Jun 15 '21 at 08:41

4 Answers4

1

It looks like you have needlessly created a local val customLogStrategyForTimber, which is only accessible within the Timber.plant() method. In effect, you're not really using the lateinit var that you have declared at the top of your class. If you just removed val from object declaration inside the Timber.plant() method, your code would work as you intend it to.

As it is now, the object that you have declared stays inside the Timber.plant() method and isn't accessible on the outside.

PurrgnarMeowbro
  • 338
  • 5
  • 14
  • I am sorry. I just updated the post in response to a comment. The local val was unintentionally added while creating the post. Could you please take a look at the updated post now – dsf Jun 15 '21 at 08:04
  • Does it still not work after removing local `val`? – Amit Dash Jun 15 '21 at 08:05
  • @AmitDash It crashes with exception: Caused by: kotlin.UninitializedPropertyAccessException: lateinit property customLogStrategyForTimber has not been initialized – dsf Jun 15 '21 at 08:05
1

Let me explain to you. Timber.plant is a thread which will take some time to complete its work. Your code where you are getting false in log it suddenly run after Timber.plan and print false meanwhile Timper.plan thread is executing parallel and initialized variable and then print where you are getting true.

You should do like this:

     class App : Application() {
 
    lateinit var customLogStrategyForTimber: LogStrategy
 
    override fun onCreate() {
        super.onCreate()
 
        Timber.plant(object : Timber.DebugTree() {
 
            override fun log(
                priorityTimber: Int, tagTimber: String?, message: String, t: Throwable?
            ) {
                customLogStrategyForTimber = object : LogcatLogStrategy() {
                    override fun log(priority: Int, tag: String?, message: String) {
                        super.log(priorityTimber, "global_tag_$tagTimber", message)
                    }
                }
                    Logger.d(message)
            }
        })
 
 
        thread {
            val formatStrategy: FormatStrategy = PrettyFormatStrategy.newBuilder()
                .showThreadInfo(false)
                .methodCount(1)
                .methodOffset(5)
                .logStrategy(customLogStrategyForTimber)
                .build()
 
            Logger.addLogAdapter(AndroidLogAdapter(formatStrategy))
 
            println("global_tag INSIDE thread: ${::customLogStrategyForTimber.isInitialized}")
 
        }
 
        Timber.d("Initialize Timber")
    }
}
Muhammad Zahab
  • 1,049
  • 10
  • 21
  • I am sorry. I just updated the post in response to a comment. The local val was unintentionally added while creating the post. Could you please take a look at the updated post now. It crashes with exception: Caused by: kotlin.UninitializedPropertyAccessException: lateinit property customLogStrategyForTimber has not been initialized – dsf Jun 15 '21 at 08:06
  • Its best practice to always check before using variable like this if (::customLogStrategyForTimber.isInitialized) { } – Muhammad Zahab Jun 15 '21 at 08:18
  • I tried that as well `isInitialized`. The code inside that Never runs. – dsf Jun 15 '21 at 08:18
  • Let me check and build then i will get back to you. – Muhammad Zahab Jun 15 '21 at 08:50
  • 1
    *Its best practice to always check before using variable like this* - where did you see this? If anything, this would hide the problem... – Joffrey Jun 15 '21 at 08:55
  • Thank you. Theres a github link in the post. Please use the latest commit. – dsf Jun 15 '21 at 08:58
  • Let me explain to you. Timber.plant is a thread which will take some time to complete its work. Your code where you are getting false in log it suddenly run after Timber.plan and print false meanwhile Timper.plan thread is executing parallel and initialized variable and then print where you are getting true. – Muhammad Zahab Jun 15 '21 at 08:58
  • @MuhammadZahabAhmadKhan that makes sense! How should one resolve it? – dsf Jun 15 '21 at 08:59
  • Place your code: var formatStrategy1 = PrettyFormatStrategy.newBuilder() .logStrategy(customLogStrategyForTimber) .build() Logger.addLogAdapter(AndroidLogAdapter(formatStrategy1)) inside block where you are getting true value. Then your app will work well. – Muhammad Zahab Jun 15 '21 at 09:02
  • I have edited my answer. Please check and don't forget to mark it as best if it really helps. Thank you very much. – Muhammad Zahab Jun 15 '21 at 09:06
  • @MuhammadZahabAhmadKhan There is a reason why I cannot do that. When doing that and using Timber.d calls, every call will print duplicate log statement. Try doing this in code anywhere: Timber.d("Nice Log 1") Timber.d("Cool Log 2") The "Cool Log 2" will print twice! If you later added another Timber.d("Awesome Log 3") The "Awesome Log 3" will print 3 times! You can try this in the code by putting formatStrategy code inside: https://github.com/shipsywor/demotimberlogger.git (latest commit) – dsf Jun 15 '21 at 09:09
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/233785/discussion-between-muhammad-zahab-ahmad-khan-and-dsf). – Muhammad Zahab Jun 15 '21 at 09:27
  • @MuhammadZahabAhmadKhan I accepted the answer. However later I realized that when we use the thread { ... } solution, the Tags are messed up. You know how Timber automatically detects Tags, but now when the thread, it always use "App" tag i.e the Application class name as tag and not appropriate class name even when doing Timber.d from Activity or some Fragment ... That is troublesome – dsf Jun 15 '21 at 13:16
  • I don't see how this solution will work. Since `customLogStrategyForTimber` is not initialized until something is logged, the code in the `thread` block is still going to see an uninitialized value for `customLogStrategyForTimber`. – Tenfour04 Jun 15 '21 at 14:10
1

The code where you initialize your lateinit var is in the log() of the DebugTree implementation you created on the spot. It is not executed when calling Timber.plant() here, you're just registering an instance of DebugTree with some implementation.

So when you reach the println with "OUTSIDE", that log method has never been called yet, so the customLogStrategyForTimber was not initialized.

Joffrey
  • 32,348
  • 6
  • 68
  • 100
  • can you please suggest a workaround or solution to make this work? – dsf Jun 15 '21 at 09:28
  • You should move your initialization code outside of the `DebugTree` declaration. I did not provide a solution because I don't know Timber, and I don't really know how it should be configured. For instance, why did you initially try to configure the logger on each log call? – Joffrey Jun 15 '21 at 09:31
  • I added the code `customLogStrategyForTimber: LogStrategy ...... ` inside Debug tree because I wanted to access the tagTimber, priorityTimber values. Someone commented about thread: "Timber.plant is a thread which will take some time to complete its work. Your code where you are getting false in log it suddenly run after Timber.plan and print false meanwhile Timper.plan thread is executing parallel and initialized variable and then print where you are getting true." I think the whole Thread thing makes sense (!?) Is there a way to use Thread to create a solution? – dsf Jun 15 '21 at 09:45
  • btw to initialize Timber, you only need ` Timber.plant(object : Timber.DebugTree())` everything inside that is a Logger intergration of customLog strategy I am trying to do: https://github.com/orhanobut/logger#advanced – dsf Jun 15 '21 at 09:46
  • But you don't need to access tagTimber, priorityTimber, etc. They are given as parameters of the log method in `LogcatLogStrategy` – Joffrey Jun 15 '21 at 09:46
  • They provide different values as seen: https://i.imgur.com/YwLy3cG.png The parameters tag and tagTimber provide different values. That is is the reason I am trying to integrate different libraries to get best of both worlds. Here, tagTimber is desirable. – dsf Jun 15 '21 at 09:52
0

Based on your comments on other answers, I think I understand what you're trying to do.

It looks like this would be less fragile code if you had a concrete implementation of LogcatLogStrategy where you have properties you can set.

class MyLogcatLogStrategy: LogcatLogStrategy() {

    var timberTag: String? = null
    var timberPriority: Int = -1

    override fun log(priority: Int, tag: String?, message: String) {
        super.log(timberPriority, timberTag, message)
    }
}

Then you can have a single instance and can update it safely from anywhere.

class App : Application() {
    val customLogStrategyForTimber = MyLogcatLogStrategy()
    
    override fun onCreate() {
        super.onCreate()
        Timber.plant(object : Timber.DebugTree() {
            override fun log(
                priority: Int, tag: String?, message: String, t: Throwable?
            ) {
                  customLogStrategyForTimber.apply {
                      timberTag = tag
                      timberPriority = priority
                  }
            }
        }
        val formatStrategy1 = PrettyFormatStrategy.newBuilder()
            .logStrategy(customLogStrategyForTimber)
            .build()
        Logger.addLogAdapter(AndroidLogAdapter(formatStrategy1))
    }
}

What I don't know is if this updates your tag value on the correct thread or in the correct order for the tag to show up in your log correctly.

It would probably be easier to eliminate Timber entirely and just copy its method of determining the tag out of its source code into your LogcatLogStrategy, but I'm not familiar with this other logging library you're using. Maybe something like this:

class MyLogcatLogStrategy: LogcatLogStrategy() {
    private val ignoredClassNames = listOf(
        MyLogcatLogStrategy::class.java.name,
        /* The names of all the classes in your logging library */
    )

    //Adapted from Timber:
    private val tag: String?
      get() = Throwable().stackTrace
          .first { it.className !in ignoredClassNames }
          .let(::createStackElementTag)

    override fun log(priority: Int, ignoredTag: String?, message: String) {
        super.log(priority, this.tag, message)
    }

    // From Timber: (https://github.com/JakeWharton/timber/blob/master/timber/src/main/java/timber/log/Timber.kt#L216)
    private fun createStackElementTag(element: StackTraceElement): String? {
      var tag = element.className.substringAfterLast('.')
      val m = ANONYMOUS_CLASS.matcher(tag)
      if (m.find()) {
        tag = m.replaceAll("")
      }
      // Tag length limit was removed in API 24.
      return if (tag.length <= MAX_TAG_LENGTH || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        tag
      } else {
        tag.substring(0, MAX_TAG_LENGTH)
      }
    }

    companion object {
      private val ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$")
    }
}
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • I came up with this code: https://pastebin.com/d9xvi3ay with others help using `thread {...}` It compiles and works BUT there is an issue, I realized that when we use the thread { ... } solution, the Tags are messed up. You know how Timber automatically detects Tags, but now when the thread is used, it always use "App" tag i.e the Application class name as tag and not the appropriate class name even when doing Timber.d from Activity or some Fragment ... That is troublesome – dsf Jun 15 '21 at 13:24
  • Can you please add code in the answer or paste gist for this you mentioned: "It would probably be easier to eliminate Timber entirely and just copy its method of determining the tag out of its source code" It would be very helpful! – dsf Jun 15 '21 at 13:27
  • I added a possible example, but since I don't know your logging library, it's a guess. – Tenfour04 Jun 15 '21 at 13:38