effect 3.5.0 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cjs/Effect.js +78 -15
  2. package/dist/cjs/Effect.js.map +1 -1
  3. package/dist/cjs/Logger.js +223 -12
  4. package/dist/cjs/Logger.js.map +1 -1
  5. package/dist/cjs/Random.js +15 -0
  6. package/dist/cjs/Random.js.map +1 -1
  7. package/dist/cjs/internal/cause.js +7 -3
  8. package/dist/cjs/internal/cause.js.map +1 -1
  9. package/dist/cjs/internal/core.js +5 -1
  10. package/dist/cjs/internal/core.js.map +1 -1
  11. package/dist/cjs/internal/effect/circular.js +1 -1
  12. package/dist/cjs/internal/effect/circular.js.map +1 -1
  13. package/dist/cjs/internal/logger.js +100 -22
  14. package/dist/cjs/internal/logger.js.map +1 -1
  15. package/dist/cjs/internal/version.js +1 -1
  16. package/dist/dts/Console.d.ts +2 -2
  17. package/dist/dts/Console.d.ts.map +1 -1
  18. package/dist/dts/Effect.d.ts +78 -15
  19. package/dist/dts/Effect.d.ts.map +1 -1
  20. package/dist/dts/Logger.d.ts +223 -12
  21. package/dist/dts/Logger.d.ts.map +1 -1
  22. package/dist/dts/Random.d.ts +15 -0
  23. package/dist/dts/Random.d.ts.map +1 -1
  24. package/dist/dts/internal/core.d.ts.map +1 -1
  25. package/dist/esm/Effect.js +78 -15
  26. package/dist/esm/Effect.js.map +1 -1
  27. package/dist/esm/Logger.js +223 -12
  28. package/dist/esm/Logger.js.map +1 -1
  29. package/dist/esm/Random.js +15 -0
  30. package/dist/esm/Random.js.map +1 -1
  31. package/dist/esm/internal/cause.js +7 -3
  32. package/dist/esm/internal/cause.js.map +1 -1
  33. package/dist/esm/internal/core.js +3 -0
  34. package/dist/esm/internal/core.js.map +1 -1
  35. package/dist/esm/internal/effect/circular.js +1 -1
  36. package/dist/esm/internal/effect/circular.js.map +1 -1
  37. package/dist/esm/internal/logger.js +99 -21
  38. package/dist/esm/internal/logger.js.map +1 -1
  39. package/dist/esm/internal/version.js +1 -1
  40. package/package.json +1 -1
  41. package/src/Console.ts +2 -2
  42. package/src/Effect.ts +78 -15
  43. package/src/Logger.ts +223 -12
  44. package/src/Random.ts +15 -0
  45. package/src/internal/cause.ts +7 -3
  46. package/src/internal/core.ts +4 -0
  47. package/src/internal/effect/circular.ts +1 -1
  48. package/src/internal/logger.ts +102 -18
  49. package/src/internal/version.ts +1 -1
package/src/Logger.ts CHANGED
@@ -73,6 +73,35 @@ export declare namespace Logger {
73
73
  }
74
74
 
75
75
  /**
76
+ * Creates a custom logger that formats log messages according to the provided
77
+ * function.
78
+ *
79
+ * @example
80
+ * import { Effect, Logger, LogLevel } from "effect"
81
+ *
82
+ * const logger = Logger.make(({ logLevel, message }) => {
83
+ * globalThis.console.log(`[${logLevel.label}] ${message}`)
84
+ * })
85
+ *
86
+ * const task1 = Effect.logDebug("task1 done")
87
+ * const task2 = Effect.logDebug("task2 done")
88
+ *
89
+ * const program = Effect.gen(function*() {
90
+ * yield* Effect.log("start")
91
+ * yield* task1
92
+ * yield* task2
93
+ * yield* Effect.log("done")
94
+ * }).pipe(
95
+ * Logger.withMinimumLogLevel(LogLevel.Debug),
96
+ * Effect.provide(Logger.replace(Logger.defaultLogger, logger))
97
+ * )
98
+ *
99
+ * // Effect.runFork(program)
100
+ * // [INFO] start
101
+ * // [DEBUG] task1 done
102
+ * // [DEBUG] task2 done
103
+ * // [INFO] done
104
+ *
76
105
  * @category constructors
77
106
  * @since 2.0.0
78
107
  */
