Skip to content
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

Merged
merged 38 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e49e4ae
feat: prototyped new logs schema
Kevin101Zhang Mar 19, 2024
cc7a10a
prototype indexer-logger class
Kevin101Zhang Mar 25, 2024
a82aa7b
chore: removed test code
Kevin101Zhang Mar 25, 2024
4f33dd4
rebase
Kevin101Zhang Mar 26, 2024
dc80f7e
moved ddl, mocked batch func, updated createLogs calls after provisio…
Kevin101Zhang Mar 26, 2024
56dea50
add batch insert
Kevin101Zhang Mar 26, 2024
e1fb2e9
removed local variable for database connections
Kevin101Zhang Mar 26, 2024
7648295
added tracer
Kevin101Zhang Mar 26, 2024
ad1f5ab
added pg-format, add constrcutor, removed unneccessary code, timestam…
Kevin101Zhang Mar 29, 2024
58623aa
Merge branch 'main' of https://github.com/near/queryapi into 299-impl…
Kevin101Zhang Mar 31, 2024
039789d
fix: readded indexer snap file
Kevin101Zhang Mar 31, 2024
81824ce
fix: modified logger-test
Kevin101Zhang Mar 31, 2024
57b6bda
fix: removed cron from logs-table
Kevin101Zhang Mar 31, 2024
8afef13
fix: removed log_ prefix
Kevin101Zhang Mar 31, 2024
cc37561
combined calls in indexer-logs, additional minor fixes
Kevin101Zhang Apr 1, 2024
f3c4b37
chore:lint indexer.ts
Kevin101Zhang Apr 1, 2024
7bb8e4e
fix: refactored indexer-logger, ran eslint, adjusted indexer.ts
Kevin101Zhang Apr 2, 2024
4083dda
fix: added test to skip log when logLevel<, renamed trace name
Kevin101Zhang Apr 2, 2024
5265128
chore: added newline at eof in docker
Kevin101Zhang Apr 2, 2024
eea34ce
fix: renamed runSql to executeSqlOnSchema
Kevin101Zhang Apr 2, 2024
9d13c85
removed duplicate postgres.dockerfile
Kevin101Zhang Apr 2, 2024
cf8803f
feat: LogLevel is now owned by IndexerLogger
Kevin101Zhang Apr 3, 2024
04a2eb3
added single typed arguments, schema blockheight is now optional
Kevin101Zhang Apr 3, 2024
f0f46f9
fix: corrected pathing for test
Kevin101Zhang Apr 3, 2024
470eab3
Merge branch 'main' into 299-implement-the-new-logs-schema
Kevin101Zhang Apr 3, 2024
849dec6
fix: docker-compose reverted to main
Kevin101Zhang Apr 3, 2024
97390b0
updated indexer test params, instantiated indexerlogger
Kevin101Zhang Apr 3, 2024
c3b5f49
chore:lint
Kevin101Zhang Apr 3, 2024
f609269
fix: added indexerLogger to latest param to integration test
Kevin101Zhang Apr 3, 2024
296401c
updated mutation name from writeLogOld to writeLog
Kevin101Zhang Apr 4, 2024
7254ad8
temp: callWriteLog for streamhandler
Kevin101Zhang Apr 4, 2024
eff6eda
fix: readded back funccall in streamhandler
Kevin101Zhang Apr 4, 2024
6aa96f1
commented out streamhandler/provisioner/test
Kevin101Zhang Apr 4, 2024
d49cb12
reverted back indexer/test
Kevin101Zhang Apr 4, 2024
a5e1af9
reverted integration test
Kevin101Zhang Apr 4, 2024
2190587
repositioned runningMessage in indexer
Kevin101Zhang Apr 4, 2024
6574db8
added new line snapfile
Kevin101Zhang Apr 4, 2024
b816f9f
renamed provisioner methods
Kevin101Zhang Apr 4, 2024
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
70 changes: 69 additions & 1 deletion runner/src/hasura-client/hasura-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,75 @@ export default class HasuraClient {
});
}

