Skip to content

7. Logging

James Shvarts edited this page Dec 11, 2018 · 6 revisions

Meaningful logs are one of the biggest benefits in a Unidirectional/MVI architecture. Logs can help during development or while writing unit tests and even in production to help debug various edge cases.

Logging Actions as they come in and the States they trigger is all we need to do. It's made easy in Roxie since we only have one pipeline for sending Actions in and one pipeline for emitting States. The pipelines are these:

  • Actions are sent via the dispatch function:
    viewModel.dispatch(Action.LoadNotes)
  • States are emitted after Reducer creates them in RxJava chain. We can simply use doOnNext() to log the resulting States:
    disposables += loadNotesChange
        .scan(initialState, reducer)
        .filter { !it.isIdle }
        .distinctUntilChanged()
        .doOnNext { Timber.d("Received state: $it") }
        .subscribe(state::postValue, Timber::e)
    }

By default, Actions are not logged.

You can enable logging in your Application's onCreate() using one of 2 ways:

Default logger:

    // Actions will be logged using println()
    override fun onCreate() {
        super.onCreate()
        Roxie.enableLogging()
    }

Custom logger:

    // Actions will be logged using Timber with the severity specified
    override fun onCreate() {
        super.onCreate()
        Roxie.enableLogging(object : Roxie.Logger {
            override fun log(msg: String) {
                Timber.d(msg)
            }
        })
    }

Displaying a note with ID of 2, for instance, generates the following logs:

I/System.out: NoteDetailViewModel: **Received action**: LoadNoteDetail(noteId=2)
D/NoteDetailViewModel$bindActions: **Received state**: State(note=Note(id=2, text=note2), isIdle=false, isLoading=false, isLoadError=false, isNoteDeleted=false, isDeleteError=false)

which gives us a helpful insight into what's going on. For instance, it would be easy to confirm that rotating the device will not produce any more logs (we don't want to reload the note again on rotation). The latest State wrapped in LiveData will simply get rendered again. Having the logging mechanism built-in is very helpful when optimizing your potentially expensive data operations.

Logging sensitive data

MVI with its single pipeline to dispatch Actions and produce States makes logging effective and easy. This simple but powerful quality can aid greatly in both development and production. However, with great power comes great responsibility. When logging user data, for instance, avoid logging sensitive user-identifiable data. One way to do it is to redact certain properties from the User object (for instance, by overwriting the toString() on the data class).

Logs as JSON

If you have large State objects with many properties, logging them using built-in toString() may not be optimal as the output won't be easy to read. A good solution to this problem would be logging these objects as JSON. This way you can format the output by viewing it using some sort of a JSON viewer (Android Studio has a good plugin for his).

Clone this wiki locally