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

Add logger from commons #33

Merged
merged 2 commits into from
Oct 25, 2023
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LOGGER_ENABLED=true
LOG_COLORIZE=true
LOG_FORMAT=pretty
LOG_LEVEL=info
89 changes: 74 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,70 @@ edit `airseeker_v2_pipeline.drawio`, preferably by cloning the repository and lo

## Configuration

Airseeker can be configured via a combination of [environment variables](#environment-variables) and
[configuration files](#configuration-files).

### Environment variables

For example:

```sh
# Defines a logger suitable for production.
LOGGER_ENABLED=true
LOG_COLORIZE=false
LOG_FORMAT=json
LOG_LEVEL=info
```

or

```sh
# Defines a logger suitable for local development or testing.
LOGGER_ENABLED=true
LOG_COLORIZE=false
LOG_FORMAT=json
LOG_LEVEL=info
```

#### `LOGGER_ENABLED`

Enables or disables logging. Options:

- `true` - Enables logging.
- `false` - Disables logging.

#### `LOG_FORMAT`

The format of the log output. Options:

- `json` - Specifies JSON log format. This is suitable when running in production and streaming logs to other services.
- `pretty` - Logs are formatted in a human-friendly "pretty" way. Ideal, when running the service locally and in
development.

#### `LOG_COLORIZE`

Enables or disables colors in the log output. Options:

- `true` - Enables colors in the log output. The output has special color setting characters that are parseable by CLI.
Recommended when running locally and in development.
- `false` - Disables colors in the log output. Recommended for production.

#### `LOG_LEVEL`

Defines the minimum level of logs. Logs with smaller level (severity) will be silenced. Options:

- `debug` - Enables all logs.
- `info` - Enables logs with level `info`, `warn` and `error`.
- `warn` - Enables logs with level `warn` and `error`.
- `error` - Enables logs with level `error`.

### Configuration files

Airseeker needs two configuration files, `airseeker.json` and `secrets.env`. All expressions of a form `${SECRET_NAME}`
are referring to values from secrets and are interpolated inside the `airseeker.json` at runtime. You are advised to put
sensitive information inside secrets.

### `sponsorWalletMnemonic`
#### `sponsorWalletMnemonic`

The mnemonic of the wallet used to derive sponsor wallets. Sponsor wallets are derived for each dAPI separately. It is
recommended to interpolate this value from secrets. For example:
Expand All @@ -34,7 +93,7 @@ recommended to interpolate this value from secrets. For example:
"sponsorWalletMnemonic": "${SPONSOR_WALLET_MNEMONIC}",
```

### `chains`
#### `chains`

A record of chain configurations. The record key is the chain ID. For example:

Expand All @@ -51,61 +110,61 @@ A record of chain configurations. The record key is the chain ID. For example:
}
```

#### `contracts` _(optional)_
##### `contracts` _(optional)_

A record of contract addresses used by Airseeker. If not specified, the addresses are loaded from
[Airnode protocol v1](https://github.com/api3dao/airnode-protocol-v1).

##### Api3ServerV1 _(optional)_
###### Api3ServerV1 _(optional)_

The address of the Api3ServerV1 contract. If not specified, the address is loaded from the Airnode protocol v1
repository.

#### `providers`
##### `providers`

A record of providers. The record key is the provider name. Provider name is only used for internal purposes and to
uniquely identify the provider for the given chain.

##### `providers[<NAME>]`
###### `providers[<NAME>]`

A provider configuration.

###### `url`
`url`

The URL of the provider.

#### `__Temporary__DapiDataRegistry`
##### `__Temporary__DapiDataRegistry`

The data needed to make the requests to signed API. This data will in the future be stored on-chain in a
`DapiDataRegistry` contract. For the time being, they are statically defined in the configuration file.

##### `airnodeToSignedApiUrl`
###### `airnodeToSignedApiUrl`

A mapping from Airnode address to signed API URL. When data from particular beacon is needed a request is made to the
signed API corresponding to the beacon address.

##### `dataFeedIdToBeacons`
###### `dataFeedIdToBeacons`

A mapping from data feed ID to a list of beacon data.

##### `dataFeedIdToBeacons<DATA_FEED_ID>`
###### `dataFeedIdToBeacons<DATA_FEED_ID>`

A single element array for a beacon data. If the data feed is a beacon set, the array contains the data for all the
beacons in the beacon set (in correct order).

###### `dataFeedIdToBeacons<DATA_FEED_ID>[n]`
`dataFeedIdToBeacons<DATA_FEED_ID>[n]`

A beacon data.

`airnode`
`dataFeedIdToBeacons<DATA_FEED_ID>[n].airnode`

The Airnode address of the beacon.

`templateId`
`dataFeedIdToBeacons<DATA_FEED_ID>[n].templateId`

The template ID of the beacon.

### `deviationThresholdCoefficient`
#### `deviationThresholdCoefficient`

The global coefficient applied to all deviation checks. Used to differentiate alternate deployments. For example:

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
"dependencies": {
"@api3/airnode-protocol-v1": "^2.10.0",
"@api3/commons": "^0.2.0",
"@api3/commons": "^0.3.0",
"@api3/promise-utils": "^0.4.0",
"axios": "^1.5.1",
"dotenv": "^16.3.1",
Expand Down
10 changes: 4 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions src/env/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { join } from 'node:path';

import dotenv from 'dotenv';

import { type EnvConfig, envConfigSchema } from './schema';

let env: EnvConfig | undefined;

export const loadEnv = () => {
if (env) return env;

dotenv.config({ path: join(__dirname, '../.env') });

const parseResult = envConfigSchema.safeParse(process.env);
if (!parseResult.success) {
throw new Error(`Invalid environment variables:\n, ${JSON.stringify(parseResult.error.format())}`);
}

env = parseResult.data;
return env;
};
45 changes: 45 additions & 0 deletions src/env/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { type LogFormat, logFormatOptions, logLevelOptions, type LogLevel } from '@api3/commons';
import { z } from 'zod';

export const envBooleanSchema = z.union([z.literal('true'), z.literal('false')]).transform((val) => val === 'true');

// We apply default values to make it convenient to omit certain environment variables. The default values should be
// primarily focused on users and production usage.
export const envConfigSchema = z
.object({
LOG_COLORIZE: envBooleanSchema.default('false'),
LOG_FORMAT: z
.string()
.transform((value, ctx) => {
if (!logFormatOptions.includes(value as any)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Invalid LOG_FORMAT',
path: ['LOG_FORMAT'],
});
return;
}

return value as LogFormat;
})
.default('json'),
LOG_LEVEL: z
.string()
.transform((value, ctx) => {
if (!logLevelOptions.includes(value as any)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Invalid LOG_LEVEL',
path: ['LOG_LEVEL'],
});
return;
}

return value as LogLevel;
})
.default('info'),
LOGGER_ENABLED: envBooleanSchema.default('true'),
})
.strip(); // We parse from ENV variables of the process which has many variables that we don't care about

