Enrichers
Enrichers are powerful tools that allow you to enhance log entries with additional context or metadata. They can be used to add custom information to each log entry or modify existing data.
- What enrichers are
- How to apply enrichers to your logging setup
- How the order of enrichers affects output
- How to create your own enrichers
- What enrichers rLog provides out of the box
What is an Enricher?
An enricher is a function that takes a log entry as input, and returns a modified log entry. This allows you to add extra data or transform the log entry in some way before it gets output to the console.
Creating enrichers
Here's a basic example of an enricher that adds a timestamp to each log entry.
import { LogEnricherCallback, LogEntry } from "@rbxts/rlog";
export const timestampEnricher: LogEnricherCallback = (entry: LogEntry) => {
entry.encoded_data["timestamp"] = DateTime.now();
return entry;
};
rLog actually already provides a timestamp property on your logs, but we'll be using this enricher as a base example regardless; for its simplicity
Using enrichers
Once you have your enricher created, you can "attach" it to your rLog instances via the enrichers
setting in your
config.
import { rLog } from "@rbxts/rlog";
import { timestampEnricher } from "./timestamp-enricher";
const logger = new rLog({ enrichers: [timestampEnricher] });
Order of Enrichers
Enrichers are applied to logs (or "ran") in the order they are "attached" to the rLog instance.
This means that the output of the first enricher is passed as input to the next one, and so on.
This allows for complex transformations and data enrichment flows.
import { LogEnricherCallback, LogEntry } from "@rbxts/rlog";
export const timestampEnricher: LogEnricherCallback = (entry: LogEntry) => {
entry.encoded_data["timestamp"] = DateTime.now();
return entry;
};
import { LogEnricherCallback, LogEntry } from "@rbxts/rlog";
export const transformTimestampEnricher: LogEnricherCallback = (entry: LogEntry) => {
if (typeIs(entry.encoded_data["timestamp"], "number")) {
const ms = DateTime.fromUnixTimestampMillis(entry.encoded_data.timestamp);
const formattedTimestamp = ms.FormatLocalTime("LL", "en-us");
entry.encoded_data.timestamp = formattedTimestamp;
}
return entry;
};
import { rLog } from "@rbxts/rlog";
import { timestampEnricher } from "./timestamp-enricher";
import { transformTimestampEnricher } from "./transform-timestamp-enricher";
const logger = new rLog().withEnrichers(timestampEnricher, transformTimestampEnricher);
logger.i("Hello world!");
{ data: { timestamp: "August 14, 2024" } }
Enricher Filters
Not all enrichers need to run for every log entry.
Since you have access to the entire LogEntry
, you can make these decisions on a case-by-case basis in your enrichers.
import { LogEnricherCallback, LogEntry, LogLevel, rLog } from "@rbxts/rlog";
export const timestampEnricher: LogEnricherCallback = (entry: LogEntry) => {
// just return the instance without changing anything
if (entry.Level < LogLevel.WARNING) return entry;
// ...
};
Common Use Cases
Enrichers are versatile and extensive, and can be used to provide a lot of different features.
Adding Contextual Information
You might want to add information like a User ID or Session ID to every log entry.
function enricherForPlayer(player: Player): LogEnricherCallback {
const uid = tostring(player.UserId);
return (entry) => {
entry.encoded_data.userId = uid;
return entry;
};
}
Dynamic Data Enrichment
Enrichers can also be used to add data that changes dynamically, such as request or transaction IDs:
const transactionEnricher: LogEnricherCallback = (entry) => {
entry.encoded_data.transactionId = generateTransactionId();
return entry;
};
Conditional Enrichment
Enrichers can modify log entries based on certain conditions.
For example, you might only want to add certain metadata for error logs.
const errorTraceEnricher: LogEnricherCallback = (entry) => {
if (entry.level === LogLevel.ERROR) {
entry.encoded_data.trace = debug.traceback(entry.message);
}
return entry;
};
Sensitive Data Scrubbing
You might want to avoid accidentally leaking certain data to your cloud provider or front end users.
const scrubApiKeysEnricher: LogEnricherCallback = (entry) => {
entry.encoded_data["api_key"] = undefined;
return entry;
};
Best Practices
-
Keep Enrichers Simple: Enrichers should be straightforward and focus on a single task, like adding a specific piece of metadata.
-
Order Enrichers Wisely: Think about the order in which you apply enrichers. The output of one enricher will affect the next.
-
Avoid Heavy Operations: Since enrichers run for every log entry, avoid performing heavy computations inside them to prevent slowing down your application.
-
Avoid yielding: If your enricher is running for every log entry, yielding inside an enricher will cause the flow of your program to stop in that thread. In some situations this is desirable- but you generally want your enrichers to be simple and non blocking.
Provided Enrichers
rLog provides some ready to use enrichers right out of the box, to cover common use cases.
If you believe you have a common use case that isn't covered by these, feel free to open an issue on the GitHub, or Create a PR to add it yourself!
Function Tags
Uses the name of the nearest function as the tag, if the tag is empty.
import { rLog } from "@rbxts/rlog";
export function doAction() {
const logger = new rLog();
logger.i("Hello world!");
}
import { rLog, functionTagEnricher } from "@rbxts/rlog";
import { doAction } from "./actions";
rLog.UpdateDefaultConfig({ enrichers: [functionTagEnricher] });
doAction();
File Tags
Uses the path of the file as the tag, if the tag is empty.
import { rLog } from "@rbxts/rlog";
export function doAction() {
const logger = new rLog();
logger.i("Hello world!");
}
import { rLog, fileTagEnricher } from "@rbxts/rlog";
import { doAction } from "./actions";
rLog.UpdateDefaultConfig({ enrichers: [fileTagEnricher] });
doAction();
Attach Source Metadata
Source Metadata is a bit of an advanced topic covered in one of our advanced guides.
Attaches source metadata to a log entry.
The metadata is attached under the source_metadata
key in encoded_data
.
import { rLog } from "@rbxts/rlog";
export function doAction() {
const logger = new rLog();
logger.i("Hello world!");
}
import { rLog, sourceMetadataEnricher } from "@rbxts/rlog";
import { doAction } from "./actions";
rLog.UpdateDefaultConfig({ enrichers: [sourceMetadataEnricher] });
doAction();
{
data: {
source_metadata: {
function_name: "doAction",
nearest_function_name: "doAction",
file_path: "ReplicatedStorage.TS.actions",
line_number: 5
}
}
}
Summary
Let's recap what we've learned about Enrichers:
- Enrichers are callbacks that can add new or change existing data on a log entry.
- You can add multiple enrichers to an instance, but they're invoked in the order they're added.
- Enrichers can decide to not operate on data by returning it as is.