Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api-log): handle error on log insert #4488

Merged
merged 6 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading