Lesson Monday

Now that you're in your level 3 Epicodus course, you've probably come to realize how much trial-and-error goes into coding. Sometimes code doesn't work on the first try. Sometimes code that once worked perfectly no longer functions after implementing new code. Bugs occur all the time, and that's simply a normal part of development.

In the next two lessons we'll explore two Android-specific approaches to debugging. First, we'll learn how to record helpful information into an area of Android Studio known as the logcat. Then, in the next lesson we'll learn how to add breakpoints to strategically pause our code and narrow down where bugs and errors are occurring. Let's get started!

The Android Log

When writing JavaScript in your Intro to Programming and JavaScript courses, you probably encountered console.log(). It’s a method that allows us to write to the JavaScript console in the browser. This allows us to see what certain variables are defined as, or check whether methods are being called.

Android Studio has the capability to write log messages in a very similar manner. We can add log methods that will write data to Android Studio's logcat. The logcat displays system messages, and messages/information you manually record with Log methods. It both displays messages in real time and also keeps a history so you may view older messages.

After placing Log methods we can run our app and look view the logcat to see what data or information has been recorded.

Log Methods

However, unlike JavaScript's console.log(), there are many different methods from Android's Log class we can use to log information. Let's briefly cover what these different methods are, and when to use each:

Log.e()

The e in Log.e() stands for error. Use this when you know an error has occurred, and you're logging details about that error. Developers will commonly use this in a block of code meant to catch an error. The Log.e() message can then print details about the error.

Log.w()

The w in Log.w() stands for warning. Use this when you suspect an issue may be occurring, but haven't yet received full-on error messages. Developers usually use this to proactively investigate unusual or unexpected behavior in an application.

Log.i()

The i in Log.i() stands for information. Use this to post useful information. For instance, maybe you want to double-check a method is being called successfully, you could print an informational message to the log reading something like "X method called!".

Log.d()

The d in Log.d() stands for debug. As you might imagine, you'll use this one for debugging purposes. You'll probably use this one most frequently out of all available Log methods.

Log.v()

The v in Log.v() stands for verbose. Use this when you're implementing many, many different log statements as a debugging approach.

Log.wtf()

(No, we're not making this up).

The wtf in Log.wtf() is said to stand for "What a terrible failure". It's meant to record particularly awful issues that should never, ever happen, but are somehow occurring anyway. It's not used as commonly as the other Log messages.

Benefits of Different Log Message Types

But why are there 6 different methods to log information anyway? Well, the logcat can contain a lot of data. By classifying the importance of the information you're logging using the methods depicted above, we can easily filter messages by their level of importance.

Log Statement Importance Level

The list of Log methods above is ordered by level of importance, also known as "log level".

Log.e() is considered to be the highest importance and priority, because it logs information about errors that are currently occurring, and Log.v() is considered the lowest importance and priority because it's meant for logging as much data as possible. (Log.wtf() is actually a bit of an outlier, and isn’t used very often.)

So, if you filter to view Log.i() messages, you'll see both messages recorded with the Log.i() method and those recorded with the Log.w() and Log.e() methods, because they are of the same importance level or higher.

Additionally, if we filtered by Log.d(), we would see any messages printed on the logcat from Log.d(), Log.i(), Log.w(), and Log.e() because they are all of Log.d()'s level of importance or higher.

Similarly, if we were to filter by Log.v(), we would see all other other Log method messages, since Log.v() is the lowest importance level, and each of the other methods is of a higher importance.

How to Log Information

Let's walk through the process of logging and viewing data in Android Studio. We'll add several Log messages to our MyRestaurants application together.

Accessing the Logcat

First, let's locate the logcat in Android Studio, so we'll know where to look for our logged messages. In previous lessons you learned how to access the Terminal in Android Studio. In the same pane near the bottom of the window, select Android Monitor from the lower options bar.

Then, once you're in the Android Monitor, select the logcat tab in the upper left:

accessing-logcat-in-android-studio

If you do not have an Android Monitor option present in the lower option bar as seen above, you can instead access the Android Monitor by clicking the "square" in the lower left-hand corner of the window. Then, select Android Monitor from the resulting menu:

accessing-logcat-if-not-available

Placing a Log Statement

Next, just like the console.log() method used in JavaScript, we can place a Log method wherever we'd like. Information will be logged when that area of code runs.

We'll use Log.v() and Log.d() methods in two separate spots. First, we'll use Log.v() in the onItemClick() method. As you begin typing Log.v(..., you'll see Android Studio will offer two suggestions in its auto-complete pop-up:

log-message-autocomplete

As you can see, this method takes either 2 or 3 arguments:

  1. A String called tag. In terms of Log messages, a tag is a quick string that provides context regarding where the log message is coming from: Generally the name of the activity from which information is being logged.
  2. A second String called msg. This is the main content of the log message.
  3. A third argument, of the class Throwable, which represents an exception or error being thrown. If you're attempting to log information about an exception or error, you'd provide this exception or argument as a third argument here.

Our MyRestaurants application shouldn't be throwing any exceptions or errors in its current state, so let's begin by only providing two arguments:

RestaurantActivity.java
...
    @Override
    protected void onCreate(Bundle savedInstanceState) {


        ...

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                ...
                Toast.makeText(RestaurantsActivity.this, restaurant, Toast.LENGTH_LONG).show();
                Log.v("RestaurantsActivity", "In the onItemClickListener!");
            }
        });
      ...
...
  • Here, we've included a tag reading "RestaurantsActivity", because we're logging this information from within our RestaurantsActivity.
  • As a second argument, we've included a message reading "In the onItemClickListener!"

