-
Notifications
You must be signed in to change notification settings - Fork 3
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
feat: Introducing Logging Class (Disabled Usage of Logger) #608
Conversation
cases until completion:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice improvements. Left various comments. I think once you've addressed all of these, you can open up the PR to get full feedback. But, you also need to create a PR for the infra repo to add cron. And make sure that the cron works. Make sure you understand what dependency on infrastructure there is for this code change. If Infra changes need to be released first, don't merge this PR until the infra changes are up.
const pgClient = pgClientInstance ?? new PgClient({ | ||
user: databaseConnectionParameters.username, | ||
password: databaseConnectionParameters.password, | ||
host: process.env.PGHOST, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
host: process.env.PGHOST, | |
host: databaseConnectionParameters.host |
With the talk of multiple database instances there is potential that this could be wrong, it's unlikely, but will avoid headache in future :)
This PR expands provisioning to also schedule the cron jobs for adding/deleting log partitions. It assumes: 1. The `cron` database exists and has `pg_cron` enabled (near/near-ops#1665) 2. The `__logs` table exists and has the partition functions defined (#608) In relation to this flow, the high-level steps are: 1. Use an admin connection to the `cron` database to grant the required access to the user 2. Use a user connection to the `cron` database to schedule the jobs The cron job is executed under the user which schedules the job, therefore the user _must_ schedule the job as they are the only ones who have access to their schemas. If the admin were to schedule the job the job itself would fail as it doesn't have the required access. Merging this before 2. is fine, the jobs will just fail, but should start to succeed after it has been implemented.
…p redundancy, removed code in provisioner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a new round of comments. It's definitely real close to being ready. I also unresolved some comments which were not actually addressed.
My suggestion is to resolve the comment only after you made that particular code change and committed it locally. Or resolve them after pushing changes up to the PR. This way, you don't lose track of them. Resolving is not necessary to merging a PR, so do what works to ensure they're handled.
…ement-the-new-logs-schema
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple problems with this PR which I've commented inline.
This PR has become massive, and very confusing to track due to the number of comments. That's partly my fault, sorry. This definitely needs more work, but I think it would create more confusion if done here.
I'd recommend merging this with the main functionality commented out:
- Provisioning of the logs table (
runLogsSql()
call inProvisioner
) - Flushing the logs to the database (the
writeLogs
call inPromise.all
inIndexer
)
That way where not running any new and potentially problematic code, and can make changes to it under the hood.
With the above disabled, functionality should be exactly as it is on main
. Therefore indexer.test.ts
and integration.test.ts
can be reverted to their original state and should pass. That should give us a fair amount of confidence that what we are pushing is not going to break anything.
With all that done, we should collate a list of pending tasks which we can break up in to future smaller PRs.
Let me know if you need help with any of this. I'm happy to sync up.
runner/tests/integration.test.ts
Outdated
@@ -54,6 +54,32 @@ describe('Indexer integration', () => { | |||
database: postgresContainer.getDatabase(), | |||
}); | |||
|
|||
const mockPgClient = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of integration tests are for integrating with real components, can you please remove these mocks.
You can use the hasuraClient
to get the real DB credentials and inject those in to IndexerLogger
. Happy to sync up if you need help here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah will go ahead and use the real instance here
runner/src/indexer/indexer.test.ts
Outdated
const indexer = new Indexer(defaultIndexerBehavior, { fetch: mockFetch as unknown as typeof fetch, DmlHandler: genericMockDmlHandler }, undefined, undefined, config); | ||
const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, HASURA_ROLE); | ||
const indexer = new Indexer(defaultIndexerBehavior, { fetch: mockFetch as unknown as typeof fetch, DmlHandler: genericMockDmlHandler, IndexerLogger: genericMockIndexerLogger }, undefined, undefined, undefined, config); | ||
const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, HASURA_ROLE, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add some tests for logging itself? We should assert that the logs themselves actually translate to calls to Postgres
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is done in indexer-logger.test.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, you're right. But, we're not testing the IndexerLogger
is actually being used within Indexer
, i.e. If I have an Indexer which logs message, are we actually calling IndexerLogger
? That flow is not covered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's being covered along with loglevel. there's a specific area in the test called "writeLog is respecting level". It is indexer.test.ts.I believe we create an indexer and initialize the logger in runfunction. We test to see if the log level is a certain level it calls writeLog in logger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No what he means is we check how many times and what logs were given to IndexerLogger in Indexer.
For example, some tests to verify:
We run an indexer which calls 5 logs, some with debug level with an info config. We then expect that IndexerLogger's writeLogs is called once. And then expect it was called with ALL FIVE LOGS since the filtering is done internally.
In other words, we don't care if IndexerLogger does the right thing. But we do care if Indexer uses the logger class correctly. So, check scenarios like, the indexer throws an error but we still call writeLogs. Or we call it with all logs produced in various scenarios. Stuff like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super duper close. Some problems with the implementation of testing and some cleanup you can do with the Indexer code.
runner/src/indexer/indexer.test.ts
Outdated
format: jest.fn().mockReturnValue('mock') | ||
} as unknown as PgClient; | ||
|
||
const indexerLoggerInstance: any = new IndexerLogger( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to actually test the implementation of indexerLogger in this suite of tests. If there is a bug in IndexerLogger, it'll show up here, which will be confusing. Please create a mock of indexerLogger with pass through functions (All functions are a jest.fn() that does the right thing) as a generic.
runner/src/indexer/indexer.test.ts
Outdated
@@ -225,7 +242,7 @@ CREATE TABLE | |||
shards: {} | |||
} as unknown as StreamerMessage) as unknown as Block; | |||
|
|||
const indexer = new Indexer(defaultIndexerBehavior, { fetch: mockFetch as unknown as typeof fetch, provisioner: genericProvisioner, DmlHandler: genericMockDmlHandler }, undefined, undefined, config); | |||
const indexer = new Indexer(defaultIndexerBehavior, { fetch: mockFetch as unknown as typeof fetch, provisioner: genericProvisioner, DmlHandler: genericMockDmlHandler, IndexerLogger: genericMockIndexerLogger }, undefined, undefined, indexerLoggerInstance, config); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable you pass into the { ... } is used differently than the one you pass in after the undefined.
The one in the {} is supposed to be used to initialize the class variable indexer_logger if it isn't already defined. You actually no longer need this as you never use it. I mentioned that later in the PR.
The indexerLoggerInstance should in fact be whatever you have genericMockIndexerLogger as.
runner/src/indexer/indexer.test.ts
Outdated
@@ -273,9 +290,9 @@ CREATE TABLE | |||
} | |||
}) | |||
}); | |||
const indexer = new Indexer(defaultIndexerBehavior, { fetch: mockFetch as unknown as typeof fetch, DmlHandler: genericMockDmlHandler }, undefined, undefined, config); | |||
const indexer = new Indexer(defaultIndexerBehavior, { fetch: mockFetch as unknown as typeof fetch, DmlHandler: genericMockDmlHandler, IndexerLogger: genericMockIndexerLogger }, undefined, undefined, undefined, config); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the behavior here is like this. For DmlHandler, there's genericMockDmlHandler in the deps object. And undefined passed as dmlHandler. We're forced to do this in order to pass in Config.
Later on, if runFunctions is called we will check if dmlHandler is not undefined. But we pass in undefined here. So, what would happen is we would then use deps to generate the object and set it. This is where we can inject our mock.
It's actually no longer necessary now to have DmlHandlerClass passed into deps now since we can avoid the creation of an actual object now by passing in a mock DmlHandler into the class directly.
Anyway, the reason I explain this all now is that you can use that to properly use IndexerLogger's mocks during testing. In this case, it's mocking the entire class, and we can probably remove it from deps.
Don't worry about changing how DmlHandler doe sit. I'll test a fix of that later myself.
runner/tests/integration.test.ts
Outdated
@@ -54,6 +54,32 @@ describe('Indexer integration', () => { | |||
database: postgresContainer.getDatabase(), | |||
}); | |||
|
|||
const mockPgClient = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we're supposed to use mocks in an integration test. That defeats the point. That being said, I don't think this should be a blocker for you to get this PR out as it's gonna take you more time to figure. out usage of this. So, it's ok for now I think. Maybe @morgsmccauley can chime in here.
Lots of remaining work. so we are going to do a phased rollout of these changes.
|
Offline resolved Morgan's comments. Doing requested phased rollout
4 PRs.
|
The provisioning flow will not be run for existing Indexers, this PR adds a separate provisioning check/step which sets up the partitioned logs table for existing users. I've opted for a in-code approach as a "manual" migration script requires specific timing, i.e. we'd need to deploy the logs change, ensuring all new Indexers are provisioned correct, and then migrate all existing users to ensure that no Indexers are missed. But since the logs provisioning change is coupled with the logging itself, existing Indexers would fail to log until the migration is complete. My only concern for this approach is a "thundering herd". After this is deployed, all Indexers will attempt to provision there logs table at the same time - I'll monitor this in Dev. As this code is temporary, I didn't bother adding instrumentation/unit-tests, nor worry about the performance impact. It will be removed promptly. This is dependant on #608 and should be merged after.
Added code for writing logs to new logs table through Postgres instead of Hasura.
https://www.loom.com/share/ff21d7099cac403d9152c905f7e4ddcc?sid=5828ae99-377b-4510-ac8c-76c02fd232f2