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')
./src/config/logging.ts
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')
./src/config/logging.ts
Driver name | Description |
---|---|
stack | A wrapper to facilitate creating "multi-channel" channels |
console | Transports the log directly to the terminal with stdout/stderr |
file | Transports the log to a file |
null | Discards all log messages |
slack | Transports the log message to a Slack channel |
discord | Transports the log message to a Discord channel |
telegram | Transports 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')
./src/config/logging.ts
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')
./src/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')
./src/config/logging.ts
import { WelcomeService } from '#src/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')
./src/config/logging.ts
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')
./src/config/logging.ts
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')
./src/config/logging.ts
Formatter name | Description |
---|---|
cli | Simple very useful when building CLI's |
json | Format the entire data provided by the log message to a JSON string |
simple | Format the log message to show the level, date, message and the MS |
request | Focus in formatting the log message with common fields that shows up in http requests |
message | Format 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')
./src/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:
import { Ignite } from '@athenna/core'
const ignite = await new Ignite().load(import.meta.url, {
bootLogs: false, 👈
shutdownLogs: false, 👈
})
await ignite.httpServer()