async createLogsTable (source: string, schemaName: string): Promise<any> {
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
return await this.executeSql(
`
set schema '${schemaName}';

CREATE TABLE __logs (
id BIGSERIAL NOT NULL,
block_height NUMERIC(20) NOT NULL,
log_date DATE NOT NULL,
log_timestamp TIMESTAMP NOT NULL,
log_type TEXT NOT NULL,
log_level TEXT NOT NULL,
message TEXT NOT NULL,
PRIMARY KEY (log_date, id)
) PARTITION BY RANGE (log_date);

CREATE INDEX logs_log_timestamp_idx ON __logs USING btree (log_timestamp);
CREATE INDEX logs_log_type_idx ON __logs USING btree (log_type);
CREATE INDEX logs_log_level_idx ON __logs USING btree (log_level);
CREATE INDEX logs_block_height_idx ON __logs USING btree (block_height);
CREATE INDEX logs_search_vector_idx ON __logs USING GIN (to_tsvector('english', message));


CREATE OR REPLACE FUNCTION fn_create_partition(_tbl text, _date date, _interval_start text, _interval_end text)
RETURNS void
LANGUAGE plpgsql AS
$func$
DECLARE
_start text;
_end text;
_partition_name text;
BEGIN
_start := TO_CHAR(date_trunc('day', _date + (_interval_start)::interval), 'YYYY-MM-DD');
_end := TO_CHAR(date_trunc('day', _date + (_interval_end)::interval), 'YYYY-MM-DD');
_partition_name := TO_CHAR(date_trunc('day', _date + (_interval_start)::interval), 'YYYYMMDD');
-- Create partition
EXECUTE 'CREATE TABLE IF NOT EXISTS ' || _tbl || '_p' || _partition_name || ' PARTITION OF ' || _tbl || ' FOR VALUES FROM (''' || _start || ''') TO (''' || _end || ''')';
END
$func$;

SELECT fn_create_partition('${schemaName}.__logs', CURRENT_DATE, '0 day', '1 day');
SELECT fn_create_partition('${schemaName}.__logs', CURRENT_DATE, '1 day', '2 day');

CREATE OR REPLACE FUNCTION fn_delete_partition(_tbl text, _date date, _interval_start text, _interval_end text)
RETURNS void
LANGUAGE plpgsql AS
$func$
DECLARE
_start text;
_end text;
_partition_name text;
BEGIN
_start := TO_CHAR(date_trunc('day', _date + (_interval_start)::interval), 'YYYY-MM-DD');
_end := TO_CHAR(date_trunc('day', _date + (_interval_end)::interval), 'YYYY-MM-DD');
_partition_name := TO_CHAR(date_trunc('day', _date + (_interval_start)::interval), 'YYYYMMDD');
-- Detach partition
EXECUTE 'ALTER TABLE ' || _tbl || ' DETACH PARTITION ' || _tbl || '_p' || _partition_name;
EXECUTE 'DROP TABLE ' || _tbl || '_p' || _partition_name;
END
$func$;

-- GRANT CREATE, USAGE ON SCHEMA cron TO ${source};
-- SELECT cron.schedule('${schemaName}___logs_create_partition', '0 1 * * *', $$SELECT fn_create_partition('${schemaName}.logs', CURRENT_DATE, '1 day', '2 day')$$);
-- SELECT cron.schedule('${schemaName}___logs_delete_partition', '0 2 * * *', $$SELECT fn_delete_partition('${schemaName}.logs', CURRENT_DATE, '-15 day', '-14 day')$$);
`,
{ source, readOnly: false }
);
}
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved

