Skip to content

Commit

Permalink
feat: JSON logger options + log spans
Browse files Browse the repository at this point in the history
  • Loading branch information
sukovanej committed Jan 28, 2024
1 parent fbd472d commit e49a5ec
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-dots-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect-log": minor
---

Add options for JSON logger. Add support for log spans.
Binary file modified assets/json.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/pretty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions examples/example-logging-effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const exampleEffect = pipe(
Effect.annotateLogs("json", { value: 1, another: { hello: [3, 2, 1] } }),
),
),
Effect.withLogSpan("span-label-1"),
Effect.annotateLogs("rootCause", "milan"),
Effect.tap(() =>
pipe(Effect.logFatal("Don Quijote"), Effect.annotateLogs("likes", "fp-ts")),
Expand All @@ -21,6 +22,7 @@ export const exampleEffect = pipe(
Effect.tap(() =>
Effect.logWarning("Lesnek is a beautiful surname, is it not?"),
),
Effect.withLogSpan("span-label-2"),
Effect.annotateLogs("myName", "Earl"),
Effect.tap(() => Effect.logDebug("Sooo sad, not annotations for me")),
Effect.tap(() => Effect.logTrace("Never Gonna Give You Up")),
Expand All @@ -29,9 +31,11 @@ export const exampleEffect = pipe(
Effect.flatMap(() =>
pipe(Effect.log(""), Effect.annotateLogs("likes", "fp-ts")),
),
Effect.withSpan("span-label-3"),
Effect.flatMap(() =>
pipe(Effect.log(undefined), Effect.annotateLogs("likes", "fp-ts")),
),
Effect.withLogSpan("hello-there"),
Effect.flatMap(() =>
pipe(Effect.log(null), Effect.annotateLogs("likes", "fp-ts")),
),
Expand Down
6 changes: 1 addition & 5 deletions examples/pretty-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,4 @@ import { PrettyLogger } from "effect-log";

import { exampleEffect } from "./example-logging-effect";

pipe(
exampleEffect,
Effect.provide(PrettyLogger.layer({ showFiberId: false, showTime: false })),
Effect.runSync,
);
pipe(exampleEffect, Effect.provide(PrettyLogger.layer()), Effect.runSync);
28 changes: 20 additions & 8 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ Logging batteris for effect-ts.
## [Pretty logger](examples/pretty-logger.ts)

Use `PrettyLogger.make` to create the pretty logger or `PrettyLogger.layer` to
obtain a layer replacing the default logger. Optionally, use the argument
to configure what information gets propagated to the output.
obtain a layer replacing the default logger. Optionally, these functions
accept an options object configuring what information gets
to the output.

```typescript
import { Effect, pipe } from "effect";
import { PrettyLog } from "effect-log";

import { exampleEffect } from "./example-logging-effect";

// These are the defaults, you can ommit the argument
// completely if you're okay with the defaults.
const logger = PrettyLog.layer({
showFiberId: false,
showTime: false,
showFiberId: true,
showTime: true,
showSpans: true,
});

pipe(exampleEffect, Effect.provide(logger), Effect.runSync);
Expand All @@ -26,17 +30,25 @@ pipe(exampleEffect, Effect.provide(logger), Effect.runSync);

## [JSON logger](examples/json-logger.ts)

Use `JsonLogger.make` to create the pretty logger or `JsonLogger.layer` to
obtain a layer replacing the default loggger. Optionally, specify a name
of the message field by the input argument.
Use `JsonLogger.make` to create the JSON logger or `JsonLogger.layer` to
obtain a layer replacing the default loggger. Optionally, these functions
accept an options object configuring what information gets
to the output.

```typescript
import { Effect, pipe } from "effect";
import { JsonLogger } from "effect-log";

import { exampleEffect } from "./example-logging-effect";

const logger = JsonLogger.layer();
// These are the defaults, you can ommit the argument
// completely if you're okay with the defaults.
const logger = JsonLogger.layer({
showFiberId: true,
showTime: true,
showSpans: true,
messageField: "message",
});

pipe(exampleEffect, Effect.provide(logger), Effect.runSync);
```
Expand Down
78 changes: 53 additions & 25 deletions src/JsonLogger.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,62 @@
import { List } from "effect";
import * as FiberId from "effect/FiberId";
import * as HashMap from "effect/HashMap";
import * as Layer from "effect/Layer";
import * as Logger from "effect/Logger";

import { serializeUnknown } from "effect-log/internal";

export const make = (messageField?: string) =>
Logger.make(({ fiberId, logLevel, message, annotations, cause, date }) => {
const tags: Record<string, unknown> = HashMap.reduce(
annotations,
{},
(acc, v, k) => ({
...acc,
[k]: v,
}),
);

tags["date"] = date;
tags["logLevel"] = logLevel.label;
tags[messageField ?? "message"] = serializeUnknown(message);
tags["fiberId"] = FiberId.threadName(fiberId);

if (cause._tag !== "Empty") {
tags["cause"] = cause;
}

console.log(JSON.stringify(tags));
});
export interface Options {
showFiberId: boolean;
showTime: boolean;
showSpans: boolean;
messageField: string;
}

const defaultOptions: Options = {
showFiberId: true,
showTime: true,
showSpans: true,
messageField: "message",
};

export const make = (options?: Partial<Options>) =>
Logger.make(
({ fiberId, logLevel, message, annotations, cause, date, spans }) => {
const _options = { ...defaultOptions, ...options };

const tags: Record<string, unknown> = HashMap.reduce(
annotations,
{},
(acc, v, k) => ({
...acc,
[k]: v,
}),
);

if (_options.showTime) {
tags["date"] = date;
}
tags["logLevel"] = logLevel.label;
tags[_options.messageField] = serializeUnknown(message);

if (_options.showFiberId) {
tags["fiberId"] = FiberId.threadName(fiberId);
}

if (_options.showSpans && List.isCons(spans)) {
tags["spans"] = List.toArray(spans).map((span) => span.label);
}

if (cause._tag !== "Empty") {
tags["cause"] = cause;
}

console.log(JSON.stringify(tags));
},
);

export const layer: (
messageFields?: string,
) => Layer.Layer<never, never, never> = (messageField) =>
Logger.replace(Logger.defaultLogger, make(messageField));
options?: Partial<Options>,
) => Layer.Layer<never, never, never> = (options) =>
Logger.replace(Logger.defaultLogger, make(options ?? {}));
71 changes: 49 additions & 22 deletions src/PrettyLogger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { List, LogSpan } from "effect";
import * as Cause from "effect/Cause";
import * as FiberId from "effect/FiberId";
import { pipe } from "effect/Function";
Expand All @@ -10,8 +11,10 @@ import * as ReadonlyArray from "effect/ReadonlyArray";
import { serializeUnknown } from "effect-log/internal";

const RESET = "\x1b[0m";
const DIM = "\x1b[2m";
const BOLD = "\x1b[1m";
const DIM = "\x1b[2m";
const ITALIC = "\x1b[3m";

const RED = "\x1b[31m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
Expand All @@ -32,11 +35,13 @@ const SEVERITY_TO_COLOR: Record<LogLevel.LogLevel["_tag"], string> = {
export interface PrettyLoggerOptions {
showFiberId: boolean;
showTime: boolean;
showSpans: boolean;
}

const defaultOptions: PrettyLoggerOptions = {
showFiberId: true,
showTime: true,
showSpans: true,
};

const createTimeString = (date: Date) => {
Expand Down Expand Up @@ -77,28 +82,50 @@ const createText = (message: unknown, cause: Cause.Cause<unknown>) =>
ReadonlyArray.join(" "),
);

const createSpanText = (spans: List.List<LogSpan.LogSpan>) => {
if (List.isNil(spans)) {
return "";
}

const text = List.reduce(
List.unsafeTail(spans),
List.unsafeHead(spans).label,
(acc, span) => `${acc} -> ${span.label}`,
);

return ` ${DIM}${ITALIC}${text}${RESET}`;
};

export const make = (options?: Partial<PrettyLoggerOptions>) =>
Logger.make(({ fiberId, logLevel, message, annotations, cause, date }) => {
const _options = { ...defaultOptions, ...options };

const logLevelStr = createLogLevelString(logLevel);
const timeText = _options.showTime ? `${createTimeString(date)} ` : "";
const fiberText = _options?.showFiberId
? `${DIM}(Fiber ${FiberId.threadName(fiberId)})${RESET} `
: "";

const text = createText(message, cause);

console.log(`${timeText}${fiberText}${logLevelStr} ${text}`);

if (!HashMap.isEmpty(annotations)) {
const text = HashMap.reduce(annotations, [] as string[], (acc, v, k) => [
...acc,
`${WHITE}"${k}"${RESET}: ${serializeUnknown(v)}`,
]);
console.log(`ᐉ ${DIM}{${RESET} ${text.join(", ")} ${DIM}}${RESET}`);
}
});
Logger.make(
({ fiberId, logLevel, message, annotations, cause, date, spans }) => {
const _options = { ...defaultOptions, ...options };

const logLevelStr = createLogLevelString(logLevel);
const timeText = _options.showTime ? `${createTimeString(date)} ` : "";
const fiberText = _options.showFiberId
? `${DIM}(Fiber ${FiberId.threadName(fiberId)})${RESET} `
: "";

const text = createText(message, cause);

const spansText = _options.showSpans ? createSpanText(spans) : "";

console.log(`${timeText}${fiberText}${logLevelStr}${spansText} ${text}`);

if (!HashMap.isEmpty(annotations)) {
const text = HashMap.reduce(
annotations,
[] as string[],
(acc, v, k) => [
...acc,
`${WHITE}"${k}"${RESET}: ${serializeUnknown(v)}`,
],
);
console.log(`ᐉ ${DIM}{${RESET} ${text.join(", ")} ${DIM}}${RESET}`);
}
},
);

export const layer: (
options?: Partial<PrettyLoggerOptions>,
Expand Down

0 comments on commit e49a5ec

Please sign in to comment.