export type EnvConfig = z.infer<typeof envConfigSchema>;
34 changes: 9 additions & 25 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
const debug = (...args: any[]) =>
// eslint-disable-next-line no-console
console.debug(...args);
const error = (...args: any[]) =>
// eslint-disable-next-line no-console
console.error(...args);
const info = (...args: any[]) => console.info(...args);
const log = (...args: any[]) =>
// eslint-disable-next-line no-console
console.log(...args);
const warn = (...args: any[]) =>
// eslint-disable-next-line no-console
console.warn(...args);
import { createLogger } from '@api3/commons';
import { loadEnv } from './env/env';

export const logErrors = (promiseResults: PromiseSettledResult<any>[], additionalText = '') => {
for (const rejectedPromise of promiseResults.filter((result) => result.status === 'rejected')) {
error(additionalText, rejectedPromise);
}
};
const env = loadEnv();

export const logger = {
debug,
error,
info,
log,
warn,
};
export const logger = createLogger({
colorize: env.LOG_COLORIZE,
enabled: env.LOGGER_ENABLED,
minLevel: env.LOG_LEVEL,
format: env.LOG_FORMAT,
});
9 changes: 2 additions & 7 deletions src/signed-api-fetch/data-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { clearInterval } from 'node:timers';
import { go, goSync } from '@api3/promise-utils';
import { go } from '@api3/promise-utils';
import axios from 'axios';
import { uniq } from 'lodash';
import { signedApiResponseSchema, type SignedData } from '../types';
import * as localDataStore from '../signed-data-store';
import { getState, setState } from '../state';
import { logger } from '../logger';
import { HTTP_SIGNED_DATA_API_ATTEMPT_TIMEOUT, HTTP_SIGNED_DATA_API_HEADROOM } from '../constants';

// Express handler/endpoint path: https://github.com/api3dao/signed-api/blob/b6e0d0700dd9e7547b37eaa65e98b50120220105/packages/api/src/server.ts#L33
Expand Down Expand Up @@ -76,11 +75,7 @@ export const runDataFetcher = async () => {
const payload = await callSignedDataApi(url);

for (const element of payload) {
const result = goSync(() => localDataStore.setStoreDataPoint(element));

if (!result.success) {
logger.warn('Error while storing datapoint in data store.', { ...result.error });
}
localDataStore.setStoreDataPoint(element);
}
},
{
Expand Down
Loading