mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(core): log error body (#1065)
This commit is contained in:
parent
b2b71898d3
commit
2ba11215ed
3 changed files with 77 additions and 30 deletions
|
@ -1,5 +1,7 @@
|
||||||
import { LogPayload, LogResult } from '@logto/schemas';
|
import { LogPayload, LogResult } from '@logto/schemas';
|
||||||
|
import i18next from 'i18next';
|
||||||
|
|
||||||
|
import RequestError from '@/errors/RequestError';
|
||||||
import { insertLog } from '@/queries/log';
|
import { insertLog } from '@/queries/log';
|
||||||
import { createContextWithRouteParameters } from '@/utils/test-utils';
|
import { createContextWithRouteParameters } from '@/utils/test-utils';
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ jest.mock('nanoid', () => ({
|
||||||
describe('koaLog middleware', () => {
|
describe('koaLog middleware', () => {
|
||||||
const insertLogMock = insertLog as jest.Mock;
|
const insertLogMock = insertLog as jest.Mock;
|
||||||
const type = 'SignInUsernamePassword';
|
const type = 'SignInUsernamePassword';
|
||||||
const payload: LogPayload = {
|
const mockPayload: LogPayload = {
|
||||||
userId: 'foo',
|
userId: 'foo',
|
||||||
username: 'Bar',
|
username: 'Bar',
|
||||||
};
|
};
|
||||||
|
@ -34,7 +36,7 @@ describe('koaLog middleware', () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('insert log with success response', async () => {
|
it('should insert a success log when next() does not throw an error', async () => {
|
||||||
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
|
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
|
||||||
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
|
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
|
||||||
// Bypass middleware context type assert
|
// Bypass middleware context type assert
|
||||||
|
@ -44,7 +46,7 @@ describe('koaLog middleware', () => {
|
||||||
ctx.request.ip = ip;
|
ctx.request.ip = ip;
|
||||||
|
|
||||||
const next = async () => {
|
const next = async () => {
|
||||||
ctx.log(type, payload);
|
ctx.log(type, mockPayload);
|
||||||
};
|
};
|
||||||
await koaLog()(ctx, next);
|
await koaLog()(ctx, next);
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ describe('koaLog middleware', () => {
|
||||||
id: nanoIdMock,
|
id: nanoIdMock,
|
||||||
type,
|
type,
|
||||||
payload: {
|
payload: {
|
||||||
...payload,
|
...mockPayload,
|
||||||
result: LogResult.Success,
|
result: LogResult.Success,
|
||||||
ip,
|
ip,
|
||||||
userAgent,
|
userAgent,
|
||||||
|
@ -60,7 +62,8 @@ describe('koaLog middleware', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should insert log with failed result if next throws error', async () => {
|
describe('should insert an error log with the error message when next() throws an error', () => {
|
||||||
|
it('should log with error message when next throws a normal Error', async () => {
|
||||||
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
|
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
|
||||||
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
|
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
|
||||||
// Bypass middleware context type assert
|
// Bypass middleware context type assert
|
||||||
|
@ -69,10 +72,11 @@ describe('koaLog middleware', () => {
|
||||||
};
|
};
|
||||||
ctx.request.ip = ip;
|
ctx.request.ip = ip;
|
||||||
|
|
||||||
const error = new Error('next error');
|
const message = 'Normal error';
|
||||||
|
const error = new Error(message);
|
||||||
|
|
||||||
const next = async () => {
|
const next = async () => {
|
||||||
ctx.log(type, payload);
|
ctx.log(type, mockPayload);
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
|
await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
|
||||||
|
@ -81,12 +85,47 @@ describe('koaLog middleware', () => {
|
||||||
id: nanoIdMock,
|
id: nanoIdMock,
|
||||||
type,
|
type,
|
||||||
payload: {
|
payload: {
|
||||||
...payload,
|
...mockPayload,
|
||||||
result: LogResult.Error,
|
result: LogResult.Error,
|
||||||
error: String(error),
|
error: { message: `Error: ${message}` },
|
||||||
ip,
|
ip,
|
||||||
userAgent,
|
userAgent,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should insert an error log with the error body when next() throws a RequestError', async () => {
|
||||||
|
const ctx: WithLogContext<ReturnType<typeof createContextWithRouteParameters>> = {
|
||||||
|
...createContextWithRouteParameters({ headers: { 'user-agent': userAgent } }),
|
||||||
|
// Bypass middleware context type assert
|
||||||
|
addLogContext,
|
||||||
|
log,
|
||||||
|
};
|
||||||
|
ctx.request.ip = ip;
|
||||||
|
|
||||||
|
const message = 'Error message';
|
||||||
|
jest.spyOn(i18next, 't').mockReturnValueOnce(message); // Used in
|
||||||
|
const code = 'connector.general';
|
||||||
|
const data = { foo: 'bar', num: 123 };
|
||||||
|
const error = new RequestError(code, data);
|
||||||
|
|
||||||
|
const next = async () => {
|
||||||
|
ctx.log(type, mockPayload);
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
|
||||||
|
|
||||||
|
expect(insertLogMock).toBeCalledWith({
|
||||||
|
id: nanoIdMock,
|
||||||
|
type,
|
||||||
|
payload: {
|
||||||
|
...mockPayload,
|
||||||
|
result: LogResult.Error,
|
||||||
|
error: { message, code, data },
|
||||||
|
ip,
|
||||||
|
userAgent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,10 @@ import { BaseLogPayload, LogPayload, LogPayloads, LogResult, LogType } from '@lo
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
import { MiddlewareType } from 'koa';
|
import { MiddlewareType } from 'koa';
|
||||||
import { IRouterParamContext } from 'koa-router';
|
import { IRouterParamContext } from 'koa-router';
|
||||||
|
import pick from 'lodash.pick';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
import RequestError from '@/errors/RequestError';
|
||||||
import { insertLog } from '@/queries/log';
|
import { insertLog } from '@/queries/log';
|
||||||
|
|
||||||
type MergeLog = <T extends LogType>(type: T, payload: LogPayloads[T]) => void;
|
type MergeLog = <T extends LogType>(type: T, payload: LogPayloads[T]) => void;
|
||||||
|
@ -90,7 +92,13 @@ export default function koaLog<
|
||||||
try {
|
try {
|
||||||
await next();
|
await next();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.set({ result: LogResult.Error, error: String(error) });
|
logger.set({
|
||||||
|
result: LogResult.Error,
|
||||||
|
error:
|
||||||
|
error instanceof RequestError
|
||||||
|
? pick(error, 'message', 'code', 'data')
|
||||||
|
: { message: String(error) },
|
||||||
|
});
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await logger.save();
|
await logger.save();
|
||||||
|
|
|
@ -7,7 +7,7 @@ export enum LogResult {
|
||||||
|
|
||||||
export interface BaseLogPayload {
|
export interface BaseLogPayload {
|
||||||
result?: LogResult;
|
result?: LogResult;
|
||||||
error?: string;
|
error?: Record<string, unknown>;
|
||||||
ip?: string;
|
ip?: string;
|
||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
applicationId?: string;
|
applicationId?: string;
|
||||||
|
|
Loading…
Reference in a new issue