fix(docs):Docs and Exports
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish unknown status

- Add documentation for recently released changes
- Update some exports to include enums
This commit is contained in:
2026-08-21 11:45:56 -03:00
parent 2134ba58b8
commit f464fa5017
18 changed files with 1350 additions and 1307 deletions
+96 -96
View File
@@ -1,127 +1,127 @@
import { describe, expect, it, vi } from "vitest";
import { createLogger, isLogCallOptions, parseLoggingOverride } from "../src/index";
import type { LogEvent, LogSink } from "../src/index";
import { createSentrySink } from "../src/sentry";
import { describe, expect, it, vi } from 'vitest';
import { createLogger, isLogCallOptions, parseLoggingOverride } from '../src/index';
import type { LogEvent, LogSink } from '../src/index';
import { createSentrySink } from '../src/sentry';
function sink(): { sink: LogSink; events: LogEvent[] } {
const events: LogEvent[] = [];
return { events, sink: { emit: (event) => events.push(event) } };
}
describe("parseLoggingOverride", () => {
it("accepts a global level and a namespace-filtered level", () => {
expect(parseLoggingOverride("debug")).toEqual({ level: "debug", namespaces: [] });
expect(parseLoggingOverride("debug:WIDGET, API")).toEqual({
level: "debug",
namespaces: ["WIDGET", "API"],
describe('parseLoggingOverride', () => {
it('accepts a global level and a namespace-filtered level', () => {
expect(parseLoggingOverride('debug')).toEqual({ level: 'debug', namespaces: [] });
expect(parseLoggingOverride('debug:WIDGET, API')).toEqual({
level: 'debug',
namespaces: ['WIDGET', 'API'],
});
expect(parseLoggingOverride("verbose")).toBeUndefined();
expect(parseLoggingOverride('verbose')).toBeUndefined();
});
});
describe("isLogCallOptions", () => {
it("accepts only known option keys", () => {
describe('isLogCallOptions', () => {
it('accepts only known option keys', () => {
expect(isLogCallOptions({ sentry: true })).toBe(true);
expect(isLogCallOptions({ suppressSentry: true })).toBe(true);
expect(isLogCallOptions({ sentry: true, suppressSentry: false })).toBe(true);
expect(isLogCallOptions({ requestId: "req_1" })).toBe(false);
expect(isLogCallOptions({ sentry: true, requestId: "req_1" })).toBe(false);
expect(isLogCallOptions({ requestId: 'req_1' })).toBe(false);
expect(isLogCallOptions({ sentry: true, requestId: 'req_1' })).toBe(false);
expect(isLogCallOptions({})).toBe(false);
});
});
describe("createLogger", () => {
it("uses environment defaults and never evaluates suppressed lazy data", () => {
describe('createLogger', () => {
it('uses environment defaults and never evaluates suppressed lazy data', () => {
const destination = sink();
const expensive = vi.fn(() => ({ huge: "snapshot" }));
const logger = createLogger({ environment: "production", sinks: [destination.sink] });
logger.debug("ignored", expensive);
logger.error("kept", expensive);
const expensive = vi.fn(() => ({ huge: 'snapshot' }));
const logger = createLogger({ environment: 'production', sinks: [destination.sink] });
logger.debug('ignored', expensive);
logger.error('kept', expensive);
expect(expensive).toHaveBeenCalledTimes(1);
expect(destination.events).toHaveLength(1);
expect(destination.events[0]?.arguments).toEqual(["kept", { huge: "snapshot" }]);
expect(destination.events[0]?.arguments).toEqual(['kept', { huge: 'snapshot' }]);
expect(destination.events[0]?.async).toBeUndefined();
});
it("never starts suppressed async lazy factories", () => {
it('never starts suppressed async lazy factories', () => {
const destination = sink();
const expensive = vi.fn(async () => ({ huge: "snapshot" }));
const logger = createLogger({ environment: "production", sinks: [destination.sink] });
logger.debug("ignored", expensive);
const expensive = vi.fn(async () => ({ huge: 'snapshot' }));
const logger = createLogger({ environment: 'production', sinks: [destination.sink] });
logger.debug('ignored', expensive);
expect(expensive).not.toHaveBeenCalled();
expect(destination.events).toHaveLength(0);
});
it("settles async lazy factories before emitting and sets async", async () => {
it('settles async lazy factories before emitting and sets async', async () => {
const destination = sink();
const factory = vi.fn(async () => ({ data: 1 }));
const logger = createLogger({ environment: "development", sinks: [destination.sink] });
logger.debug("response", factory);
const logger = createLogger({ environment: 'development', sinks: [destination.sink] });
logger.debug('response', factory);
expect(factory).toHaveBeenCalledTimes(1);
expect(destination.events).toHaveLength(0);
await vi.waitFor(() => expect(destination.events).toHaveLength(1));
expect(destination.events[0]).toMatchObject({
arguments: ["response", { data: 1 }],
arguments: ['response', { data: 1 }],
async: true,
});
});
it("unwraps direct Promise arguments and keeps rejection reasons", async () => {
it('unwraps direct Promise arguments and keeps rejection reasons', async () => {
const destination = sink();
const reason = new Error("boom");
const logger = createLogger({ environment: "development", sinks: [destination.sink] });
logger.error("failed", { id: 1 }, Promise.reject(reason));
const reason = new Error('boom');
const logger = createLogger({ environment: 'development', sinks: [destination.sink] });
logger.error('failed', { id: 1 }, Promise.reject(reason));
expect(destination.events).toHaveLength(0);
await vi.waitFor(() => expect(destination.events).toHaveLength(1));
expect(destination.events[0]).toMatchObject({
arguments: ["failed", { id: 1 }, reason],
arguments: ['failed', { id: 1 }, reason],
async: true,
});
});
it("applies session namespace filtering to namespace descendants", () => {
it('applies session namespace filtering to namespace descendants', () => {
const destination = sink();
const storage = { getItem: () => "debug:WIDGET" };
const storage = { getItem: () => 'debug:WIDGET' };
createLogger({
environment: "production",
namespace: "WIDGET:Button",
environment: 'production',
namespace: 'WIDGET:Button',
sessionStorage: storage,
sinks: [destination.sink],
}).debug("shown");
}).debug('shown');
createLogger({
environment: "production",
namespace: "API",
environment: 'production',
namespace: 'API',
sessionStorage: storage,
sinks: [destination.sink],
}).debug("hidden");
expect(destination.events.map((event) => event.arguments[0])).toEqual(["shown"]);
}).debug('hidden');
expect(destination.events.map((event) => event.arguments[0])).toEqual(['shown']);
});
it("builds immutable child namespaces and reports failed assertions as errors", () => {
it('builds immutable child namespaces and reports failed assertions as errors', () => {
const destination = sink();
const root = createLogger({
environment: "development",
namespace: "WIDGET",
environment: 'development',
namespace: 'WIDGET',
sinks: [destination.sink],
});
root.child("Button").assert(false, "missing label");
expect(root.namespace).toBe("WIDGET");
root.child('Button').assert(false, 'missing label');
expect(root.namespace).toBe('WIDGET');
expect(destination.events[0]).toMatchObject({
level: "error",
namespace: "WIDGET:Button",
arguments: ["Assertion failed", "missing label"],
level: 'error',
namespace: 'WIDGET:Button',
arguments: ['Assertion failed', 'missing label'],
});
});
it("sends production browser errors to Sentry without writing to the console", () => {
it('sends production browser errors to Sentry without writing to the console', () => {
const destination = sink();
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const logger = createLogger({
environment: "production",
runtime: "browser",
environment: 'production',
runtime: 'browser',
sentry: destination.sink,
});
logger.error("captured");
logger.error('captured');
expect(destination.events).toHaveLength(1);
expect(destination.events[0]).toMatchObject({
sendToSentryIssue: true,
@@ -132,19 +132,19 @@ describe("createLogger", () => {
consoleError.mockRestore();
});
it("forces selected events to Sentry Logs without writing them to the console", () => {
it('forces selected events to Sentry Logs without writing them to the console', () => {
const destination = sink();
const consoleInfo = vi.spyOn(console, "info").mockImplementation(() => undefined);
const consoleInfo = vi.spyOn(console, 'info').mockImplementation(() => undefined);
const logger = createLogger({
environment: "production",
runtime: "browser",
environment: 'production',
runtime: 'browser',
sentry: destination.sink,
});
logger.info("Cache warmed", { sentry: true });
logger.info('Cache warmed', { sentry: true });
expect(destination.events).toHaveLength(1);
expect(destination.events[0]).toMatchObject({
level: "info",
arguments: ["Cache warmed"],
level: 'info',
arguments: ['Cache warmed'],
sendToSentryLogs: true,
sendToSentryIssue: false,
sendToConsole: false,
@@ -153,42 +153,42 @@ describe("createLogger", () => {
consoleInfo.mockRestore();
});
it("strips trailing call options from event arguments and supports data plus options", () => {
it('strips trailing call options from event arguments and supports data plus options', () => {
const destination = sink();
const logger = createLogger({
environment: "production",
runtime: "browser",
environment: 'production',
runtime: 'browser',
sentry: destination.sink,
});
logger.warn("slow", { ms: 1200 }, { sentry: true });
logger.warn('slow', { ms: 1200 }, { sentry: true });
expect(destination.events[0]).toMatchObject({
level: "warn",
arguments: ["slow", { ms: 1200 }],
level: 'warn',
arguments: ['slow', { ms: 1200 }],
sendToSentryLogs: true,
sendToConsole: false,
});
});
it("suppresses Sentry Issues when suppressSentry is set", () => {
it('suppresses Sentry Issues when suppressSentry is set', () => {
const destination = sink();
const logger = createLogger({
environment: "production",
runtime: "browser",
environment: 'production',
runtime: 'browser',
sentry: destination.sink,
});
logger.error("expected", new Error("nope"), { suppressSentry: true });
logger.error('expected', new Error('nope'), { suppressSentry: true });
expect(destination.events).toHaveLength(1);
expect(destination.events[0]).toMatchObject({
sendToSentryIssue: false,
sendToSentryLogs: false,
arguments: ["expected", expect.any(Error)],
arguments: ['expected', expect.any(Error)],
});
});
it("never marks events for Sentry in development", () => {
it('never marks events for Sentry in development', () => {
const destination = sink();
const logger = createLogger({
environment: "development",
environment: 'development',
sinks: [
destination.sink,
createSentrySink(
@@ -213,8 +213,8 @@ describe("createLogger", () => {
),
],
});
logger.error("local only");
logger.info("probe", { sentry: true });
logger.error('local only');
logger.info('probe', { sentry: true });
expect(destination.events).toHaveLength(2);
expect(
destination.events.every(
@@ -223,25 +223,25 @@ describe("createLogger", () => {
).toBe(true);
});
it("keeps production Node errors on stderr as well as sending them to Sentry", () => {
it('keeps production Node errors on stderr as well as sending them to Sentry', () => {
const destination = sink();
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const logger = createLogger({
environment: "production",
runtime: "node",
environment: 'production',
runtime: 'node',
sentry: destination.sink,
});
logger.error("captured");
logger.error('captured');
expect(destination.events).toHaveLength(1);
expect(consoleError).toHaveBeenCalledTimes(1);
consoleError.mockRestore();
});
it("mirrors warn to Sentry Logs in production when logs are enabled on the sink", () => {
it('mirrors warn to Sentry Logs in production when logs are enabled on the sink', () => {
const destination = sink();
const logger = createLogger({
environment: "production",
runtime: "browser",
environment: 'production',
runtime: 'browser',
sinks: [
destination.sink,
createSentrySink(
@@ -261,21 +261,21 @@ describe("createLogger", () => {
),
],
});
logger.warn("noisy");
logger.info("below default production logs threshold");
logger.warn('noisy');
logger.info('below default production logs threshold');
expect(destination.events).toHaveLength(1);
expect(destination.events[0]).toMatchObject({
level: "warn",
level: 'warn',
sendToSentryLogs: true,
sendToConsole: false,
});
});
it("mirrors info to Sentry Logs in staging when logs are enabled", () => {
it('mirrors info to Sentry Logs in staging when logs are enabled', () => {
const destination = sink();
const logger = createLogger({
environment: "staging",
runtime: "browser",
environment: 'staging',
runtime: 'browser',
sinks: [
destination.sink,
createSentrySink(
@@ -295,10 +295,10 @@ describe("createLogger", () => {
),
],
});
logger.info("visible in staging logs");
logger.debug("still below staging logs threshold");
logger.info('visible in staging logs');
logger.debug('still below staging logs threshold');
expect(destination.events.map((event) => event.arguments[0])).toEqual([
"visible in staging logs",
'visible in staging logs',
]);
expect(destination.events[0]).toMatchObject({
sendToSentryLogs: true,