Skip to content

feat(logging): add iam logger #3427

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

Merged
merged 13 commits into from
May 7, 2025
12 changes: 0 additions & 12 deletions embed/__tests__/metadataHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,6 @@ describe("GET /embed/stamps/metadata", () => {
});

describe("unexpected errors", () => {
let logSpy: any;

beforeEach(() => {
logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
});

afterEach(() => {
logSpy.mockRestore();
});

it("should handle errors from the embedWeightsUrl API correctly", async () => {
mockedAxios.get.mockImplementationOnce(() => {
throw new Error("Failed to fetch embed weights");
Expand All @@ -114,8 +104,6 @@ describe("GET /embed/stamps/metadata", () => {
code: 500,
error: expect.stringMatching(/Unexpected server error \(ID: \S+\)/),
});

expect(logSpy).toHaveBeenCalledWith("Unexpected error:", expect.any(String));
});
});

Expand Down
22 changes: 13 additions & 9 deletions embed/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import type { JestConfigWithTsJest } from "ts-jest";
import { type JestConfigWithTsJest } from "ts-jest";

const config: JestConfigWithTsJest = {
extensionsToTreatAsEsm: [".ts"],
preset: "ts-jest",
preset: "ts-jest/presets/default-esm",
testEnvironment: "node",
moduleNameMapper: {
"^@gitcoin/passport-identity$": "<rootDir>/../node_modules/@gitcoin/passport-identity/dist/esm/index.js",
"^multiformats/(.*)$": "<rootDir>/../node_modules/multiformats/dist/src/$1.js",
"^multiformats$": "<rootDir>/../node_modules/multiformats/dist/index.min.js",
"^@ipld/dag-cbor$": "<rootDir>/../node_modules/@ipld/dag-cbor/esm/index.js",
"^uint8arrays(/|$)": "<rootDir>/../node_modules/uint8arrays/dist/index.min.js",
"^(\.\.?/.*)\\.js$": "$1",
"^(..?/.*)\\.js$": "$1",
},
transform: {
"^.+\\.ts$": "ts-jest",
// For transforming the dependencies that aren't playing nicely
"^.+\\.js$": [
"^.+\\.(j|t)s$": [
// Use babel-jest to transpile both local typescript files and
// the dependencies that only available in esm format (dids, etc)
"babel-jest",
{
presets: [["@babel/preset-env", { targets: { node: "current" } }]],
plugins: ["@babel/plugin-transform-modules-commonjs"],
presets: ["@babel/preset-typescript", ["@babel/preset-env", { targets: { node: "current" } }]],
plugins: [
"@babel/plugin-syntax-import-assertions",
"babel-plugin-transform-import-meta",
["babel-plugin-replace-import-extension", { extMapping: { ".js": "" } }],
"@babel/plugin-transform-modules-commonjs",
],
},
],
},
Expand Down
1 change: 1 addition & 0 deletions embed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"express-rate-limit": "^7.5.0",
"ioredis": "^5.4.1",
"luxon": "^2.4.0",
"pino": "^9.6.0",
"rate-limit-redis": "^4.2.0",
"uuid": "^8.3.2"
},
Expand Down
10 changes: 7 additions & 3 deletions embed/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import "dotenv/config";

// Initialize logger for identity package
import { logger as identityLogger } from "@gitcoin/passport-identity";
identityLogger.setLogger(logger);

// ---- Main App from index
import { app } from "./server.js";

import { logger } from "./utils/logger.js";
// default port to listen on
const port = process.env.EMBED_PORT || 80;

const startServer = (): void => {
const server = app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`server started at http://localhost:${port}`);
logger.info(`server started at http://localhost:${port}`);
});

// This should be > the ELB idle timeout, which is 60 seconds
Expand All @@ -21,5 +25,5 @@ try {
startServer();
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
logger.error(error);
}
6 changes: 3 additions & 3 deletions embed/src/redis.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Redis } from "ioredis";

import { logger } from "./utils/logger.js";
export const redis = new Redis(process.env.REDIS_URL as string);

// Log errors and connection success
redis.on("connect", () => {
console.log("Connected to Redis");
logger.info("Connected to Redis");
});

redis.on("error", (err) => {
console.error("Redis connection error:", err);
logger.error("Redis connection error:", err);
});
3 changes: 2 additions & 1 deletion embed/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { keyGenerator } from "./rateLimiterKeyGenerator.js";
import { autoVerificationHandler, verificationHandler, getChallengeHandler } from "./handlers.js";
import { metadataHandler } from "./metadata.js";
import { serverUtils } from "./utils/identityHelper.js";
import { logger } from "./utils/logger.js";

