We saw Console.log
in the our "Effect Hello World", which we used to print something on the console.
While Console
is mostly a debugging tool, in real apps you usually need a full logging system.
Difference between stdout printing and logging
Console
implements a simple stdout printing solution. Under the hood it uses console.log
to print messages on the console:
- Limited control over output format
- No metadata: No additional information about the data is recorded
- Suitable for small applications with minimal logging requirements (mostly debugging)
For more complex applications we need a logging system:
- Flexible formatting: Control over output format, levels, and content
- Metadata: Records additional information like timestamps, log levels, and spans
- Output not limited to console: files, telemetry, and more
- Define multiple loggers sending log information to multiple sources
- Suitable for medium to large-scale applications with complex logging requirements
Logging in effect with Logger
Instead of Console
you can use Logger
and Effect.log*
:
import { Effect } from "effect";
const main = Console.log("Hello world");
const main = Effect.log("Hello world");
Effect.runSync(main);
If you run this you will quickly notice the difference:
> effect-getting-started-course@1.0.0 logging
> tsx src/+1/logging.ts
timestamp=2024-07-16T10:02:28.453Z level=INFO fiber=#0 message="Hello world"
Effect.log
by default prints more information on the console:
- timestamp: when the log is executed
- level:
LogLevel
(by default INFO) - fiber: the id of the "process" that is running this effect
- message: the message you provide
LogLevel
One benefit of using Effect.log
is controlling when a message should be printed based on LogLevel
:
export type LogLevel = All | Fatal | Error | Warning | Info | Debug | Trace | None
The Effect
module has different log
functions for each level:
Effect.log
(equivalent toEffect.logInfo
)Effect.logDebug
Effect.logError
Effect.logFatal
Effect.logTrace
Effect.logWarning
You can also use
Effect.logWithLevel
directly and specifyLogLevel
manually.
You can then change the current LogLevel
of any Effect
by using Logger.withMinimumLogLevel
:
import { Effect, Logger, LogLevel } from "effect";
const main = Effect.logInfo("Hello world");
Effect.runSync(Logger.withMinimumLogLevel(main, LogLevel.Error));
In this example no log will be printed, since Effect.logInfo
does not pass the minimum check on LogLevel.Error
.
Defining a custom Logger
Effect comes with 2 default loggers:
- Logger that uses
console.log
to print logging information (as we saw in the example above) - Logger that sends the logs to the tracer spans as events
You can then define your custom logger as well using Logger.make
:
const customLogger = Logger.make(({ logLevel, message }) => {
// 👇 Define where/how to handle logging information
console.log(`[${logLevel.label}] ${message}`);
});
You can then add customLogger
to the list of loggers when running an effect:
import { Effect, Logger } from "effect";
const customLogger = Logger.make(({ logLevel, message }) => {
console.log(`[${logLevel.label}] ${message}`);
});
const loggerLayer = Logger.add(customLogger);
const main = Effect.logInfo("Hello world").pipe(Effect.provide(loggerLayer));
Effect.runSync(main);
Logger.add
creates a new Layer<never>
. Providing this layer using Effect.provide
adds our new logger when running the effect.
Since we added a new logger we will now see two printed messages:
> effect-getting-started-course@1.0.0 logging
> tsx src/+1/logging.ts
timestamp=2024-07-16T10:32:12.340Z level=INFO fiber=#0 message="Hello world"
[INFO] Hello world
You can add as many loggers as you want. A
Logger
might not output anything to the console, it might instead send information direct to file or database.
I leave you to explore more about logging with effect:
- You can have more than one logger (on top of the 2 default loggers)
- Loggers can access context to add metadata (
Context
/Config
) - Loggers can abstract 3rd party logging services (e.g. sentry)
- Loggers can be controlled using
LogLevel