Skip to content

Commit 3b294aa

Browse files
authored
refactor: singleton pattern for logging (#47)
# 🤖 Linear Closes GIT-204 ## Description We re-analyzed the multiple instances of Logger and agreed that is best to have a singleton. So we rollbacked to the original solution of a singleton instance and improved its capabilities with a context object to log more information ## Checklist before requesting a review - [x] I have conducted a self-review of my code. - [x] I have conducted a QA. - [ ] If it is a core feature, I have included comprehensive tests.
1 parent 9b0b864 commit 3b294aa

20 files changed

+321
-103
lines changed

apps/processing/src/services/processing.service.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
Orchestrator,
1010
RetroactiveProcessor,
1111
} from "@grants-stack-indexer/data-flow";
12-
import { ChainId, Logger } from "@grants-stack-indexer/shared";
12+
import { ChainId, ILogger } from "@grants-stack-indexer/shared";
1313

1414
import { Environment } from "../config/env.js";
1515
import { SharedDependencies, SharedDependenciesService } from "./index.js";
@@ -29,50 +29,46 @@ import { SharedDependencies, SharedDependenciesService } from "./index.js";
2929
*/
3030
export class ProcessingService {
3131
private readonly orchestrators: Map<ChainId, [Orchestrator, RetroactiveProcessor]> = new Map();
32-
private readonly logger = new Logger({ className: "ProcessingService" });
32+
private readonly logger: ILogger;
3333
private readonly kyselyDatabase: SharedDependencies["kyselyDatabase"];
3434

3535
private constructor(
3636
orchestrators: Map<ChainId, [Orchestrator, RetroactiveProcessor]>,
3737
kyselyDatabase: SharedDependencies["kyselyDatabase"],
38+
logger: ILogger,
3839
) {
3940
this.orchestrators = orchestrators;
4041
this.kyselyDatabase = kyselyDatabase;
42+
this.logger = logger;
4143
}
4244

4345
static async initialize(env: Environment): Promise<ProcessingService> {
4446
const sharedDependencies = await SharedDependenciesService.initialize(env);
4547
const { CHAINS: chains } = env;
46-
const { core, registriesRepositories, indexerClient, kyselyDatabase } = sharedDependencies;
48+
const { core, registriesRepositories, indexerClient, kyselyDatabase, logger } =
49+
sharedDependencies;
4750
const {
4851
eventRegistryRepository,
4952
strategyRegistryRepository,
5053
strategyProcessingCheckpointRepository,
5154
} = registriesRepositories;
5255
const orchestrators: Map<ChainId, [Orchestrator, RetroactiveProcessor]> = new Map();
5356

54-
const strategyRegistry = new DatabaseStrategyRegistry(
55-
new Logger({ className: "DatabaseStrategyRegistry" }),
56-
strategyRegistryRepository,
57-
);
58-
const eventsRegistry = new DatabaseEventRegistry(
59-
new Logger({ className: "DatabaseEventRegistry" }),
60-
eventRegistryRepository,
61-
);
57+
const strategyRegistry = new DatabaseStrategyRegistry(logger, strategyRegistryRepository);
58+
const eventsRegistry = new DatabaseEventRegistry(logger, eventRegistryRepository);
6259

6360
for (const chain of chains) {
64-
const chainLogger = new Logger({ chainId: chain.id as ChainId });
6561
// Initialize EVM provider
66-
const evmProvider = new EvmProvider(chain.rpcUrls, optimism, chainLogger);
62+
const evmProvider = new EvmProvider(chain.rpcUrls, optimism, logger);
6763

6864
// Initialize events registry for the chain
6965
const cachedEventsRegistry = await InMemoryCachedEventRegistry.initialize(
70-
new Logger({ className: "InMemoryCachedEventRegistry" }),
66+
logger,
7167
eventsRegistry,
7268
[chain.id as ChainId],
7369
);
7470
const cachedStrategyRegistry = await InMemoryCachedStrategyRegistry.initialize(
75-
new Logger({ className: "InMemoryCachedStrategyRegistry" }),
71+
logger,
7672
strategyRegistry,
7773
chain.id as ChainId,
7874
);
@@ -87,7 +83,7 @@ export class ProcessingService {
8783
},
8884
chain.fetchLimit,
8985
chain.fetchDelayMs,
90-
chainLogger,
86+
logger,
9187
);
9288
const retroactiveProcessor = new RetroactiveProcessor(
9389
chain.id as ChainId,
@@ -99,13 +95,13 @@ export class ProcessingService {
9995
checkpointRepository: strategyProcessingCheckpointRepository,
10096
},
10197
chain.fetchLimit,
102-
chainLogger,
98+
logger,
10399
);
104100

105101
orchestrators.set(chain.id as ChainId, [orchestrator, retroactiveProcessor]);
106102
}
107103

108-
return new ProcessingService(orchestrators, kyselyDatabase);
104+
return new ProcessingService(orchestrators, kyselyDatabase, logger);
109105
}
110106

111107
/**

apps/processing/src/services/sharedDependencies.service.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
KyselyStrategyProcessingCheckpointRepository,
1717
KyselyStrategyRegistryRepository,
1818
} from "@grants-stack-indexer/repository";
19-
import { Logger } from "@grants-stack-indexer/shared";
19+
import { ILogger, Logger } from "@grants-stack-indexer/shared";
2020

2121
import { Environment } from "../config/index.js";
2222

@@ -29,6 +29,7 @@ export type SharedDependencies = {
2929
};
3030
indexerClient: EnvioIndexerClient;
3131
kyselyDatabase: ReturnType<typeof createKyselyDatabase>;
32+
logger: ILogger;
3233
};
3334

3435
/**
@@ -39,10 +40,15 @@ export type SharedDependencies = {
3940
*/
4041
export class SharedDependenciesService {
4142
static async initialize(env: Environment): Promise<SharedDependencies> {
43+
const logger = Logger.getInstance();
44+
4245
// Initialize repositories
43-
const kyselyDatabase = createKyselyDatabase({
44-
connectionString: env.DATABASE_URL,
45-
});
46+
const kyselyDatabase = createKyselyDatabase(
47+
{
48+
connectionString: env.DATABASE_URL,
49+
},
50+
logger,
51+
);
4652

4753
const projectRepository = new KyselyProjectRepository(kyselyDatabase, env.DATABASE_SCHEMA);
4854
const roundRepository = new KyselyRoundRepository(kyselyDatabase, env.DATABASE_SCHEMA);
@@ -59,13 +65,10 @@ export class SharedDependenciesService {
5965
env.DATABASE_SCHEMA,
6066
);
6167
const pricingProvider = PricingProviderFactory.create(env, {
62-
logger: new Logger({ className: "PricingProvider" }),
68+
logger,
6369
});
6470

65-
const metadataProvider = new IpfsProvider(
66-
env.IPFS_GATEWAYS_URL,
67-
new Logger({ className: "IpfsProvider" }),
68-
);
71+
const metadataProvider = new IpfsProvider(env.IPFS_GATEWAYS_URL, logger);
6972

7073
const eventRegistryRepository = new KyselyEventRegistryRepository(
7174
kyselyDatabase,
@@ -102,6 +105,7 @@ export class SharedDependenciesService {
102105
},
103106
indexerClient,
104107
kyselyDatabase,
108+
logger,
105109
};
106110
}
107111
}

apps/processing/test/unit/processing.service.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ vi.mock("../../src/services/sharedDependencies.service.js", () => ({
2222
kyselyDatabase: {
2323
destroy: vi.fn(),
2424
},
25+
logger: {
26+
info: vi.fn(),
27+
error: vi.fn(),
28+
debug: vi.fn(),
29+
warn: vi.fn(),
30+
},
2531
})),
2632
},
2733
}));

apps/processing/test/unit/sharedDependencies.service.spec.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,29 @@ import { EnvioIndexerClient } from "@grants-stack-indexer/indexer-client";
44
import { IpfsProvider } from "@grants-stack-indexer/metadata";
55
import { PricingProviderFactory } from "@grants-stack-indexer/pricing";
66
import { createKyselyDatabase } from "@grants-stack-indexer/repository";
7-
import { Logger } from "@grants-stack-indexer/shared";
87

98
import type { Environment } from "../../src/config/env.js";
109
import { SharedDependenciesService } from "../../src/services/sharedDependencies.service.js";
1110

