Skip to content

Commit

Permalink
add Logger.withLeveledConsole (#3540)
Browse files Browse the repository at this point in the history
  • Loading branch information
Schniz authored and tim-smart committed Sep 13, 2024
1 parent 2fc81f8 commit ce870e3
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 1 deletion.
20 changes: 20 additions & 0 deletions .changeset/calm-houses-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"effect": minor
---

add `Logger.withLeveledConsole`

In browsers and different platforms, `console.error` renders differently than `console.info`. This helps to distinguish between different levels of logging. `Logger.withLeveledConsole` takes any logger and calls the respective `Console` method based on the log level. For instance, `Effect.logError` will call `Console.error` and `Effect.logInfo` will call `Console.info`.

To use it, you can replace the default logger with a `Logger.withLeveledConsole` logger:

```ts
import { Logger, Effect } from "effect"

const loggerLayer = Logger.withLeveledConsole(Logger.stringLogger)

Effect.gen(function* () {
yield* Effect.logError("an error")
yield* Effect.logInfo("an info")
}).pipe(Effect.provide(loggerLayer))
```
22 changes: 22 additions & 0 deletions packages/effect/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,28 @@ export const batched: {
*/
export const withConsoleLog: <M, O>(self: Logger<M, O>) => Logger<M, void> = fiberRuntime.loggerWithConsoleLog

/**
* Takes a `Logger<M, O>` and returns a logger that calls the respective `Console` method
* based on the log level.
*
* @example
* import { Logger, Effect } from "effect"
*
* const loggerLayer = Logger.replace(
* Logger.defaultLogger,
* Logger.withLeveledConsole(Logger.stringLogger),
* )
*
* Effect.gen(function* () {
* yield* Effect.logError("an error")
* yield* Effect.logInfo("an info")
* }).pipe(Effect.provide(loggerLayer))
*
* @since 3.8.0
* @category console
*/
export const withLeveledConsole: <M, O>(self: Logger<M, O>) => Logger<M, void> = fiberRuntime.loggerWithLeveledLog

/**
* @since 2.0.0
* @category console
Expand Down
22 changes: 22 additions & 0 deletions packages/effect/src/internal/fiberRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,28 @@ export const loggerWithConsoleLog = <M, O>(self: Logger<M, O>): Logger<M, void>
Context.get(services, consoleTag).unsafe.log(self.log(opts))
})

/** @internal */
export const loggerWithLeveledLog = <M, O>(self: Logger<M, O>): Logger<M, void> =>
internalLogger.makeLogger((opts) => {
const services = FiberRefs.getOrDefault(opts.context, defaultServices.currentServices)
const unsafeLogger = Context.get(services, consoleTag).unsafe
switch (opts.logLevel._tag) {
case "Debug":
return unsafeLogger.debug(self.log(opts))
case "Info":
return unsafeLogger.info(self.log(opts))
case "Trace":
return unsafeLogger.trace(self.log(opts))
case "Warning":
return unsafeLogger.warn(self.log(opts))
case "Error":
case "Fatal":
return unsafeLogger.error(self.log(opts))
default:
return unsafeLogger.log(self.log(opts))
}
})

/** @internal */
export const loggerWithConsoleError = <M, O>(self: Logger<M, O>): Logger<M, void> =>
internalLogger.makeLogger((opts) => {
Expand Down
52 changes: 51 additions & 1 deletion packages/effect/test/Logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import * as HashMap from "effect/HashMap"
import { logLevelInfo } from "effect/internal/core"
import * as List from "effect/List"
import * as Logger from "effect/Logger"
import * as LogLevel from "effect/LogLevel"
import * as LogSpan from "effect/LogSpan"
import { afterEach, assert, beforeEach, describe, expect, it, vi } from "vitest"
import { assert, describe, expect, it } from "effect/test/utils/extend"
import { afterEach, beforeEach, vi } from "vitest"

describe("Logger", () => {
it("isLogger", () => {
Expand All @@ -21,6 +23,54 @@ describe("Logger", () => {
})
})

describe("withLeveledConsole", () => {
it.effect("calls the respsective Console functions on a given level", () =>
Effect.gen(function*() {
const c = yield* Effect.console
const logs: Array<{ level: string; value: unknown }> = []
const pusher = (level: string) => (value: unknown) => {
logs.push({ level, value })
}
const newConsole: typeof c = {
...c,
unsafe: {
...c.unsafe,
log: pusher("log"),
warn: pusher("warn"),
error: pusher("error"),
info: pusher("info"),
debug: pusher("debug"),
trace: pusher("trace")
}
}

const logger = Logger.make((o) => String(o.message)).pipe(Logger.withLeveledConsole)
yield* Effect.gen(function*() {
yield* Effect.log("log plain")
yield* Effect.logInfo("log info")
yield* Effect.logWarning("log warn")
yield* Effect.logError("log err")
yield* Effect.logFatal("log fatal")
yield* Effect.logDebug("log debug")
yield* Effect.logTrace("log trace")
}).pipe(
Effect.provide(Logger.replace(Logger.defaultLogger, logger)),
Logger.withMinimumLogLevel(LogLevel.Trace),
Effect.withConsole(newConsole)
)

expect(logs).toEqual([
{ level: "info", value: "log plain" },
{ level: "info", value: "log info" },
{ level: "warn", value: "log warn" },
{ level: "error", value: "log err" },
{ level: "error", value: "log fatal" },
{ level: "debug", value: "log debug" },
{ level: "trace", value: "log trace" }
])
}))
})

describe("stringLogger", () => {
beforeEach(() => {
vi.useFakeTimers()
Expand Down

0 comments on commit ce870e3

Please sign in to comment.