Logging is an important aspect of software development, especially for debugging and monitoring the behavior of your applications. In this section, we’ll explore Effect’s logging utilities and see how they compare to traditional logging methods.
Advantages Over Traditional Logging
Effect’s logging utilities provide several benefits over conventional logging approaches:
Dynamic Log Level Control: With Effect’s logging, you have the ability to change the log level dynamically. This means you can control which log messages get displayed based on their severity. For example, you can configure your application to log only warnings or errors, which can be extremely helpful in production environments to reduce noise.
Custom Logging Output: Effect’s logging utilities allow you to change how logs are handled. You can direct log messages to various destinations, such as a service or a file, using a custom logger. This flexibility ensures that logs are stored and processed in a way that best suits your application’s requirements.
Fine-Grained Logging: Effect enables fine-grained control over logging on a per-part basis of your program. You can set different log levels for different parts of your application, tailoring the level of detail to each specific component. This can be invaluable for debugging and troubleshooting, as you can focus on the information that matters most.
Environment-Based Logging: Effect’s logging utilities can be combined with deployment environments to achieve granular logging strategies. For instance, during development, you might choose to log everything at a trace level and above for detailed debugging. In contrast, your production version could be configured to log only errors or critical issues, minimizing the impact on performance and noise in production logs.
Additional Features: Effect’s logging utilities come with additional features such as the ability to measure time spans, alter log levels on a per-effect basis, and integrate spans for performance monitoring.
log
The Effect.log function allows you to log a message at the default INFO level.
Example (Logging a Simple Message)
The default logger in Effect adds several useful details to each log entry:
Annotation
Description
timestamp
The timestamp when the log message was generated.
level
The log level at which the message is logged (e.g., INFO, ERROR).
fiber
The identifier of the fiber executing the program.
message
The log message content, which can include multiple strings or values.
span
(Optional) The duration of a span in milliseconds, providing insight into the timing of operations.
You can also log multiple messages at once.
Example (Logging Multiple Messages)
For added context, you can also include one or more Cause instances in your logs,
which provide detailed error information under an additional cause annotation:
Example (Logging with Causes)
Log Levels
logDebug
By default, DEBUG messages are not displayed. To enable DEBUG logs, you can adjust the logging configuration using Logger.withMinimumLogLevel, setting the minimum level to LogLevel.Debug.
Example (Enabling Debug Logs)
logInfo
The INFO log level is displayed by default. This level is typically used for general application events or progress updates.
Example (Logging at the Info Level)
logWarning
The WARN log level is displayed by default. This level is intended for potential issues or warnings that do not immediately disrupt the flow of the program but should be monitored.
Example (Logging at the Warning Level)
logError
The ERROR log level is displayed by default. These messages represent issues that need to be addressed.
Example (Logging at the Error Level)
logFatal
The FATAL log level is displayed by default. This log level is typically reserved for unrecoverable errors.
Example (Logging at the Fatal Level)
Custom Annotations
You can enhance your log outputs by adding custom annotations using the Effect.annotateLogs function. This allows you to attach extra metadata to each log entry, making it easier to trace and add context to your logs.
Enhance your log outputs by incorporating custom annotations with the Effect.annotateLogs function.
This function allows you to append additional metadata to each log entry of an effect, enhancing traceability and context.
Adding a Single Annotation
You can apply a single annotation as a key/value pair to all log entries within an effect.
Example (Single Key/Value Annotation)
In this example, all logs generated within the program will include the annotation key=value.
Adding Multiple Annotations
You can also apply multiple annotations at once by passing an object with key/value pairs. Each key/value pair will be added to every log entry within the effect.
Example (Multiple Annotations)
In this case, each log will contain both key1=value1 and key2=value2.
Scoped Annotations
If you want to limit the scope of your annotations so that they only apply to certain log entries, you can use Effect.annotateLogsScoped. This function confines the annotations to logs produced within a specific scope.
Example (Scoped Annotations)
Log Spans
Effect provides built-in support for log spans, which allow you to measure and log the duration of specific tasks or sections of your code. This feature is helpful for tracking how long certain operations take, giving you better insights into the performance of your application.
Example (Measuring Task Duration with a Log Span)
Disabling Default Logging
Sometimes, perhaps during test execution, you might want to disable default logging in your application. Effect provides several ways to turn off logging when needed. In this section, we’ll look at different methods to disable logging in the Effect framework.
Example (Using Logger.withMinimumLogLevel)
One convenient way to disable logging is by using the Logger.withMinimumLogLevel function. This allows you to set the minimum log level to None, effectively turning off all log output.
Example (Using a Layer)
Another approach to disable logging is by creating a layer that sets the minimum log level to LogLevel.None, effectively turning off all log output.
Example (Using a Custom Runtime)
You can also disable logging by creating a custom runtime that includes the configuration to turn off logging:
Loading the Log Level from Configuration
To dynamically load the log level from a configuration and apply it to your program, you can use the Logger.minimumLogLevel layer. This allows your application to adjust its logging behavior based on external configuration.
Example (Loading Log Level from Configuration)
Custom loggers
In this section, you’ll learn how to define a custom logger and set it as the default logger in your application. Custom loggers give you control over how log messages are handled, such as routing them to external services, writing to files, or formatting logs in a specific way.
Defining a Custom Logger
You can define your own logger using the Logger.make function. This function allows you to specify how log messages should be processed.
Example (Defining a Simple Custom Logger)
In this example, the custom logger logs messages to the console with the log level and message formatted as [LogLevel] Message.
Using a Custom Logger in a Program
Let’s assume you have the following tasks and a program where you log some messages:
To replace the default logger with your custom logger, you can use the Logger.replace function. After creating a layer that replaces the default logger, you provide it to your program using Effect.provide.
Example (Replacing the Default Logger with a Custom Logger)
When you run the above program, the following log messages are printed to the console:
Built-in Loggers
Effect provides several built-in loggers that you can use depending on your logging needs. These loggers offer different formats, each suited for different environments or purposes, such as development, production, or integration with external logging services.
logFmt (default)
The logFmt logger outputs logs in a human-readable key-value format. This format is often used in development and production for its simplicity and readability in the console.
Output:
pretty
The pretty logger enhances log output by using color and indentation for better readability, making it particularly useful during development when visually scanning logs in the console.
Output:
structured
The structured logger provides highly structured logs. This format is useful for situations that require detailed traceability of events, often in environments where logs are processed and analyzed by automated systems.
Output:
json
The json logger formats log entries as JSON objects, which is ideal for environments where logs are ingested by systems that process JSON.