Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/fast-coins-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensindexer": minor
---

Enhanced application logging approach to use a streamlined logger implementation across ENSIndexer app.
5 changes: 5 additions & 0 deletions .changeset/warm-geese-fall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ensnode/ponder-sdk": minor
---

Added `logger` field to `PonderAppContext` data model.
17 changes: 10 additions & 7 deletions apps/ensindexer/ponder/ponder.config.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import config from "@/config";

import { PluginName } from "@ensnode/ensnode-sdk";
import { prettyPrintJson } from "@ensnode/ensnode-sdk/internal";

import { redactENSIndexerConfig } from "@/config/redact";
import { logger } from "@/lib/logger";
import ponderConfig from "@/ponder/config";

////////
// Log redacted ENSIndexerConfig for debugging.
////////

console.log("ENSIndexer running with config:");
console.log(prettyPrintJson(redactENSIndexerConfig(config)));
logger.info({
msg: "ENSIndexer starting",
config: redactENSIndexerConfig(config),
});

// log warning about dual activation of subgraph and ensv2 plugins
// Log warning about dual activation of subgraph and ensv2 plugins
if (config.plugins.includes(PluginName.Subgraph) && config.plugins.includes(PluginName.ENSv2)) {
console.warn(
`Both the '${PluginName.Subgraph}' and '${PluginName.ENSv2}' plugins are enabled. This results in the availability of both the legacy Subgraph-Compatible GraphQL API (/subgraph) _and_ ENSNode's Omnigraph API (/api/omnigraph), and comes with an associated increase in indexing time. If your intent is to have both APIs available in parallel, excellent, otherwise you may benefit from only enabling the plugin for the API you plan to use.`,
);
logger.warn({
msg: `Both the '${PluginName.Subgraph}' and '${PluginName.ENSv2}' plugins are enabled. This results in the availability of both the legacy Subgraph-Compatible GraphQL API (/subgraph) _and_ ENSNode's Omnigraph API (/api/omnigraph), and comes with an associated increase in indexing time.`,
advice: `If your intent is to have both APIs available in parallel, excellent, otherwise you may benefit from only enabling the plugin for the API you plan to use.`,
});
}

////////
Expand Down
9 changes: 7 additions & 2 deletions apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "@ensnode/ensnode-sdk";

import { ensDbClient } from "@/lib/ensdb/singleton";
import { logger } from "@/lib/logger";

const app = new Hono();

Expand Down Expand Up @@ -55,8 +56,12 @@ app.get("/indexing-status", async (c) => {
} satisfies IndexingStatusResponseOk),
);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
console.error(`Indexing Status Snapshot is currently not available: ${errorMessage}`);
logger.error({
msg: "Indexing status snapshot unavailable",
error,
module: "ensnode-api",
endpoint: "/indexing-status",
});

