Better Logging in Android using Timber

Better Logging in Android using Timber

Ikromjonov Ma'rufjon
timber library.

Android studioda ishlaganimizda biz Loglarni chiqarib ko'rishga o'rganib qolganmiz, bu yerda Loglar sifatida errorlarni, ma'lumotlarni mavjudligi yoki dasturimizni qaysidir qismlari ishlashini tekshirish va shunga o'xshagan hollarda foydalanamiz. Buning uchun biz Log classidan foydalanganmiz.

Masalan: MainActivityda onCreate() da Log yozilgan.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.d("TTT","work")
        Log.e("TTT","work")
    }
}

Shu o'rinda dasturimizni tugatganimizdan keyin bizda muammo yuzaga keladi, butun dasturimiz ichida ko'p joylarda Loglar yozilgan bo'lsa biz ularni o'chirib chiqishimiz kerak bo'ladi albatta bu yoqimsiz holat ayniqsa Loglar juda ko'p bo'lsa, negaki dasturimiz release bo'lgandan keyin biz uni ommaga tarqatganda, dasturimizni boshqalar loglarini bilishi mumkin. Bunga yechim sifatida juda ko'plab yechimlar bor.

Masalan: Bunda Logimiz dasturmizni DEBUG holatda ishlaydi.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (BuildConfig.DEBUG){
            Log.d("TTT","work")
        }
    }
}

Lekin bu yechimni har bitta Logda ishlatsak dasturimizda ortiqcha kodlar ko'payib ketadi. Hozir sizlarga boshqacha yechim taklif qilmoqchiman, bu Jake Wharton tomonidan yaratilgan Timber kutubxonasi haqida aytmoqchiman. Undan foydalanish uchun bir nechta oddiy qadamlarni amalga oshirishimiz kerak.

1- qadam uni Gradelga qo'shishimiz kerak

implementation 'com.jakewharton.timber:timber:4.7.1'

2- qadam Uni dasturimiz kirish qismida registratsiya qilishimiz kerak, Bunda men Application classdan voris olib yangi App class yaratish usulini taklif qilaman.

class App : Application() {

    override fun onCreate() {
        super.onCreate()

        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree())
        }
    }
}

3-qadam App classimizni manifestda ko'rsatishimiz kerak.

android:name=".App"

Bo'ldi endi biz Timberdan bemalol foydalansak bo'ladi.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Timber.d("example")
}}   
Natijalarni anroidni pastki qismidagi LogCat qismini tanlab ko'rishimiz mumkin.


Timberdan foydalanishning foydali tomonlari.

  1. TAGlar haqida xavotirlanishga hojat yo'q: Timber o'zi biz uchun TAG larni avtomatik ravishda yaratadi, shuning uchun har bir classga global TAG qo'yilishi haqida tashvishlanmasak ham bo'ladi.

2. Dasturdagi Loglarini qo'lda olib tashlashning hojati yo'q: Yuqoridagi koddan ko'rinib turganidek Loglar faqat DEBUG holatda chiqadi, RELEASE da chiqmaydi, va yaxshi tomoni biz bu ishni qo'lda qilmaymiz.

3. Dasturda yuzaga kelishi mumkin bo'gan kamchiliklar: Ishlab chiqarishda har qancha testlasak ham baribir ommaga taqdim qilsak xatolar chiqishi ehtimoli bor, shunda bizga Timber yordam beradi. Qanday deysizmi DEBUG holatda Loglarni ekranga RELEASE da esa barcha Loglarni o'zimizning crashlyticsga yuborishimiz mumkin.

4. Xatolikni tezroq aniqlash: Yuqorida aytilgan fikr davomi sifatida biz Timberga dasturimiz VERSION_CODE ni hamda class kengaytmasini qo'shib qo'yishimiz crashlyticsda biz xatolikni qayerdaligi tezroq aniqlashga xizmat qiladi.

5. Yengilligi: Ilova hajmini va undagi methodlar sonini ko'paytirmaydi, chunki u allaqachon mavjud bo'lgan Log classining ustiga qurilgan.

Yuqorida fikrlarimizni misollar yordamida kengaytirishga harakat qilaman. TAG ni Timber o'zi har bitta class uchun unique (takrorlanmas) qilib beradi.

Masalan:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    Timber.d("example")
    button.setOnClickListener {
       Timber.d("onClick")
    }
  }
}
//
Result : "example" xabarning tag = MainActivity, button id li Buttonning xabari uchun tag = MainActivity$onCreat bo'ladi.

