fix(docs):Docs and Exports
- Add documentation for recently released changes - Update some exports to include enums
This commit is contained in:
+15
-15
@@ -1,14 +1,14 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createConsoleSink } from "../src/index";
|
||||
import type { LogEvent } from "../src/index";
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createConsoleSink } from '../src/index';
|
||||
import type { LogEvent } from '../src/index';
|
||||
|
||||
function event(overrides: Partial<LogEvent> = {}): LogEvent {
|
||||
return {
|
||||
level: "debug",
|
||||
namespace: "API",
|
||||
arguments: ["hello"],
|
||||
level: 'debug',
|
||||
namespace: 'API',
|
||||
arguments: ['hello'],
|
||||
timestamp: new Date(),
|
||||
environment: "development",
|
||||
environment: 'development',
|
||||
sendToSentryLogs: false,
|
||||
sendToSentryIssue: false,
|
||||
sendToConsole: true,
|
||||
@@ -16,25 +16,25 @@ function event(overrides: Partial<LogEvent> = {}): LogEvent {
|
||||
};
|
||||
}
|
||||
|
||||
describe("createConsoleSink", () => {
|
||||
describe('createConsoleSink', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("appends [async] to the namespace label when event.async is set", () => {
|
||||
const debug = vi.spyOn(console, "debug").mockImplementation(() => undefined);
|
||||
it('appends [async] to the namespace label when event.async is set', () => {
|
||||
const debug = vi.spyOn(console, 'debug').mockImplementation(() => undefined);
|
||||
createConsoleSink().emit(event({ async: true }));
|
||||
expect(debug).toHaveBeenCalled();
|
||||
const first = debug.mock.calls[0]?.[0];
|
||||
expect(String(first)).toContain("[API][async]");
|
||||
expect(String(first)).toContain('[API][async]');
|
||||
});
|
||||
|
||||
it("omits [async] for synchronous events", () => {
|
||||
const debug = vi.spyOn(console, "debug").mockImplementation(() => undefined);
|
||||
it('omits [async] for synchronous events', () => {
|
||||
const debug = vi.spyOn(console, 'debug').mockImplementation(() => undefined);
|
||||
createConsoleSink().emit(event());
|
||||
expect(debug).toHaveBeenCalled();
|
||||
const first = debug.mock.calls[0]?.[0];
|
||||
expect(String(first)).toContain("[API]");
|
||||
expect(String(first)).not.toContain("[async]");
|
||||
expect(String(first)).toContain('[API]');
|
||||
expect(String(first)).not.toContain('[async]');
|
||||
});
|
||||
});
|
||||
|
||||
+96
-96
@@ -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,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Compile-time regression: official Sentry SDKs must assign to {@link SentryLike}
|
||||
* without casts or consumer adapters. Checked by `pnpm check` (`tsc --noEmit`).
|
||||
*/
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { createSentrySink, type SentryLike } from "../src/sentry";
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { createSentrySink, type SentryLike } from '../src/sentry';
|
||||
|
||||
const _sdk: SentryLike = Sentry;
|
||||
createSentrySink(Sentry);
|
||||
|
||||
+69
-69
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createLogger, type LogEvent } from "../src/index";
|
||||
import { createSentrySink, toSentryLogPayload, toSentrySeverity } from "../src/sentry";
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createLogger, type LogEvent } from '../src/index';
|
||||
import { createSentrySink, toSentryLogPayload, toSentrySeverity } from '../src/sentry';
|
||||
|
||||
function createFakeSentry() {
|
||||
const scope = { setLevel: vi.fn(), setTag: vi.fn(), setExtras: vi.fn() };
|
||||
@@ -23,10 +23,10 @@ function createFakeSentry() {
|
||||
|
||||
function issueEvent(overrides: Partial<LogEvent> = {}): LogEvent {
|
||||
return {
|
||||
level: "error",
|
||||
arguments: ["message"],
|
||||
level: 'error',
|
||||
arguments: ['message'],
|
||||
timestamp: new Date(),
|
||||
environment: "production",
|
||||
environment: 'production',
|
||||
sendToSentryLogs: false,
|
||||
sendToSentryIssue: true,
|
||||
sendToConsole: false,
|
||||
@@ -34,46 +34,46 @@ function issueEvent(overrides: Partial<LogEvent> = {}): LogEvent {
|
||||
};
|
||||
}
|
||||
|
||||
describe("toSentrySeverity", () => {
|
||||
it("maps logger levels to Sentry Issue severities", () => {
|
||||
expect(toSentrySeverity("warn")).toBe("warning");
|
||||
expect(toSentrySeverity("trace")).toBe("debug");
|
||||
expect(toSentrySeverity("error")).toBe("error");
|
||||
expect(toSentrySeverity("info")).toBe("info");
|
||||
expect(toSentrySeverity("debug")).toBe("debug");
|
||||
describe('toSentrySeverity', () => {
|
||||
it('maps logger levels to Sentry Issue severities', () => {
|
||||
expect(toSentrySeverity('warn')).toBe('warning');
|
||||
expect(toSentrySeverity('trace')).toBe('debug');
|
||||
expect(toSentrySeverity('error')).toBe('error');
|
||||
expect(toSentrySeverity('info')).toBe('info');
|
||||
expect(toSentrySeverity('debug')).toBe('debug');
|
||||
});
|
||||
});
|
||||
|
||||
describe("toSentryLogPayload", () => {
|
||||
it("joins string messages and flattens primitive object attributes", () => {
|
||||
describe('toSentryLogPayload', () => {
|
||||
it('joins string messages and flattens primitive object attributes', () => {
|
||||
expect(
|
||||
toSentryLogPayload({
|
||||
level: "info",
|
||||
namespace: "API",
|
||||
arguments: ["Cache warmed", { entries: 42, ok: true, nested: { a: 1 } }],
|
||||
level: 'info',
|
||||
namespace: 'API',
|
||||
arguments: ['Cache warmed', { entries: 42, ok: true, nested: { a: 1 } }],
|
||||
timestamp: new Date(),
|
||||
environment: "production",
|
||||
environment: 'production',
|
||||
sendToSentryLogs: true,
|
||||
sendToSentryIssue: false,
|
||||
sendToConsole: false,
|
||||
}),
|
||||
).toEqual({
|
||||
message: "Cache warmed",
|
||||
message: 'Cache warmed',
|
||||
attributes: {
|
||||
"logger.namespace": "API",
|
||||
'logger.namespace': 'API',
|
||||
entries: 42,
|
||||
ok: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("includes async when the event was deferred for thenables", () => {
|
||||
it('includes async when the event was deferred for thenables', () => {
|
||||
expect(
|
||||
toSentryLogPayload({
|
||||
level: "debug",
|
||||
arguments: ["response"],
|
||||
level: 'debug',
|
||||
arguments: ['response'],
|
||||
timestamp: new Date(),
|
||||
environment: "development",
|
||||
environment: 'development',
|
||||
sendToSentryLogs: true,
|
||||
sendToSentryIssue: false,
|
||||
sendToConsole: true,
|
||||
@@ -83,112 +83,112 @@ describe("toSentryLogPayload", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("createSentrySink", () => {
|
||||
it("captures errors as Issues with namespace and structured attributes", () => {
|
||||
describe('createSentrySink', () => {
|
||||
it('captures errors as Issues with namespace and structured attributes', () => {
|
||||
const { scope, sentry } = createFakeSentry();
|
||||
const logger = createLogger({
|
||||
environment: "production",
|
||||
namespace: "API",
|
||||
environment: 'production',
|
||||
namespace: 'API',
|
||||
sinks: [createSentrySink(sentry)],
|
||||
});
|
||||
const error = new Error("offline");
|
||||
logger.warn("ignored without logs");
|
||||
logger.error("Request failed", error, { requestId: "req_1" });
|
||||
const error = new Error('offline');
|
||||
logger.warn('ignored without logs');
|
||||
logger.error('Request failed', error, { requestId: 'req_1' });
|
||||
expect(sentry.captureException).toHaveBeenCalledWith(error);
|
||||
expect(sentry.captureMessage).not.toHaveBeenCalled();
|
||||
expect(sentry.logger.info).not.toHaveBeenCalled();
|
||||
expect(scope.setLevel).toHaveBeenCalledWith("error");
|
||||
expect(scope.setTag).toHaveBeenCalledWith("logger.namespace", "API");
|
||||
expect(scope.setLevel).toHaveBeenCalledWith('error');
|
||||
expect(scope.setTag).toHaveBeenCalledWith('logger.namespace', 'API');
|
||||
expect(scope.setExtras).toHaveBeenCalledWith({
|
||||
"logger.namespace": "API",
|
||||
requestId: "req_1",
|
||||
'logger.namespace': 'API',
|
||||
requestId: 'req_1',
|
||||
});
|
||||
});
|
||||
|
||||
it("writes non-error events to Sentry Logs when enabled, not as Issues", () => {
|
||||
it('writes non-error events to Sentry Logs when enabled, not as Issues', () => {
|
||||
const { sentry } = createFakeSentry();
|
||||
const logger = createLogger({
|
||||
environment: "production",
|
||||
namespace: "API",
|
||||
environment: 'production',
|
||||
namespace: 'API',
|
||||
sinks: [createSentrySink(sentry, { logs: true })],
|
||||
});
|
||||
logger.warn("slow", { ms: 1200 });
|
||||
logger.info("forced", { id: 1 }, { sentry: true });
|
||||
logger.warn('slow', { ms: 1200 });
|
||||
logger.info('forced', { id: 1 }, { sentry: true });
|
||||
expect(sentry.captureMessage).not.toHaveBeenCalled();
|
||||
expect(sentry.captureException).not.toHaveBeenCalled();
|
||||
expect(sentry.logger.warn).toHaveBeenCalledWith("slow", {
|
||||
"logger.namespace": "API",
|
||||
expect(sentry.logger.warn).toHaveBeenCalledWith('slow', {
|
||||
'logger.namespace': 'API',
|
||||
ms: 1200,
|
||||
});
|
||||
expect(sentry.logger.info).toHaveBeenCalledWith("forced", {
|
||||
"logger.namespace": "API",
|
||||
expect(sentry.logger.info).toHaveBeenCalledWith('forced', {
|
||||
'logger.namespace': 'API',
|
||||
id: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not create an Issue when suppressSentry is set", () => {
|
||||
it('does not create an Issue when suppressSentry is set', () => {
|
||||
const { sentry } = createFakeSentry();
|
||||
const logger = createLogger({
|
||||
environment: "production",
|
||||
runtime: "node",
|
||||
environment: 'production',
|
||||
runtime: 'node',
|
||||
sinks: [createSentrySink(sentry, { logs: true })],
|
||||
});
|
||||
logger.error("expected", new Error("nope"), { suppressSentry: true });
|
||||
logger.error('expected', new Error('nope'), { suppressSentry: true });
|
||||
expect(sentry.captureException).not.toHaveBeenCalled();
|
||||
expect(sentry.logger.error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("never sends in development", () => {
|
||||
it('never sends in development', () => {
|
||||
const { sentry } = createFakeSentry();
|
||||
const logger = createLogger({
|
||||
environment: "development",
|
||||
environment: 'development',
|
||||
sinks: [createSentrySink(sentry, { logs: true })],
|
||||
});
|
||||
logger.error("local", new Error("x"));
|
||||
logger.warn("local warn", { sentry: true });
|
||||
logger.error('local', new Error('x'));
|
||||
logger.warn('local warn', { sentry: true });
|
||||
expect(sentry.captureException).not.toHaveBeenCalled();
|
||||
expect(sentry.logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("maps warn → warning on the Issues path and keeps warn on the Logs path", () => {
|
||||
it('maps warn → warning on the Issues path and keeps warn on the Logs path', () => {
|
||||
const { scope, sentry } = createFakeSentry();
|
||||
const sink = createSentrySink(sentry, { logs: true });
|
||||
|
||||
sink.emit(
|
||||
issueEvent({
|
||||
level: "warn",
|
||||
arguments: ["degraded"],
|
||||
level: 'warn',
|
||||
arguments: ['degraded'],
|
||||
}),
|
||||
);
|
||||
expect(scope.setLevel).toHaveBeenCalledWith("warning");
|
||||
expect(sentry.captureMessage).toHaveBeenCalledWith("degraded", "warning");
|
||||
expect(scope.setLevel).toHaveBeenCalledWith('warning');
|
||||
expect(sentry.captureMessage).toHaveBeenCalledWith('degraded', 'warning');
|
||||
expect(sentry.logger.warn).not.toHaveBeenCalled();
|
||||
|
||||
sink.emit({
|
||||
level: "warn",
|
||||
namespace: "API",
|
||||
arguments: ["slow", { ms: 10 }],
|
||||
level: 'warn',
|
||||
namespace: 'API',
|
||||
arguments: ['slow', { ms: 10 }],
|
||||
timestamp: new Date(),
|
||||
environment: "production",
|
||||
environment: 'production',
|
||||
sendToSentryLogs: true,
|
||||
sendToSentryIssue: false,
|
||||
sendToConsole: false,
|
||||
});
|
||||
expect(sentry.logger.warn).toHaveBeenCalledWith("slow", {
|
||||
"logger.namespace": "API",
|
||||
expect(sentry.logger.warn).toHaveBeenCalledWith('slow', {
|
||||
'logger.namespace': 'API',
|
||||
ms: 10,
|
||||
});
|
||||
});
|
||||
|
||||
it("maps error Issue severity as error", () => {
|
||||
it('maps error Issue severity as error', () => {
|
||||
const { scope, sentry } = createFakeSentry();
|
||||
createSentrySink(sentry).emit(
|
||||
issueEvent({
|
||||
level: "error",
|
||||
arguments: ["failed"],
|
||||
level: 'error',
|
||||
arguments: ['failed'],
|
||||
}),
|
||||
);
|
||||
expect(scope.setLevel).toHaveBeenCalledWith("error");
|
||||
expect(sentry.captureMessage).toHaveBeenCalledWith("failed", "error");
|
||||
expect(scope.setLevel).toHaveBeenCalledWith('error');
|
||||
expect(sentry.captureMessage).toHaveBeenCalledWith('failed', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user