@@ -160,25 +189,36 @@ export const map: {
160
189
  } = internal.map
161
190
 
162
191
  /**
163
- * @since 2.0.0
164
- * @category mapping
192
+ * Creates a batched logger that groups log messages together and processes them
193
+ * in intervals.
194
+ *
195
+ * @param window - The time window in which to batch log messages.
196
+ *
165
197
  * @example
166
- * import { Console, Effect, Logger } from "effect";
198
+ * import { Console, Effect, Logger } from "effect"
167
199
  *
168
200
  * const LoggerLive = Logger.replaceScoped(
169
201
  * Logger.defaultLogger,
170
202
  * Logger.logfmtLogger.pipe(
171
- * Logger.batched("500 millis", (messages) =>
172
- * Console.log("BATCH", messages.join("\n"))
173
- * )
203
+ * Logger.batched("500 millis", (messages) => Console.log("BATCH", `[\n${messages.join("\n")}\n]`))
174
204
  * )
175
- * );
205
+ * )
206
+ *
207
+ * const program = Effect.gen(function*() {
208
+ * yield* Effect.log("one")
209
+ * yield* Effect.log("two")
210
+ * yield* Effect.log("three")
211
+ * }).pipe(Effect.provide(LoggerLive))
212
+ *
213
+ * // Effect.runFork(program)
214
+ * // BATCH [
215
+ * // timestamp=... level=INFO fiber=#0 message=one
216
+ * // timestamp=... level=INFO fiber=#0 message=two
217
+ * // timestamp=... level=INFO fiber=#0 message=three
218
+ * // ]
176
219
  *
177
- * Effect.gen(function* (_) {
178
- * yield* _(Effect.log("one"));
179
- * yield* _(Effect.log("two"));
180
- * yield* _(Effect.log("three"));
181
- * }).pipe(Effect.provide(LoggerLive), Effect.runFork);
220
+ * @since 2.0.0
221
+ * @category mapping
182
222
  */
