palabres
Overview
Palabres is an opinionated logger, thought out to be compatible out-of-the-box with BEAM and every JavaScript runtimes, whether it’s in browser or not, in Node or Deno, etc. In it’s simplest form, Palabres will simply creates a logger configured according to your needs, and will take care of the rest. Palabres considers logs as structured logs, to help you add context to your logs, and help you in future debugging sessions.
Structured logs
Logs can simply be strings output to your standard output, and have various
roles. Most of the time, it is used to help debugging, and to see what’s
happening in real time in production servers. However, you can also use
logs to perform some analytics. Or maybe you want to track some user
activities. That’s where structured logs shine: instead of outputting
simple strings, it’s possible to output complex, rich data. To simplify that
usage, Palabres helps by providing constructors functions. You can use
palabres.string
, palabres.int
or palabres.float
to add a field, with
its name and a value. They will automatically be part of the resulting log.
In Palabres, every log is not only a text message, but also context data
that you’re free to add to help in further usages. Those additionnal data
are free, so it’s up to you to determine how to use them efficiently!
Handling logs
Because logs are handled in multiple manners nowadays, Palabres provides two output formats: as string and as JSON. In the former format, every string will be formatted like query strings, but in a human readable way: no weird ampersand, no escaping. You can enjoy structured format while keeping it short, simple, and still easy to parse if needed. However, for processing logs, Palabres provides an easier JSON format. JSON formatting in logs allows to simply read and process them, and is widly implemented. DataDog, AWS CloudWatch, PaperTrail or NewRelic, all of them handle JSON logs without further configuration. Doing so also allows you to build on top of Palabres, and add specific structured fields required in your log processor.
Default values
Palabres imposes 4 standard data in every log: the level of the log, its timestamp, an ID in UUID format, and an arbitrary message string. You don’t have anything to do to add timestamp, id and level, they will automatically be added to every log you create. They’re all here to help you in future log processing sessions. In the worst case, they can be safely ignored. In the best case, they help you figure out what’s happening, when an where.
Arbitrary text messages are parts of log, and instead of putting them in a
specific field, they’re simply dumped as is in the string format, or under
the message
field in JSON.
Usage Example
import palabres
import palabres/options
pub fn configure_logger() {
use json_output <- result.try(is_json_output())
options.defaults()
|> options.json(json_output)
|> palabres.configure
}
pub fn log_message(message, user) {
palabres.info(message)
|> palabres.string("node", get_node_info())
|> palabres.string("user_id", user.id)
|> palabres.int("user_age", user.age)
|> palabres.log
}
fn is_json_output() {
use output <- result.try(envoy.get("JSON_OUTPUT"))
use <- bool.guard(when: json_output != "true", return: False)
True
}
How does it work?
Behind the scenes, Palabres has two different behaviours, depending on your
target. When targetting Erlang, Palabres will act as a logger
layer, and
will take care of the formatting, filtering, and helping to deal with
logger
for you. You don’t need to dive in its interface, Palabres got you
covered. You can use Palabres to create your logs, but nothing stops you to
use something like wisp.log_info
in your code. In that case, Palabres
can still intervene and apply styling. That allow you to write simple
logs functions, while still leveraging structured logs. Because Palabres
relies on logger
, you can rather easily interact with Palabres in Erlang,
provided you know how to use logger
.
On JavaScript though, Palabres instanciate a full-fledged, custom logger.
Because JavaScript does not have any logger concept in its root, Palabres
provides a logger with similar abilities than logger
on BEAM. As such,
every logs should go through the Palabres package, otherwise the logger
won’t be able to format them accordingly.
Types
Functions
pub fn alert(message: String) -> Log
Creates an Alert
Log
instance. Should be used as a starting point
to create a log.
pub fn log() {
palabres.alert("Example message")
|> palabres.log
}
pub fn at(
log: Log,
module module: String,
function function: String,
) -> Log
Debug information, used to easily pinpoint a location in your code. at
should point to a module and a function, and will display in your logs
a data looking like at=module.function
with proper colored output if
activated.
pub fn log() {
palabres.info("Example message")
|> palabres.at("my_logger", "log")
|> palabres.log
// Turns in:
// level=info when=2024-12-15T15:59:17Z id=3a5c0fc2-5f74-4796-84df-cbfe4000eef6 at=my_logger.log Example message
}
pub fn configure(options: Options) -> Nil
Configure Palabres logger. Because logger is a singleton, it only needs to be
configured once at startup. Select your options, and run configurations, to
get Palabres logger running. Each logger call will then go through Palabres
on BEAM. On JavaScript, calling palabres
functions are still required.
pub fn critical(message: String) -> Log
Creates a Critical
Log
instance. Should be used as a starting point
to create a log.
pub fn log() {
palabres.critical("Example message")
|> palabres.log
}
pub fn debug(message: String) -> Log
Creates a Debug
Log
instance. Should be used as a starting point
to create a log.
pub fn log() {
palabres.debug("Example message")
|> palabres.log
}
pub fn emergency(message: String) -> Log
Creates an Emergency
Log
instance. Should be used as a starting point
to create a log.
pub fn log() {
palabres.emergency("Example message")
|> palabres.log
}
pub fn error(message: String) -> Log
Creates an Error
Log
instance. Should be used as a starting point
to create a log.
pub fn log() {
palabres.error("Example message")
|> palabres.log
}
pub fn float(log: Log, key: String, value: Float) -> Log
Add a float field to your structured data.
pub fn info(message: String) -> Log
Creates an Info
Log
instance. Should be used as a starting point
to create a log.
pub fn log() {
palabres.info("Example message")
|> palabres.log
}
pub fn int(log: Log, key: String, value: Int) -> Log
Add an int field to your structured data.
pub fn log_request(
req: Request(Connection),
handler: fn() -> Response(Body),
) -> Response(Body)
Provides a middleware to display every incoming request for a Wisp server.
Use it in your router to log request with status code, path and method.
pub fn router(req: wisp.Request, ctx: context) {
use <- palabres.log_request(req)
route_request(req)
}
pub fn notice(message: String) -> Log
Creates a Notice
Log
instance. Should be used as a starting point
to create a log.
pub fn log() {
palabres.notice("Example message")
|> palabres.log
}
pub fn string(log: Log, key: String, value: String) -> Log
Add a string field to your structured data.