@voltro/logger 0.1.0
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.
- package/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +11 -0
- package/dist/index.d.ts +298 -0
- package/dist/index.js +498 -0
- package/package.json +40 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `@voltro/*` packages are recorded here. The format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## Stability contract — read this before you pin a version
|
|
7
|
+
|
|
8
|
+
Voltro is **`0.x` — pre-1.0, and deliberately so.** Under SemVer, `0.y.z` means
|
|
9
|
+
"anything MAY change." We hold to exactly that, stated out loud:
|
|
10
|
+
|
|
11
|
+
- **Pin exact versions.** Depend on `@voltro/runtime@0.4.2`, never `^0.4.2` or
|
|
12
|
+
`~0.4.2`. There is no compatible-range promise below 1.0.
|
|
13
|
+
- **Every MINOR may break.** Breaking changes land on a minor bump (`0.4.x →
|
|
14
|
+
0.5.0`); patches (`0.4.1 → 0.4.2`) are additive or fixes only. This is the
|
|
15
|
+
standard `0.x` reading of SemVer — the minor slot carries breaking under `0.x`.
|
|
16
|
+
- **Read this changelog before upgrading.** The `⚠ BREAKING` section of each
|
|
17
|
+
release lists every incompatible change with its migration. It is the only
|
|
18
|
+
migration path we provide — there is no deprecation cycle, no compat shim.
|
|
19
|
+
- **All packages release together (lockstep).** One coordinated version across
|
|
20
|
+
the whole framework; the git tag is the source of truth.
|
|
21
|
+
|
|
22
|
+
This is not a placeholder disclaimer — it is the contract. The framework is
|
|
23
|
+
refactored aggressively while it has no external stability obligations, and that
|
|
24
|
+
velocity is the point. A stable-core tier and per-package `1.0` graduation are
|
|
25
|
+
planned, but not yet; until then, treat the whole surface as movable and pin
|
|
26
|
+
exact.
|
|
27
|
+
|
|
28
|
+
The **public API** of each package is its `publishConfig.exports` entry points,
|
|
29
|
+
minus anything marked `@internal` (those are cross-package internals and are
|
|
30
|
+
stripped from the published `.d.ts`). Importing a deep path that isn't an
|
|
31
|
+
exported entry point is unsupported.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## [Unreleased]
|
|
36
|
+
|
|
37
|
+
_Changes staged for the next release accumulate here (rolled up from
|
|
38
|
+
`.changes/*.md` at tag time — see `.changes/README.md`)._
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## [0.1.0]
|
|
43
|
+
|
|
44
|
+
Initial pre-release baseline. This is the starting point the changelog tracks
|
|
45
|
+
from; it is not an exhaustive history of prior development. The framework ships
|
|
46
|
+
as `@voltro/*` packages spanning the runtime, database/query layer, web client
|
|
47
|
+
and router, durable workflows, the CLI, and the plugin ecosystem
|
|
48
|
+
(auth, storage, mail, billing, observability, and more). Not published to a
|
|
49
|
+
public registry yet.
|
|
50
|
+
|
|
51
|
+
Subsequent releases record their deltas from here under dated headings, with
|
|
52
|
+
`⚠ BREAKING` first.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Voltro — Proprietary Software License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Voltro UG. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and all associated source code, documentation, and other
|
|
6
|
+
materials (the "Software") are the proprietary property of Voltro UG
|
|
7
|
+
("Voltro"). This is NOT open-source software.
|
|
8
|
+
|
|
9
|
+
Your access to and use of the Software is governed exclusively by the Voltro
|
|
10
|
+
Terms of Service at:
|
|
11
|
+
|
|
12
|
+
https://voltro.cloud/legal/terms
|
|
13
|
+
|
|
14
|
+
By downloading, installing, or using the Software you agree to those Terms.
|
|
15
|
+
Subject to and conditioned on your continued compliance with them, Voltro
|
|
16
|
+
grants you a limited, non-exclusive, non-transferable, non-sublicensable,
|
|
17
|
+
revocable license to use the Software. All rights not expressly granted in the
|
|
18
|
+
Terms are reserved by Voltro. If you do not agree to the Terms, you have no
|
|
19
|
+
license and may not use the Software.
|
|
20
|
+
|
|
21
|
+
The Terms of Service — including the scope of permitted use, plan
|
|
22
|
+
entitlements, fees, and pricing — may change from time to time. The version in
|
|
23
|
+
force is the one published at the URL above. Commercial terms and pricing are
|
|
24
|
+
NOT part of this notice and are not fixed by, or granted in perpetuity by, the
|
|
25
|
+
version of the Software in which this file appears.
|
|
26
|
+
|
|
27
|
+
Permitted use — your own software. Voltro is a development framework. You may
|
|
28
|
+
use the Software to build, run, and commercially distribute your own
|
|
29
|
+
applications and services, and to charge your own customers for them, without
|
|
30
|
+
owing Voltro any share of that revenue or any further permission beyond the
|
|
31
|
+
Terms. Applications you build with the Software are yours.
|
|
32
|
+
|
|
33
|
+
Prohibited use — reselling Voltro. You may NOT redistribute, resell,
|
|
34
|
+
sublicense, publish, or otherwise provide the Software itself — in whole or in
|
|
35
|
+
part, in source or compiled form — to third parties as a library, framework,
|
|
36
|
+
SDK, template, starter, or development tool for them to build upon, whether
|
|
37
|
+
standalone or repackaged. Shipping the Software as a non-separable, bundled
|
|
38
|
+
dependency inside an application you deliver (for example, compiled into your
|
|
39
|
+
deployed app) is permitted; extracting or re-exposing it as a reusable
|
|
40
|
+
component for others is not.
|
|
41
|
+
|
|
42
|
+
Except as expressly permitted above or by the Terms, you may not copy, modify,
|
|
43
|
+
merge, publish, sublicense, sell, distribute, or create derivative works of the
|
|
44
|
+
Software, in whole or in part.
|
|
45
|
+
|
|
46
|
+
Third-party open-source components distributed alongside the Software are the
|
|
47
|
+
property of their respective owners and are licensed under their own terms; see
|
|
48
|
+
THIRD-PARTY-NOTICES.md where provided. Nothing in this notice limits your
|
|
49
|
+
rights, or extends Voltro's, in those components.
|
|
50
|
+
|
|
51
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
52
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
53
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
54
|
+
VOLTRO BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM,
|
|
55
|
+
OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
|
56
|
+
|
|
57
|
+
For licensing inquiries, contact Voltro UG.
|
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# @voltro/logger
|
|
4
|
+
|
|
5
|
+
**Effect-based structured logger with pretty terminal output + JSON for production.**
|
|
6
|
+
|
|
7
|
+
[📖 Documentation](https://docs.voltro.dev/docs/observability/overview) · [Changelog](./CHANGELOG.md) · [voltro.dev](https://voltro.dev) · [Voltro Cloud](https://voltro.cloud)
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @voltro/logger
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> ### [→ Read the documentation for @voltro/logger](https://docs.voltro.dev/docs/observability/overview)
|
|
18
|
+
|
|
19
|
+
Installation, guides, the full API reference, and examples all live in the
|
|
20
|
+
**[Voltro documentation](https://docs.voltro.dev/docs/intro/getting-started)** — the single source of truth. This README is
|
|
21
|
+
deliberately short so there is nothing here to drift out of date.
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
Proprietary. © 2026 Voltro UG. Your use is governed by the
|
|
26
|
+
[Terms of Service](https://voltro.cloud/legal/terms). See the bundled `LICENSE`.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
We take the security of Voltro and the applications built on it seriously. This
|
|
4
|
+
document explains how to report a vulnerability and what to expect in return.
|
|
5
|
+
|
|
6
|
+
## Reporting a vulnerability
|
|
7
|
+
|
|
8
|
+
**Please do not open public issues or discuss suspected vulnerabilities in
|
|
9
|
+
public channels.** Report them privately by email:
|
|
10
|
+
|
|
11
|
+
**security contact: [support@voltro.dev](mailto:support@voltro.dev)**
|
|
12
|
+
(subject line: `SECURITY`).
|
|
13
|
+
|
|
14
|
+
Include, where you can:
|
|
15
|
+
|
|
16
|
+
- the affected package(s) and version(s) (e.g. `@voltro/runtime@x.y.z`);
|
|
17
|
+
- a description of the issue and its impact;
|
|
18
|
+
- a minimal reproduction or proof of concept;
|
|
19
|
+
- any suggested remediation.
|
|
20
|
+
|
|
21
|
+
If you'd like to encrypt your report, ask in a first plain email and we'll
|
|
22
|
+
arrange a key.
|
|
23
|
+
|
|
24
|
+
## What to expect
|
|
25
|
+
|
|
26
|
+
- **Acknowledgement** of your report within **3 business days**.
|
|
27
|
+
- An initial assessment (severity, affected surface) and a remediation plan as
|
|
28
|
+
soon as we've reproduced the issue.
|
|
29
|
+
- **Coordinated disclosure:** we'll agree a disclosure timeline with you and
|
|
30
|
+
credit you in the release notes if you wish. Please give us reasonable time to
|
|
31
|
+
ship a fix before any public disclosure.
|
|
32
|
+
|
|
33
|
+
We do not currently run a paid bug-bounty program, but we genuinely appreciate
|
|
34
|
+
responsible disclosure and will credit reporters.
|
|
35
|
+
|
|
36
|
+
## Software Bill of Materials (SBOM)
|
|
37
|
+
|
|
38
|
+
A CycloneDX SBOM of the third-party runtime dependency graph is generated for the
|
|
39
|
+
published packages (`pnpm gen:sbom` → `sbom.cdx.json`). If your procurement or
|
|
40
|
+
security team needs it, request it at
|
|
41
|
+
[support@voltro.dev](mailto:support@voltro.dev).
|
|
42
|
+
|
|
43
|
+
## Supported versions
|
|
44
|
+
|
|
45
|
+
Voltro is pre-1.0 and ships in lockstep: **only the latest published minor of
|
|
46
|
+
each `@voltro/*` package receives security fixes.** Please upgrade to the latest
|
|
47
|
+
release before reporting, and pin exact versions in production.
|
|
48
|
+
|
|
49
|
+
## Scope
|
|
50
|
+
|
|
51
|
+
In scope: the published `@voltro/*` packages and the framework's own code.
|
|
52
|
+
|
|
53
|
+
Out of scope: vulnerabilities in third-party dependencies that are already
|
|
54
|
+
tracked upstream (we monitor these via a production-dependency audit gate in
|
|
55
|
+
CI), and issues that require a non-default, explicitly-unsafe configuration. If
|
|
56
|
+
you're unsure, report it anyway — we'd rather hear about it.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Third-Party Notices — @voltro/logger
|
|
2
|
+
|
|
3
|
+
This package depends on the open-source software listed below. Each is the
|
|
4
|
+
property of its respective copyright holders and is used under the terms of
|
|
5
|
+
its license. This file is provided for attribution; it grants no rights in
|
|
6
|
+
@voltro/logger itself, which is proprietary (see LICENSE).
|
|
7
|
+
|
|
8
|
+
Generated from the resolved runtime dependency closure (0 packages).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { Layer } from 'effect';
|
|
2
|
+
import { Logger } from 'effect';
|
|
3
|
+
import { LogLevel as LogLevel_2 } from 'effect';
|
|
4
|
+
|
|
5
|
+
export declare const addSink: (fn: LogSink) => (() => void);
|
|
6
|
+
|
|
7
|
+
export declare const ansi: {
|
|
8
|
+
reset: string;
|
|
9
|
+
dim: (s: string) => string;
|
|
10
|
+
bold: (s: string) => string;
|
|
11
|
+
italic: (s: string) => string;
|
|
12
|
+
gray: (s: string) => string;
|
|
13
|
+
red: (s: string) => string;
|
|
14
|
+
green: (s: string) => string;
|
|
15
|
+
yellow: (s: string) => string;
|
|
16
|
+
blue: (s: string) => string;
|
|
17
|
+
magenta: (s: string) => string;
|
|
18
|
+
cyan: (s: string) => string;
|
|
19
|
+
white: (s: string) => string;
|
|
20
|
+
brightRed: (s: string) => string;
|
|
21
|
+
brightGreen: (s: string) => string;
|
|
22
|
+
brightYellow: (s: string) => string;
|
|
23
|
+
brightBlue: (s: string) => string;
|
|
24
|
+
brightCyan: (s: string) => string;
|
|
25
|
+
bgRed: (s: string) => string;
|
|
26
|
+
bgYellow: (s: string) => string;
|
|
27
|
+
bgBlue: (s: string) => string;
|
|
28
|
+
bgCyan: (s: string) => string;
|
|
29
|
+
bgGray: (s: string) => string;
|
|
30
|
+
brand: (s: string) => string;
|
|
31
|
+
brandDim: (s: string) => string;
|
|
32
|
+
bgBrand: (s: string) => string;
|
|
33
|
+
red24: (s: string) => string;
|
|
34
|
+
brightRed24: (s: string) => string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export declare type AppKind = 'web' | 'api' | 'preview';
|
|
38
|
+
|
|
39
|
+
export declare interface AppSurfaceOptions {
|
|
40
|
+
readonly appName: string;
|
|
41
|
+
readonly queries: ReadonlyArray<string>;
|
|
42
|
+
readonly mutations: ReadonlyArray<string>;
|
|
43
|
+
readonly actions: ReadonlyArray<string>;
|
|
44
|
+
readonly streams?: ReadonlyArray<string>;
|
|
45
|
+
readonly workflows: ReadonlyArray<string>;
|
|
46
|
+
/** Cron schedules (`*.cron.tsx` discovery). Rendered as its own
|
|
47
|
+
* category alongside the rpc primitives so the boot surface shows
|
|
48
|
+
* the WHOLE app at a glance — not just the request-driven half. */
|
|
49
|
+
readonly schedules?: ReadonlyArray<string>;
|
|
50
|
+
/** Materialised aggregates (`*.aggregate.ts` discovery). Same
|
|
51
|
+
* motivation as schedules — they're part of the app's surface even
|
|
52
|
+
* though no rpc tag points at them. */
|
|
53
|
+
readonly aggregates?: ReadonlyArray<string>;
|
|
54
|
+
/** Event triggers (`*.trigger.tsx`) — rendered as `event→workflow`. */
|
|
55
|
+
readonly triggers?: ReadonlyArray<string>;
|
|
56
|
+
/** Agents (`*.agent.tsx`) — each synthesizes `<name>.send` + `.messages`. */
|
|
57
|
+
readonly agents?: ReadonlyArray<string>;
|
|
58
|
+
/** Public REST routes (`*.route.tsx` / `restRoutes`) — `METHOD /path`. */
|
|
59
|
+
readonly restRoutes?: ReadonlyArray<string>;
|
|
60
|
+
/** Non-wire lifecycle primitives. Shown count-only because their
|
|
61
|
+
* names load later in boot (after this banner, which prints BEFORE
|
|
62
|
+
* the risky seed/startup runs on purpose). */
|
|
63
|
+
readonly lifecycle?: {
|
|
64
|
+
readonly seeds?: number;
|
|
65
|
+
readonly startups?: number;
|
|
66
|
+
readonly subscribers?: number;
|
|
67
|
+
readonly webhooks?: number;
|
|
68
|
+
readonly emails?: number;
|
|
69
|
+
};
|
|
70
|
+
readonly tenantTables?: ReadonlyArray<string>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export declare interface BufferedSink<T> {
|
|
74
|
+
/** Buffer an item. Applies the `maxBuffer` drop-oldest guard, then the
|
|
75
|
+
* `admit` early-flush policy. */
|
|
76
|
+
readonly push: (item: T) => void;
|
|
77
|
+
/** Drain + ship whatever is buffered right now. Fail-soft — never
|
|
78
|
+
* throws; a `send` failure is routed to `onError`. A no-op when empty. */
|
|
79
|
+
readonly flush: () => Promise<void>;
|
|
80
|
+
/** Stop the timer + do one final best-effort drain. Idempotent —
|
|
81
|
+
* call it from a plugin's `onDeactivate`. */
|
|
82
|
+
readonly dispose: () => Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* What `admit` decides for each pushed item. The helper appends the item
|
|
87
|
+
* to the buffer, then consults `admit` to decide whether the buffer must
|
|
88
|
+
* flush NOW (a size / byte budget crossed) — the buffer is handed in so
|
|
89
|
+
* the policy can measure it however it likes.
|
|
90
|
+
*/
|
|
91
|
+
export declare interface BufferedSinkOptions<T> {
|
|
92
|
+
/** Flush interval in ms — the periodic timer fires `flush` this often. */
|
|
93
|
+
readonly intervalMs: number;
|
|
94
|
+
/**
|
|
95
|
+
* Ship a drained batch. MAY reject/throw — the helper catches it and
|
|
96
|
+
* routes it to `onError`, so a backend outage never propagates into the
|
|
97
|
+
* app. Called with the whole drained batch; an empty batch is never
|
|
98
|
+
* passed (the helper short-circuits).
|
|
99
|
+
*/
|
|
100
|
+
readonly send: (batch: ReadonlyArray<T>) => Promise<void> | void;
|
|
101
|
+
/**
|
|
102
|
+
* Consulted BEFORE the incoming item is appended, with the CURRENT
|
|
103
|
+
* (non-empty) buffer. Return `true` to ship what's buffered FIRST so the
|
|
104
|
+
* outgoing batch stays under a budget (logship's byte cap), then start a
|
|
105
|
+
* fresh batch with the incoming item. Only called when the buffer is
|
|
106
|
+
* non-empty. Omit when no pre-append flush is needed.
|
|
107
|
+
*/
|
|
108
|
+
readonly flushBefore?: (buffer: ReadonlyArray<T>, incoming: T) => boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Consulted AFTER the item was appended. Return `'flush'` to ship the
|
|
111
|
+
* buffered batch immediately (a count budget was reached), or `'keep'`
|
|
112
|
+
* to leave it buffered for the timer. Omit for pure interval-only
|
|
113
|
+
* batching. The buffer passed in INCLUDES the just-appended item.
|
|
114
|
+
*/
|
|
115
|
+
readonly admit?: (buffer: ReadonlyArray<T>, added: T) => 'flush' | 'keep';
|
|
116
|
+
/**
|
|
117
|
+
* Hard cap on buffered items — when exceeded, the OLDEST item is
|
|
118
|
+
* dropped (a ring buffer) so an unreachable backend can't grow the
|
|
119
|
+
* buffer without bound. Omit for no cap (rely on `admit` + interval).
|
|
120
|
+
*/
|
|
121
|
+
readonly maxBuffer?: number;
|
|
122
|
+
/** Fail-soft error reporter — receives any error `send` produced. The
|
|
123
|
+
* app keeps running regardless; this is purely for a warn line. */
|
|
124
|
+
readonly onError?: (error: unknown, batchSize: number) => void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export declare const createLogger: (config?: SyncLoggerConfig) => SyncLogger;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* True when stdout looks like an interactive TTY. We use this to
|
|
131
|
+
* auto-disable colours on plain pipes / file redirection / CI runs.
|
|
132
|
+
*/
|
|
133
|
+
export declare const isTty: () => boolean;
|
|
134
|
+
|
|
135
|
+
export declare const jsonFormat: (record: LogRecord) => string;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Default logger instance — useful for one-off scripts that don't
|
|
139
|
+
* want to manage their own. Has no scope; consumers can `.child(…)`
|
|
140
|
+
* to specialise.
|
|
141
|
+
*/
|
|
142
|
+
export declare const log: SyncLogger;
|
|
143
|
+
|
|
144
|
+
export declare interface LoggerConfig {
|
|
145
|
+
readonly format?: 'pretty' | 'json';
|
|
146
|
+
readonly colour?: boolean;
|
|
147
|
+
/** With `LoggerLayer` this sets the runtime minimum log level
|
|
148
|
+
* (`Logger.minimumLogLevel`, env default). A bare `makeLogger`
|
|
149
|
+
* honours it too — an explicit `minLevel` drops sub-threshold
|
|
150
|
+
* entries inside the logger; unset, the Effect runtime's minimum
|
|
151
|
+
* governs (so `Logger.withMinimumLogLevel` keeps working). */
|
|
152
|
+
readonly minLevel?: LogLevel_2.LogLevel;
|
|
153
|
+
/** Default scope tag applied when an annotation doesn't set one. */
|
|
154
|
+
readonly defaultScope?: string;
|
|
155
|
+
/** Opt-in: mask any field (or nested value, or plain-object cause)
|
|
156
|
+
* whose KEY matches one of these (case-insensitive). OFF by default.
|
|
157
|
+
* Mirrors the sync `createLogger` surface so both code paths redact. */
|
|
158
|
+
readonly redactKeys?: ReadonlyArray<string>;
|
|
159
|
+
/** Opt-in: a custom record→record transform applied before the record
|
|
160
|
+
* is formatted. Runs after `redactKeys`. */
|
|
161
|
+
readonly redact?: Redactor;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Ready-to-install layer. Replaces the default Effect logger + sets
|
|
166
|
+
* the minimum log level from `VOLTRO_LOG_LEVEL`. Apps just add this
|
|
167
|
+
* to their main Layer chain:
|
|
168
|
+
*
|
|
169
|
+
* const Main = Layer.mergeAll(
|
|
170
|
+
* LoggerLayer({ defaultScope: 'voltro:cli' }),
|
|
171
|
+
* OtherLayers,
|
|
172
|
+
* )
|
|
173
|
+
*/
|
|
174
|
+
export declare const LoggerLayer: (config?: LoggerConfig) => Layer.Layer<never>;
|
|
175
|
+
|
|
176
|
+
export declare type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
177
|
+
|
|
178
|
+
export declare interface LogRecord {
|
|
179
|
+
readonly ts: Date;
|
|
180
|
+
readonly level: LogLevel;
|
|
181
|
+
/** Optional logical scope ("voltro:cli", "@voltro-cloud/api", …). */
|
|
182
|
+
readonly scope?: string;
|
|
183
|
+
/** Optional Effect fiber id for correlating across async boundaries. */
|
|
184
|
+
readonly fiberId?: string;
|
|
185
|
+
readonly message: string;
|
|
186
|
+
/** Free-form structured fields. Effect logger annotations end up here. */
|
|
187
|
+
readonly fields: Readonly<Record<string, unknown>>;
|
|
188
|
+
/** Captured error (`cause` annotation from Effect) — separate so the
|
|
189
|
+
* formatters can format it specially (stack trace etc.). */
|
|
190
|
+
readonly cause?: unknown;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
declare type LogSink = (record: LogRecord) => void;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Build a buffered sink: a bounded buffer drained on an interval and on
|
|
197
|
+
* demand, fail-soft, with a final drain on dispose. See
|
|
198
|
+
* `BufferedSinkOptions` for the buffer-membership hooks each consumer
|
|
199
|
+
* customises.
|
|
200
|
+
*/
|
|
201
|
+
export declare const makeBufferedSink: <T>(options: BufferedSinkOptions<T>) => BufferedSink<T>;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Build a `Redactor` that masks every field (and nested object/array
|
|
205
|
+
* value, and plain-object `cause`) whose KEY matches one of `keys`
|
|
206
|
+
* (case-insensitive). Matched values become `options.mask`
|
|
207
|
+
* (default `'[redacted]'`).
|
|
208
|
+
*
|
|
209
|
+
* createLogger({ redactKeys: ['password', 'apiKey', 'authorization'] })
|
|
210
|
+
*
|
|
211
|
+
* Note: `cause` values that are `Error` INSTANCES are passed through
|
|
212
|
+
* unchanged — masking an Error's internals would break the pretty
|
|
213
|
+
* stack rendering, and an Error's own enumerable props are diagnostic
|
|
214
|
+
* (name / message / pg-driver fields), not secret-keyed config. Put a
|
|
215
|
+
* secret in a `fields` entry, not in an Error you throw, if you want
|
|
216
|
+
* it masked.
|
|
217
|
+
*/
|
|
218
|
+
export declare const makeKeyRedactor: (keys: ReadonlyArray<string>, options?: {
|
|
219
|
+
readonly mask?: string;
|
|
220
|
+
}) => Redactor;
|
|
221
|
+
|
|
222
|
+
export declare const makeLogger: (config?: LoggerConfig) => Logger.Logger<unknown, void>;
|
|
223
|
+
|
|
224
|
+
export declare const prettyFormat: (record: LogRecord, options?: PrettyFormatOptions) => string;
|
|
225
|
+
|
|
226
|
+
export declare interface PrettyFormatOptions {
|
|
227
|
+
/** When false, suppress every ANSI code (file sinks, redirected stdout). */
|
|
228
|
+
readonly colour?: boolean;
|
|
229
|
+
/** Suppress the scope tag (when the consumer is a single app). */
|
|
230
|
+
readonly hideScope?: boolean;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export declare const printAppSurface: (options: AppSurfaceOptions) => void;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Print the ready block to stdout. Idempotent — safe to call once
|
|
237
|
+
* per boot. Always emits a trailing newline so the next prompt /
|
|
238
|
+
* log line starts clean.
|
|
239
|
+
*/
|
|
240
|
+
export declare const printReadyBlock: (options: ReadyBlockOptions) => void;
|
|
241
|
+
|
|
242
|
+
export declare interface ReadyBlockOptions {
|
|
243
|
+
/** App's human-readable name (`app.config.ts.name`). */
|
|
244
|
+
readonly app: string;
|
|
245
|
+
readonly kind: AppKind;
|
|
246
|
+
/** Primary URL — appears on its own line, brand-styled. */
|
|
247
|
+
readonly url: string;
|
|
248
|
+
/** Optional extra URLs (admin, ws, inspector). Listed below the primary. */
|
|
249
|
+
readonly extras?: ReadonlyArray<{
|
|
250
|
+
readonly label: string;
|
|
251
|
+
readonly url: string;
|
|
252
|
+
}>;
|
|
253
|
+
/** Boot duration in milliseconds (start of boot → ready). */
|
|
254
|
+
readonly bootMs: number;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** A pure transform applied to a record before it is formatted / fanned out. */
|
|
258
|
+
export declare type Redactor = (record: LogRecord) => LogRecord;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Strip ANSI sequences from a string. Useful when the same payload
|
|
262
|
+
* is being emitted to a file sink that doesn't grok escape codes.
|
|
263
|
+
*/
|
|
264
|
+
export declare const stripAnsi: (s: string) => string;
|
|
265
|
+
|
|
266
|
+
export declare interface SyncLogger {
|
|
267
|
+
readonly trace: (msg: string, fields?: Record<string, unknown>) => void;
|
|
268
|
+
readonly debug: (msg: string, fields?: Record<string, unknown>) => void;
|
|
269
|
+
readonly info: (msg: string, fields?: Record<string, unknown>) => void;
|
|
270
|
+
readonly warn: (msg: string, fields?: Record<string, unknown>, cause?: unknown) => void;
|
|
271
|
+
readonly error: (msg: string, fields?: Record<string, unknown>, cause?: unknown) => void;
|
|
272
|
+
readonly fatal: (msg: string, fields?: Record<string, unknown>, cause?: unknown) => void;
|
|
273
|
+
/** Child logger with merged scope + fields. Useful for "every log
|
|
274
|
+
* from this sub-system gets `{ component: 'codegen' }` baked in".
|
|
275
|
+
* Pass `options.minLevel` to raise (or lower) verbosity for JUST this
|
|
276
|
+
* sub-system — e.g. `log.child('codegen', {}, { minLevel: 'debug' })`
|
|
277
|
+
* keeps the rest of the app at `info` while the codegen scope traces. */
|
|
278
|
+
readonly child: (scope: string, fields?: Record<string, unknown>, options?: {
|
|
279
|
+
readonly minLevel?: LogLevel;
|
|
280
|
+
}) => SyncLogger;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export declare interface SyncLoggerConfig {
|
|
284
|
+
readonly scope?: string;
|
|
285
|
+
readonly fields?: Record<string, unknown>;
|
|
286
|
+
readonly format?: 'pretty' | 'json';
|
|
287
|
+
readonly colour?: boolean;
|
|
288
|
+
readonly minLevel?: LogLevel;
|
|
289
|
+
/** Opt-in: mask any field (or nested value, or plain-object cause)
|
|
290
|
+
* whose KEY matches one of these (case-insensitive). OFF by default. */
|
|
291
|
+
readonly redactKeys?: ReadonlyArray<string>;
|
|
292
|
+
/** Opt-in: a custom record→record transform applied before the record
|
|
293
|
+
* is formatted AND before the sink fan-out (so a masked value never
|
|
294
|
+
* reaches stdout OR a downstream sink). Runs after `redactKeys`. */
|
|
295
|
+
readonly redact?: Redactor;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { Cause as e, FiberId as t, HashMap as n, Layer as r, List as i, LogLevel as a, Logger as o } from "effect";
|
|
2
|
+
//#region src/ansi.ts
|
|
3
|
+
var s = "\x1B[", c = `${s}0m`, l = (e) => (t) => `${e}${t}${c}`, u = {
|
|
4
|
+
reset: c,
|
|
5
|
+
dim: l(`${s}2m`),
|
|
6
|
+
bold: l(`${s}1m`),
|
|
7
|
+
italic: l(`${s}3m`),
|
|
8
|
+
gray: l(`${s}90m`),
|
|
9
|
+
red: l(`${s}31m`),
|
|
10
|
+
green: l(`${s}32m`),
|
|
11
|
+
yellow: l(`${s}33m`),
|
|
12
|
+
blue: l(`${s}34m`),
|
|
13
|
+
magenta: l(`${s}35m`),
|
|
14
|
+
cyan: l(`${s}36m`),
|
|
15
|
+
white: l(`${s}37m`),
|
|
16
|
+
brightRed: l(`${s}91m`),
|
|
17
|
+
brightGreen: l(`${s}92m`),
|
|
18
|
+
brightYellow: l(`${s}93m`),
|
|
19
|
+
brightBlue: l(`${s}94m`),
|
|
20
|
+
brightCyan: l(`${s}96m`),
|
|
21
|
+
bgRed: l(`${s}41m`),
|
|
22
|
+
bgYellow: l(`${s}43m`),
|
|
23
|
+
bgBlue: l(`${s}44m`),
|
|
24
|
+
bgCyan: l(`${s}46m`),
|
|
25
|
+
bgGray: l(`${s}100m`),
|
|
26
|
+
brand: l(`${s}38;2;166;140;255m`),
|
|
27
|
+
brandDim: l(`${s}38;2;124;101;208m`),
|
|
28
|
+
bgBrand: l(`${s}48;2;103;78;204m`),
|
|
29
|
+
red24: l(`${s}38;2;220;38;38m`),
|
|
30
|
+
brightRed24: l(`${s}38;2;239;68;68m`)
|
|
31
|
+
}, d = (e) => e.replace(/\x1b\[[0-9;]*m/g, ""), f = () => {
|
|
32
|
+
try {
|
|
33
|
+
return !!process.stdout.isTTY;
|
|
34
|
+
} catch {
|
|
35
|
+
return !1;
|
|
36
|
+
}
|
|
37
|
+
}, p = "[redacted]", m = (e, t, n, r) => {
|
|
38
|
+
if (typeof e != "object" || !e || e instanceof Error || r.has(e)) return e;
|
|
39
|
+
if (r.add(e), Array.isArray(e)) return e.map((e) => m(e, t, n, r));
|
|
40
|
+
let i = {};
|
|
41
|
+
for (let [a, o] of Object.entries(e)) i[a] = t.has(a.toLowerCase()) ? n : m(o, t, n, r);
|
|
42
|
+
return i;
|
|
43
|
+
}, h = (e, t = {}) => {
|
|
44
|
+
let n = new Set(e.map((e) => e.toLowerCase())), r = t.mask ?? p;
|
|
45
|
+
return n.size === 0 ? (e) => e : (e) => {
|
|
46
|
+
let t = m(e.fields, n, r, /* @__PURE__ */ new WeakSet()), i = {
|
|
47
|
+
...e,
|
|
48
|
+
fields: t
|
|
49
|
+
};
|
|
50
|
+
return e.cause === void 0 ? i : {
|
|
51
|
+
...i,
|
|
52
|
+
cause: m(e.cause, n, r, /* @__PURE__ */ new WeakSet())
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
}, g = (e) => {
|
|
56
|
+
let t = e.redactKeys && e.redactKeys.length > 0 ? h(e.redactKeys) : void 0, n = e.redact;
|
|
57
|
+
return t && n ? (e) => n(t(e)) : t ?? n;
|
|
58
|
+
}, _ = (e) => e < 10 ? `0${e}` : String(e), v = (e) => e < 10 ? `00${e}` : e < 100 ? `0${e}` : String(e), y = (e) => `${_(e.getHours())}:${_(e.getMinutes())}:${_(e.getSeconds())}.${v(e.getMilliseconds())}`, b = (e) => typeof e == "object" && !!e && !Array.isArray(e), x = (e) => {
|
|
59
|
+
if (e === null) return "null";
|
|
60
|
+
if (e === void 0) return "undefined";
|
|
61
|
+
if (typeof e == "string") return /[\s"=]/.test(e) ? JSON.stringify(e) : e;
|
|
62
|
+
if (typeof e == "number" || typeof e == "boolean") return String(e);
|
|
63
|
+
if (e instanceof Date) return e.toISOString();
|
|
64
|
+
try {
|
|
65
|
+
return JSON.stringify(e);
|
|
66
|
+
} catch {
|
|
67
|
+
return String(e);
|
|
68
|
+
}
|
|
69
|
+
}, S = {
|
|
70
|
+
trace: (e) => u.dim(u.gray(e.toUpperCase().padEnd(5))),
|
|
71
|
+
debug: (e) => u.cyan(e.toUpperCase().padEnd(5)),
|
|
72
|
+
info: (e) => u.brand(e.toUpperCase().padEnd(5)),
|
|
73
|
+
warn: (e) => u.yellow(e.toUpperCase().padEnd(5)),
|
|
74
|
+
error: (e) => u.brightRed24(e.toUpperCase().padEnd(5)),
|
|
75
|
+
fatal: (e) => u.bgRed(u.brightRed24(` ${e.toUpperCase()} `))
|
|
76
|
+
}, C = (e, t = {}) => {
|
|
77
|
+
let n = t.colour ?? !0, r = n ? u : k, i = r.dim(y(e.ts)), a = n ? S[e.level](e.level) : e.level.toUpperCase().padEnd(5), o = !t.hideScope && e.scope ? `${r.dim("[")}${r.brandDim(e.scope)}${r.dim("]")}` : "", s = [];
|
|
78
|
+
for (let [t, n] of Object.entries(e.fields)) s.push(`${r.dim(`${t}=`)}${x(n)}`);
|
|
79
|
+
let c = e.fiberId ? r.dim(`fiber=${e.fiberId}`) : "", l = [...s, c].filter((e) => e.length > 0).join(" "), d = [
|
|
80
|
+
i,
|
|
81
|
+
a,
|
|
82
|
+
o,
|
|
83
|
+
e.message,
|
|
84
|
+
l
|
|
85
|
+
].filter((e) => e.length > 0).join(" ");
|
|
86
|
+
if (e.cause !== void 0) {
|
|
87
|
+
let t = w(e.cause, n);
|
|
88
|
+
return t.length > 0 ? `${d}\n${t}` : d;
|
|
89
|
+
}
|
|
90
|
+
return d;
|
|
91
|
+
}, w = (e, t) => {
|
|
92
|
+
let n = t ? u : k;
|
|
93
|
+
if (e instanceof Error) {
|
|
94
|
+
let t = n.red(` ${e.name}: ${e.message}`);
|
|
95
|
+
if (typeof e.stack == "string") {
|
|
96
|
+
let r = e.stack.split("\n").slice(1).map((e) => n.dim(` ${e.trim()}`)).join("\n");
|
|
97
|
+
return r.length > 0 ? `${t}\n${r}` : t;
|
|
98
|
+
}
|
|
99
|
+
return t;
|
|
100
|
+
}
|
|
101
|
+
if (b(e) && typeof e.stack == "string") {
|
|
102
|
+
let [t, ...r] = e.stack.split("\n"), i = n.red(` ${t ?? ""}`), a = r.map((e) => n.dim(` ${e.trimEnd()}`)).join("\n");
|
|
103
|
+
return a.length > 0 ? `${i}\n${a}` : i;
|
|
104
|
+
}
|
|
105
|
+
if (b(e)) try {
|
|
106
|
+
return n.red(` ${JSON.stringify(e, null, 2).replace(/\n/g, "\n ")}`);
|
|
107
|
+
} catch {
|
|
108
|
+
return n.red(` ${String(e)}`);
|
|
109
|
+
}
|
|
110
|
+
return n.red(` ${String(e)}`);
|
|
111
|
+
}, T = [
|
|
112
|
+
"severity",
|
|
113
|
+
"code",
|
|
114
|
+
"detail",
|
|
115
|
+
"hint",
|
|
116
|
+
"position",
|
|
117
|
+
"internalPosition",
|
|
118
|
+
"internalQuery",
|
|
119
|
+
"where",
|
|
120
|
+
"schema",
|
|
121
|
+
"table",
|
|
122
|
+
"column",
|
|
123
|
+
"dataType",
|
|
124
|
+
"constraint",
|
|
125
|
+
"file",
|
|
126
|
+
"line",
|
|
127
|
+
"routine"
|
|
128
|
+
], E = (e, t = 0, n = /* @__PURE__ */ new WeakSet()) => {
|
|
129
|
+
if (t > 8) return "[cause: max-depth]";
|
|
130
|
+
if (typeof e != "object" || !e) return e;
|
|
131
|
+
if (n.has(e)) return "[cause: circular]";
|
|
132
|
+
if (n.add(e), e instanceof Error) {
|
|
133
|
+
let r = {
|
|
134
|
+
name: e.name,
|
|
135
|
+
message: e.message
|
|
136
|
+
};
|
|
137
|
+
typeof e.stack == "string" && (r.stack = e.stack);
|
|
138
|
+
let i = e;
|
|
139
|
+
for (let e of T) {
|
|
140
|
+
let t = i[e];
|
|
141
|
+
t !== void 0 && (r[e] = t);
|
|
142
|
+
}
|
|
143
|
+
return "cause" in e && e.cause !== void 0 && (r.cause = E(e.cause, t + 1, n)), r;
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(e)) return e.map((e) => E(e, t + 1, n));
|
|
146
|
+
let r = {};
|
|
147
|
+
for (let [i, a] of Object.entries(e)) r[i] = E(a, t + 1, n);
|
|
148
|
+
return r;
|
|
149
|
+
}, D = (e) => {
|
|
150
|
+
let t = {};
|
|
151
|
+
for (let [n, r] of Object.entries(e.fields)) t[n] = E(r);
|
|
152
|
+
let n = {
|
|
153
|
+
ts: e.ts.toISOString(),
|
|
154
|
+
level: e.level,
|
|
155
|
+
msg: e.message,
|
|
156
|
+
...e.scope ? { scope: e.scope } : {},
|
|
157
|
+
...e.fiberId ? { fiberId: e.fiberId } : {},
|
|
158
|
+
...t
|
|
159
|
+
};
|
|
160
|
+
e.cause !== void 0 && (n.cause = E(e.cause));
|
|
161
|
+
try {
|
|
162
|
+
return JSON.stringify(n);
|
|
163
|
+
} catch {
|
|
164
|
+
return JSON.stringify({
|
|
165
|
+
ts: n.ts,
|
|
166
|
+
level: n.level,
|
|
167
|
+
msg: n.msg,
|
|
168
|
+
...n.scope ? { scope: n.scope } : {},
|
|
169
|
+
_serialiseError: !0
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}, O = (e) => e, k = {
|
|
173
|
+
reset: "",
|
|
174
|
+
dim: O,
|
|
175
|
+
bold: O,
|
|
176
|
+
italic: O,
|
|
177
|
+
gray: O,
|
|
178
|
+
red: O,
|
|
179
|
+
green: O,
|
|
180
|
+
yellow: O,
|
|
181
|
+
blue: O,
|
|
182
|
+
magenta: O,
|
|
183
|
+
cyan: O,
|
|
184
|
+
white: O,
|
|
185
|
+
brightRed: O,
|
|
186
|
+
brightGreen: O,
|
|
187
|
+
brightYellow: O,
|
|
188
|
+
brightBlue: O,
|
|
189
|
+
brightCyan: O,
|
|
190
|
+
bgRed: O,
|
|
191
|
+
bgYellow: O,
|
|
192
|
+
bgBlue: O,
|
|
193
|
+
bgCyan: O,
|
|
194
|
+
bgGray: O,
|
|
195
|
+
brand: O,
|
|
196
|
+
brandDim: O,
|
|
197
|
+
bgBrand: O,
|
|
198
|
+
red24: O,
|
|
199
|
+
brightRed24: O
|
|
200
|
+
}, A = /* @__PURE__ */ new Set(), j = (e) => (A.add(e), () => {
|
|
201
|
+
A.delete(e);
|
|
202
|
+
}), M = (e) => {
|
|
203
|
+
if (A.size !== 0) for (let t of A) try {
|
|
204
|
+
t(e);
|
|
205
|
+
} catch {}
|
|
206
|
+
}, ee = () => {
|
|
207
|
+
let e = (process.env.VOLTRO_LOG_FORMAT ?? "").toLowerCase();
|
|
208
|
+
return e === "pretty" ? "pretty" : e === "json" ? "json" : f() ? "pretty" : "json";
|
|
209
|
+
}, N = () => {
|
|
210
|
+
let e = (process.env.VOLTRO_LOG_COLOUR ?? "").toLowerCase();
|
|
211
|
+
return e === "on" || e === "true" || e === "1" ? !0 : e === "off" || e === "false" || e === "0" ? !1 : f();
|
|
212
|
+
}, P = () => {
|
|
213
|
+
switch ((process.env.VOLTRO_LOG_LEVEL ?? "").toLowerCase()) {
|
|
214
|
+
case "trace": return a.Trace;
|
|
215
|
+
case "debug": return a.Debug;
|
|
216
|
+
case "info": return a.Info;
|
|
217
|
+
case "warn": return a.Warning;
|
|
218
|
+
case "error": return a.Error;
|
|
219
|
+
case "fatal": return a.Fatal;
|
|
220
|
+
default: {
|
|
221
|
+
let e = (process.env.NODE_ENV ?? "").toLowerCase();
|
|
222
|
+
return e !== "production" && e !== "prod" ? a.Debug : a.Info;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}, F = (e) => {
|
|
226
|
+
switch (e.label) {
|
|
227
|
+
case "TRACE": return "trace";
|
|
228
|
+
case "DEBUG": return "debug";
|
|
229
|
+
case "INFO": return "info";
|
|
230
|
+
case "WARN": return "warn";
|
|
231
|
+
case "ERROR": return "error";
|
|
232
|
+
case "FATAL": return "fatal";
|
|
233
|
+
default: return "info";
|
|
234
|
+
}
|
|
235
|
+
}, I = (r) => {
|
|
236
|
+
let a = {}, o;
|
|
237
|
+
n.forEach(r.annotations, (e, t) => {
|
|
238
|
+
if (t === "scope" && typeof e == "string") {
|
|
239
|
+
o = e;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
a[t] = e;
|
|
243
|
+
});
|
|
244
|
+
let s = [];
|
|
245
|
+
i.forEach(r.spans, (e) => {
|
|
246
|
+
s.push(e.label);
|
|
247
|
+
}), s.length > 0 && (a.span = s.join("."));
|
|
248
|
+
let c = r.fiberId !== void 0 && r.fiberId !== null && t.threadName(r.fiberId) || void 0, l = r.cause !== void 0 && r.cause !== null ? e.isEmpty(r.cause) ? void 0 : e.squash(r.cause) : void 0, u = L(r.message);
|
|
249
|
+
return {
|
|
250
|
+
ts: new Date(r.date.getTime()),
|
|
251
|
+
level: F(r.logLevel),
|
|
252
|
+
message: u,
|
|
253
|
+
fields: a,
|
|
254
|
+
...o ? { scope: o } : {},
|
|
255
|
+
...c ? { fiberId: c } : {},
|
|
256
|
+
...l === void 0 ? {} : { cause: l }
|
|
257
|
+
};
|
|
258
|
+
}, L = (e) => typeof e == "string" ? e : Array.isArray(e) ? e.map((e) => typeof e == "string" ? e : e === void 0 ? "undefined" : e === null ? "null" : typeof e == "object" ? R(e) : String(e)).join(" ") : e === void 0 ? "" : e === null ? "null" : typeof e == "object" ? R(e) : String(e), R = (e) => {
|
|
259
|
+
try {
|
|
260
|
+
return JSON.stringify(e);
|
|
261
|
+
} catch {
|
|
262
|
+
return String(e);
|
|
263
|
+
}
|
|
264
|
+
}, z = (e, t) => {
|
|
265
|
+
(e.level === "error" || e.level === "fatal" ? process.stderr : process.stdout).write(`${t}\n`);
|
|
266
|
+
}, B = (e, t, n) => t === "json" ? D(e) : C(e, n), V = (e = {}) => {
|
|
267
|
+
let t = e.format ?? ee(), n = { colour: e.colour ?? N() }, r = g(e), i = e.minLevel;
|
|
268
|
+
return o.make((o) => {
|
|
269
|
+
if (i !== void 0 && a.lessThan(o.logLevel, i)) return;
|
|
270
|
+
let s = I(o), c = !s.scope && e.defaultScope ? {
|
|
271
|
+
...s,
|
|
272
|
+
scope: e.defaultScope
|
|
273
|
+
} : s, l = r ? r(c) : c;
|
|
274
|
+
z(l, B(l, t, n)), M(l);
|
|
275
|
+
});
|
|
276
|
+
}, H = (e = {}) => r.mergeAll(o.replace(o.defaultLogger, V(e)), o.minimumLogLevel(e.minLevel ?? P())), U = {
|
|
277
|
+
trace: 10,
|
|
278
|
+
debug: 20,
|
|
279
|
+
info: 30,
|
|
280
|
+
warn: 40,
|
|
281
|
+
error: 50,
|
|
282
|
+
fatal: 60
|
|
283
|
+
}, W = (e) => {
|
|
284
|
+
try {
|
|
285
|
+
return typeof process < "u" && process.env && process.env[e] || "";
|
|
286
|
+
} catch {
|
|
287
|
+
return "";
|
|
288
|
+
}
|
|
289
|
+
}, G = () => {
|
|
290
|
+
let e = W("VOLTRO_LOG_FORMAT").toLowerCase();
|
|
291
|
+
return e === "pretty" ? "pretty" : e === "json" ? "json" : f() ? "pretty" : "json";
|
|
292
|
+
}, K = () => {
|
|
293
|
+
let e = W("VOLTRO_LOG_COLOUR").toLowerCase();
|
|
294
|
+
return e === "on" || e === "true" || e === "1" ? !0 : e === "off" || e === "false" || e === "0" ? !1 : f();
|
|
295
|
+
}, q = () => {
|
|
296
|
+
let e = W("VOLTRO_LOG_LEVEL").toLowerCase();
|
|
297
|
+
if (e === "trace" || e === "debug" || e === "info" || e === "warn" || e === "error" || e === "fatal") return e;
|
|
298
|
+
let t = W("NODE_ENV").toLowerCase();
|
|
299
|
+
return t !== "production" && t !== "prod" ? "debug" : "info";
|
|
300
|
+
}, J = globalThis.window !== void 0;
|
|
301
|
+
if (!J && typeof process < "u") for (let e of [process.stdout, process.stderr]) e && typeof e.on == "function" && e.on("error", (e) => {
|
|
302
|
+
if (e?.code !== "EPIPE") throw e;
|
|
303
|
+
});
|
|
304
|
+
var Y = (e, t, n, r) => {
|
|
305
|
+
let i = r ? r(e) : e;
|
|
306
|
+
if (J) {
|
|
307
|
+
let e = t === "json" ? D(i) : C(i, {
|
|
308
|
+
...n,
|
|
309
|
+
colour: !1
|
|
310
|
+
});
|
|
311
|
+
(console[X(i.level)] ?? console.log).call(console, e);
|
|
312
|
+
} else {
|
|
313
|
+
let e = t === "json" ? D(i) : C(i, n), r = i.level === "error" || i.level === "fatal" ? process.stderr : process.stdout;
|
|
314
|
+
try {
|
|
315
|
+
r.write(`${e}\n`);
|
|
316
|
+
} catch {}
|
|
317
|
+
}
|
|
318
|
+
M(i);
|
|
319
|
+
}, X = (e) => {
|
|
320
|
+
switch (e) {
|
|
321
|
+
case "trace":
|
|
322
|
+
case "debug": return "debug";
|
|
323
|
+
case "info": return "log";
|
|
324
|
+
case "warn": return "warn";
|
|
325
|
+
case "error":
|
|
326
|
+
case "fatal": return "error";
|
|
327
|
+
}
|
|
328
|
+
}, Z = (e = {}) => {
|
|
329
|
+
let t = e.format ?? G(), n = e.colour ?? K(), r = U[e.minLevel ?? q()], i = e.fields ?? {}, a = e.scope, o = g(e), s = (e, s, c, l) => {
|
|
330
|
+
U[e] < r || Y({
|
|
331
|
+
ts: /* @__PURE__ */ new Date(),
|
|
332
|
+
level: e,
|
|
333
|
+
...a ? { scope: a } : {},
|
|
334
|
+
message: s,
|
|
335
|
+
fields: {
|
|
336
|
+
...i,
|
|
337
|
+
...c ?? {}
|
|
338
|
+
},
|
|
339
|
+
...l === void 0 ? {} : { cause: l }
|
|
340
|
+
}, t, { colour: n }, o);
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
trace: (e, t) => s("trace", e, t),
|
|
344
|
+
debug: (e, t) => s("debug", e, t),
|
|
345
|
+
info: (e, t) => s("info", e, t),
|
|
346
|
+
warn: (e, t, n) => s("warn", e, t, n),
|
|
347
|
+
error: (e, t, n) => s("error", e, t, n),
|
|
348
|
+
fatal: (e, t, n) => s("fatal", e, t, n),
|
|
349
|
+
child: (t, n, r) => Z({
|
|
350
|
+
...e,
|
|
351
|
+
scope: a ? `${a}:${t}` : t,
|
|
352
|
+
fields: {
|
|
353
|
+
...i,
|
|
354
|
+
...n ?? {}
|
|
355
|
+
},
|
|
356
|
+
...r?.minLevel === void 0 ? {} : { minLevel: r.minLevel }
|
|
357
|
+
})
|
|
358
|
+
};
|
|
359
|
+
}, Q = Z(), te = (e) => {
|
|
360
|
+
let t = [], n, r = !1, i = async () => {
|
|
361
|
+
if (t.length === 0) return;
|
|
362
|
+
let n = t.splice(0, t.length);
|
|
363
|
+
try {
|
|
364
|
+
await e.send(n);
|
|
365
|
+
} catch (t) {
|
|
366
|
+
e.onError?.(t, n.length);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
return n = setInterval(() => void i(), e.intervalMs), typeof n.unref == "function" && n.unref(), {
|
|
370
|
+
push: (n) => {
|
|
371
|
+
r || (e.flushBefore && t.length > 0 && e.flushBefore(t, n) && i(), t.push(n), e.maxBuffer !== void 0 && t.length > e.maxBuffer && t.shift(), e.admit && e.admit(t, n) === "flush" && i());
|
|
372
|
+
},
|
|
373
|
+
flush: i,
|
|
374
|
+
dispose: async () => {
|
|
375
|
+
if (r) {
|
|
376
|
+
await i();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
r = !0, n && clearInterval(n), n = void 0, await i(), t = [];
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}, ne = {
|
|
383
|
+
web: "Web app",
|
|
384
|
+
api: "API app",
|
|
385
|
+
preview: "Preview"
|
|
386
|
+
}, re = (e) => e < 1e3 ? `${Math.round(e)} ms` : `${(e / 1e3).toFixed(1)} s`, ie = (e) => {
|
|
387
|
+
let t = f() ? u : ae(), n = [];
|
|
388
|
+
n.push(""), n.push(` ${t.brand("▲")} ${t.bold("Voltro")} ${t.dim("·")} ${t.dim(ne[e.kind])} ${t.dim("·")} ${e.app}`), n.push(""), n.push(` ${t.dim("➜")} ${t.dim("Local: ")}${t.bold(t.brand(e.url))}`);
|
|
389
|
+
for (let r of e.extras ?? []) {
|
|
390
|
+
let e = `${r.label}:`.padEnd(8, " ");
|
|
391
|
+
n.push(` ${t.dim("➜")} ${t.dim(e)}${t.brandDim(r.url)}`);
|
|
392
|
+
}
|
|
393
|
+
n.push(""), n.push(` ${t.dim(`ready in ${re(e.bootMs)}`)}`), n.push(""), process.stdout.write(n.join("\n") + "\n");
|
|
394
|
+
}, ae = () => {
|
|
395
|
+
let e = (e) => e;
|
|
396
|
+
return {
|
|
397
|
+
reset: "",
|
|
398
|
+
dim: e,
|
|
399
|
+
bold: e,
|
|
400
|
+
italic: e,
|
|
401
|
+
gray: e,
|
|
402
|
+
red: e,
|
|
403
|
+
green: e,
|
|
404
|
+
yellow: e,
|
|
405
|
+
blue: e,
|
|
406
|
+
magenta: e,
|
|
407
|
+
cyan: e,
|
|
408
|
+
white: e,
|
|
409
|
+
brightRed: e,
|
|
410
|
+
brightGreen: e,
|
|
411
|
+
brightYellow: e,
|
|
412
|
+
brightBlue: e,
|
|
413
|
+
brightCyan: e,
|
|
414
|
+
bgRed: e,
|
|
415
|
+
bgYellow: e,
|
|
416
|
+
bgBlue: e,
|
|
417
|
+
bgCyan: e,
|
|
418
|
+
bgGray: e,
|
|
419
|
+
brand: e,
|
|
420
|
+
brandDim: e,
|
|
421
|
+
bgBrand: e,
|
|
422
|
+
red24: e,
|
|
423
|
+
brightRed24: e
|
|
424
|
+
};
|
|
425
|
+
}, $ = (e) => {
|
|
426
|
+
let t = /* @__PURE__ */ new Map();
|
|
427
|
+
for (let n of e) {
|
|
428
|
+
let e = n.indexOf("."), r = e >= 0 ? n.slice(0, e) : "(top)", i = e >= 0 ? n.slice(e + 1) : n, a = t.get(r);
|
|
429
|
+
a ? a.push(i) : t.set(r, [i]);
|
|
430
|
+
}
|
|
431
|
+
return [...t.entries()].sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => ({
|
|
432
|
+
namespace: e,
|
|
433
|
+
operations: t.slice().sort()
|
|
434
|
+
}));
|
|
435
|
+
}, oe = (e) => {
|
|
436
|
+
let t = f() ? u : se(), n = e.schedules ?? [], r = e.aggregates ?? [], i = e.streams ?? [], a = e.triggers ?? [], o = e.agents ?? [], s = e.restRoutes ?? [], c = e.lifecycle ?? {}, l = (c.seeds ?? 0) + (c.startups ?? 0) + (c.subscribers ?? 0) + (c.webhooks ?? 0) + (c.emails ?? 0);
|
|
437
|
+
if (e.queries.length + e.mutations.length + e.actions.length + i.length + e.workflows.length + n.length + r.length + a.length + o.length + s.length + l === 0) return;
|
|
438
|
+
let d = [""], p = [
|
|
439
|
+
...$(e.queries).map((e) => e.namespace),
|
|
440
|
+
...$(e.mutations).map((e) => e.namespace),
|
|
441
|
+
...$(e.actions).map((e) => e.namespace),
|
|
442
|
+
...$(i).map((e) => e.namespace),
|
|
443
|
+
...$(e.workflows).map((e) => e.namespace),
|
|
444
|
+
...$(n).map((e) => e.namespace),
|
|
445
|
+
...$(r).map((e) => e.namespace),
|
|
446
|
+
...$(a).map((e) => e.namespace),
|
|
447
|
+
...$(o).map((e) => e.namespace)
|
|
448
|
+
].reduce((e, t) => Math.max(e, t.length), 0), m = (e, n) => {
|
|
449
|
+
if (n.length !== 0) {
|
|
450
|
+
d.push(` ${t.bold(e)} ${t.dim(`(${n.length})`)}`);
|
|
451
|
+
for (let { namespace: e, operations: r } of $(n)) {
|
|
452
|
+
let n = e.padEnd(p, " ");
|
|
453
|
+
d.push(` ${t.brand(n)} ${t.dim("·")} ${r.join(t.dim(", "))}`);
|
|
454
|
+
}
|
|
455
|
+
d.push("");
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
if (m("Queries", e.queries), m("Mutations", e.mutations), m("Actions", e.actions), m("Streams", i), m("REST routes", s), m("Workflows", e.workflows), m("Triggers", a), m("Schedules", n), m("Agents", o), m("Aggregates", r), l > 0) {
|
|
459
|
+
let e = [], n = (t, n, r) => {
|
|
460
|
+
t && t > 0 && e.push(`${t} ${t === 1 ? n : r}`);
|
|
461
|
+
};
|
|
462
|
+
n(c.seeds, "seed", "seeds"), n(c.startups, "startup", "startups"), n(c.subscribers, "subscriber", "subscribers"), n(c.webhooks, "webhook", "webhooks"), n(c.emails, "email", "emails"), d.push(` ${t.bold("Lifecycle")} ${t.dim("·")} ${e.join(t.dim(" · "))}`), d.push("");
|
|
463
|
+
}
|
|
464
|
+
e.tenantTables && e.tenantTables.length > 0 && (d.push(` ${t.dim("Tenant-scoped tables")} ${t.dim("·")} ${e.tenantTables.join(t.dim(", "))}`), d.push("")), process.stdout.write(d.join("\n"));
|
|
465
|
+
}, se = () => {
|
|
466
|
+
let e = (e) => e;
|
|
467
|
+
return {
|
|
468
|
+
reset: "",
|
|
469
|
+
dim: e,
|
|
470
|
+
bold: e,
|
|
471
|
+
italic: e,
|
|
472
|
+
gray: e,
|
|
473
|
+
red: e,
|
|
474
|
+
green: e,
|
|
475
|
+
yellow: e,
|
|
476
|
+
blue: e,
|
|
477
|
+
magenta: e,
|
|
478
|
+
cyan: e,
|
|
479
|
+
white: e,
|
|
480
|
+
brightRed: e,
|
|
481
|
+
brightGreen: e,
|
|
482
|
+
brightYellow: e,
|
|
483
|
+
brightBlue: e,
|
|
484
|
+
brightCyan: e,
|
|
485
|
+
bgRed: e,
|
|
486
|
+
bgYellow: e,
|
|
487
|
+
bgBlue: e,
|
|
488
|
+
bgCyan: e,
|
|
489
|
+
bgGray: e,
|
|
490
|
+
brand: e,
|
|
491
|
+
brandDim: e,
|
|
492
|
+
bgBrand: e,
|
|
493
|
+
red24: e,
|
|
494
|
+
brightRed24: e
|
|
495
|
+
};
|
|
496
|
+
};
|
|
497
|
+
//#endregion
|
|
498
|
+
export { H as LoggerLayer, j as addSink, u as ansi, Z as createLogger, f as isTty, D as jsonFormat, Q as log, te as makeBufferedSink, h as makeKeyRedactor, V as makeLogger, C as prettyFormat, oe as printAppSurface, ie as printReadyBlock, d as stripAnsi };
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voltro/logger",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Effect-based structured logger with pretty terminal output + JSON for production.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"voltro",
|
|
7
|
+
"typescript",
|
|
8
|
+
"framework"
|
|
9
|
+
],
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"homepage": "https://voltro.dev",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"email": "support@voltro.dev"
|
|
14
|
+
},
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Voltro UG",
|
|
17
|
+
"url": "https://voltro.dev"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"effect": "^3.21.4"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
}
|
|
40
|
+
}
|