Strategically logging messages to the console
May 21st, 2019
Developers often log messages to the console for all kinds of reasons. The most common reason is that it provides helpful output to assist them in making sure that their app is working as they expect it to. This post will show several examples for strategic uses in console.log and console.error.
The most commonly used method to log messages to the console is console.log. Its simplicity just makes life a lot better to use them in your development flow (in my opinion). Console.log takes in a value (majority of the time you will be passing a string, however you can pass in other types like objects and numbers) as an argument and uses it to log it to the console.
const logSomething = (str) => {
console.log(str)
}
logSomething('this is going to the console')
Result:
A handy little trick to make these console messages more useful is to colorize their output in the console. By passing in a string prefixed with %c
as the first argument, it will act as a placeholder for CSS values and automatically apply the styles you provide to the second argument. The styles argument is a string delimited by semi-colons ";
" used to apply multiple styles to the message that will be displayed.
For example, if you were to log the message "Three seconds has passed" in orange whenever your function has timed out after 3 seconds from an http request, you can do something like:
// timeout in milliseconds
const message =
'%cThis message will turn orange when the timeout gets to zero after 3 seconds'
setTimeout(() => {
const styles = 'color:orange;'
console.log(message, styles)
}, 3000)
Output:
Now if you were wondering when this would be useful other than making console messages look pretty, you can use colors to customize and differentiate what part of your app is doing what, which line of code may be important to look out for, which line of code is giving an abnormal behavior---the list is endless.
Suppose you were working on an app and you needed to set up headers for future requests. You created a utility function that manages a headers object which you can use for constructing request objects.
You decide to name this utility function makeHeaders and it will return 3 methods:
Method | Description |
---|---|
get | Retrieves a key/value pair from the headers object |
set | Sets a key/value to the headers object |
returnHeaders | Returns the whole headers object |
And the constructor in code:
const makeHeaders = function(options) {
const headers = {}
return {
get: function(key) {
return headers[key] || null
},
set: function(key, value) {
if (key === undefined) return
if (value === null) {
console.warn(
`WARNING! You tried to set "${key}" to headers but the value was null or undefined. The operation was skipped.`,
)
return
}
headers[key] = value
},
returnHeaders: function() {
return headers
},
}
}
Now in your app you might have some code that uses it like this:
const headers = makeHeaders()
headers.set('Content-Type', 'application/json')
For our example use case, we want to make a post request to https://something.com/v1/api/user/
(this is not a real endpoint) to authenticate ourselves so we can have access to data from future API calls. A basic authentication service usually asks for a username and password as the parameters to the body in a post request:
const url = 'https://someapi.com'
const method = 'post'
const data = {
username: 'myUsername',
password: 'myPassword123',
}
And then you would apply it to your request object along with the other options for the API request. For this example we will be using axios:
const callApi = async (params) => {
try {
const response = await axios(params)
if (response.data) {
console.log(response.data) // result: { "token": "..." }
}
return response.data
} catch (error) {
// do something with error
}
}
callApi({ url, method, data, headers })
.then(({ token }) => console.log(token))
.catch((error) => console.error(error))
The authentication service will validate the credentials and return an expiring token with a default expiration date of 1 hour after the creation of the token. All is good, however lets say they support an optional third parameter, expires_in
. By passing in a number in milliseconds the default expiration date can be overrided:
const headers = makeHeaders()
headers.set('Content-Type', 'application/json')
const url = 'https://someapi.com'
const method = 'post'
const expiringMinutesToMs = (mins) => {
return mins * 60000
}
const data = {
username: 'myUsername',
password: 'myPassword123',
expires_in: expiringMinutesToMs(75), // 1 hour and 15 minutes
}
const callApi = async (params) => {
try {
const response = await axios(params)
if (response.data) {
console.log(response.data) // result: { "token": "..." }
}
return response.data
} catch (error) {
// do something with error
}
}
callApi({ url, method, data, headers })
.then(({ token }) => console.log(token))
.catch((error) => console.error(error))
If we tried to pass in a number to minutesToMs
and received an undefined
value, we can log it with a plain ole console.log('some message here')
, but if there are several messages getting sent to the console for different reasons it may look messy and hard to read if there is a lot of output to the console.
By incorporating colors, you're able to distinguish and draw quicker attention to important messages from the console. This improves the effectiveness of your development flow by making your development process quicker and funner!
const headers = makeHeaders()
headers.set('Content-Type', 'application/json')
const url = 'https://someapi.com'
const method = 'post'
const expiringMinutesToMs = (mins) => {
if (mins === undefined) {
const msg =
'WARNING! You tried to pass in "mins" to the "expiringMinutesToMs" call but the value was undefined. It was defaulted to 60 minutes (1 hour) instead'
console.log(`%c${msg}`, 'color:#FF4500')
}
return mins * 60000
}
let expiresInMinutes
const data = {
username: 'myUsername',
password: 'myPassword123',
expires_in: expiringMinutesToMs(expiresInMinutes),
}
const callApi = async (params) => {
try {
const response = await axios(params)
if (response.data) {
console.log(response.data) // result: { "token": "..." }
}
return response.data
} catch (error) {
// do something with error
}
}
callApi({ url, method, data, headers })
.then(({ token }) => console.log(token))
.catch((error) => console.error(error))
Using console.error
will print errors to the console. This will attempt to format anything you pass to it as an error.
What I like about console.error
is that it includes a stack trace which allows you to navigate to previous calls that lead to the error. I wouldn't use this other than logging errors to the console otherwise it may become misleading to other developers who edit your code.
And that concludes the end of this post. I leave the rest to you to play around with!