Skip to content

Commit

Permalink
fix(api-log): handle error on log insert (#4488)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunozoric authored Jan 14, 2025
1 parent 70d6ade commit 6759734
Show file tree
Hide file tree
Showing 32 changed files with 499 additions and 39 deletions.
9 changes: 9 additions & 0 deletions packages/api-log/__tests__/mocks/getIdentity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SecurityIdentity } from "@webiny/api-security/types";

export const getIdentity = (): Pick<SecurityIdentity, "id" | "displayName" | "type"> => {
return {
id: "mocked-identity-id",
displayName: "mocked-identity-display-name",
type: "mocked-identity-type"
};
};
27 changes: 27 additions & 0 deletions packages/api-log/__tests__/tasks/pruneLogs/PruneLogs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
import { Entity } from "@webiny/db-dynamodb/toolbox";
import { create } from "~/db";
import { createMockLogger } from "~tests/mocks/logger";
import { GetValueResult, IStore, RemoveValueResult, StorageKey } from "@webiny/db";
import { getIdentity } from "~tests/mocks/getIdentity";

describe("PruneLogs", () => {
let prune: PruneLogs;
Expand All @@ -23,6 +25,27 @@ describe("PruneLogs", () => {

let logger: ILogger;

const store: Pick<IStore, "getValue" | "removeValue"> = {
async getValue(key: StorageKey): Promise<GetValueResult<any>> {
return {
key,
data: {
identity: getIdentity(),
taskId: "1234"
}
};
},
async removeValue(key: StorageKey): Promise<RemoveValueResult<any>> {
return {
key,
data: {
identity: getIdentity(),
taskId: "1234"
}
};
}
};

beforeEach(async () => {
prune = new PruneLogs({
documentClient,
Expand Down Expand Up @@ -64,6 +87,7 @@ describe("PruneLogs", () => {
};
};
const result = await prune.execute({
store,
list,
response,
input: {},
Expand Down Expand Up @@ -186,6 +210,7 @@ describe("PruneLogs", () => {
* Should not prune anything because the default date is too far into the past.
*/
const pruneNothingResult = await prune.execute({
store,
list,
response,
input: {},
Expand All @@ -209,6 +234,7 @@ describe("PruneLogs", () => {
* Only prune from anotherTenant.
*/
const pruneResult = await prune.execute({
store,
list,
response,
input: {
Expand Down Expand Up @@ -256,6 +282,7 @@ describe("PruneLogs", () => {
* And then prune everything.
*/
const pruneAllResult = await prune.execute({
store,
list,
response,
input: {
Expand Down
2 changes: 2 additions & 0 deletions packages/api-log/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
"@webiny/api-security": "0.0.0",
"@webiny/api-tenancy": "0.0.0",
"@webiny/aws-sdk": "0.0.0",
"@webiny/db": "0.0.0",
"@webiny/db-dynamodb": "0.0.0",
"@webiny/error": "0.0.0",
"@webiny/handler": "0.0.0",
"@webiny/handler-graphql": "0.0.0",
"@webiny/plugins": "0.0.0",
Expand Down
4 changes: 3 additions & 1 deletion packages/api-log/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface ICreateLoggerContextParams {
documentClient?: DynamoDBDocument;
getTenant?: () => string;
getLocale?: () => string;
createGraphQL?: boolean;
}

const getDocumentClient = (context: Context) => {
Expand Down Expand Up @@ -55,11 +56,12 @@ export const createContextPlugin = (params?: ICreateLoggerContextParams) => {
context.logger = {
log: logger,
...createCrud({
getContext,
storageOperations,
checkPermission: checkPermissionFactory({ getContext })
})
};
context.plugins.register(createGraphQl());
context.plugins.register(createGraphQl(params));
});

plugin.name = "logger.createContext";
Expand Down
50 changes: 48 additions & 2 deletions packages/api-log/src/crud/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,24 @@ import {
ILoggerCrudListLogsParams,
ILoggerCrudListLogsResponse,
ILoggerLog,
ILoggerPruneLogsResponse,
ILoggerStorageOperations,
ILoggerWithSource
ILoggerWithSource,
IPruneLogsStoredValue
} from "~/types";
import { NotFoundError } from "@webiny/handler-graphql";
import { WebinyError } from "@webiny/error";
import { PRUNE_LOGS_TASK } from "~/tasks/constants";
import { createStoreKey } from "~/utils/storeKey";

export interface ICreateCrudParams {
getContext: () => Pick<Context, "tasks" | "db" | "security">;
storageOperations: ILoggerStorageOperations;
checkPermission(): Promise<void>;
}

export const createCrud = (params: ICreateCrudParams): ILoggerCrud => {
const { storageOperations, checkPermission } = params;
const { storageOperations, checkPermission, getContext } = params;

return {
async getLog(params: ILoggerCrudGetLogsParams): Promise<ILoggerCrudGetLogResponse> {
Expand Down Expand Up @@ -78,6 +84,46 @@ export const createCrud = (params: ICreateCrudParams): ILoggerCrud => {
return this.log.flush();
}
};
},
async pruneLogs(): Promise<ILoggerPruneLogsResponse> {
await checkPermission();

const context = getContext();

const key = createStoreKey();

const alreadyPruning = await context.db.store.getValue<IPruneLogsStoredValue>(key);

if (alreadyPruning?.data?.taskId) {
throw new WebinyError({
message: "Already pruning logs.",
code: "ALREADY_PRUNING_LOGS",
data: {
identity: alreadyPruning.data.identity,
taskId: alreadyPruning.data.taskId
}
});
}

const task = await context.tasks.trigger({
definition: PRUNE_LOGS_TASK,
name: "Prune all Webiny logs"
});

const identity = context.security.getIdentity();

await context.db.store.storeValue<IPruneLogsStoredValue>(key, {
identity: {
id: identity.id,
displayName: identity.displayName,
type: identity.type
},
taskId: task.id
});

return {
task
};
}
};
};
12 changes: 10 additions & 2 deletions packages/api-log/src/graphql/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { Plugin } from "@webiny/plugins/types";
import { createGraphQlPlugin } from "~/graphql/plugin";

export const createGraphQl = (): Plugin[] => {
if (process.env.DEBUG !== "true") {
export interface ICreateGraphQlParams {
createGraphQL?: boolean;
}

export const createGraphQl = (params?: ICreateGraphQlParams): Plugin[] => {
/**
* If the `createGraphQl` flag is set to `true` or debug mode is enabled, we'll create the GraphQL plugin.
*/
const debug = [params?.createGraphQL === true, process.env.DEBUG === "true"].some(Boolean);
if (!debug) {
return [];
}

Expand Down
71 changes: 56 additions & 15 deletions packages/api-log/src/graphql/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ import { Context, LogType } from "~/types";
import zod from "zod";
import { createZodError } from "@webiny/utils";

export const emptyResolver = () => ({});

const getLogArgsSchema = zod.object({
where: zod.object({
id: zod.string()
})
});

const listLogsArgsSchema = zod.object({
where: zod.object({
tenant: zod.string().optional(),
source: zod.string().optional(),
type: zod
.enum([LogType.DEBUG, LogType.NOTICE, LogType.INFO, LogType.WARN, LogType.ERROR])
.optional()
}),
where: zod
.object({
tenant: zod.string().optional(),
source: zod.string().optional(),
type: zod
.enum([LogType.DEBUG, LogType.NOTICE, LogType.INFO, LogType.WARN, LogType.ERROR])
.optional()
})
.optional(),
sort: zod.array(zod.enum(["ASC", "DESC"])).optional(),
limit: zod.number().optional(),
after: zod.string().optional()
Expand All @@ -39,14 +43,22 @@ const deleteLogsArgsSchema = zod.object({
export const createGraphQlPlugin = () => {
return new GraphQLSchemaPlugin<Context>({
typeDefs: /* GraphQL */ `
type LogsQuery {
_empty: String
}
type LogsMutation {
_empty: String
}
extend type Query {
log: LogQuery
logs: LogsQuery
}
extend type Mutation {
log: LogMutation
logs: LogsMutation
}
enum LogType {
${LogType.DEBUG}
${LogType.NOTICE}
Expand Down Expand Up @@ -92,14 +104,18 @@ export const createGraphQlPlugin = () => {
source: String
type: LogType
}
input GetLogWhereInput {
id: ID!
}
enum ListLogsSortEnum {
ASC
DESC
}
type LogQuery {
getLog(id: ID!): LogQueryGetResponse!
extend type LogsQuery {
getLog(where: GetLogWhereInput!): LogQueryGetResponse!
listLogs(
where: ListLogsWhereInput
sort: ListLogsSortEnum
Expand All @@ -118,6 +134,19 @@ export const createGraphQlPlugin = () => {
error: LogQueryResponseError
}
type LogMutationPruneLogsResponseDataTask {
id: ID!
}
type LogMutationPruneLogsResponseData {
task: LogMutationPruneLogsResponseDataTask!
}
type LogMutationPruneLogsResponse {
data: LogMutationPruneLogsResponseData
error: LogQueryResponseError
}
input DeleteLogWhereInput {
id: ID!
}
Expand All @@ -126,13 +155,20 @@ export const createGraphQlPlugin = () => {
items: [ID!]!
}
type LogMutation {
extend type LogsMutation {
pruneLogs: LogMutationPruneLogsResponse!
deleteLog(where: DeleteLogWhereInput!): LogMutationDeleteLogResponse!
deleteLogs(where: DeleteLogsWhereInput!): LogMutationDeleteLogsResponse!
}
`,
resolvers: {
LogQuery: {
Query: {
logs: emptyResolver
},
Mutation: {
logs: emptyResolver
},
LogsQuery: {
getLog: async (_: unknown, args: unknown, context) => {
return resolve(async () => {
const result = getLogArgsSchema.safeParse(args);
Expand All @@ -152,7 +188,7 @@ export const createGraphQlPlugin = () => {
});
}
},
LogMutation: {
LogsMutation: {
deleteLog: async (_, args, context) => {
return resolve(async () => {
const result = deleteLogArgsSchema.safeParse(args);
Expand All @@ -170,6 +206,11 @@ export const createGraphQlPlugin = () => {
}
return await context.logger.deleteLogs(result.data);
});
},
pruneLogs: async (_: unknown, __: unknown, context) => {
return resolve(async () => {
return await context.logger.pruneLogs();
});
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions packages/api-log/src/logger/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ export const loggerFactory = ({ getTenant, getLocale, documentClient }: ILoggerF
return {
logger: createDynamoDbLogger({
onFlush: async items => {
return await storageOperations.insert({
items
});
try {
return await storageOperations.insert({
items
});
} catch (ex) {
console.error("Error flushing logs.");
console.log(ex);
}
return [];
},
getLocale,
getTenant
Expand Down
1 change: 1 addition & 0 deletions packages/api-log/src/tasks/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const PRUNE_LOGS_TASK = "pruneLogs";
6 changes: 3 additions & 3 deletions packages/api-log/src/tasks/createPruneLogsTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { createTaskDefinition } from "@webiny/tasks";
import { Context, IPruneLogsInput, IPruneLogsOutput } from "~/tasks/pruneLogs/types";
import { LogType } from "~/types";
import { NonEmptyArray } from "@webiny/api/types";

export const PRUNE_LOGS_TASK = "pruneLogs";
import { PRUNE_LOGS_TASK } from "./constants";

export const createPruneLogsTask = () => {
return createTaskDefinition<Context, IPruneLogsInput, IPruneLogsOutput>({
Expand All @@ -29,6 +28,7 @@ export const createPruneLogsTask = () => {
keys: new DynamoDbLoggerKeys()
});
return await prune.execute({
store: params.context.db.store,
input: params.input,
list: params.context.logger.listLogs,
response: params.response,
Expand All @@ -48,7 +48,7 @@ export const createPruneLogsTask = () => {
return {
tenant: validator.string().optional(),
source: validator.string().optional(),
keys: validator.object({}).passthrough(),
keys: validator.object({}).passthrough().optional(),
type: validator.enum(Object.keys(LogType) as NonEmptyArray<LogType>).optional(),
createdAfter: validator
.string()
Expand Down
Loading

0 comments on commit 6759734

Please sign in to comment.