async runMigrations (source: string, schemaName: string, migration: string): Promise<any> {
return await this.executeSql(
`
Expand All @@ -146,7 +215,6 @@ export default class HasuraClient {
source,
}
);

Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
return tablesInSource
.filter(({ schema }: { schema: string }) => schema === schemaName)
.map(({ name }: { name: string }) => name);
Expand Down
83 changes: 83 additions & 0 deletions runner/src/indexer-logger/indexer-logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import pgFormat from 'pg-format';
import IndexerLogger from './indexer-logger';
import PgClient from '../pg-client';
import { LogLevel } from '../stream-handler/stream-handler';

describe('IndexerLogger', () => {
let pgClient: PgClient;
let query: any;

beforeEach(() => {
query = jest.fn().mockReturnValue({ rows: [] });
pgClient = {
query,
format: pgFormat
} as unknown as PgClient;
});

const mockDatabaseConnectionParameters = {
username: 'test_user',
password: 'test_password',
host: 'test_host',
port: 5432,
database: 'test_database'
};

describe('create', () => {
it('should create an instance of IndexerLogger with provided PgClient instance', () => {
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
const indexerLogger = IndexerLogger.create(mockDatabaseConnectionParameters, pgClient);
expect(indexerLogger).toBeInstanceOf(IndexerLogger);
});

it('should create an instance of IndexerLogger with a new PgClient instance if none provided', () => {
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
const indexerLogger = IndexerLogger.create(mockDatabaseConnectionParameters);
expect(indexerLogger).toBeInstanceOf(IndexerLogger);
});
});

describe('writeLog', () => {
it('should call the query method with the correct parameters', async () => {

const spyFormatDate = jest.spyOn(IndexerLogger.prototype, 'formatDate');

const indexerLogger = IndexerLogger.create(mockDatabaseConnectionParameters, pgClient);
const blockHeight = 123;
const functionName = 'testFunction';
const logDate = new Date();
const logTimestamp = new Date();
const logType = 'system';
const logLevel = LogLevel.INFO;
const message = 'Test message';

await indexerLogger.writeLog(
blockHeight,
functionName,
logDate,
logTimestamp,
logType,
logLevel,
message
);

expect(typeof blockHeight).toBe('number');
expect(typeof functionName).toBe('string');
expect(logDate).toBeInstanceOf(Date);
expect(logTimestamp).toBeInstanceOf(Date);
expect(typeof logType).toBe('string');
expect(typeof logLevel).toBe('number');
expect(typeof message).toBe('string');
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved

expect(spyFormatDate).toHaveBeenCalledWith(logDate);
expect(query.mock.calls.length).toBe(1);
expect(query.mock.calls[0][0]).toEqual('INSERT INTO testFunction.__logs (block_height, log_date, log_timestamp, log_type, log_level, message) VALUES ($1, $2, $3, $4, $5, $6)');
expect(query.mock.calls[0][1]).toEqual([
blockHeight,
expect.any(Date),
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
logTimestamp,
logType,
LogLevel[logLevel],
message
]);
});
});
});
60 changes: 60 additions & 0 deletions runner/src/indexer-logger/indexer-logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { wrapError } from '../utility';
import PgClient from '../pg-client';
import { type DatabaseConnectionParameters } from '../provisioner/provisioner';
import { LogLevel } from '../stream-handler/stream-handler';

export default class IndexerLogger {

Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
private constructor (
private readonly pgClient: PgClient
) {}

Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
private extractDateParts(date: Date): { year: number, month: number, day: number } {
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
return {
year: date.getFullYear(),
month: date.getMonth(),
day: date.getDate()
};
}

formatDate(date: Date): Date {
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
const { year, month, day } = this.extractDateParts(date);
return new Date(year, month, day);
}

static create (
databaseConnectionParameters: DatabaseConnectionParameters,
pgClientInstance: PgClient | undefined = undefined
): IndexerLogger {
const pgClient = pgClientInstance ?? new PgClient({
user: databaseConnectionParameters.username,
password: databaseConnectionParameters.password,
host: process.env.PGHOST,
port: Number(databaseConnectionParameters.port),
database: databaseConnectionParameters.database,
});
return new IndexerLogger(pgClient);
}

async writeLog(
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
blockHeight: number,
functionName: string,
logDate: Date,
logTimestamp: Date,
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
logType: string,
logLevel: LogLevel,
message: string,
): Promise<void> {
const schemaName = functionName.replace(/[^a-zA-Z0-9]/g, '_');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't imagine we'd have a single IndexerLogger instance log for multiple different indexers, it's really even possible given that each indexer has it's own thread.

This would be better suited as a class property, which is passed in during construction, i.e. we instantiate an IndexerLogger for a particular Indexer. This would simplify LogEntry.


const formattedLogDate = this.formatDate(logDate);
const logLevelString = LogLevel[logLevel];

const query =
`INSERT INTO ${schemaName}.__logs (block_height, log_date, log_timestamp, log_type, log_level, message) VALUES ($1, $2, $3, $4, $5, $6)`;

const values = [blockHeight, formattedLogDate, logTimestamp, logType, logLevelString, message];

await wrapError(async () => await this.pgClient.query(query, values), `Failed to execute '${query}' on ${schemaName}`);
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
}
}
Kevin101Zhang marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading