logquill 0.4.0 → 1.0.1
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/README.md +315 -7
- package/dist/browser.d.ts +468 -0
- package/dist/browser.mjs +783 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/index.cjs +569 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +471 -9
- package/dist/index.d.ts +471 -9
- package/dist/index.mjs +556 -8
- package/dist/index.mjs.map +1 -1
- package/dist/langchain.cjs +6 -0
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.d.cts +7 -1
- package/dist/langchain.d.ts +7 -1
- package/dist/langchain.mjs +6 -0
- package/dist/langchain.mjs.map +1 -1
- package/dist/logger-CRdmCDfC.d.cts +299 -0
- package/dist/logger-CRdmCDfC.d.ts +299 -0
- package/dist/winston.cjs +108 -0
- package/dist/winston.cjs.map +1 -0
- package/dist/winston.d.cts +55 -0
- package/dist/winston.d.ts +55 -0
- package/dist/winston.mjs +101 -0
- package/dist/winston.mjs.map +1 -0
- package/package.json +40 -5
- package/dist/logger-D1_THnBJ.d.cts +0 -163
- package/dist/logger-D1_THnBJ.d.ts +0 -163
package/README.md
CHANGED
|
@@ -9,12 +9,15 @@ A logging framework for Node/TypeScript that shares one mental model and one
|
|
|
9
9
|
JSON log shape with its Python sibling, [`logquill`](https://pypi.org/project/logquill/)
|
|
10
10
|
(repo: `logquill-python`).
|
|
11
11
|
|
|
12
|
-
Status:
|
|
13
|
-
|
|
14
|
-
sampling/alerting),
|
|
15
|
-
console/file/HTTP, SQL,
|
|
16
|
-
|
|
17
|
-
`
|
|
12
|
+
Status: **1.0** — the full core `Logger`, level filtering, non-blocking
|
|
13
|
+
async dispatch with configurable backpressure, a full plugin pipeline
|
|
14
|
+
(context/redaction/PII/tamper-evidence/sampling/rate-limiting/alerting),
|
|
15
|
+
`JSONFormatter`, a broad transport catalog — console/file/HTTP, SQL,
|
|
16
|
+
NoSQL, message queues, and cloud-native log platforms — agentic/harness
|
|
17
|
+
tracing (including `LangChainAdapter`/`LangGraphAdapter` and
|
|
18
|
+
`OtelSpanProcessor`), a separate `logquill/browser` build, and
|
|
19
|
+
`winston`/`pino` migration bridges are all shipped; see `CHANGELOG.md` for
|
|
20
|
+
the full history.
|
|
18
21
|
|
|
19
22
|
## Features
|
|
20
23
|
|
|
@@ -28,7 +31,11 @@ platforms — are implemented; non-blocking async dispatch is not yet — see
|
|
|
28
31
|
- **Dual package** — works via both `require()` (CJS) and `import` (ESM) from the same published package
|
|
29
32
|
- **Tracing & agentic logging** — `.thought()/.action()/.observation()/.decision()`, `Logger.span()` for nested/durationMs-stamped spans, `RunPlugin` (per-run id + step counter), and `TraceContextPlugin` (cross-service `traceId`, OTel-aware) — see [Tracing & agentic logging](#tracing--agentic-logging)
|
|
30
33
|
- **LangChain.js / LangGraph.js adapter** — `LangChainAdapter`, a `BaseCallbackHandler` that maps chain/LLM/tool/agent events onto the calls above with zero manual instrumentation, from a separate `logquill/langchain` entry point — see [Agentic framework adapters](#agentic-framework-adapters)
|
|
31
|
-
-
|
|
34
|
+
- **OpenTelemetry-native integration** — `OtelSpanProcessor` bridges frameworks that emit OTel spans directly (e.g. the Vercel AI SDK) onto `.action()`/`.observation()`/`.error()`, with zero real dependency on `@opentelemetry/*` — see [OpenTelemetry-native integration](#opentelemetry-native-integration)
|
|
35
|
+
- **Non-blocking async dispatch** — a call returns before its write runs, via a bounded internal queue with a configurable backpressure policy (`dropOldest`/`dropNewest`/`block`); `logger.flush()`, `withLambda`/`withCloudFunction`/`withAzureFunction`, and `installShutdownHandlers` cover draining it before a process pauses or exits — see [Async dispatch & shutdown](#async-dispatch--shutdown)
|
|
36
|
+
- **Request-scoped context propagation** — `bindContext()`, `AsyncLocalStorage`-backed, so a value set once is visible in every nested log call underneath it without threading it through every signature by hand; `RateLimitPlugin` caps a noisy loop without silencing everything else — see [Context propagation & error capture](#context-propagation--error-capture)
|
|
37
|
+
- **Migration bridges** — `LogQuillWinstonTransport` (from `logquill/winston`) and `LogQuillPinoDestination` let an existing `winston`/`pino` app adopt LogQuill's transports/plugins with no call-site changes — see [Migration bridges](#migration-bridges)
|
|
38
|
+
- **Browser build** — a separate `logquill/browser` entry (`Logger`, `JSONFormatter`, the plugin pipeline, `ConsoleTransport`, `BeaconTransport`) with no Node built-ins in its module graph — see [Browser build](#browser-build)
|
|
32
39
|
|
|
33
40
|
## Contents
|
|
34
41
|
|
|
@@ -44,8 +51,16 @@ platforms — are implemented; non-blocking async dispatch is not yet — see
|
|
|
44
51
|
- [PII redaction](#pii-redaction)
|
|
45
52
|
- [Tamper-evident logs](#tamper-evident-logs)
|
|
46
53
|
- [Alerting](#alerting)
|
|
54
|
+
- [Rate limiting](#rate-limiting)
|
|
47
55
|
- [Tracing & agentic logging](#tracing--agentic-logging)
|
|
48
56
|
- [Agentic framework adapters](#agentic-framework-adapters)
|
|
57
|
+
- [OpenTelemetry-native integration](#opentelemetry-native-integration)
|
|
58
|
+
- [Async dispatch & shutdown](#async-dispatch--shutdown)
|
|
59
|
+
- [Kubernetes](#kubernetes)
|
|
60
|
+
- [Context propagation & error capture](#context-propagation--error-capture)
|
|
61
|
+
- [Migration bridges](#migration-bridges)
|
|
62
|
+
- [Browser build](#browser-build)
|
|
63
|
+
- [API reference](#api-reference)
|
|
49
64
|
- [Development](#development)
|
|
50
65
|
- [License](#license)
|
|
51
66
|
|
|
@@ -119,6 +134,7 @@ actually import; nothing is installed on your behalf.
|
|
|
119
134
|
| Transport | Backend | Peer dependency | Setup |
|
|
120
135
|
|---|---|---|---|
|
|
121
136
|
| `ConsoleTransport` | stdout/stderr via `console.*` | *(none)* | zero-setup, isomorphic |
|
|
137
|
+
| `BeaconTransport` | browser log endpoint, via `navigator.sendBeacon` | *(none, isomorphic)* | zero-setup — see [Browser build](#browser-build) |
|
|
122
138
|
| `FileTransport` | local file, with rotation | *(none)* | zero-setup |
|
|
123
139
|
| `HTTPTransport` | any HTTP log endpoint | *(none, uses `fetch`)* | zero-setup |
|
|
124
140
|
| `SQLiteTransport` | SQLite | `better-sqlite3` | zero-setup (file or `:memory:`) |
|
|
@@ -506,6 +522,25 @@ logger.error("payment webhook failed", { orderId: "o-123" });
|
|
|
506
522
|
inject a `sender`) follow the same shape. Write your own by extending
|
|
507
523
|
`AlertingPlugin` and implementing `sendAlert(record, occurrences)`.
|
|
508
524
|
|
|
525
|
+
### Rate limiting
|
|
526
|
+
|
|
527
|
+
`RateLimitPlugin` drops records once a key — by default `(logger, level)`
|
|
528
|
+
— exceeds `maxRecords` within a rolling `perSeconds` window, so a noisy
|
|
529
|
+
loop (a retry logging the same error every iteration) can't drown out a
|
|
530
|
+
logger's other messages. Each key gets its own window, so unrelated keys
|
|
531
|
+
never reset in lockstep; pass `keyFunc` to key on something else, e.g. an
|
|
532
|
+
error message or a `meta` field identifying the caller.
|
|
533
|
+
|
|
534
|
+
```ts
|
|
535
|
+
import { Logger, RateLimitPlugin } from "logquill";
|
|
536
|
+
|
|
537
|
+
const logger = new Logger("app", { plugins: [new RateLimitPlugin(5, 60)] }); // at most 5 per key per minute
|
|
538
|
+
|
|
539
|
+
for (let i = 0; i < 10; i++) {
|
|
540
|
+
logger.error("db connection failed"); // only the first 5 in any 60s window ship
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
509
544
|
## Tracing & agentic logging
|
|
510
545
|
|
|
511
546
|
`.thought()/.action()/.observation()/.decision()` are `.info()` with
|
|
@@ -621,6 +656,278 @@ not the main `"logquill"` import — because `LangChainAdapter` has to
|
|
|
621
656
|
optional peer dependency) — no separate `@langchain/langgraph` dependency
|
|
622
657
|
is needed for `LangGraphAdapter`.
|
|
623
658
|
|
|
659
|
+
### OpenTelemetry-native integration
|
|
660
|
+
|
|
661
|
+
Some frameworks aren't callback-handler-based — the Vercel AI SDK's
|
|
662
|
+
`experimental_telemetry` is the main JS example, emitting OpenTelemetry
|
|
663
|
+
spans directly instead of calling into a handler object like
|
|
664
|
+
`BaseCallbackHandler`. `OtelSpanProcessor` covers that case: register it on
|
|
665
|
+
any OTel tracer provider, and every span becomes one `.action()` call on
|
|
666
|
+
start plus one `.observation()` (or `.error()`, on an error status) call on
|
|
667
|
+
end, carrying the span's own `spanId`/`parentSpanId` — OTel span ids are
|
|
668
|
+
already the same 16-hex-char shape LogQuill's own ids use — plus
|
|
669
|
+
`meta.durationMs` on the end record:
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
|
|
673
|
+
import { Logger, OtelSpanProcessor } from "logquill";
|
|
674
|
+
|
|
675
|
+
const logger = new Logger("agent");
|
|
676
|
+
const provider = new BasicTracerProvider({
|
|
677
|
+
spanProcessors: [new OtelSpanProcessor(logger)],
|
|
678
|
+
});
|
|
679
|
+
const tracer = provider.getTracer("my-app");
|
|
680
|
+
|
|
681
|
+
const span = tracer.startSpan("callLlm");
|
|
682
|
+
span.end();
|
|
683
|
+
// logs two records: one .action() on start, one .observation() on end
|
|
684
|
+
// (or .error() if span.setStatus({ code: SpanStatusCode.ERROR }) was called)
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Unlike `LangChainAdapter`, this is exported from the **main** `"logquill"`
|
|
688
|
+
entry point, not a separate subpath — it's fully duck-typed against the
|
|
689
|
+
`Span`/`ReadableSpan` shape, the same approach `TraceContextPlugin` uses
|
|
690
|
+
for its own OTel lookup, so it never imports `@opentelemetry/api` or
|
|
691
|
+
`@opentelemetry/sdk-trace-base` for real. Non-empty span attributes are
|
|
692
|
+
copied onto `meta.attributes` (configurable via `attributesKey`) verbatim;
|
|
693
|
+
mapping onto the OTel `gen_ai.*` semantic conventions is left to the
|
|
694
|
+
planned `OTLPTransport` (v2.0).
|
|
695
|
+
|
|
696
|
+
## Async dispatch & shutdown
|
|
697
|
+
|
|
698
|
+
Every `Logger` call returns before the write it triggers actually runs —
|
|
699
|
+
the write (and any plugin `afterLog` hooks) is handed to an internal,
|
|
700
|
+
bounded dispatch queue, drained outside the caller's own call stack:
|
|
701
|
+
|
|
702
|
+
```ts
|
|
703
|
+
import { CollectingTransport, Logger } from "logquill";
|
|
704
|
+
|
|
705
|
+
const transport = new CollectingTransport();
|
|
706
|
+
const logger = new Logger("app", { transports: [transport] });
|
|
707
|
+
|
|
708
|
+
logger.info("hello");
|
|
709
|
+
console.log(transport.records.length); // 0 — still queued
|
|
710
|
+
await logger.flush();
|
|
711
|
+
console.log(transport.records.length); // 1 — now written
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
The queue has a configurable size and backpressure policy, so a sustained
|
|
715
|
+
burst can't grow memory without bound:
|
|
716
|
+
|
|
717
|
+
```ts
|
|
718
|
+
const logger = new Logger("app", {
|
|
719
|
+
transports: [transport],
|
|
720
|
+
queue: {
|
|
721
|
+
maxSize: 10_000, // default
|
|
722
|
+
policy: "dropOldest", // "dropOldest" (default) | "dropNewest" | "block"
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
- `"dropOldest"` — once the queue is full, the longest-waiting record is
|
|
728
|
+
discarded to make room for the new one.
|
|
729
|
+
- `"dropNewest"` — the incoming record is discarded; everything already
|
|
730
|
+
queued is left alone.
|
|
731
|
+
- `"block"` — nothing is ever dropped: once the queue is full, a call runs
|
|
732
|
+
its write immediately, on the caller's own stack, instead of queueing it.
|
|
733
|
+
|
|
734
|
+
Any dropped-record policy calls `onDrop(count, policy)` — rate-limited to
|
|
735
|
+
once per `warnIntervalMs` (default 5000) — so sustained overload is
|
|
736
|
+
visible without flooding your own logs with one warning per drop.
|
|
737
|
+
|
|
738
|
+
`logger.flush()` waits for every record dispatched so far to reach its
|
|
739
|
+
transports. Note it does *not* force a batching transport (SQL, a queue,
|
|
740
|
+
`HTTPTransport`, ...) to send a batch still under its own threshold early
|
|
741
|
+
— for that, use `withLambda`/`withCloudFunction`/`withAzureFunction`
|
|
742
|
+
(same wrapper, three platform-matching names) around a serverless handler,
|
|
743
|
+
which additionally forces every batching transport's buffer out before
|
|
744
|
+
the wrapped function's result settles — important because a frozen or
|
|
745
|
+
recycled execution environment may never come back to finish a partial
|
|
746
|
+
batch on its own:
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
import { withLambda } from "logquill";
|
|
750
|
+
|
|
751
|
+
export const handler = withLambda(logger, async (event) => {
|
|
752
|
+
logger.info("handling request", { requestId: event.requestId });
|
|
753
|
+
return { statusCode: 200 };
|
|
754
|
+
});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
For a long-running process, `installShutdownHandlers` flushes and closes a
|
|
758
|
+
logger once on `SIGTERM`/`SIGINT`/`beforeExit`, so nothing queued is lost
|
|
759
|
+
when the process stops:
|
|
760
|
+
|
|
761
|
+
```ts
|
|
762
|
+
import { installShutdownHandlers } from "logquill";
|
|
763
|
+
|
|
764
|
+
installShutdownHandlers(logger); // Node only
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### Kubernetes
|
|
768
|
+
|
|
769
|
+
Default to `ConsoleTransport`, not `FileTransport`, for anything running in
|
|
770
|
+
a container. A container's filesystem is ephemeral and invisible to the
|
|
771
|
+
rest of the cluster — a log file written inside it disappears the moment
|
|
772
|
+
the pod is rescheduled, and nothing aggregates it in the meantime unless
|
|
773
|
+
you also run a sidecar to tail it back out. Writing to stdout/stderr
|
|
774
|
+
instead costs nothing extra: every major container runtime already
|
|
775
|
+
captures both streams, and the node-level log agent your cluster runs
|
|
776
|
+
(Fluentd, Fluent Bit, Vector, or your cloud provider's own) ships them to
|
|
777
|
+
your aggregator without any code on your side that needs to know that
|
|
778
|
+
agent exists:
|
|
779
|
+
|
|
780
|
+
```ts
|
|
781
|
+
import { ConsoleTransport, Logger } from "logquill";
|
|
782
|
+
|
|
783
|
+
const logger = new Logger("app", { transports: [new ConsoleTransport()] });
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Kubernetes sends `SIGTERM` and then kills the process after
|
|
787
|
+
`terminationGracePeriodSeconds` (30s by default) regardless of whether
|
|
788
|
+
it's finished shutting down, so a record still sitting in the dispatch
|
|
789
|
+
queue at that instant can be lost if nothing catches the signal.
|
|
790
|
+
`installShutdownHandlers(logger)` closes that gap the same way
|
|
791
|
+
`withLambda` closes it for a serverless freeze — call it once, on process
|
|
792
|
+
start, so a pod that's told to stop still flushes and closes the logger
|
|
793
|
+
before Kubernetes kills it.
|
|
794
|
+
|
|
795
|
+
## Context propagation & error capture
|
|
796
|
+
|
|
797
|
+
`bindContext()` merges values into a request-scoped context, backed by
|
|
798
|
+
`AsyncLocalStorage`: every `Logger` call underneath it — through any
|
|
799
|
+
number of function calls and `await`s deep — picks them up in `meta`
|
|
800
|
+
automatically, without threading them through every signature by hand.
|
|
801
|
+
Concurrent async operations sharing one `Logger` never see each other's
|
|
802
|
+
bound context.
|
|
803
|
+
|
|
804
|
+
```ts
|
|
805
|
+
import { bindContext, Logger } from "logquill";
|
|
806
|
+
|
|
807
|
+
const logger = new Logger("app");
|
|
808
|
+
|
|
809
|
+
await bindContext({ requestId: "abc123" }, async () => {
|
|
810
|
+
await handleRequest(); // any logging in here, or in what it calls,
|
|
811
|
+
// gets meta.requestId = "abc123" for free
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
function handleRequest() {
|
|
815
|
+
logger.info("handled"); // meta: { requestId: "abc123" }
|
|
816
|
+
}
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Nested `bindContext()` calls merge, with the inner value winning on key
|
|
820
|
+
collision — the same way an explicit call-site `meta` value always wins
|
|
821
|
+
over anything bound this way.
|
|
822
|
+
|
|
823
|
+
Pass an `Error` as `meta.err` and it's replaced with a formatted
|
|
824
|
+
`meta.stack`, the same shape `Logger.span()` already produces internally
|
|
825
|
+
for a thrown error:
|
|
826
|
+
|
|
827
|
+
```ts
|
|
828
|
+
try {
|
|
829
|
+
await riskyOperation();
|
|
830
|
+
} catch (err) {
|
|
831
|
+
logger.error("operation failed", { err, orderId: "o-123" });
|
|
832
|
+
// meta: { orderId: "o-123", stack: "Error: ...\n at ..." } — no meta.err
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
## Migration bridges
|
|
837
|
+
|
|
838
|
+
For an app already using `winston` or `pino`, LogQuill can sit alongside
|
|
839
|
+
either with no call-site changes, so a migration can happen transport by
|
|
840
|
+
transport instead of all at once.
|
|
841
|
+
|
|
842
|
+
`LogQuillWinstonTransport` (from the separate `logquill/winston` entry
|
|
843
|
+
point, since it has to extend `winston-transport`'s own class) plugs a
|
|
844
|
+
LogQuill `Logger` into an existing `winston.createLogger()` as one more
|
|
845
|
+
transport:
|
|
846
|
+
|
|
847
|
+
```ts
|
|
848
|
+
import winston from "winston";
|
|
849
|
+
import { Logger } from "logquill";
|
|
850
|
+
import { LogQuillWinstonTransport } from "logquill/winston";
|
|
851
|
+
|
|
852
|
+
const logquill = new Logger("app");
|
|
853
|
+
const winstonLogger = winston.createLogger({
|
|
854
|
+
transports: [new LogQuillWinstonTransport(logquill)],
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
winstonLogger.info("still works exactly as before", { userId: 42 });
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
`LogQuillPinoDestination` is a pino `destination` — pass it straight into
|
|
861
|
+
`pino()` and pino's own NDJSON output is parsed back into LogQuill calls.
|
|
862
|
+
It needs no dependency on `pino` itself (a destination only has to be
|
|
863
|
+
duck-type compatible with a Node `Writable`), so it ships from the main
|
|
864
|
+
entry point:
|
|
865
|
+
|
|
866
|
+
```ts
|
|
867
|
+
import pino from "pino";
|
|
868
|
+
import { Logger, LogQuillPinoDestination } from "logquill";
|
|
869
|
+
|
|
870
|
+
const logquill = new Logger("app");
|
|
871
|
+
const log = pino(new LogQuillPinoDestination(logquill));
|
|
872
|
+
|
|
873
|
+
log.info({ userId: 42 }, "still works exactly as before");
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
Both re-filter by the LogQuill `Logger`'s own `level` after the
|
|
877
|
+
source library's own filtering runs — the stricter of the two wins — and
|
|
878
|
+
map that library's levels onto LogQuill's via a `levelMap` option,
|
|
879
|
+
overridable for a non-default level configuration.
|
|
880
|
+
|
|
881
|
+
## Browser build
|
|
882
|
+
|
|
883
|
+
`import ... from "logquill/browser"` is a separate entry point shipping
|
|
884
|
+
the same `Logger`, levels, `JSONFormatter`, and plugin pipeline
|
|
885
|
+
(`ContextPlugin`/`RedactPlugin`/`PIIRedactPlugin`/`SamplingPlugin`), plus
|
|
886
|
+
`ConsoleTransport` and `BeaconTransport`. `FileTransport`, `HTTPTransport`,
|
|
887
|
+
every SQL/NoSQL/queue/cloud-native transport, and the LangChain adapters
|
|
888
|
+
are absent from this entry's module graph entirely — not tree-shaken,
|
|
889
|
+
simply never imported — so this bundle never pulls in a Node built-in.
|
|
890
|
+
|
|
891
|
+
```ts
|
|
892
|
+
import { BeaconTransport, ConsoleTransport, Logger } from "logquill/browser";
|
|
893
|
+
|
|
894
|
+
const logger = new Logger("app", {
|
|
895
|
+
transports: [
|
|
896
|
+
new ConsoleTransport(),
|
|
897
|
+
new BeaconTransport("https://logs.example.com/ingest", { batchSize: 20 }),
|
|
898
|
+
],
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
logger.info("page loaded", { path: location.pathname });
|
|
902
|
+
window.addEventListener("pagehide", () => logger.close()); // flushes the pending beacon batch
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
`BeaconTransport` batches formatted records and sends them via
|
|
906
|
+
`navigator.sendBeacon`, which — unlike `fetch` — can complete even after
|
|
907
|
+
the page that queued it starts unloading; it falls back to a `keepalive`
|
|
908
|
+
`fetch` where `sendBeacon` isn't available (a worker, an older browser).
|
|
909
|
+
Keep `batchSize` small — `sendBeacon` payloads are capped (64KB in most
|
|
910
|
+
browsers).
|
|
911
|
+
|
|
912
|
+
One behavioral difference from the Node build: `Logger.span()`'s
|
|
913
|
+
`parentSpanId` nesting is backed by a plain stack here instead of
|
|
914
|
+
`AsyncLocalStorage` (which browsers don't have), so two spans on the same
|
|
915
|
+
`Logger` running concurrently across an `await` can interleave and stamp
|
|
916
|
+
the wrong `parentSpanId` — fine for the common case of one span in flight
|
|
917
|
+
at a time.
|
|
918
|
+
|
|
919
|
+
## API reference
|
|
920
|
+
|
|
921
|
+
Every exported class and function carries a TSDoc comment; the full
|
|
922
|
+
reference, generated from those comments with
|
|
923
|
+
[TypeDoc](https://typedoc.org), is published at
|
|
924
|
+
[nikhilvdev.github.io/logquill-js](https://nikhilvdev.github.io/logquill-js/)
|
|
925
|
+
and rebuilt on every push to `main`. To build it locally:
|
|
926
|
+
|
|
927
|
+
```sh
|
|
928
|
+
npm run docs # writes static HTML to site/
|
|
929
|
+
```
|
|
930
|
+
|
|
624
931
|
## Development
|
|
625
932
|
|
|
626
933
|
```sh
|
|
@@ -629,6 +936,7 @@ npm run build # dist/index.{mjs,cjs,d.ts} via tsup
|
|
|
629
936
|
npm run lint # eslint
|
|
630
937
|
npm run typecheck # tsc --noEmit
|
|
631
938
|
npm run coverage # vitest run --coverage
|
|
939
|
+
npm run docs # typedoc -> site/
|
|
632
940
|
```
|
|
633
941
|
|
|
634
942
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the PR workflow, the
|