// ---- Config - check for all required env variables
// We want to prevent the app from starting with default values or if it is misconfigured
Expand Down Expand Up @@ -41,7 +42,7 @@ if (!process.env.REDIS_URL) {
}

if (configErrors.length > 0) {
configErrors.forEach((error) => console.error(error)); // eslint-disable-line no-console
configErrors.forEach((error) => logger.error(error));
throw new Error("Missing required configuration: " + configErrors.join(",\n"));
}

Expand Down
7 changes: 7 additions & 0 deletions embed/src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pinoImport from "pino";
const pino = pinoImport.default;
// https://github.com/pinojs/pino

export const logger = pino();

logger.info("Logger initialized");
9 changes: 5 additions & 4 deletions iam/__tests__/easFees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { parseEther } from "ethers";
import { getEASFeeAmount } from "../src/utils/easFees.js";
import Moralis from "moralis";
import { PassportCache } from "@gitcoin/passport-platforms";
import { logger } from "../src/utils/logger.js";

jest.mock("moralis", () => ({
EvmApi: {
Expand Down Expand Up @@ -44,7 +45,7 @@ describe("EthPriceLoader", () => {
});

it("should handle Moralis errors gracefully", async () => {
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
const loggerSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
let count = 0;
jest.spyOn(PassportCache.prototype, "get").mockImplementation((key) => {
count += 1;
Expand All @@ -63,11 +64,11 @@ describe("EthPriceLoader", () => {

(Moralis.EvmApi.token.getTokenPrice as jest.Mock<any>).mockRejectedValueOnce(new Error("Failed fetching price"));
await getEASFeeAmount(2);
expect(consoleSpy).toHaveBeenCalledWith("MORALIS ERROR: Failed to get ETH price, Error: Failed fetching price");
expect(loggerSpy).toHaveBeenCalledWith("MORALIS ERROR: Failed to get ETH price, Error: Failed fetching price");
});

it("should handle Redis errors gracefully", async () => {
const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => {});
const loggerSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
let count = 0;
jest.spyOn(PassportCache.prototype, "get").mockImplementation((key) => {
count += 1;
Expand All @@ -85,7 +86,7 @@ describe("EthPriceLoader", () => {
jest.spyOn(PassportCache.prototype, "set").mockRejectedValueOnce(new Error("Failed to store in cache"));

await getEASFeeAmount(2);
expect(consoleSpy).toHaveBeenCalledWith(
expect(loggerSpy).toHaveBeenCalledWith(
"REDIS CONNECTION ERROR: Failed to cache ETH price, Error: Failed to store in cache"
);
});
Expand Down
11 changes: 1 addition & 10 deletions iam/__tests__/index_verify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "@gitcoin/passport-types";

import { app } from "../src/index.js";

import * as identityMock from "../src/utils/identityHelper";

jest.mock("../src/utils/revocations", () => ({
Expand Down Expand Up @@ -596,14 +597,6 @@ describe("POST /verify", function () {
});

describe("for unexpected errors", () => {
let logSpy: jest.SpyInstance;
beforeEach(() => {
logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
});
afterEach(() => {
logSpy.mockRestore();
});

it("handles unexpected errors", async () => {
(identityMock.verifyCredential as jest.Mock).mockRejectedValueOnce(
// new identityMock.serverUtils.InternalApiError("Verify Credential Error"),
Expand Down Expand Up @@ -643,8 +636,6 @@ describe("POST /verify", function () {
error: expect.stringMatching(/Unexpected server error \(ID: \S+?\)/),
code: 500,
});

expect(logSpy).toHaveBeenCalledWith("Unexpected error:", expect.stringMatching(/^Error at/));
});
});

Expand Down
5 changes: 4 additions & 1 deletion iam/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"luxon": "^2.4.0",
"moralis": "^2.24.2",
"multiformats": "^9.9.0",
"pino": "^9.6.0",
"twitter-api-v2": "^1.15.1",
"uuid": "^8.3.2"
},
Expand All @@ -51,6 +52,7 @@
"@babel/plugin-transform-modules-commonjs": "^7.23.3",
"@babel/preset-env": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@eslint/eslintrc": "^3.3.1",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/luxon": "^2.3.2",
Expand All @@ -65,7 +67,8 @@
"supertest": "^6.2.2",
"ts-node": "^10.9.1",
"tsx": "^4.7.0",
"typescript": "^5.7.3"
"typescript": "^5.7.3",
"typescript-eslint": "^8.32.0"
},
"resolutions": {
"leveldown": "6.1.1"
Expand Down
3 changes: 2 additions & 1 deletion iam/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { router as procedureRouter } from "@gitcoin/passport-platforms/procedure
import { challengeHandler, checkHandler, easScoreV2Handler, verifyHandler } from "./handlers/index.js";

import { serverUtils } from "./utils/identityHelper.js";
import { logger } from "./utils/logger.js";

// ---- Config - check for all required env variables
// We want to prevent the app from starting with default values or if it is misconfigured
Expand All @@ -32,7 +33,7 @@ const configErrors = [
.filter(Boolean);

if (configErrors.length > 0) {
configErrors.forEach((error) => console.error(error)); // eslint-disable-line no-console
configErrors.forEach((error) => logger.error(error));
throw new Error("Missing required configuration");
}

Expand Down
12 changes: 8 additions & 4 deletions iam/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import "dotenv/config";

// Initialize logger for identity package
import { logger as identityLogger } from "@gitcoin/passport-identity";
identityLogger.setLogger(logger);

import { logger } from "./utils/logger.js";

// ---- Main App from index
import { app } from "./index.js";
import Moralis from "moralis";
Expand All @@ -13,15 +19,13 @@ const startServer = async (): Promise<void> => {
});

const server = app.listen(port, () => {
// eslint-disable-next-line no-console
console.log(`server started at http://localhost:${port}`);
logger.info(`server started at http://localhost:${port}`);
});

// This should be > the ELB idle timeout, which is 60 seconds
server.keepAliveTimeout = 61 * 1000;
};

startServer().catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
logger.error(error);
});
5 changes: 3 additions & 2 deletions iam/src/utils/easFees.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { parseEther } from "ethers";
import Moralis from "moralis";
import { PassportCache } from "@gitcoin/passport-platforms";
import { logger } from "./logger.js";

const FIVE_MINUTES = 1000 * 60 * 5;
const WETH_CONTRACT = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
Expand Down Expand Up @@ -43,12 +44,12 @@ class EthPriceLoader {
} catch (e) {
let message = "Failed to cache ETH price";
if (e instanceof Error) message += `, ${e.name}: ${e.message}`;
console.error(`REDIS CONNECTION ERROR: ${message}`);
logger.error(`REDIS CONNECTION ERROR: ${message}`);
}
} catch (e) {
let message = "Failed to get ETH price";
if (e instanceof Error) message += `, ${e.name}: ${e.message}`;
console.error(`MORALIS ERROR: ${message}`);
logger.error(`MORALIS ERROR: ${message}`);
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions iam/src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pinoImport from "pino";
const pino = pinoImport.default;
// https://github.com/pinojs/pino

export const logger = pino();

logger.info("Logger initialized");
4 changes: 2 additions & 2 deletions iam/src/utils/revocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { handleAxiosError } from "@gitcoin/passport-platforms";
import axios from "axios";
import { VerifiableCredential, VerifiableEip712Credential } from "@gitcoin/passport-types";
import { serverUtils } from "../utils/identityHelper.js";

import { logger } from "./logger.js";
const { InternalApiError } = serverUtils;

const SCORER_ENDPOINT = process.env.SCORER_ENDPOINT;
Expand Down Expand Up @@ -48,7 +48,7 @@ export const filterRevokedCredentials = async (
const fetchRevocations = async (proofValues: string[]): Promise<Revocation[]> => {
const payload = { proof_values: proofValues };

console.log("Checking revocations", payload);
logger.info("Checking revocations", payload);

try {
const revocationResponse: {
Expand Down
12 changes: 10 additions & 2 deletions identity/__tests__/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
NullifierGenerator,
} from "../src/nullifierGenerators";
import { humanNetworkOprf } from "../src/humanNetworkOprf";
import * as logger from "../src/logger";

// ---- original DIDKit lib
import * as OriginalDIDKit from "@spruceid/didkit-wasm-node";
Expand Down Expand Up @@ -39,6 +40,13 @@ jest.mock("../src/humanNetworkOprf", () => ({
initMishti: jest.fn(),
}));

jest.mock("../src/logger", () => ({
error: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
}));

const mockIssuerKey = generateEIP712PairJWK();

// Set up nullifier generators
Expand Down Expand Up @@ -466,7 +474,7 @@ describe("issueNullifiableCredential with ignorable errors", () => {
})
).rejects.toThrow("Unable to generate nullifiers");

expect(console.error).toHaveBeenCalled();
expect(logger.error).toHaveBeenCalled();
});

it("handles mix of successful, ignorable, and unexpected errors", async () => {
Expand Down Expand Up @@ -499,7 +507,7 @@ describe("issueNullifiableCredential with ignorable errors", () => {
})
).rejects.toThrow("Unable to generate nullifiers");

expect(console.error).toHaveBeenCalled();
expect(logger.error).toHaveBeenCalled();
});
});
});
Loading
Loading