Logging
On this page
Logging is a crucial aspect of software development, especially when it comes to debugging and monitoring the behavior of your applications. In this section, we'll delve into Effect's logging utilities and explore their advantages over traditional methods like console.log
.
Advantages Over Traditional Logging
Effect's logging utilities offer several advantages over traditional logging methods like console.log
:
-
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.
Now, let's dive into the specific logging utilities provided by Effect.
log
The Effect.log
function outputs a log message at the default INFO
level.
ts
import {Effect } from "effect"constprogram =Effect .log ("Application started")Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="Application started"*/
ts
import {Effect } from "effect"constprogram =Effect .log ("Application started")Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="Application started"*/
When you use the default logger with Effect.log
, it incorporates several important details into each log entry:
timestamp
: The timestamp when the log message was generated.level
: The log level at which the message is logged.fiber
: The identifier of the fiber executing the program.message
: The log content, which can include multiple items.span
: (Optional) The duration of the span in milliseconds.
For information on how to tailor the logging setup to meet specific needs, such as integrating a custom logging framework or adjusting log formats, please consult the section on customizing loggers
You can log multiple messages simultaneously:
ts
import {Effect } from "effect"constprogram =Effect .log ("message1", "message2", "message3")Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 message=message2 message=message3*/
ts
import {Effect } from "effect"constprogram =Effect .log ("message1", "message2", "message3")Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 message=message2 message=message3*/
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:
ts
import {Effect ,Cause } from "effect"constprogram =Effect .log ("message1","message2",Cause .die ("Oh no!"),Cause .die ("Oh uh!"))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 message=message2 cause="Error: Oh no!Error: Oh uh!"*/
ts
import {Effect ,Cause } from "effect"constprogram =Effect .log ("message1","message2",Cause .die ("Oh no!"),Cause .die ("Oh uh!"))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 message=message2 cause="Error: Oh no!Error: Oh uh!"*/
Log Levels
logDebug
By default, DEBUG
messages are not printed.
However, you can configure the default logger to enable them using Logger.withMinimumLogLevel
and setting the minimum log level to LogLevel.Debug
.
Here's an example that demonstrates how to enable DEBUG
messages for a specific task (task1
):
ts
import {Effect ,Logger ,LogLevel } from "effect"consttask1 =Effect .gen (function* () {yield*Effect .sleep ("2 seconds")yield*Effect .logDebug ("task1 done")}).pipe (Logger .withMinimumLogLevel (LogLevel .Debug ))consttask2 =Effect .gen (function* () {yield*Effect .sleep ("1 second")yield*Effect .logDebug ("task2 done")})constprogram =Effect .gen (function* () {yield*Effect .log ("start")yield*task1 yield*task2 yield*Effect .log ("done")})Effect .runFork (program )/*Output:timestamp=... level=INFO message=starttimestamp=... level=DEBUG message="task1 done" <-- 2 seconds latertimestamp=... level=INFO message=done <-- 1 second later*/
ts
import {Effect ,Logger ,LogLevel } from "effect"consttask1 =Effect .gen (function* () {yield*Effect .sleep ("2 seconds")yield*Effect .logDebug ("task1 done")}).pipe (Logger .withMinimumLogLevel (LogLevel .Debug ))consttask2 =Effect .gen (function* () {yield*Effect .sleep ("1 second")yield*Effect .logDebug ("task2 done")})constprogram =Effect .gen (function* () {yield*Effect .log ("start")yield*task1 yield*task2 yield*Effect .log ("done")})Effect .runFork (program )/*Output:timestamp=... level=INFO message=starttimestamp=... level=DEBUG message="task1 done" <-- 2 seconds latertimestamp=... level=INFO message=done <-- 1 second later*/
In the above example, we enable DEBUG
messages specifically for task1
by using the Logger.withMinimumLogLevel
function.
By using Logger.withMinimumLogLevel(effect, level)
, you have the flexibility to selectively enable different log levels for specific effects in your program. This allows you to control the level of detail in your logs and focus on the information that is most relevant to your debugging and troubleshooting needs.
logInfo
By default, INFO
messages are printed.
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .logInfo ("start")yield*Effect .sleep ("2 seconds")yield*Effect .sleep ("1 second")yield*Effect .logInfo ("done")})Effect .runFork (program )/*Output:timestamp=... level=INFO message=starttimestamp=... level=INFO message=done <-- 3 seconds later*/
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .logInfo ("start")yield*Effect .sleep ("2 seconds")yield*Effect .sleep ("1 second")yield*Effect .logInfo ("done")})Effect .runFork (program )/*Output:timestamp=... level=INFO message=starttimestamp=... level=INFO message=done <-- 3 seconds later*/
In the above example, the Effect.log
function is used to log an INFO
message with the content "start"
and "done"
. These messages will be printed during the execution of the program.
logWarning
By default, WARN
messages are printed.
ts
import {Effect ,Either } from "effect"consttask =Effect .fail ("Oh uh!").pipe (Effect .as (2))constprogram =Effect .gen (function* () {constfailureOrSuccess = yield*Effect .either (task )if (Either .isLeft (failureOrSuccess )) {yield*Effect .logWarning (failureOrSuccess .left )return 0} else {returnfailureOrSuccess .right }})Effect .runFork (program )/*Output:timestamp=... level=WARN fiber=#0 message="Oh uh!"*/
ts
import {Effect ,Either } from "effect"consttask =Effect .fail ("Oh uh!").pipe (Effect .as (2))constprogram =Effect .gen (function* () {constfailureOrSuccess = yield*Effect .either (task )if (Either .isLeft (failureOrSuccess )) {yield*Effect .logWarning (failureOrSuccess .left )return 0} else {returnfailureOrSuccess .right }})Effect .runFork (program )/*Output:timestamp=... level=WARN fiber=#0 message="Oh uh!"*/
logError
By default, ERROR
messages are printed.
ts
import {Effect ,Either } from "effect"consttask =Effect .fail ("Oh uh!").pipe (Effect .as (2))constprogram =Effect .gen (function* () {constfailureOrSuccess = yield*Effect .either (task )if (Either .isLeft (failureOrSuccess )) {yield*Effect .logError (failureOrSuccess .left )return 0} else {returnfailureOrSuccess .right }})Effect .runFork (program )/*Output:timestamp=... level=ERROR fiber=#0 message="Oh uh!"*/
ts
import {Effect ,Either } from "effect"consttask =Effect .fail ("Oh uh!").pipe (Effect .as (2))constprogram =Effect .gen (function* () {constfailureOrSuccess = yield*Effect .either (task )if (Either .isLeft (failureOrSuccess )) {yield*Effect .logError (failureOrSuccess .left )return 0} else {returnfailureOrSuccess .right }})Effect .runFork (program )/*Output:timestamp=... level=ERROR fiber=#0 message="Oh uh!"*/
logFatal
By default, FATAL
messages are printed.
ts
import {Effect ,Either } from "effect"consttask =Effect .fail ("Oh uh!").pipe (Effect .as (2))constprogram =Effect .gen (function* () {constfailureOrSuccess = yield*Effect .either (task )if (Either .isLeft (failureOrSuccess )) {yield*Effect .logFatal (failureOrSuccess .left )return 0} else {returnfailureOrSuccess .right }})Effect .runFork (program )/*Output:timestamp=... level=FATAL fiber=#0 message="Oh uh!"*/
ts
import {Effect ,Either } from "effect"consttask =Effect .fail ("Oh uh!").pipe (Effect .as (2))constprogram =Effect .gen (function* () {constfailureOrSuccess = yield*Effect .either (task )if (Either .isLeft (failureOrSuccess )) {yield*Effect .logFatal (failureOrSuccess .left )return 0} else {returnfailureOrSuccess .right }})Effect .runFork (program )/*Output:timestamp=... level=FATAL fiber=#0 message="Oh uh!"*/
Custom Annotations
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.
Here's how to apply a single annotation as a key/value pair:
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("message1")yield*Effect .log ("message2")}).pipe (Effect .annotateLogs ("key", "value")) // Annotation as key/value pairEffect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 key=valuetimestamp=... level=INFO fiber=#0 message=message2 key=value*/
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("message1")yield*Effect .log ("message2")}).pipe (Effect .annotateLogs ("key", "value")) // Annotation as key/value pairEffect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 key=valuetimestamp=... level=INFO fiber=#0 message=message2 key=value*/
To apply multiple annotations at once, you can pass an object containing several key/value pairs:
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("message1")yield*Effect .log ("message2")}).pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 key2=value2 key1=value1timestamp=... level=INFO fiber=#0 message=message2 key2=value2 key1=value1*/
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("message1")yield*Effect .log ("message2")}).pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message=message1 key2=value2 key1=value1timestamp=... level=INFO fiber=#0 message=message2 key2=value2 key1=value1*/
Annotations can also be applied with a scoped lifetime using Effect.annotateLogsScoped
.
This method confines the application of annotations to logs within a specific Scope of your effect computation:
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("no annotations")yield*Effect .annotateLogsScoped ({key : "value" })yield*Effect .log ("message1") // Annotation is applied to this logyield*Effect .log ("message2") // Annotation is applied to this log}).pipe (Effect .scoped ,Effect .andThen (Effect .log ("no annotations again")))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="no annotations"timestamp=... level=INFO fiber=#0 message=message1 key=valuetimestamp=... level=INFO fiber=#0 message=message2 key=valuetimestamp=... level=INFO fiber=#0 message="no annotations again"*/
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("no annotations")yield*Effect .annotateLogsScoped ({key : "value" })yield*Effect .log ("message1") // Annotation is applied to this logyield*Effect .log ("message2") // Annotation is applied to this log}).pipe (Effect .scoped ,Effect .andThen (Effect .log ("no annotations again")))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="no annotations"timestamp=... level=INFO fiber=#0 message=message1 key=valuetimestamp=... level=INFO fiber=#0 message=message2 key=valuetimestamp=... level=INFO fiber=#0 message="no annotations again"*/
Log Spans
Effect also provides support for log spans, allowing you to measure the duration of specific operations or tasks within your program.
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .sleep ("1 second")yield*Effect .log ("The job is finished!")}).pipe (Effect .withLogSpan ("myspan"))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="The job is finished!" myspan=1011ms*/
ts
import {Effect } from "effect"constprogram =Effect .gen (function* () {yield*Effect .sleep ("1 second")yield*Effect .log ("The job is finished!")}).pipe (Effect .withLogSpan ("myspan"))Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="The job is finished!" myspan=1011ms*/
In the above example, a log span is created using the Effect.withLogSpan(label)
function.
It measures the duration of the code block within the span.
The resulting duration is then automatically recorded as an annotation within the log message.
Disabling Default Logging
If you ever find yourself needing to turn off default logging, perhaps during test execution, there are various ways to achieve this within the Effect framework. In this section, we'll explore different methods to disable default logging.
Using withMinimumLogLevel
Effect provides a convenient function called withMinimumLogLevel
that allows you to set the minimum log level, effectively disabling logging:
ts
import {Effect ,Logger ,LogLevel } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("Executing task...")yield*Effect .sleep ("100 millis")console .log ("task done")})// Logging enabled (default)Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="Executing task..."task done*/// Logging disabled using withMinimumLogLevelEffect .runFork (program .pipe (Logger .withMinimumLogLevel (LogLevel .None )))/*Output:task done*/
ts
import {Effect ,Logger ,LogLevel } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("Executing task...")yield*Effect .sleep ("100 millis")console .log ("task done")})// Logging enabled (default)Effect .runFork (program )/*Output:timestamp=... level=INFO fiber=#0 message="Executing task..."task done*/// Logging disabled using withMinimumLogLevelEffect .runFork (program .pipe (Logger .withMinimumLogLevel (LogLevel .None )))/*Output:task done*/
By setting the log level to LogLevel.None
, you effectively disable logging, and only the final result will be displayed.
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 logging:
ts
import {Effect ,Logger ,LogLevel } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("Executing task...")yield*Effect .sleep ("100 millis")console .log ("task done")})constlayer =Logger .minimumLogLevel (LogLevel .None )// Logging disabled using a layerEffect .runFork (program .pipe (Effect .provide (layer )))/*Output:task done*/
ts
import {Effect ,Logger ,LogLevel } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("Executing task...")yield*Effect .sleep ("100 millis")console .log ("task done")})constlayer =Logger .minimumLogLevel (LogLevel .None )// Logging disabled using a layerEffect .runFork (program .pipe (Effect .provide (layer )))/*Output:task done*/
Using a Custom Runtime
You can also disable logging by creating a custom runtime that includes the configuration to turn off logging:
ts
import {Effect ,Logger ,LogLevel ,ManagedRuntime } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("Executing task...")yield*Effect .sleep ("100 millis")console .log ("task done")})constcustomRuntime =ManagedRuntime .make (Logger .minimumLogLevel (LogLevel .None ))customRuntime .runPromise (program )/*Output:task done*/
ts
import {Effect ,Logger ,LogLevel ,ManagedRuntime } from "effect"constprogram =Effect .gen (function* () {yield*Effect .log ("Executing task...")yield*Effect .sleep ("100 millis")console .log ("task done")})constcustomRuntime =ManagedRuntime .make (Logger .minimumLogLevel (LogLevel .None ))customRuntime .runPromise (program )/*Output:task done*/
In this approach, you create a custom runtime that incorporates the configuration to disable logging, and then you execute your program using this custom runtime.
Loading the Log Level from Configuration
To retrieve the log level from a configuration and incorporate it into your program, utilize the layer produced by Logger.minimumLogLevel
:
ts
import {Effect ,Config ,Logger ,Layer ,ConfigProvider ,LogLevel } from "effect"// Simulate a program with logsconstprogram =Effect .gen (function* () {yield*Effect .logError ("ERROR!")yield*Effect .logWarning ("WARNING!")yield*Effect .logInfo ("INFO!")yield*Effect .logDebug ("DEBUG!")})// Load the log level from the configuration as a layerconstLogLevelLive =Config .logLevel ("LOG_LEVEL").pipe (Effect .andThen ((level ) =>Logger .minimumLogLevel (level )),Layer .unwrapEffect )// Configure the program with the loaded log levelconstconfigured =Effect .provide (program ,LogLevelLive )// Test the configured program using ConfigProvider.fromMapconsttest =Effect .provide (configured ,Layer .setConfigProvider (ConfigProvider .fromMap (newMap ([["LOG_LEVEL",LogLevel .Warning .label ]]))))Effect .runFork (test )/*Output:... level=ERROR fiber=#0 message=ERROR!... level=WARN fiber=#0 message=WARNING!*/
ts
import {Effect ,Config ,Logger ,Layer ,ConfigProvider ,LogLevel } from "effect"// Simulate a program with logsconstprogram =Effect .gen (function* () {yield*Effect .logError ("ERROR!")yield*Effect .logWarning ("WARNING!")yield*Effect .logInfo ("INFO!")yield*Effect .logDebug ("DEBUG!")})// Load the log level from the configuration as a layerconstLogLevelLive =Config .logLevel ("LOG_LEVEL").pipe (Effect .andThen ((level ) =>Logger .minimumLogLevel (level )),Layer .unwrapEffect )// Configure the program with the loaded log levelconstconfigured =Effect .provide (program ,LogLevelLive )// Test the configured program using ConfigProvider.fromMapconsttest =Effect .provide (configured ,Layer .setConfigProvider (ConfigProvider .fromMap (newMap ([["LOG_LEVEL",LogLevel .Warning .label ]]))))Effect .runFork (test )/*Output:... level=ERROR fiber=#0 message=ERROR!... level=WARN fiber=#0 message=WARNING!*/
To evaluate the configured program, you can utilize ConfigProvider.fromMap
for testing (refer to Testing Services for more details).
Custom loggers
In this section, we will learn how to define a custom logger and set it as the default logger.
First, let's define our custom logger using Logger.make
:
ts
import {Logger } from "effect"export constlogger =Logger .make (({logLevel ,message }) => {globalThis .console .log (`[${logLevel .label }] ${message }`)})
ts
import {Logger } from "effect"export constlogger =Logger .make (({logLevel ,message }) => {globalThis .console .log (`[${logLevel .label }] ${message }`)})
Assuming we have defined the following program:
ts
import {Effect } from "effect"consttask1 =Effect .gen (function* () {yield*Effect .sleep ("2 seconds")yield*Effect .logDebug ("task1 done")})consttask2 =Effect .gen (function* () {yield*Effect .sleep ("1 second")yield*Effect .logDebug ("task2 done")})export constprogram =Effect .gen (function* () {yield*Effect .log ("start")yield*task1 yield*task2 yield*Effect .log ("done")})
ts
import {Effect } from "effect"consttask1 =Effect .gen (function* () {yield*Effect .sleep ("2 seconds")yield*Effect .logDebug ("task1 done")})consttask2 =Effect .gen (function* () {yield*Effect .sleep ("1 second")yield*Effect .logDebug ("task2 done")})export constprogram =Effect .gen (function* () {yield*Effect .log ("start")yield*task1 yield*task2 yield*Effect .log ("done")})
To replace the default logger, we simply need to create a specific layer using Logger.replace
and provide it to our program using Effect.provide
before executing it:
ts
import {Effect ,Logger ,LogLevel } from "effect"import * asCustomLogger from "./CustomLogger"import {program } from "./program"// Replace the default logger with the custom loggerconstlayer =Logger .replace (Logger .defaultLogger ,CustomLogger .logger )Effect .runFork (program .pipe (Logger .withMinimumLogLevel (LogLevel .Debug ),Effect .provide (layer )))
ts
import {Effect ,Logger ,LogLevel } from "effect"import * asCustomLogger from "./CustomLogger"import {program } from "./program"// Replace the default logger with the custom loggerconstlayer =Logger .replace (Logger .defaultLogger ,CustomLogger .logger )Effect .runFork (program .pipe (Logger .withMinimumLogLevel (LogLevel .Debug ),Effect .provide (layer )))
This is what we see printed on the console after executing the program:
bash
[INFO] start[DEBUG] task1 done[DEBUG] task2 done[INFO] done
bash
[INFO] start[DEBUG] task1 done[DEBUG] task2 done[INFO] done
Built-in Loggers
json
The json
logger formats log entries as JSON objects, making them easy to integrate with logging systems that consume JSON data.
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .json )))// {"message":["message1","message2"],"logLevel":"INFO","timestamp":"...","annotations":{"key2":"value2","key1":"value1"},"spans":{"myspan":0},"fiberId":"#0"}
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .json )))// {"message":["message1","message2"],"logLevel":"INFO","timestamp":"...","annotations":{"key2":"value2","key1":"value1"},"spans":{"myspan":0},"fiberId":"#0"}
logFmt
This logger outputs logs in a human-readable format that is easy to read during development or in a production console.
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .logFmt )))// timestamp=... level=INFO fiber=#0 message=message1 message=message2 myspan=0ms key2=value2 key1=value1
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .logFmt )))// timestamp=... level=INFO fiber=#0 message=message1 message=message2 myspan=0ms key2=value2 key1=value1
structured
The structured logger provides detailed log outputs, structured in a way that retains comprehensive traceability of the events, suitable for deeper analysis and troubleshooting.
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .structured )))/*{message: [ 'message1', 'message2' ],logLevel: 'INFO',timestamp: '2024-07-09T14:05:41.623Z',cause: undefined,annotations: { key2: 'value2', key1: 'value1' },spans: { myspan: 0 },fiberId: '#0'}*/
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .structured )))/*{message: [ 'message1', 'message2' ],logLevel: 'INFO',timestamp: '2024-07-09T14:05:41.623Z',cause: undefined,annotations: { key2: 'value2', key1: 'value1' },spans: { myspan: 0 },fiberId: '#0'}*/
pretty
The pretty
logger utilizes the capabilities of the console
API to generate visually engaging and color-enhanced log outputs.
This feature is particularly useful for improving the readability of log messages during development and debugging processes.
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .pretty )))/*green --v v-- bold and cyan[07:51:54.434] INFO (#0) myspan=1ms: message1message2v-- boldkey2: value2key1: value1*/
ts
import {Effect ,Logger } from "effect"constprogram =Effect .log ("message1", "message2").pipe (Effect .annotateLogs ({key1 : "value1",key2 : "value2" }),Effect .withLogSpan ("myspan"))Effect .runFork (program .pipe (Effect .provide (Logger .pretty )))/*green --v v-- bold and cyan[07:51:54.434] INFO (#0) myspan=1ms: message1message2v-- boldkey2: value2key1: value1*/
Log levels are colored as follows:
Log Level | Color |
---|---|
Trace | Gray |
Debug | Blue |
Info | Green |
Warning | Yellow |
Error | Red |
Fatal | White on Red |