feat: pino logger for formbricks (#2296)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Shubham Palriwala
2024-03-22 18:51:44 +05:30
committed by GitHub
parent b72cda05c1
commit dadc337955
59 changed files with 321 additions and 92 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
import Stripe from "stripe";
import { env } from "@formbricks/lib/env";
import { logger } from "@formbricks/lib/utils/logger";
import { handleCheckoutSessionCompleted } from "../handlers/checkoutSessionCompleted";
import { handleSubscriptionUpdatedOrCreated } from "../handlers/subscriptionCreatedOrUpdated";
@@ -19,7 +20,7 @@ const webhookHandler = async (requestBody: string, stripeSignature: string) => {
event = stripe.webhooks.constructEvent(requestBody, stripeSignature, webhookSecret);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Unknown error";
if (err! instanceof Error) console.log(err);
if (err! instanceof Error) logger.error(err);
return { status: 400, message: `Webhook Error: ${errorMessage}` };
}
@@ -7,6 +7,7 @@ import {
getTeam,
updateTeam,
} from "@formbricks/lib/team/service";
import { logger } from "@formbricks/lib/utils/logger";
import { ProductFeatureKeys, StripePriceLookupKeys, StripeProductNames } from "../lib/constants";
import { reportUsage } from "../lib/reportUsage";
@@ -45,7 +46,7 @@ export const handleSubscriptionUpdatedOrCreated = async (event: Stripe.Event) =>
}
if (!teamId) {
console.error("No teamId found in subscription");
logger.error("No teamId found in subscription");
return { status: 400, message: "skipping, no teamId found" };
}
@@ -2,6 +2,7 @@ import Stripe from "stripe";
import { env } from "@formbricks/lib/env";
import { getTeam, updateTeam } from "@formbricks/lib/team/service";
import { logger } from "@formbricks/lib/utils/logger";
import { ProductFeatureKeys, StripeProductNames } from "../lib/constants";
import { unsubscribeCoreAndAppSurveyFeatures, unsubscribeLinkSurveyProFeatures } from "../lib/downgradePlan";
@@ -15,7 +16,7 @@ export const handleSubscriptionDeleted = async (event: Stripe.Event) => {
const stripeSubscriptionObject = event.data.object as Stripe.Subscription;
const teamId = stripeSubscriptionObject.metadata.teamId;
if (!teamId) {
console.error("No teamId found in subscription");
logger.error("No teamId found in subscription");
return { status: 400, message: "skipping, no teamId found" };
}
@@ -3,6 +3,7 @@ import Stripe from "stripe";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { env } from "@formbricks/lib/env";
import { getTeam } from "@formbricks/lib/team/service";
import { logger } from "@formbricks/lib/utils/logger";
import { StripePriceLookupKeys } from "./constants";
@@ -177,7 +178,7 @@ export const createSubscription = async (
url: "",
};
} catch (err) {
console.error(err);
logger.error(err);
return {
status: 500,
data: "Something went wrong!",
@@ -3,6 +3,7 @@ import Stripe from "stripe";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { env } from "@formbricks/lib/env";
import { getTeam, updateTeam } from "@formbricks/lib/team/service";
import { logger } from "@formbricks/lib/utils/logger";
import { StripePriceLookupKeys } from "./constants";
import { getFirstOfNextMonthTimestamp } from "./createSubscription";
@@ -112,7 +113,7 @@ export const removeSubscription = async (
url: "",
};
} catch (err) {
console.log("Error in removing subscription:", err);
logger.error(`Error in removing subscription: ${err}`);
return {
status: 500,
+2 -1
View File
@@ -18,6 +18,7 @@ import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { actionClassCache } from "./cache";
@@ -176,7 +177,7 @@ export const createActionClass = async (
return actionClassPrisma;
} catch (error) {
console.error(error);
logger.error(error);
throw new DatabaseError(`Database error when creating an action for environment ${environmentId}`);
}
};
+3 -2
View File
@@ -17,6 +17,7 @@ import {
import { AIRTABLE_CLIENT_ID } from "../constants";
import { createOrUpdateIntegration, deleteIntegration, getIntegrationByType } from "../integration/service";
import { logger } from "../utils/logger";
interface ConnectAirtableOptions {
environmentId: string;
@@ -95,7 +96,7 @@ export const fetchAirtableAuthToken = async (formData: Record<string, any>) => {
const parsedToken = ZIntegrationAirtableTokenSchema.safeParse(tokenRes);
if (!parsedToken.success) {
console.error(parsedToken.error);
logger.error(parsedToken.error);
throw new Error(parsedToken.error.message);
}
const { access_token, refresh_token, expires_in } = parsedToken.data;
@@ -255,6 +256,6 @@ export const writeData = async (
}
await addRecords(key, configData.baseId, configData.tableId, data);
} catch (error: any) {
console.error(error?.message);
logger.error(error?.message);
}
};
+3 -2
View File
@@ -31,6 +31,7 @@ import { createMembership } from "./membership/service";
import { createProduct } from "./product/service";
import { createTeam, getTeam } from "./team/service";
import { createUser, getUserByEmail, updateUser } from "./user/service";
import { logger } from "./utils/logger";
export const authOptions: NextAuthOptions = {
providers: [
@@ -63,7 +64,7 @@ export const authOptions: NextAuthOptions = {
},
});
} catch (e) {
console.error(e);
logger.error(e);
throw Error("Internal server error. Please try again later");
}
@@ -115,7 +116,7 @@ export const authOptions: NextAuthOptions = {
},
});
} catch (e) {
console.error(e);
logger.error(e);
throw new Error("Either a user does not match the provided token or the token is invalid");
}
+3 -2
View File
@@ -25,6 +25,7 @@ import { TPerson } from "@formbricks/types/people";
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { createPerson, getPersonByUserId } from "../person/service";
import { formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { displayCache } from "./cache";
@@ -111,7 +112,7 @@ export const updateDisplay = async (
return display;
} catch (error) {
console.error(error);
logger.error(error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
@@ -152,7 +153,7 @@ export const updateDisplayLegacy = async (
return display;
} catch (error) {
console.error(error);
logger.error(error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
+2 -1
View File
@@ -16,6 +16,7 @@ import { getProductByEnvironmentId } from "../product/service";
import { getQuestionResponseMapping } from "../responses";
import { getOriginalFileNameFromUrl } from "../storage/utils";
import { getTeamByEnvironmentId } from "../team/service";
import { logger } from "../utils/logger";
import { withEmailTemplate } from "./email-template";
const nodemailer = require("nodemailer");
@@ -65,7 +66,7 @@ export const sendEmail = async (emailData: sendEmailData) => {
};
await transporter.sendMail({ ...emailDefaults, ...emailData });
} else {
console.error(`Could not Email :: SMTP not configured :: ${emailData.subject}`);
logger.error(`Could not Email :: SMTP not configured :: ${emailData.subject}`);
}
} catch (error) {
throw error;
+3 -2
View File
@@ -22,6 +22,7 @@ import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { getProducts } from "../product/service";
import { getTeamsByUserId } from "../team/service";
import { formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { environmentCache } from "./cache";
@@ -39,7 +40,7 @@ export const getEnvironment = async (environmentId: string): Promise<TEnvironmen
return environment;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
@@ -90,7 +91,7 @@ export const getEnvironments = async (productId: string): Promise<TEnvironment[]
return environments;
} catch (error) {
if (error instanceof z.ZodError) {
console.error(JSON.stringify(error.errors, null, 2));
logger.error(JSON.stringify(error.errors, null, 2));
}
throw new ValidationError("Data validation of environments array failed");
}
+2 -1
View File
@@ -16,6 +16,7 @@ import {
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { integrationCache } from "./cache";
@@ -50,7 +51,7 @@ export async function createOrUpdateIntegration(
return integration;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
+2 -1
View File
@@ -3,6 +3,7 @@ import jwt, { JwtPayload } from "jsonwebtoken";
import { prisma } from "@formbricks/database";
import { env } from "./env";
import { logger } from "./utils/logger";
export function createToken(userId: string, userEmail: string, options = {}): string {
return jwt.sign({ id: userId }, env.NEXTAUTH_SECRET + userEmail, options);
@@ -59,7 +60,7 @@ export const verifyInviteToken = (token: string): { inviteId: string; email: str
email,
};
} catch (error) {
console.error("Error verifying invite token:", error);
logger.error(`Error verifying invite token: ${error}`);
throw new Error("Invalid or expired invite token");
}
};
+5 -4
View File
@@ -13,6 +13,7 @@ import {
import { productCache } from "../product/cache";
import { surveyCache } from "../survey/cache";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
const languageSelect = {
@@ -53,7 +54,7 @@ export const createLanguage = async (
return language;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
@@ -81,7 +82,7 @@ export const getSurveysUsingGivenLanguage = async (languageId: string): Promise<
return surveyNames;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
@@ -116,7 +117,7 @@ export const deleteLanguage = async (environmentId: string, languageId: string):
return language;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
@@ -156,7 +157,7 @@ export const updateLanguage = async (
return language;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
+2
View File
@@ -30,6 +30,8 @@
"nanoid": "^5.0.6",
"next-auth": "^4.24.7",
"nodemailer": "^6.9.12",
"pino": "^8.19.0",
"pino-pretty": "^10.3.1",
"posthog-node": "^3.6.3",
"server-only": "^0.0.1",
"tailwind-merge": "^2.2.1"
+5 -4
View File
@@ -16,6 +16,7 @@ import { environmentCache } from "../environment/cache";
import { createEnvironment } from "../environment/service";
import { deleteLocalFilesByEnvironmentId, deleteS3FilesByEnvironmentId } from "../storage/service";
import { formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { productCache } from "./cache";
@@ -90,7 +91,7 @@ export const getProductByEnvironmentId = async (environmentId: string): Promise<
return productPrisma;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
@@ -149,7 +150,7 @@ export const updateProduct = async (
return product;
} catch (error) {
if (error instanceof z.ZodError) {
console.error(JSON.stringify(error.errors, null, 2));
logger.error(JSON.stringify(error.errors, null, 2));
}
throw new ValidationError("Data validation of product failed");
}
@@ -204,7 +205,7 @@ export const deleteProduct = async (productId: string): Promise<TProduct> => {
await Promise.all(s3FilesPromises);
} catch (err) {
// fail silently because we don't want to throw an error if the files are not deleted
console.error(err);
logger.error(err);
}
} else {
const localFilesPromises = product.environments.map(async (environment) => {
@@ -215,7 +216,7 @@ export const deleteProduct = async (productId: string): Promise<TProduct> => {
await Promise.all(localFilesPromises);
} catch (err) {
// fail silently because we don't want to throw an error if the files are not deleted
console.error(err);
logger.error(err);
}
}
+6 -5
View File
@@ -12,6 +12,7 @@ import { TResponseNote, ZResponseNote } from "@formbricks/types/responses";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { responseCache } from "../response/cache";
import { formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { responseNoteCache } from "./cache";
@@ -64,7 +65,7 @@ export const createResponseNote = async (
});
return responseNote;
} catch (error) {
console.error(error);
logger.error(error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
@@ -85,7 +86,7 @@ export const getResponseNote = async (responseNoteId: string): Promise<TResponse
});
return responseNote;
} catch (error) {
console.error(error);
logger.error(error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
@@ -116,7 +117,7 @@ export const getResponseNotes = async (responseId: string): Promise<TResponseNot
}
return responseNotes;
} catch (error) {
console.error(error);
logger.error(error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
@@ -158,7 +159,7 @@ export const updateResponseNote = async (responseNoteId: string, text: string):
return updatedResponseNote;
} catch (error) {
console.error(error);
logger.error(error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
@@ -194,7 +195,7 @@ export const resolveResponseNote = async (responseNoteId: string): Promise<TResp
return responseNote;
} catch (error) {
console.error(error);
logger.error(error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
+5 -4
View File
@@ -3,6 +3,7 @@ import { TResponseUpdate } from "@formbricks/types/responses";
import SurveyState from "./surveyState";
import { delay } from "./utils";
import { logger } from "./utils/logger";
interface QueueConfig {
apiHost: string;
@@ -55,14 +56,14 @@ export class ResponseQueue {
this.queue.shift(); // remove the successfully sent response from the queue
break; // exit the retry loop
}
console.error("Formbricks: Failed to send response. Retrying...", attempts);
logger.error(`Formbricks: Failed to send response. Retrying... ${attempts}`);
await delay(1000); // wait for 1 second before retrying
attempts++;
}
if (attempts >= this.config.retryAttempts) {
// Inform the user after 2 failed attempts
console.error("Failed to send response after 2 attempts.");
logger.error("Failed to send response after 2 attempts.");
// If the response fails finally, inform the user
if (this.config.onResponseSendingFailed) {
this.config.onResponseSendingFailed(responseUpdate);
@@ -97,7 +98,7 @@ export class ResponseQueue {
responseId: response.data.id,
});
} catch (error) {
console.error("Failed to update display, proceeding with the response.", error);
logger.error(`Failed to update display, proceeding with the response. ${error}`);
}
}
this.surveyState.updateResponseId(response.data.id);
@@ -107,7 +108,7 @@ export class ResponseQueue {
}
return true;
} catch (error) {
console.error(error);
logger.error(error);
return false;
}
}
+2 -1
View File
@@ -31,6 +31,7 @@ import {
} from "../constants";
import { generateLocalSignedUrl } from "../crypto";
import { env } from "../env";
import { logger } from "../utils/logger";
import { storageCache } from "./cache";
// S3Client Singleton
@@ -66,7 +67,7 @@ export const testS3BucketAccess = async () => {
return true;
} catch (error) {
console.error("Failed to access S3 bucket:", error);
logger.error(`Failed to access S3 bucket: ${error}`);
throw new Error(`S3 Bucket Access Test Failed: ${error}`);
}
};
+3 -1
View File
@@ -1,3 +1,5 @@
import { logger } from "../utils/logger";
export const getOriginalFileNameFromUrl = (fileURL: string) => {
try {
const fileNameFromURL = fileURL.startsWith("/storage/")
@@ -16,6 +18,6 @@ export const getOriginalFileNameFromUrl = (fileURL: string) => {
const fileName = originalFileName ? decodeURIComponent(`${originalFileName}.${fileExt}` || "") : "";
return fileName;
} catch (error) {
console.error("Error parsing file URL:", error);
logger.error(`Error parsing file URL: ${error}`);
}
};
+6 -6
View File
@@ -29,6 +29,7 @@ import { createSegment, evaluateSegment, getSegment, updateSegment } from "../se
import { transformSegmentFiltersToAttributeFilters } from "../segment/utils";
import { subscribeTeamMembersToSurveyResponses } from "../team/service";
import { diffInDays, formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { surveyCache } from "./cache";
import { anySurveyHasFilters } from "./util";
@@ -187,10 +188,9 @@ export const getSurvey = async (surveyId: string): Promise<TSurvey | null> => {
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
}
@@ -306,7 +306,7 @@ export const getSurveys = async (
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
@@ -379,7 +379,7 @@ export const getSurveyCount = async (environmentId: string): Promise<number> =>
return surveyCount;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
@@ -472,7 +472,7 @@ export const updateSurvey = async (updatedSurvey: TSurvey): Promise<TSurvey> =>
try {
await updateSegment(segment.id, segment);
} catch (error) {
console.error(error);
logger.error(error);
throw new Error("Error updating survey");
}
}
@@ -512,7 +512,7 @@ export const updateSurvey = async (updatedSurvey: TSurvey): Promise<TSurvey> =>
return modifiedSurvey;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
+2 -1
View File
@@ -22,6 +22,7 @@ import { environmentCache } from "../environment/cache";
import { getProducts } from "../product/service";
import { getUsersWithTeam, updateUser } from "../user/service";
import { formatDateFields } from "../utils/datetime";
import { logger } from "../utils/logger";
import { validateInputs } from "../utils/validate";
import { teamCache } from "./cache";
@@ -100,7 +101,7 @@ export const getTeamByEnvironmentId = async (environmentId: string): Promise<TTe
return team;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(error);
logger.error(error);
throw new DatabaseError(error.message);
}
+2 -1
View File
@@ -3,6 +3,7 @@
and we cannot trace anything back to you or your customers. If you still want to
disable telemetry, set the environment variable TELEMETRY_DISABLED=1 */
import { env } from "./env";
import { logger } from "./utils/logger";
export const captureTelemetry = async (eventName: string, properties = {}) => {
if (env.TELEMETRY_DISABLED !== "1" && process.env.NODE_ENV === "production" && process.env.INSTANCE_ID) {
@@ -21,7 +22,7 @@ export const captureTelemetry = async (eventName: string, properties = {}) => {
}),
});
} catch (error) {
console.log("error sending telemetry:", error);
logger.error(`error sending telemetry: ${error}`);
}
}
};
+3 -1
View File
@@ -1,5 +1,7 @@
import z from "zod";
import { logger } from "./logger";
// Helper function to calculate difference in days between two dates
export const diffInDays = (date1: Date, date2: Date) => {
const diffTime = Math.abs(date2.getTime() - date1.getTime());
@@ -35,7 +37,7 @@ export function formatDateFields<T extends z.ZodRawShape>(
(formattedObject as any)[key] = new Date(dateStr);
}
} catch (error) {
console.error(`Error parsing date for key ${key}:`, error);
logger.error(`Error parsing date for key ${key}:`, error);
}
}
}
+3 -1
View File
@@ -1,6 +1,8 @@
import { AsyncParser } from "@json2csv/node";
import * as xlsx from "xlsx";
import { logger } from "./logger";
export const convertToCsv = async (fields: string[], jsonData: Record<string, string | number>[]) => {
let csv: string = "";
@@ -11,7 +13,7 @@ export const convertToCsv = async (fields: string[], jsonData: Record<string, st
try {
csv = await parser.parse(jsonData).promise();
} catch (err) {
console.log({ err });
logger.error({ err });
throw new Error("Failed to convert to CSV");
}
return csv;
+22
View File
@@ -0,0 +1,22 @@
import pino from "pino";
export const logger = pino({
level: "info",
timestamp: pino.stdTimeFunctions.isoTime,
transport: {
targets: [
{
level: "info",
target: "pino-pretty",
options: {
colorize: true,
levelFirst: true,
translateTime: "yyyy-dd-mm, h:MM:ss TT",
},
},
],
},
formatters: {
bindings: () => ({}),
},
});
+3 -1
View File
@@ -2,6 +2,8 @@ import z from "zod";
import { ValidationError } from "@formbricks/types/errors";
import { logger } from "./logger";
type ValidationPair = [any, z.ZodSchema<any>];
export const validateInputs = (...pairs: ValidationPair[]): void => {
@@ -9,7 +11,7 @@ export const validateInputs = (...pairs: ValidationPair[]): void => {
const inputValidation = schema.safeParse(value);
if (!inputValidation.success) {
console.error(
logger.error(
`Validation failed for ${JSON.stringify(value)} and ${JSON.stringify(schema)}: ${inputValidation.error.message}`
);
throw new ValidationError("Validation failed");