Skip to main content

Logging

Understand how you can use the Athenna logging API.

Introduction

To help you learn more about what's happening within your application, Athenna provides robust logging services that allow you to log messages to file, console, buckets and even to Slack to notify your entire team.

Athenna logging is based on "channels". Each channel represents a specific way of writing log information. For example, the application channel writes log of your application when bootstrapping, while the request channel writes log of the http requests that your application receives.

Configuration

All the configuration options for your application's logging behavior are housed in the Path.config('logging.ts') configuration file. This file allows you to configure your application's log channels, so be sure to review each of the available channels and their options. We'll review a few common options below.

Available channel drivers

Each log channel is powered by a "driver". The driver determines how and where the log message is actually transported. The following log channel drivers are available in every Athenna application. An entry for most of these drivers is already present in your application's Path.config('logging.ts') configuration file, so be sure to review this file to become familiar with its contents:

Driver nameDescription
stackA wrapper to facilitate creating "multi-channel" channels
consoleTransports the log directly to the terminal with stdout/stderr
fileTransports the log to a file
nullDiscards all log messages
slackTransports the log message to a Slack channel
discordTransports the log message to a Discord channel
telegramTransports the log message to a Telegram chat

Channel prerequisites

Almost all channels doesn't need any additional configuration to work. But some of than need a couple of changes in Path.config('logging.ts') file to be able to use. They are slack, discord and telegram.

Configuring the Slack channel

The slack channel only requires the url property inside options. This URL should match a URL for an incoming webhook that you have configured for your Slack team.

slack: {
driver: 'slack',
level: 'fatal',
url: 'your-slack-webhook-url', 👈

formatter: 'message',
formatterConfig: {}
}

Configuring the Discord channel

The discord channel requires the url and username properties inside options. The URL should match a URL for an incoming webhook that you have configured for your Discord server. And the username is just the name of the bot that will deliver the message.

discord: {
driver: 'discord',
level: 'fatal',
username: 'Athenna', 👈
url: 'your-discord-webhook-url', 👈

formatter: 'message',
formatterConfig: {}
}

Configuring the Telegram channel

The telegram channel requires the token and chatId properties inside options. The token should match a Telegram bot token that you have created talking with Bot Father. And the chatId is just the id of the chat that the bot will deliver the message.

telegram: {
driver: 'telegram',
level: 'fatal',
chatId: 0, 👈
parseMode: 'HTML',
token: 'your-telegram-bot-token', 👈

formatter: 'message',
formatterConfig: {}
}

Implementing your own driver

You can implement your own Log driver using the DriverFactory class, but your class needs to implement the transport method and extends Driver class:

import { Driver } from '@athenna/logger'

export class ConsoleLogDriver extends Driver {
public transport(level: string, message: any) {
/**
* Verify if this log could be transported based on the level.
*/
if (!this.couldBeTransported(level)) {
return
}

let formatted = this.format(level, message)

if (this.driverConfig.addBar) {
formatted = formatted.concat('- BAR')
}

console.log(formatted)
}
}

Now we just need to use the DriverFactory to register our new driver and set a name for him:

import { DriverFactory } from '@athenna/logger'

DriverFactory.createDriver('consoleLog', ConsoleLogDriver)

Finally, we can start using our new driver in channels of Path.config('logging.ts'):

channels: {
consoleLogChannel: {
driver: 'consoleLog',
addBar: true,

formatter: 'simple'
}
}

Writing log messages

You may write information to the logs using the Log facade. The logger provides the seven logging levels:

import { Log } from '@athenna/logger'

const message = 'Hello Athenna!'

Log.trace(message)
Log.debug(message)
Log.info(message)
Log.success(message)
Log.warn(message)
Log.error(message)
Log.fatal(message)

You may call any of these methods to log a message for the corresponding level. By default, the message will be written to the default log channel as configured by your Path.config('logging.ts') configuration file:

import { WelcomeService } from '#app/services/WelcomeService'

export class WelcomeController {
@Inject()
private welcomeService: WelcomeService

public async show({ request, response }) {
const data = await this.welcomeService.findOne()

Log.info(`Showing the welcome message for ip: ${request.ip}`)

return response.status(200).send(data)
}
}

Writing to specific channels

Sometimes you may wish to log a message to a channel other than your application's default channel. You may use the channel method on the Log facade to retrieve and log to any channel defined in your configuration file:

Log.channel('slack').info('Hello from Athenna!')

Building log stacks

As mentioned previously, the stack driver allows you to combine multiple channels into a single log channel for convenience. To illustrate how to use log stacks, let's take a look at an example configuration that you might see in a production application:

Take note of the level configuration option present in slack channel of your Path.config('logging.ts') file:

channels: {
stack: {
driver: 'stack',
channels: ['application', 'slack']
},

application: {
driver: 'console',
level: 'trace',

formatter: 'simple'
},

slack: {
driver: 'slack',
level: 'error',
url: 'your-slack-webhook-url',

formatter: 'message'
}
}

Let's dissect this configuration. First, notice our stack channel aggregates two other channels via its channel option: application and slack. So, when logging messages, both of these channels will have the opportunity to log the message. However, as we will see below, whether these channels actually log the message may be determined by the message's severity / "level".

Log levels

Take note of the level configuration option present on the application and slack channel configurations in the example above. This option determines the minimum "level" a message must be in order to be logged by the channel. In descending order of severity, these log levels are: fatal, error, warn, success, info, debug and trace.

So, imagine we log a message using the debug method:

Log.debug('An informational message.')

Given our configuration, the application channel will write the message to the system log; however, since the error message is not error or above, it will not be sent to Slack. However, if we log an fatal message, it will be sent to both the system log and Slack since the fatal level is above our minimum level threshold for both channels:

Log.fatal('The application is down!')

Writing styled log messages

When using the Log facade you have the power to add style to your log messages:

Log.info('({yellow, bold} Hello) ({green, italic} World)!')

Athenna uses chalk API under the hood to parse the methods inside the brackets (({...chalkMethods} yourLogString)), the example above could also be done using chalk directly:

import chalk from 'chalk'

Log.info(`${chalk.yellow.bold('Hello')} ${chalk.green.italic('World')}!`)

Runtime configurations

It is also possible to set runtime configurations when using the Log facade. This way you will never be total dependent from Path.config('logging.ts') configuration file. To accomplish this, you may pass a configuration object to the config method of Log facade and then call the channel method again to set up the configurations for the specified driver:

const config = { url: 'other-slack-webhook-url' }

// GOOD!! Configuration is now set for drivers.
Log.config(config).channel('slack').info('Hello from Athenna!')

// BAD!! Configuration is not going to be set for drivers.
Log.config(config).info('Hello from Athenna!')

Available channel formatters

Each log channel is powered by a "formatter". The formatter determines how the log message is actually formatted. The following log channel formatters are available in every Athenna application. An entry for most of these formatters is already present in your application's Path.config('logging.ts') configuration file, so be sure to review this file to become familiar with its contents:

Formatter nameDescription
cliSimple very useful when building CLI's
jsonFormat the entire data provided by the log message to a JSON string
simpleFormat the log message to show the level, date, message and the MS
requestFocus in formatting the log message with common fields that shows up in http requests
messageFormat the log message with an emoji of it respective level, useful for using with drivers like Discord

Writing using specific formatter

It is also possible to set formatter and formatterConfig in runtime configurations when using the Log facade:

import chalk from 'chalk'

const config = {
formatter: 'simple',
formatterConfig: { chalk: chalk.green }
}

Log.config(config).channel('slack').info('Hello from Athenna!')

Implementing your own formatter

You can implement your own Log formatter using the FormatterFactory class, but your class needs to implement the format method and extends Formatter class:

import { Formatter } from '@athenna/logger'

export class ConsoleLogFormatter extends Formatter {
public format(message: string): string {
const level = this.simpleLevel()

let messageFormatted = this.clean(
`${level} - ${this.timestamp()} - (${this.pid()}) ${this.applyColors(
message,
)}`,
)

if (this.configs.addFoo) {
messageFormatted = messageFormatted.concat('- FOO')
}

return messageFormatted
}
}

Now we just need to use the FormatterFactory to register our new formatter and set a name for him:

import { FormatterFactory } from '@athenna/logger'

FormatterFactory.createFormatter('consoleLog', ConsoleLogFormatter)

Finally, we can start using our new formatter in channels of Path.config('logging.ts'):

channels: {
consoleLogChannel: {
driver: 'consoleLog',
addBar: true,

formatter: 'consoleLog',
formatterConfig: {
addFoo: true
}
}
}

Disabling boot and shutdown logs

When your application starts, Athenna creates some logs to help you to track the application boot process. The same happens when your application is shuting down.

If you wish to run your application in silent mode, you can disable this kind of logs using the Ignite::load() static method:

Path.bootstrap('main.ts')
import { Ignite } from '@athenna/core'

const ignite = await new Ignite().load(import.meta.url, {
bootLogs: false, 👈
shutdownLogs: false, 👈
})

await ignite.httpServer()