return c.json(
serializeIndexingStatusResponse({
Expand Down
14 changes: 12 additions & 2 deletions apps/ensindexer/ponder/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ErrorResponse } from "@ensnode/ensnode-sdk";

import { migrateEnsNodeSchema } from "@/lib/ensdb/migrate-ensnode-schema";
import { startEnsDbWriterWorker } from "@/lib/ensdb-writer-worker/singleton";
import { logger } from "@/lib/logger";

import ensNodeApi from "./handlers/ensnode-api";

Expand All @@ -19,7 +20,11 @@ import ensNodeApi from "./handlers/ensnode-api";
migrateEnsNodeSchema()
.then(startEnsDbWriterWorker)
.catch((error) => {
console.error("Failed to migrate ENSNode Schema — ", error);
logger.error({
msg: "Failed to initialize ENSNode metadata",
error,
module: "ponder-api",
});
process.exit(1);
});

Expand All @@ -39,7 +44,12 @@ app.route("/api", ensNodeApi);

// log hono errors to console
app.onError((error, ctx) => {
console.error(error);
logger.error({
msg: "Internal server error",
error,
path: ctx.req.path,
module: "ponder-api",
});
return ctx.json({ message: "Internal Server Error" } satisfies ErrorResponse, 500);
});

Expand Down
21 changes: 21 additions & 0 deletions apps/ensindexer/src/lib/__test__/mockLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { vi } from "vitest";

// Set up the global PONDER_COMMON.logger before mocking to allow importOriginal to work
const mockLogger = {
trace: vi.fn(),
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};

(globalThis as any).PONDER_COMMON = { logger: mockLogger };

/**
* Mock the logger module to avoid the globalThis.PONDER_COMMON check.
*/
vi.mock("@/lib/logger", async () => {
return {
logger: mockLogger,
};
});
2 changes: 2 additions & 0 deletions apps/ensindexer/src/lib/dns-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { bytesToHex, decodeEventLog, stringToHex, zeroHash } from "viem";
import { packetToBytes } from "viem/ens";
import { describe, expect, it } from "vitest";

import "@/lib/__test__/mockLogger";

import { getDatasource } from "@ensnode/datasources";
import type { DNSEncodedLiteralName } from "@ensnode/ensnode-sdk";

Expand Down
25 changes: 17 additions & 8 deletions apps/ensindexer/src/lib/dns-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "@ensnode/ensnode-sdk";
import { interpretTextRecordKey, interpretTextRecordValue } from "@ensnode/ensnode-sdk/internal";

import { logger } from "@/lib/logger";
import { isLabelSubgraphIndexable } from "@/lib/subgraph/is-label-subgraph-indexable";

/**
Expand Down Expand Up @@ -111,15 +112,16 @@ export function decodeTXTData(data: Buffer[]): string | null {

// soft-invariant: we never receive 0 data results in a TXT record
if (decoded.length === 0) {
console.warn(`decodeTXTData zero 'data' results, this is unexpected.`);
logger.warn({ msg: `decodeTXTData zero 'data' results, this is unexpected.` });
return null;
}

// soft-invariant: we never receive more than 1 data result in a TXT record
if (decoded.length > 1) {
console.warn(
`decodeTXTData received multiple 'data' results, this is unexpected. data = '${decoded.join(",")}'`,
);
logger.warn({
msg: `decodeTXTData received multiple 'data' results, this is unexpected.`,
data: decoded,
});
}

// biome-ignore lint/style/noNonNullAssertion: guaranteed to exist due to length check above
Expand Down Expand Up @@ -166,16 +168,23 @@ export function parseDnsTxtRecordArgs({
});

if (txtDatas.length === 0) {
console.warn(`parseDNSRecordArgs: No TXT answers found in DNS record for key '${key}'`);
logger.warn({
msg: "No TXT answers found in DNS record",
fn: "parseDnsTxtRecordArgs",
textRecordKey: key,
});

// no text answers? interpret as deletion
return { key, value: null };
}

if (txtDatas.length > 1) {
console.warn(
`parseDNSRecordArgs: received multiple TXT answers, this is unexpected. answers = '${txtDatas.join(",")}'. Only using the first one.`,
);
logger.warn({
msg: `Received multiple TXT answers, this is unexpected. Only using the first one.`,
fn: "parseDnsTxtRecordArgs",
textRecordKey: key,
answers: txtDatas,
});
}

// biome-ignore lint/style/noNonNullAssertion: ok due to checks above
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
validateEnsIndexerPublicConfigCompatibility,
} from "@ensnode/ensnode-sdk";

import "@/lib/__test__/mockLogger";

import type { IndexingStatusBuilder } from "@/lib/indexing-status-builder/indexing-status-builder";
import type { PublicConfigBuilder } from "@/lib/public-config-builder/public-config-builder";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import type { LocalPonderClient } from "@ensnode/ponder-sdk";

import type { IndexingStatusBuilder } from "@/lib/indexing-status-builder/indexing-status-builder";
import { logger } from "@/lib/logger";
import type { PublicConfigBuilder } from "@/lib/public-config-builder/public-config-builder";

/**
Expand Down Expand Up @@ -100,16 +101,24 @@ export class EnsDbWriterWorker {
const inMemoryConfig = await this.getValidatedEnsIndexerPublicConfig();

// Task 1: upsert ENSDb version into ENSDb.
console.log(`[EnsDbWriterWorker]: Upserting ENSDb version into ENSDb...`);
logger.debug({ msg: "Upserting ENSDb version", module: "EnsDbWriterWorker" });
await this.ensDbClient.upsertEnsDbVersion(inMemoryConfig.versionInfo.ensDb);
console.log(
`[EnsDbWriterWorker]: ENSDb version upserted successfully: ${inMemoryConfig.versionInfo.ensDb}`,
);
logger.info({
msg: "Upserted ENSDb version",
ensDbVersion: inMemoryConfig.versionInfo.ensDb,
module: "EnsDbWriterWorker",
});

// Task 2: upsert of EnsIndexerPublicConfig into ENSDb.
console.log(`[EnsDbWriterWorker]: Upserting ENSIndexer Public Config into ENSDb...`);
logger.debug({
msg: "Upserting ENSIndexer public config",
module: "EnsDbWriterWorker",
});
await this.ensDbClient.upsertEnsIndexerPublicConfig(inMemoryConfig);
console.log(`[EnsDbWriterWorker]: ENSIndexer Public Config upserted successfully`);
logger.info({
msg: "Upserted ENSIndexer public config",
module: "EnsDbWriterWorker",
});

// Task 3: recurring upsert of Indexing Status Snapshot into ENSDb.
this.indexingStatusInterval = setInterval(
Expand Down Expand Up @@ -163,12 +172,23 @@ export class EnsDbWriterWorker {
* will be thrown and the worker will not start, as the ENSIndexer Public Config
* is a critical dependency for the worker's tasks.
*/
const configFetchRetries = 3;

logger.debug({
msg: "Fetching ENSIndexer public config",
retries: configFetchRetries,
module: "EnsDbWriterWorker",
});

const inMemoryConfigPromise = pRetry(() => this.publicConfigBuilder.getPublicConfig(), {
retries: 3,
onFailedAttempt: ({ error, attemptNumber, retriesLeft }) => {
console.warn(
`ENSIndexer Config fetch attempt ${attemptNumber} failed (${error.message}). ${retriesLeft} retries left.`,
);
retries: configFetchRetries,
onFailedAttempt: ({ attemptNumber, retriesLeft }) => {
logger.warn({
msg: "Config fetch attempt failed",
attempt: attemptNumber,
retriesLeft,
module: "EnsDbWriterWorker",
});
},
});

Expand All @@ -180,12 +200,19 @@ export class EnsDbWriterWorker {
this.ensDbClient.getEnsIndexerPublicConfig(),
inMemoryConfigPromise,
]);
logger.info({
msg: "Fetched ENSIndexer public config",
module: "EnsDbWriterWorker",
config: inMemoryConfig,
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";

console.error(
`[EnsDbWriterWorker]: Failed to fetch ENSIndexer Public Config: ${errorMessage}`,
);
logger.error({
msg: "Failed to fetch ENSIndexer public config",
error,
module: "EnsDbWriterWorker",
});

// Throw the error to terminate the ENSIndexer process due to failed fetch of critical dependency
throw new Error(errorMessage, {
Expand All @@ -205,9 +232,11 @@ export class EnsDbWriterWorker {
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";

console.error(
`[EnsDbWriterWorker]: In-memory ENSIndexer Public Config object is not compatible with its counterpart stored in ENSDb. Cause: ${errorMessage}`,
);
logger.error({
msg: "In-memory config incompatible with stored config",
error,
module: "EnsDbWriterWorker",
});

// Throw the error to terminate the ENSIndexer process due to
// found config incompatibility
Expand Down Expand Up @@ -240,10 +269,11 @@ export class EnsDbWriterWorker {

await this.ensDbClient.upsertIndexingStatusSnapshot(crossChainSnapshot);
} catch (error) {
console.error(
`[EnsDbWriterWorker]: Error retrieving or validating Indexing Status Snapshot:`,
logger.error({
msg: "Failed to upsert indexing status snapshot",
error,
);
module: "EnsDbWriterWorker",
});
// Do not throw the error, as failure to retrieve the Indexing Status
// should not cause the ENSDb Writer Worker to stop functioning.
}
Expand Down
6 changes: 5 additions & 1 deletion apps/ensindexer/src/lib/ensdb-writer-worker/singleton.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ensDbClient } from "@/lib/ensdb/singleton";
import { indexingStatusBuilder } from "@/lib/indexing-status-builder/singleton";
import { localPonderClient } from "@/lib/local-ponder-client";
import { logger } from "@/lib/logger";
import { publicConfigBuilder } from "@/lib/public-config-builder/singleton";

import { EnsDbWriterWorker } from "./ensdb-writer-worker";
Expand Down Expand Up @@ -35,7 +36,10 @@ export function startEnsDbWriterWorker() {
// Abort the worker on error to trigger cleanup
ensDbWriterWorker.stop();

console.error("EnsDbWriterWorker encountered an error:", error);
logger.error({
msg: "EnsDbWriterWorker encountered an error",
error,
});

// Re-throw the error to ensure the application shuts down with a non-zero exit code.
process.exitCode = 1;
Expand Down
12 changes: 10 additions & 2 deletions apps/ensindexer/src/lib/ensdb/migrate-ensnode-schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createRequire } from "node:module";
import { join } from "node:path";

import { logger } from "@/lib/logger";

import { ensDbClient } from "./singleton";

// Resolve the path to the migrations directory within the ENSDb SDK package
Expand All @@ -13,7 +15,13 @@ const migrationsDirPath = join(
* Execute database migrations for ENSNode Schema in ENSDb.
*/
export async function migrateEnsNodeSchema(): Promise<void> {
console.log(`Running database migrations for ENSNode Schema in ENSDb.`);
logger.debug({
msg: "Started database migrations",
module: "migrate-ensnode-schema",
});
await ensDbClient.migrateEnsNodeSchema(migrationsDirPath);
console.log(`Database migrations for ENSNode Schema in ENSDb completed successfully.`);
logger.info({
msg: "Completed database migrations",
module: "migrate-ensnode-schema",
});
}
Loading
Loading