Shu o'rinda yana bir savol paydo bo'ladi, biz androidda ko'plab joylarda anonymous classlar ishlatamiz unda tag qanday bo'ladi?

Masalan: Quyida Example Interface mavjud va biz undan MainActivityning onCreatida anonym obyekt oldik, va button.setOnClickListenerida xabar yozdik, ular uchun

tag = "MainActivity$onCreate$example1" bo'ladi.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val example1 = object : Example {
            override fun finish(message: String) {
                Timber.d(message)
            }

            override fun error(error: String) {
                Timber.e(error)
            }
        }

        button.setOnClickListener {
            example1.finish("completed")
            example1.error("error")
        }

    }
   
}

interface Example {
    fun finish(message : String)
    fun error(error : String)
}

Endi dasturimiz RELEASEga chiqqandan keyin Loglarni crashlyticsga yig'ish mumkin dedik, offline rejimni hisobga olsak, loglarni ROOMga yig'ib keyin internet mavjud bo'lganda serverga yoki firebasega jo'natish ham yaxshi yechim sanaladi. Hozir RELEASE holda Loglarni ROOMga yig'ishni ko'ramiz.

1 - qadam Gradlega ROOM qo'shamiz.

2- qadam Loglar uchun Entity va Daolar yaratamiz.

@Entity
class LogEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val tag: String,
    val message : String
)
/********* Dao
@Dao
interface LogDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(data: LogEntity): Long
    
    @Query("SELECT * FROM LogEntity")
    fun getAllLog() : LiveData<List<LogEntity>>

}

3 - qadam AppDatabase classini yaratamiz.

   @Database(entities = [LogEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {

    abstract fun LogDao(): LogDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java,
                        "TimberExample").build()
                INSTANCE = instance
                return instance
            }
        }
    }
}

4-qadam RoomLogsTree classini Timber.DegugTree classidan voris olib yaratishimiz kerak.

 class RoomLogsTree(context: Context) : Timber.DebugTree() {
    private val database = App.database

    override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
        super.log(priority, tag, message, t)

        val d = tag?.let { LogEntity(0, it,message) }
        d?.let { database!!.LogDao().insert(it) }
    }
}

5 - qadam oxirgi qadam Maqola boshida foydalangan App classimizga ozgina o'zgartirish kiritamiz. Bunda Loglar dastur RELEASE bo'lganda ROOMga yig'ilib boriladi.

class App : Application() {

    companion object {
        var database : AppDatabase? = null
    }
    override fun onCreate() {
        super.onCreate()

        database = Room.databaseBuilder(this, AppDatabase::class.java, "TimberLogs")
                .allowMainThreadQueries()
                .build()

        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree())
        } else {
            Timber.plant(RoomLogsTree(this))
        }

    }
}

Endi dasturimizda qolgan qismlarini yaratamiz:

class MainActivity : AppCompatActivity() {

    val database = App.database
    var pressCount = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Timber.d("example")

        database!!.LogDao().getAllLog().observe(this,logsObserver)

        button.setOnClickListener {
            pressCount += 1
            Timber.d("pressCount $pressCount")
        }

    }
    private val logsObserver = Observer<List<LogEntity>> {
        var st = "Logs \n"
        for (i in it) st += "id = ${i.id}, tag= ${i.tag}, message= ${i.message}\n"
        text.text = st
    }
}

Dasturni RELEASE holidagi loglarini roomga yig'ib borishimiz mumkin, quyidagi rasmda shu roomdagi loglar String holida text idli TextViewga chiqarilgan.


Xulosa, Biz dasturlar yaratishimizda albatta Loglarga ehtiyoj sezamiz, bu o'rinda Timber kutubxonasidan foydalanish bizga yordam beradi va yuqorida gaplashganimizdek ko'plab foydali imkoniyatlari ham bor. Siz Timber haqida yana googledan qidirib ko'plab maqolalar o'qib o'rganib olishinggiz mumkin, shu o'rinda sizga qo'shimcha sifatida linkni taklif qilaman. Dastur kodlarini linkdan ko'rishinggiz mumkin. E'tiboringgiz uchun raxmat, maqoladagi kamchiliklar uchun uzr so'rayman va bular haqida gruppamizda xabardor qilsangiz xursand bo'lardim.

Maqolani Gita dasturchilar akademiyasi kanali uchun Ikromjonov Ma'rufjon tayyorladi.





Report Page