diff --git a/src/env.ts b/src/env.ts index 0a98140..c8b4044 100644 --- a/src/env.ts +++ b/src/env.ts @@ -12,6 +12,7 @@ export const envSchema = z.object({ CLEAR_TABLE_ON_START: z.string().default("false"), MATCH_KEY: z.string().optional().default(""), CONVERT_VALUES: z.string().optional().default("false"), + LOG_LEVEL: z.string().optional().default("INFO"), }); export const env = envSchema.parse(process.env); \ No newline at end of file diff --git a/src/functions/start.entrypoint.ts b/src/functions/start.entrypoint.ts index 41f1667..d0a26c3 100644 --- a/src/functions/start.entrypoint.ts +++ b/src/functions/start.entrypoint.ts @@ -5,16 +5,16 @@ import { db } from "../db"; import { env } from "../env"; +import { Logger } from "../utils/logger"; const TABLE_NAME = env.TABLE_NAME; const CLEAR_TABLE_ON_START = env.CLEAR_TABLE_ON_START.toLowerCase() === "true"; export default async function() { - console.log("Hello from start"); if (CLEAR_TABLE_ON_START) { - console.info("CLEAR_TABLE_ON_START tag detected! dropping table if it exists..."); + Logger.info("CLEAR_TABLE_ON_START tag detected! dropping table if it exists..."); await db.schema.dropTableIfExists(TABLE_NAME); - console.info(`Table ${TABLE_NAME} dropped!`); + Logger.info(`Table ${TABLE_NAME} dropped!`); } } \ No newline at end of file diff --git a/src/functions/transform.entrypoint.ts b/src/functions/transform.entrypoint.ts index 21704e0..55de189 100644 --- a/src/functions/transform.entrypoint.ts +++ b/src/functions/transform.entrypoint.ts @@ -9,6 +9,8 @@ import { db } from "../db"; import { base64Decode } from "../utils/base-64-decode"; import { createTable } from "../utils/create-table"; import { getSchema, tryExtendSchemaWithKeyValue } from "../utils/get-schema"; +import { Logger } from "../utils/logger"; + export interface Input { eventId: string; @@ -22,17 +24,17 @@ const TABLE_SCHEMA = env.TABLE_SCHEMA_BASE64 && base64Decode(env.TABLE_SCHEMA_BA const CONVERT_VALUES = env.CONVERT_VALUES; export default async function(input: Input) { - console.debug(`Received event ${input.eventId}, with payload ${JSON.stringify(input.payload)} and valid time ${input.validTime}`); + Logger.debug(`Received event ${input.eventId}, with payload ${JSON.stringify(input.payload)} and valid time ${input.validTime}`); const combinedPayload = { eventid: input.eventId, validTime: input.validTime, ...input.payload }; const schema = getSchema(TABLE_SCHEMA, combinedPayload); if (schema.error) { - console.error("Failed to parse schema:", schema.error); + Logger.error("Failed to parse schema:", schema.error); return; } if (!schema.value) { - console.error("Schema is empty"); + Logger.error("Schema is empty"); return; } @@ -50,7 +52,7 @@ export default async function(input: Input) { const tableMissing = !await db.schema.hasTable(TABLE_NAME); if (tableMissing) { - console.info(`Table "${TABLE_NAME}" does not exist! creating it now...`); + Logger.info(`Table "${TABLE_NAME}" does not exist! creating it now...`); await createTable(TABLE_NAME, finalSchema); } @@ -61,22 +63,23 @@ export default async function(input: Input) { const finalName = value.mapFrom || name; const entry = combinedPayload[finalName]; if (!entry && value.required) { - console.warn(`Missing entry for ${finalName}`); + Logger.warn(`Missing entry for ${finalName}`); continue; } + if (typeof entry === "object") { - console.debug(`Converting ${finalName} to JSON`); + Logger.debug(`Converting ${finalName} to JSON`); finalPayload[name] = JSON.stringify(entry); continue; } if(CONVERT_VALUES === "true" && value.type === "integer") { try { - console.debug(`Converting ${finalName} to integer`); + Logger.debug(`Converting ${finalName} to integer`); finalPayload[name] = parseInt(entry, 10); }catch (e) { - console.error(`Failed to convert ${finalName} to integer, setting to null`); + Logger.error(`Failed to convert ${finalName} to integer, setting to null`); finalPayload[name] = null; } continue; @@ -87,12 +90,12 @@ export default async function(input: Input) { if (MATCH_KEY) { const result = await db(TABLE_NAME).insert(finalPayload).onConflict(MATCH_KEY).merge(finalPayload); if (result.length <= 0) { - console.error("Failed to update data"); + Logger.error("Failed to update data"); } } else { const result = await db(TABLE_NAME).insert(finalPayload); if (result.length <= 0) { - console.error("Failed to insert data"); + Logger.error("Failed to insert data"); } } diff --git a/src/main.ts b/src/main.ts index 032a859..76fd868 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import express, {Request, Response} from "express"; import health from "@functions/health.entrypoint"; import transform from "@functions/transform.entrypoint"; import start from "@functions/start.entrypoint"; +import { Logger } from "./utils/logger"; const app = express(); @@ -30,10 +31,10 @@ const run = async (): Promise => { try { await start(); app.listen(port, () => { - console.log(`Server started on port ${port}`); + Logger.info(`Server started on port ${port}`); }); } catch (error) { - console.error(error); + Logger.error(error); process.exit(1); } }; diff --git a/src/utils/create-table.ts b/src/utils/create-table.ts index b78a958..750a1ea 100644 --- a/src/utils/create-table.ts +++ b/src/utils/create-table.ts @@ -1,6 +1,7 @@ import { TableSchema } from "../contracts/tableSchema"; import { db } from "../db"; import { Knex } from "knex"; +import { Logger } from "./logger"; // todo: adjust this code to be more context aware once we support multiple dbs (e.g. not all dbs support json, so instead, it would be stringified and stored as a text or string) @@ -29,13 +30,13 @@ export async function createTable(tableName: string, schema: TableSchema) { for (const [key, value] of Object.entries(schema)) { if (!value.type) { - console.error(`Type missing for key ${key}`); + Logger.error(`Type missing for key ${key}`); continue; } const factory = COLUMN_FACTORY[value.type.toLowerCase()]; if (!factory) { - console.error(`Unknown type ${value.type} for key ${key}`); + Logger.error(`Unknown type ${value.type} for key ${key}`); continue; } @@ -46,7 +47,7 @@ export async function createTable(tableName: string, schema: TableSchema) { } }); - console.info(`Table ${tableName} created successfully.`); + Logger.info(`Table ${tableName} created successfully.`); } type TableMapper = { diff --git a/src/utils/get-schema.ts b/src/utils/get-schema.ts index 7049702..0926ba8 100644 --- a/src/utils/get-schema.ts +++ b/src/utils/get-schema.ts @@ -1,5 +1,6 @@ import { ColumnDefinition, TableSchema } from "../contracts/tableSchema"; import { jsonTryParse, JsonTryParseResult } from "./json-try-parse"; +import { Logger } from "./logger"; const AUTO_SCHEMA_FACTORY: Record ColumnDefinition> = { "string": () => ({ type: "string" }), @@ -16,7 +17,7 @@ export function getSchema(schemaString: string | undefined, fa return jsonTryParse(schemaString); } - console.info("No schema provided, generating schema from input"); + Logger.info("No schema provided, generating schema from input"); return { value: generateSchemaFromInput(fallbackObject), @@ -26,7 +27,7 @@ export function getSchema(schemaString: string | undefined, fa export function tryExtendSchemaWithKeyValue(schema: TableSchema, key: string, value: unknown, definition?: Partial): TableSchema { if (value === undefined) { - console.warn(`Value associated with key "${key}" was undefined, skipping...`); + Logger.warn(`Value associated with key "${key}" was undefined, skipping...`); return schema; } diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..894c211 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,36 @@ +import { env } from "../env"; + +export enum LogLevel { + ERROR = 0, + WARN = 1, + INFO = 2, + DEBUG = 3 +} + +export class Logger { + private static logLevel: LogLevel = LogLevel[env.LOG_LEVEL.toUpperCase() as keyof typeof LogLevel] || LogLevel.INFO; + + static error(...args: any[]) { + if (this.logLevel >= LogLevel.ERROR) { + console.error(...args); + } + } + + static warn(...args: any[]) { + if (this.logLevel >= LogLevel.WARN) { + console.warn(...args); + } + } + + static info(...args: any[]) { + if (this.logLevel >= LogLevel.INFO) { + console.info(...args); + } + } + + static debug(...args: any[]) { + if (this.logLevel >= LogLevel.DEBUG) { + console.debug(...args); + } + } +} \ No newline at end of file