11+
const mocks = vi.hoisted(() => {
12+
return {
13+
logger: {
14+
info: vi.fn(),
15+
error: vi.fn(),
16+
debug: vi.fn(),
17+
warn: vi.fn(),
18+
},
19+
};
20+
});
21+
22+
vi.mock("@grants-stack-indexer/shared", () => {
23+
return {
24+
Logger: {
25+
getInstance: vi.fn().mockReturnValue(mocks.logger),
26+
},
27+
};
28+
});
29+
1230
// Mock dependencies
1331
vi.mock("@grants-stack-indexer/repository", () => ({
1432
createKyselyDatabase: vi.fn(),
@@ -93,15 +111,18 @@ describe("SharedDependenciesService", () => {
93111
const dependencies = await SharedDependenciesService.initialize(mockEnv as Environment);
94112

95113
// Verify database initialization
96-
expect(createKyselyDatabase).toHaveBeenCalledWith({
97-
connectionString: mockEnv.DATABASE_URL,
98-
});
114+
expect(createKyselyDatabase).toHaveBeenCalledWith(
115+
{
116+
connectionString: mockEnv.DATABASE_URL,
117+
},
118+
mocks.logger,
119+
);
99120

100121
// Verify providers initialization
101122
expect(PricingProviderFactory.create).toHaveBeenCalledWith(mockEnv, {
102-
logger: expect.any(Logger) as Logger,
123+
logger: mocks.logger,
103124
});
104-
expect(IpfsProvider).toHaveBeenCalledWith(mockEnv.IPFS_GATEWAYS_URL, expect.any(Logger));
125+
expect(IpfsProvider).toHaveBeenCalledWith(mockEnv.IPFS_GATEWAYS_URL, mocks.logger);
105126

106127
// Verify indexer client initialization
107128
expect(EnvioIndexerClient).toHaveBeenCalledWith(
@@ -114,6 +135,7 @@ describe("SharedDependenciesService", () => {
114135
expect(dependencies).toHaveProperty("registriesRepositories");
115136
expect(dependencies).toHaveProperty("indexerClient");
116137
expect(dependencies).toHaveProperty("kyselyDatabase");
138+
expect(dependencies).toHaveProperty("logger");
117139

118140
// Verify core dependencies
119141
expect(dependencies.core).toHaveProperty("projectRepository");

packages/data-flow/src/orchestrator.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isNativeError } from "util/types";
2+
13
import { IIndexerClient } from "@grants-stack-indexer/indexer-client";
24
import {
35
existsHandler,
@@ -106,6 +108,13 @@ export class Orchestrator {
106108
event = this.eventsQueue.pop();
107109

108110
if (!event) {
111+
this.logger.debug(
112+
`No event to process, sleeping for ${this.fetchDelayInMs}ms`,
113+
{
114+
className: Orchestrator.name,
115+
chainId: this.chainId,
116+
},
117+
);
109118
await delay(this.fetchDelayInMs);
110119
continue;
111120
}
@@ -125,6 +134,11 @@ export class Orchestrator {
125134
);
126135
} else if (event.contractName === "Strategy" && "strategyId" in event) {
127136
if (!existsHandler(event.strategyId)) {
137+
this.logger.debug("Skipping event", {
138+
event,
139+
className: Orchestrator.name,
140+
chainId: this.chainId,
141+
});
128142
// we skip the event if the strategy id is not handled yet
129143
continue;
130144
}
@@ -139,6 +153,10 @@ export class Orchestrator {
139153
`Failed to apply changesets. ${executionResult.errors.join("\n")} Event: ${stringify(
140154
event,
141155
)}`,
156+
{
157+
className: Orchestrator.name,
158+
chainId: this.chainId,
159+
},
142160
);
143161
}
144162
} catch (error: unknown) {
@@ -152,12 +170,29 @@ export class Orchestrator {
152170
// `Current event cannot be handled. ${error.name}: ${error.message}. Event: ${stringify(event)}`,
153171
// );
154172
} else {
155-
this.logger.error(`Error processing event: ${stringify(event)} ${error}`);
173+
if (error instanceof Error || isNativeError(error)) {
174+
this.logger.error(error, {
175+
event,
176+
className: Orchestrator.name,
177+
chainId: this.chainId,
178+
});
179+
} else {
180+
this.logger.error(
181+
new Error(`Error processing event: ${stringify(event)} ${error}`),
182+
{
183+
className: Orchestrator.name,
184+
chainId: this.chainId,
185+
},
186+
);
187+
}
156188
}
157189
}
158190
}
159191

160-
this.logger.info("Shutdown signal received. Exiting...");
192+
this.logger.info("Shutdown signal received. Exiting...", {
193+
className: Orchestrator.name,
194+
chainId: this.chainId,
195+
});
161196
}
162197

163198
/**

packages/data-flow/src/registries/event/cachedEventRegistry.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ export class InMemoryCachedEventRegistry implements IEventsRegistry {
3232

3333
/** @inheritdoc */
3434
async saveLastProcessedEvent(chainId: ChainId, event: NewProcessedEvent): Promise<void> {
35-
this.logger.debug(`Saving last processed event: ${stringify(event, undefined, 4)}`);
35+
this.logger.debug(`Saving last processed event: ${stringify(event, undefined, 4)}`, {
36+
className: InMemoryCachedEventRegistry.name,
37+
chainId,
38+
});
3639
await this.eventRegistry.saveLastProcessedEvent(chainId, event);
3740
this.cache.set(chainId, { ...event, chainId });
3841
}

packages/data-flow/src/registries/strategy/cachedStrategyRegistry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ export class InMemoryCachedStrategyRegistry implements IStrategyRegistry {
8383

8484
this.logger.debug(
8585
`Saving strategy id ${strategyId} for address ${strategyAddress} and chainId ${chainId}`,
86+
{
87+
className: InMemoryCachedStrategyRegistry.name,
88+
chainId,
89+
},
8690
);
8791
await this.strategyRegistry.saveStrategyId(chainId, strategyAddress, strategyId, handled);
8892

packages/data-flow/src/registries/strategy/dbStrategyRegistry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export class DatabaseStrategyRegistry implements IStrategyRegistry {
3333
): Promise<void> {
3434
this.logger.debug(
3535
`Saving strategy id ${strategyId} for address ${strategyAddress} and chainId ${chainId} in Database`,
36+
{
37+
className: DatabaseStrategyRegistry.name,
38+
chainId,
39+
},
3640
);
3741
await this.strategyRepository.saveStrategy({
3842
chainId,

0 commit comments

Comments
 (0)