183
223
  export const batched: {
184
224
  <Output, R>(
@@ -278,6 +318,17 @@ export const test: {
278
318
  } = internalCircular.test
279
319
 
280
320
  /**
321
+ * Sets the minimum log level for subsequent logging operations, allowing
322
+ * control over which log messages are displayed based on their severity.
323
+ *
324
+ * @example
325
+ * import { Effect, Logger, LogLevel } from "effect"
326
+ *
327
+ * const program = Effect.logDebug("message1").pipe(Logger.withMinimumLogLevel(LogLevel.Debug))
328
+ *
329
+ * // Effect.runFork(program)
330
+ * // timestamp=... level=DEBUG fiber=#0 message=message1
331
+ *
281
332
  * @since 2.0.0
282
333
  * @category context
283
334
  */
@@ -345,12 +396,40 @@ export const zipRight: {
345
396
  export const defaultLogger: Logger<unknown, void> = fiberRuntime.defaultLogger
346
397
 
347
398
  /**
399
+ * The `jsonLogger` logger formats log entries as JSON objects, making them easy to
400
+ * integrate with logging systems that consume JSON data.
401
+ *
402
+ * @example
403
+ * import { Effect, Logger } from "effect"
404
+ *
405
+ * const program = Effect.log("message1", "message2").pipe(
406
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
407
+ * Effect.withLogSpan("myspan")
408
+ * )
409
+ *
410
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.json)))
411
+ * // {"message":["message1","message2"],"logLevel":"INFO","timestamp":"...","annotations":{"key2":"value2","key1":"value1"},"spans":{"myspan":0},"fiberId":"#0"}
412
+ *
348
413
  * @since 2.0.0
349
414
  * @category constructors
350
415
  */
351
416
  export const jsonLogger: Logger<unknown, string> = internal.jsonLogger
352
417
 
353
418
  /**
419
+ * This logger outputs logs in a human-readable format that is easy to read
420
+ * during development or in a production console.
421
+ *
422
+ * @example
423
+ * import { Effect, Logger } from "effect"
424
+ *
425
+ * const program = Effect.log("message1", "message2").pipe(
426
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
427
+ * Effect.withLogSpan("myspan")
428
+ * )
429
+ *
430
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.logFmt)))
431
+ * // timestamp=... level=INFO fiber=#0 message=message1 message=message2 myspan=0ms key2=value2 key1=value1
432
+ *
354
433
  * @since 2.0.0
355
434
  * @category constructors
356
435
  */
@@ -363,6 +442,27 @@ export const logfmtLogger: Logger<unknown, string> = internal.logfmtLogger
363
442
  export const stringLogger: Logger<unknown, string> = internal.stringLogger
364
443
 
365
444
  /**
445
+ * The pretty logger utilizes the capabilities of the console API to generate
446
+ * visually engaging and color-enhanced log outputs. This feature is
447
+ * particularly useful for improving the readability of log messages during
448
+ * development and debugging processes.
449
+ *
450
+ * @example
451
+ * import { Effect, Logger } from "effect"
452
+ *
453
+ * const program = Effect.log("message1", "message2").pipe(
454
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
455
+ * Effect.withLogSpan("myspan")
456
+ * )
457
+ *
458
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.pretty)))
459
+ * // green --v v-- bold and cyan
460
+ * // [07:51:54.434] INFO (#0) myspan=1ms: message1
461
+ * // message2
462
+ * // v-- bold
463
+ * // key2: value2
464
+ * // key1: value1
465
+ *
366
466
  * @since 3.5.0
367
467
  * @category constructors
368
468
  */
@@ -376,6 +476,29 @@ export const prettyLogger: (
376
476
  ) => Logger<unknown, void> = internal.prettyLogger
377
477
 
378
478
  /**
479
+ * The structured logger provides detailed log outputs, structured in a way that
480
+ * retains comprehensive traceability of the events, suitable for deeper
481
+ * analysis and troubleshooting.
482
+ *
483
+ * @example
484
+ * import { Effect, Logger } from "effect"
485
+ *
486
+ * const program = Effect.log("message1", "message2").pipe(
487
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
488
+ * Effect.withLogSpan("myspan")
489
+ * )
490
+ *
491
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.structured)))
492
+ * // {
493
+ * // message: [ 'message1', 'message2' ],
494
+ * // logLevel: 'INFO',
495
+ * // timestamp: '2024-07-09T14:05:41.623Z',
496
+ * // cause: undefined,
497
+ * // annotations: { key2: 'value2', key1: 'value1' },
498
+ * // spans: { myspan: 0 },
499
+ * // fiberId: '#0'
500
+ * // }
501
+ *
379
502
  * @since 2.0.0
380
503
  * @category constructors
381
504
  */
@@ -399,30 +522,118 @@ export const structuredLogger: Logger<
399
522
  export const tracerLogger: Logger<unknown, void> = fiberRuntime.tracerLogger
400
523
 
401
524
  /**
525
+ * The `json` logger formats log entries as JSON objects, making them easy to
526
+ * integrate with logging systems that consume JSON data.
527
+ *
528
+ * @example
529
+ * import { Effect, Logger } from "effect"
530
+ *
531
+ * const program = Effect.log("message1", "message2").pipe(
532
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
533
+ * Effect.withLogSpan("myspan")
534
+ * )
535
+ *
536
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.json)))
537
+ * // {"message":["message1","message2"],"logLevel":"INFO","timestamp":"...","annotations":{"key2":"value2","key1":"value1"},"spans":{"myspan":0},"fiberId":"#0"}
538
+ *
402
539
  * @since 2.0.0
403
540
  * @category constructors
404
541
  */
405
542
  export const json: Layer.Layer<never> = replace(fiberRuntime.defaultLogger, fiberRuntime.jsonLogger)
406
543
 
407
544
  /**
545
+ * This logger outputs logs in a human-readable format that is easy to read
546
+ * during development or in a production console.
547
+ *
548
+ * @example
549
+ * import { Effect, Logger } from "effect"
550
+ *
551
+ * const program = Effect.log("message1", "message2").pipe(
552
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
553
+ * Effect.withLogSpan("myspan")
554
+ * )
555
+ *
556
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.logFmt)))
557
+ * // timestamp=... level=INFO fiber=#0 message=message1 message=message2 myspan=0ms key2=value2 key1=value1
558
+ *
408
559
  * @since 2.0.0
409
560
  * @category constructors
410
561
  */
411
562
  export const logFmt: Layer.Layer<never> = replace(fiberRuntime.defaultLogger, fiberRuntime.logFmtLogger)
412
563
 
413
564
  /**
565
+ * The pretty logger utilizes the capabilities of the console API to generate
566
+ * visually engaging and color-enhanced log outputs. This feature is
567
+ * particularly useful for improving the readability of log messages during
568
+ * development and debugging processes.
569
+ *
570
+ * @example
571
+ * import { Effect, Logger } from "effect"
572
+ *
573
+ * const program = Effect.log("message1", "message2").pipe(
574
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
575
+ * Effect.withLogSpan("myspan")
576
+ * )
577
+ *
578
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.pretty)))
579
+ * // green --v v-- bold and cyan
580
+ * // [07:51:54.434] INFO (#0) myspan=1ms: message1
581
+ * // message2
582
+ * // v-- bold
583
+ * // key2: value2
584
+ * // key1: value1
585
+ *
414
586
  * @since 3.5.0
415
587
  * @category constructors
416
588
  */
417
589
  export const pretty: Layer.Layer<never> = replace(fiberRuntime.defaultLogger, fiberRuntime.prettyLogger)
418
590
 
419
591
  /**
592
+ * The structured logger provides detailed log outputs, structured in a way that
593
+ * retains comprehensive traceability of the events, suitable for deeper
594
+ * analysis and troubleshooting.
595
+ *
596
+ * @example
597
+ * import { Effect, Logger } from "effect"
598
+ *
599
+ * const program = Effect.log("message1", "message2").pipe(
600
+ * Effect.annotateLogs({ key1: "value1", key2: "value2" }),
601
+ * Effect.withLogSpan("myspan")
602
+ * )
603
+ *
604
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.structured)))
605
+ * // {
606
+ * // message: [ 'message1', 'message2' ],
607
+ * // logLevel: 'INFO',
608
+ * // timestamp: '2024-07-09T14:05:41.623Z',
609
+ * // cause: undefined,
610
+ * // annotations: { key2: 'value2', key1: 'value1' },
611
+ * // spans: { myspan: 0 },
612
+ * // fiberId: '#0'
613
+ * // }
614
+ *
420
615
  * @since 2.0.0
421
616
  * @category constructors
422
617
  */
423
618
  export const structured: Layer.Layer<never> = replace(fiberRuntime.defaultLogger, fiberRuntime.structuredLogger)
424
619
 
425
620
  /**
621
+ * Sets the minimum log level for logging operations, allowing control over
622
+ * which log messages are displayed based on their severity.
623
+ *
624
+ * @example
625
+ * import { Effect, Logger, LogLevel } from "effect"
626
+ *
627
+ * const program = Effect.gen(function*() {
628
+ * yield* Effect.log("Executing task...")
629
+ * yield* Effect.sleep("100 millis")
630
+ * console.log("task done")
631
+ * })
632
+ *
633
+ * // Logging disabled using a layer
634
+ * // Effect.runFork(program.pipe(Effect.provide(Logger.minimumLogLevel(LogLevel.None))))
635
+ * // task done
636
+ *
426
637
  * @since 2.0.0
427
638
  * @category context
428
639
  */
package/src/Random.ts CHANGED
@@ -122,6 +122,21 @@ export const Random: Context.Tag<Random, Random> = internal.randomTag
122
122
  /**
123
123
  * Constructs the `Random` service, seeding the pseudo-random number generator
124
124
  * with an hash of the specified seed.
125
+ * This constructor is useful for generating predictable sequences of random values for specific use cases.
126
+ *
127
+ * Example uses:
128
+ * - Generating random UI data for visual tests.
129
+ * - Creating data that needs to change daily but remain the same throughout a single day, such as using a date as the seed.
130
+ *
131
+ * @param seed - The seed value used to initialize the generator.
132
+ *
133
+ * @example
134
+ * import { Effect, Random } from "effect"
135
+ *
136
+ * const random1 = Random.make("myseed")
137
+ * const random2 = Random.make("myseed")
138
+ *
139
+ * assert.equal(Effect.runSync(random1.next), Effect.runSync(random2.next))
125
140
  *
126
141
  * @since 3.5.0
127
142
  * @category constructors
@@ -1100,9 +1100,13 @@ const prettyErrorStack = (message: string, stack: string, span?: Span | undefine
1100
1100
  const stackFn = spanToTrace.get(current)
1101
1101
  if (typeof stackFn === "function") {
1102
1102
  const stack = stackFn()
1103
- const locationMatch = stack.match(locationRegex)
1104
- const location = locationMatch ? locationMatch[1] : stack.replace(/^at /, "")
1105
- out.push(` at ${current.name} (${location})`)
1103
+ if (typeof stack === "string") {
1104
+ const locationMatch = stack.match(locationRegex)
1105
+ const location = locationMatch ? locationMatch[1] : stack.replace(/^at /, "")
1106
+ out.push(` at ${current.name} (${location})`)
1107
+ } else {
1108
+ out.push(` at ${current.name}`)
1109
+ }
1106
1110
  } else {
1107
1111
  out.push(` at ${current.name}`)
1108
1112
  }
@@ -5,6 +5,7 @@ import * as Chunk from "../Chunk.js"
5
5
  import * as Context from "../Context.js"
6
6
  import type * as Deferred from "../Deferred.js"
7
7
  import type * as Differ from "../Differ.js"
8
+ import * as Duration from "../Duration.js"
8
9
  import type * as Effect from "../Effect.js"
9
10
  import * as Either from "../Either.js"
10
11
  import * as Equal from "../Equal.js"
@@ -2297,6 +2298,9 @@ export const TimeoutExceptionTypeId: Cause.TimeoutExceptionTypeId = Symbol.for(
2297
2298
  export const TimeoutException = makeException<Cause.TimeoutException>({
2298
2299
  [TimeoutExceptionTypeId]: TimeoutExceptionTypeId
2299
2300
  }, "TimeoutException")
2301
+ /** @internal */
2302
+ export const timeoutExceptionFromDuration = (duration: Duration.DurationInput): Cause.TimeoutException =>
2303
+ new TimeoutException(`Operation timed out before the specified duration of '${Duration.format(duration)}' elapsed`)
2300
2304
 
2301
2305
  /** @internal */
2302
2306
  export const isTimeoutException = (u: unknown): u is Cause.TimeoutException => hasProperty(u, TimeoutExceptionTypeId)
@@ -419,7 +419,7 @@ export const timeout = dual<
419
419
  ) => Effect.Effect<A, E | Cause.TimeoutException, R>
420
420
  >(2, (self, duration) =>
421
421
  timeoutFail(self, {
422
- onTimeout: () => new core.TimeoutException(),
422
+ onTimeout: () => core.timeoutExceptionFromDuration(duration),
423
423
  duration
424
424
  }))
425
425
 
@@ -369,9 +369,6 @@ export const isLogger = (u: unknown): u is Logger.Logger<unknown, unknown> => {
369
369
  return typeof u === "object" && u != null && LoggerTypeId in u
370
370
  }
371
371
 
372
- const processStdoutIsTTY = typeof process === "object" && "stdout" in process && process.stdout.isTTY === true
373
- const hasWindow = typeof window === "object"
374
-
375
372
  const withColor = (text: string, ...colors: ReadonlyArray<string>) => {
376
373
  let out = ""
377
374
  for (let i = 0; i < colors.length; i++) {
@@ -403,12 +400,26 @@ const logLevelColors: Record<LogLevel.LogLevel["_tag"], ReadonlyArray<string>> =
403
400
  Error: [colors.red],
404
401
  Fatal: [colors.bgBrightRed, colors.black]
405
402
  }
403
+ const logLevelStyle: Record<LogLevel.LogLevel["_tag"], string> = {
404
+ None: "",
405
+ All: "",
406
+ Trace: "color:gray",
407
+ Debug: "color:blue",
408
+ Info: "color:green",
409
+ Warning: "color:orange",
410
+ Error: "color:red",
411
+ Fatal: "background-color:red;color:white"
412
+ }
406
413
 
407
414
  const defaultDateFormat = (date: Date): string =>
408
415
  `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${
409
416
  date.getSeconds().toString().padStart(2, "0")
410
417
  }.${date.getMilliseconds().toString().padStart(3, "0")}`
411
418
 
419
+ const processStdoutIsTTY = typeof process === "object" && "stdout" in process && process.stdout.isTTY === true
420
+ const hasWindow = typeof window === "object"
421
+ const isWorker = typeof self === "object" && self.constructor && self.constructor.name.includes("Worker")
422
+
412
423
  /** @internal */
413
424
  export const prettyLogger = (options?: {
414
425
  readonly colors?: "auto" | boolean | undefined
@@ -417,21 +428,31 @@ export const prettyLogger = (options?: {
417
428
  readonly mode?: "browser" | "tty" | "auto" | undefined
418
429
  }) => {
419
430
  const mode_ = options?.mode ?? "auto"
420
- const mode = mode_ === "auto" ? (hasWindow ? "browser" : "tty") : mode_
431
+ const mode = mode_ === "auto" ? (hasWindow || isWorker ? "browser" : "tty") : mode_
421
432
  const isBrowser = mode === "browser"
422
433
  const showColors = typeof options?.colors === "boolean" ? options.colors : processStdoutIsTTY || isBrowser
423
- const color = showColors ? withColor : withColorNoop
424
434
  const formatDate = options?.formatDate ?? defaultDateFormat
435
+ return isBrowser
436
+ ? prettyLoggerBrowser({ colors: showColors, formatDate })
437
+ : prettyLoggerTty({ colors: showColors, formatDate, stderr: options?.stderr === true })
438
+ }
425
439
 
440
+ const prettyLoggerTty = (options: {
441
+ readonly colors: boolean
442
+ readonly stderr: boolean
443
+ readonly formatDate: (date: Date) => string
444
+ }) => {
445
+ const processIsBun = typeof process === "object" && "isBun" in process && process.isBun === true
446
+ const color = options.colors && processStdoutIsTTY ? withColor : withColorNoop
426
447
  return makeLogger<unknown, void>(
427
448
  ({ annotations, cause, context, date, fiberId, logLevel, message: message_, spans }) => {
428
449
  const services = FiberRefs.getOrDefault(context, defaultServices.currentServices)
429
450
  const console = Context.get(services, consoleTag).unsafe
430
- const log = options?.stderr === true ? console.error : console.log
451
+ const log = options.stderr === true ? console.error : console.log
431
452
 
432
453
  const message = Arr.ensure(message_)
433
454
 
434
- let firstLine = color(`[${formatDate(date)}]`, colors.white)
455
+ let firstLine = color(`[${options.formatDate(date)}]`, colors.white)
435
456
  + ` ${color(logLevel.label, ...logLevelColors[logLevel._tag])}`
436
457
  + ` (${_fiberId.threadName(fiberId)})`
437
458
 
@@ -453,18 +474,11 @@ export const prettyLogger = (options?: {
453
474
  }
454
475
  }
455
476
 
456
- if (isBrowser) {
457
- console.groupCollapsed(firstLine)
458
- } else {
459
- log(firstLine)
460
- console.group()
461
- }
477
+ log(firstLine)
478
+ if (!processIsBun) console.group()
479
+
462
480
  if (!Cause.isEmpty(cause)) {
463
- if (isBrowser) {
464
- console.error(Cause.pretty(cause, { renderErrorCause: true }))
465
- } else {
466
- log(Cause.pretty(cause, { renderErrorCause: true }))
467
- }
481
+ log(Cause.pretty(cause, { renderErrorCause: true }))
468
482
  }
469
483
 
470
484
  if (messageIndex < message.length) {
@@ -478,6 +492,76 @@ export const prettyLogger = (options?: {
478
492
  log(color(`${key}:`, colors.bold, colors.white), value)
479
493
  }
480
494
  }
495
+
496
+ if (!processIsBun) console.groupEnd()
497
+ }
498
+ )
499
+ }
500
+
501
+ const prettyLoggerBrowser = (options: {
502
+ readonly colors: boolean
503
+ readonly formatDate: (date: Date) => string
504
+ }) => {
505
+ const color = options.colors ? "%c" : ""
506
+ return makeLogger<unknown, void>(
507
+ ({ annotations, cause, context, date, fiberId, logLevel, message: message_, spans }) => {
508
+ const services = FiberRefs.getOrDefault(context, defaultServices.currentServices)
509
+ const console = Context.get(services, consoleTag).unsafe
510
+ const message = Arr.ensure(message_)
511
+
512
+ let firstLine = `${color}[${options.formatDate(date)}]`
513
+ const firstParams = []
514
+ if (options.colors) {
515
+ firstParams.push("color:gray")
516
+ }
517
+ firstLine += ` ${color}${logLevel.label}${color} (${_fiberId.threadName(fiberId)})`
518
+ if (options.colors) {
519
+ firstParams.push(logLevelStyle[logLevel._tag], "")
520
+ }
521
+ if (List.isCons(spans)) {
522
+ const now = date.getTime()
523
+ const render = renderLogSpanLogfmt(now)
524
+ for (const span of spans) {
525
+ firstLine += " " + render(span)
526
+ }
527
+ }
528
+
529
+ firstLine += ":"
530
+
531
+ let messageIndex = 0
532
+ if (message.length > 0) {
533
+ const firstMaybeString = structuredMessage(message[0])
534
+ if (typeof firstMaybeString === "string") {
535
+ firstLine += ` ${color}${firstMaybeString}`
536
+ if (options.colors) {
537
+ firstParams.push("color:blue")
538
+ }
539
+ messageIndex++
540
+ }
541
+ }
542
+
543
+ console.groupCollapsed(firstLine, ...firstParams)
544
+
545
+ if (!Cause.isEmpty(cause)) {
546
+ console.error(Cause.pretty(cause, { renderErrorCause: true }))
547
+ }
548
+
549
+ if (messageIndex < message.length) {
550
+ for (; messageIndex < message.length; messageIndex++) {
551
+ console.log(message[messageIndex])
552
+ }
553
+ }
554
+
555
+ if (HashMap.size(annotations) > 0) {
556
+ for (const [key, value] of annotations) {
557
+ if (options.colors) {
558
+ console.log(`%c${key}:`, "color:gray", value)
559
+ } else {
560
+ console.log(`${key}:`, value)
561
+ }
562
+ }
563
+ }
564
+
481
565
  console.groupEnd()
482
566
  }
483
567
  )
@@ -1,4 +1,4 @@
1
- let moduleVersion = "3.5.0"
1
+ let moduleVersion = "3.5.2"
2
2
 
3
3
  export const getCurrentVersion = () => moduleVersion
4
4