So, when this code is triggered (ie: a restaurant from our list is clicked), the message "In the onItemClickListener!" should appear in our logcat. Putting a log message in this location is great to test and confirm whether a click listener is being triggered successfully.

In order to experiment filtering different types of Log messages, let's include a second Log method of a different importance level. This time, we'll use Log.d() just below our Toast:

RestaurantActivity.java
...
    @Override
    protected void onCreate(Bundle savedInstanceState) {


        ...

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                ...
                Log.v("RestaurantsActivity", "In the onItemClickListener!");
            }
        });


        ...


        mLocationTextView.setText("Here are all the restaurants near: " + location);
        Log.d("RestaurantsActivity", "In the onCreate method!");


    }
...

Again, we include two arguments. The first is a String containing the location from which we're logging. The second is the message we'd like to print to the logcat.

Running the Log Statement

Next, let's launch our application in the emulator. Because we placed our Log methods in the onCreate() and onItemClick() methods of our RestaurantsActivity, we'll need to trigger those methods in order for the Log methods to record data in our logcat.

We'll navigate to our RestaurantsActivity. As soon as we do this, the onCreate() method will be triggered. Therefore, we should see our first message appear in the logcat:

first-message-in-logcat

Then, we can trigger our second log message by clicking any of the restaurants in our list:

two-messages-in-logcat

Since the Log.v() method is located in our onItemClick() method, notice that it is triggered whenever an item is clicked, instead of just once. For instance, if we clicked a restaurant many times in a row, we would see:

many-click-listener-messages

Filtering Messages in the Logcat

This should be fairly reminiscent of console.log() in JavaScript. However, as you can see, there's a lot of information in that logcat! Thankfully, we can quickly filter this breadth of information.

In the top bar of the logcat panel, there are multiple options to filter logcat contents. Let's explore these options now.

logcat-filtering-options

1. Source

filter-by-source

This dropdown allows you to select which device's log messages to view. This should be the device you're currently running your application on. This will be the emulator, unless you're running your application on an standalone Android device.

2. Application

filter-by-application

This dropdown allows you to select which application's log messages to view. This should always be the application you're currently debugging, or attempting to view log messages for.

3. Log Level

filter-by-log-level

Here, you can filter by the log level. As we discussed previously, each of the Log methods listed above is of a different priority level. When you filter by a specific log level, you'll only see messages of that priority level or higher.

For instance, if we filter by verbose, we can see both of our log statements are visible:

filter-by-verbose

Yet, if we filter instead by debug, we can see that only our Log.d() statement is visible, because we're viewing only log statements of the "Debug" priority or higher. Because verbose (Log.v()) is of a priority lower than debug (Log.d()), we no longer see it in the logcat:

filter-by-debug

4. Search Field

As the name implies, the search field allows us to search for particular words, statement, or other content in a log message. For instance, if you logged a certain piece of data with our "RestaurantsActivity" tag, but couldn't locate the log message, you could search "RestaurantsActivity" to confirm whether or not the Log method ever ran and recorded data in the logcat.

5. Application Filters

app-filter

This dropdown allows us to choose whether we'd like to see log messages from only the selected application (the application chosen in #2), or from all currently-running applications and processes on the device.

Defining Tag Constants

Additionally, it's common practice for Android Developers to add a special constant called TAG that containis the name of the activity to each class. This constant is then used as the first argument in any Log methods.

Defining a TAG constant for use in an activity's Log methods looks like this:

public static final String TAG = YourActivityName.class.getSimpleName();

By defining the constant as YourActivityName.class.getSimpleName();, instead of a String containing the activity name, Android Studio's built-in refactoring tools will automatically change the value of this constant if you ever re-name your activity. This is considered best practice.

Let's add a similar constant to our RestaurantActivity, and use it in both our Log methods:

RestaurantActivity.java
...
  public static final String TAG = RestaurantActivity.class.getSimpleName();
  ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {


        ...

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                ...
                Toast.makeText(RestaurantsActivity.this, restaurant, Toast.LENGTH_LONG).show();
                Log.v(TAG, "In the onItemClickListener!");
            }
        });

        ...


        mLocationTextView.setText("Here are all the restaurants near: " + location);
        Log.d(TAG, "In the onCreate method!");
   ...
...

Logging Other Data

As you saw earlier, Log methods take 2-3 arguments. Most of the time, you'll only ever need to use 2: A tag containing information about the context the log message is coming from, and the contents of the message. Both of these arguments must be String type.

This means, you can only log String information. At first this may sound limiting, but note that you can simply use the .toString() method to turn non-String data into String format for the purposes of logging.

Additional Resources

  • For more information on debugging in general, checkout the Debug Your App portion of the Android Studio User Guide.

  • For more information on Android Studio's logcat, check out the logcat Command-line Tool article.

Terminology


  • Logcat: An area of Android Studio that displays system messages, and messages/information you manually record with Log methods. It displays messages in real time and also keeps a history so you may view older messages.

  • Tag: A string that provides context regarding where the log message is coming from: Generally the name of the activity from which information is being logged.

Overview


Android Log Methods

  • Log.d(): Debug. You'll use this one for debugging purposes. You'll probably use this one most frequently out of all available Log methods.

  • Log.e(): Error. Use this when you know an error has occurred, and you're logging details about that error.

  • Log.i(): Information. Use this to post useful information. For instance, maybe you want to double-check a method is being called successfully, you could print an informational message to the log reading something like "X method called!".

  • Log.v(): Verbose. Use this when you're implementing many, many different log statements as a debugging approach.

  • Log.w(): Warning. Use this when you suspect an issue may be occurring, but haven't yet received full-on error messages.

  • Log.wtf(): "What a terrible failure". Meant to record particularly awful issues that should never, ever happen, but are somehow occurring anyway.

Additional Resources