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 / log service #539

Merged
merged 1 commit into from
Jul 4, 2024
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
feat: add log service
  • Loading branch information
ChristiaanScheermeijer committed Jul 4, 2024
commit 35303c89743e6aeb95e05f0dc4a91836aba381cd
13 changes: 9 additions & 4 deletions packages/common/src/controllers/AccountController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import i18next from 'i18next';
import { inject, injectable } from 'inversify';

import { DEFAULT_FEATURES } from '../constants';
import { logDev } from '../utils/common';
import type { IntegrationType } from '../../types/config';
import CheckoutService from '../services/integrations/CheckoutService';
import AccountService, { type AccountServiceFeatures } from '../services/integrations/AccountService';
Expand All @@ -24,6 +23,7 @@ import { useAccountStore } from '../stores/AccountStore';
import { useConfigStore } from '../stores/ConfigStore';
import { useProfileStore } from '../stores/ProfileStore';
import { FormValidationError } from '../errors/FormValidationError';
import { logError } from '../logger';

import WatchHistoryController from './WatchHistoryController';
import ProfileController from './ProfileController';
Expand Down Expand Up @@ -68,7 +68,7 @@ export default class AccountController {
await this.getAccount();
}
} catch (error: unknown) {
logDev('Failed to get user', error);
logError('AccountController', 'Failed to get user', { error });

// clear the session when the token was invalid
// don't clear the session when the error is unknown (network hiccup or something similar)
Expand Down Expand Up @@ -386,7 +386,12 @@ export default class AccountController {
return !!responseData?.accessGranted;
};

reloadSubscriptions = async ({ delay, retry }: { delay?: number; retry?: number } = { delay: 0, retry: 0 }): Promise<unknown> => {
reloadSubscriptions = async (
{ delay, retry }: { delay?: number; retry?: number } = {
delay: 0,
retry: 0,
},
): Promise<unknown> => {
useAccountStore.setState({ loading: true });

const { getAccountInfo } = useAccountStore.getState();
Expand Down Expand Up @@ -436,7 +441,7 @@ export default class AccountController {
pendingOffer = offerResponse.responseData;
}
} catch (error: unknown) {
logDev('Failed to fetch the pending offer', error);
logError('AccountController', 'Failed to fetch the pending offer', { error });
}

// let the app know to refresh the entitlements
Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/controllers/AppController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Config } from '../../types/config';
import type { CalculateIntegrationType } from '../../types/calculate-integration-type';
import { DETERMINE_INTEGRATION_TYPE } from '../modules/types';
import { useConfigStore } from '../stores/ConfigStore';
import { logDebug } from '../logger';

import WatchHistoryController from './WatchHistoryController';
import FavoritesController from './FavoritesController';
Expand Down Expand Up @@ -63,6 +64,8 @@ export default class AppController {
};

initializeApp = async (url: string, refreshEntitlements?: () => Promise<void>) => {
logDebug('AppController', 'Initializing app', { url });

const settings = await this.settingsService.initialize();
const configSource = await this.settingsService.getConfigSource(settings, url);
const config = await this.loadAndValidateConfig(configSource);
Expand Down
12 changes: 2 additions & 10 deletions packages/common/src/controllers/EpgController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import livePlaylistFixture from '@jwp/ott-testing/fixtures/livePlaylist.json';

import EpgService from '../services/EpgService';
import type { Playlist } from '../../types/playlist';
import { mockService } from '../../test/mockService';

import EpgController from './EpgController';

Expand All @@ -13,15 +14,6 @@ const livePlaylist = livePlaylistFixture as Playlist;
const transformProgram = vi.fn();
const fetchSchedule = vi.fn();

vi.mock('@jwp/ott-common/src/modules/container', () => ({
getNamedModule: (type: typeof EpgService) => {
switch (type) {
case EpgService:
return { transformProgram, fetchSchedule };
}
},
}));

const epgController = new EpgController();

const mockProgram1 = {
Expand All @@ -46,13 +38,13 @@ const mockProgram2 = {

describe('epgService', () => {
beforeEach(() => {
mockService(EpgService, { transformProgram, fetchSchedule });
vi.useFakeTimers();
});

afterEach(() => {
// must be called before `vi.useRealTimers()`
unregister();
vi.restoreAllMocks();
vi.useRealTimers();
});

Expand Down
8 changes: 4 additions & 4 deletions packages/common/src/controllers/EpgController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { injectable } from 'inversify';
import EpgService from '../services/EpgService';
import { EPG_TYPE } from '../constants';
import { getNamedModule } from '../modules/container';
import { logDev } from '../utils/common';
import type { PlaylistItem } from '../../types/playlist';
import type { EpgChannel, EpgProgram } from '../../types/epg';
import { logDebug, logError, logWarn } from '../logger';

export const isFulfilled = <T>(input: PromiseSettledResult<T>): input is PromiseFulfilledResult<T> => {
if (input.status === 'fulfilled') {
return true;
}

logDev(`An error occurred resolving a promise: `, input.reason);
logError('EpgController', `An error occurred resolving a promise: `, { error: input.reason });
return false;
};

Expand Down Expand Up @@ -58,7 +58,7 @@ export default class EpgController {
?.transformProgram(program)
// This quiets promise resolution errors in the console
.catch((error) => {
logDev(error);
logDebug('EpgController', 'Failed to transform a program', { error, program });
return undefined;
}),
),
Expand Down Expand Up @@ -100,7 +100,7 @@ export default class EpgController {
const service = getNamedModule(EpgService, scheduleType, false);

if (!service) {
console.error(`No epg service was added for the ${scheduleType} schedule type`);
logWarn('EpgController', `No epg service was added for the ${scheduleType} schedule type`);
}

return service;
Expand Down
26 changes: 26 additions & 0 deletions packages/common/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getAllModules } from './modules/container';
import { LogLevel } from './services/logging/LogLevel';
import LogTransporter from './services/logging/LogTransporter';

export type LogParams = { error?: unknown; [key: string]: unknown };

const wrapError = (error: unknown) => {
return error instanceof Error ? error : new Error(String(error));
};

export const makeLogFn =
(logLevel: LogLevel) =>
(scope: string, message: string, { error, ...extra }: LogParams = {}) => {
const transporters = getAllModules(LogTransporter);

// call log on all transporters, the transporters should decide to handle the call or not
transporters.forEach((transporter) => {
transporter.log(logLevel, scope, message, extra, error ? wrapError(error) : undefined);
});
};

export const logDebug = makeLogFn(LogLevel.DEBUG);
export const logInfo = makeLogFn(LogLevel.INFO);
export const logWarn = makeLogFn(LogLevel.WARN);
export const logError = makeLogFn(LogLevel.ERROR);
export const logFatal = makeLogFn(LogLevel.FATAL);
9 changes: 6 additions & 3 deletions packages/common/src/modules/container.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Container, injectable, type interfaces, inject } from 'inversify';

import { logDev } from '../utils/common';

export const container = new Container({ defaultScope: 'Singleton', skipBaseClassChecks: true });

export { injectable, inject };
Expand All @@ -17,6 +15,10 @@ export function getModule<T>(constructorFunction: interfaces.ServiceIdentifier<T
return module;
}

export function getAllModules<T>(constructorFunction: interfaces.ServiceIdentifier<T>): T[] {
return container.getAll(constructorFunction);
}

export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required: false): T | undefined;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null, required: true): T;
export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentifier<T>, name: string | null): T;
Expand All @@ -40,7 +42,8 @@ export function getNamedModule<T>(constructorFunction: interfaces.ServiceIdentif
return;
}

logDev('Error caught while initializing service', err);
// log service can't be used here
console.error('Error caught while initializing service', err);
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/common/src/services/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { ContentList, GetContentSearchParams } from '../../types/content-li
import type { AdSchedule } from '../../types/ad-schedule';
import type { EpisodeInSeries, EpisodesRes, EpisodesWithPagination, GetSeriesParams, Series } from '../../types/series';
import env from '../env';
import { logError } from '../logger';

// change the values below to change the property used to look up the alternate image
enum ImageProperty {
Expand Down Expand Up @@ -38,7 +39,7 @@ export default class ApiService {
const date = item[prop] as string | undefined;

if (date && !isValid(new Date(date))) {
console.error(`Invalid "${prop}" date provided for the "${item.title}" media item`);
logError('ApiService', `Invalid "${prop}" date provided for the "${item.title}" media item`, { error: new Error('Invalid date') });
return undefined;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/common/src/services/FavoriteService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { inject, injectable } from 'inversify';
import { object, array, string } from 'yup';
import { array, object, string } from 'yup';

import type { Favorite, SerializedFavorite } from '../../types/favorite';
import type { PlaylistItem } from '../../types/playlist';
import type { Customer } from '../../types/account';
import { getNamedModule } from '../modules/container';
import { INTEGRATION_TYPE } from '../modules/types';
import { logDev } from '../utils/common';
import { MAX_WATCHLIST_ITEMS_COUNT } from '../constants';
import { logError } from '../logger';

import ApiService from './ApiService';
import StorageService from './StorageService';
Expand Down Expand Up @@ -66,7 +66,7 @@ export default class FavoriteService {

return (playlistItems || []).map((item) => this.createFavorite(item));
} catch (error: unknown) {
logDev('Failed to get favorites', error);
logError('FavoriteService', 'Failed to get favorites', { error });
}

return [];
Expand Down
12 changes: 6 additions & 6 deletions packages/common/src/services/SettingsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import ini from 'ini';
import { getI18n } from 'react-i18next';

import { CONFIG_FILE_STORAGE_KEY, CONFIG_QUERY_KEY, OTT_GLOBAL_PLAYER_ID } from '../constants';
import { logDev } from '../utils/common';
import { AppError } from '../utils/error';
import type { Settings } from '../../types/settings';
import env from '../env';
import { logDebug, logWarn } from '../logger';

import StorageService from './StorageService';

Expand Down Expand Up @@ -44,7 +44,7 @@ export default class SettingsService {
return configKey;
}

logDev(`Invalid app-config query param: ${configKey}`);
logWarn('SettingsService', `Invalid app-config query param: ${configKey}`);
}
// Yes this falls through from above to look up the stored value if the query string is invalid and that's OK

Expand All @@ -56,7 +56,7 @@ export default class SettingsService {
return storedSource;
}

logDev('Invalid stored config: ' + storedSource);
logWarn('SettingsService', 'Invalid stored config: ' + storedSource);
await this.storageService.removeItem(CONFIG_FILE_STORAGE_KEY);
}

Expand All @@ -78,8 +78,8 @@ export default class SettingsService {
const settings = await fetch('/.webapp.ini')
.then((result) => result.text())
.then((iniString) => ini.parse(iniString) as Settings)
.catch((e) => {
logDev(e);
.catch((error) => {
logDebug('SettingsService', 'Failed to fetch or parse the ini settings', { error });
// It's possible to not use the ini settings files, so an error doesn't have to be fatal
return {} as Settings;
});
Expand All @@ -106,7 +106,7 @@ export default class SettingsService {

// The player key should be set if using the global ott player
if (settings.playerId === OTT_GLOBAL_PLAYER_ID && !settings.playerLicenseKey) {
console.warn('Using Global OTT Player without setting player key. Some features, such as analytics, may not work correctly.');
logWarn('SettingsService', 'Using Global OTT Player without setting player key. Some features, such as analytics, may not work correctly.');
}

// This will result in an unusable app
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/services/WatchHistoryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import type { SerializedWatchHistoryItem, WatchHistoryItem } from '../../types/w
import type { Customer } from '../../types/account';
import { getNamedModule } from '../modules/container';
import { INTEGRATION_TYPE } from '../modules/types';
import { logDev } from '../utils/common';
import { MAX_WATCHLIST_ITEMS_COUNT } from '../constants';
import { logError } from '../logger';

import ApiService from './ApiService';
import StorageService from './StorageService';
Expand Down Expand Up @@ -107,7 +107,7 @@ export default class WatchHistoryService {
})
.filter((item): item is WatchHistoryItem => Boolean(item));
} catch (error: unknown) {
logDev('Failed to get watch history items', error);
logError('WatchHistoryService', 'Failed to get watch history items', { error });
}

return [];
Expand Down
6 changes: 3 additions & 3 deletions packages/common/src/services/epg/JWEpgService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import EpgService from '../EpgService';
import type { PlaylistItem } from '../../../types/playlist';
import type { EpgProgram } from '../../../types/epg';
import { getDataOrThrow } from '../../utils/api';
import { logDev } from '../../utils/common';
import { logError, logWarn } from '../../logger';

const AUTHENTICATION_HEADER = 'API-KEY';

Expand Down Expand Up @@ -46,7 +46,7 @@ export default class JWEpgService extends EpgService {

fetchSchedule = async (item: PlaylistItem) => {
if (!item.scheduleUrl) {
logDev('Tried requesting a schedule for an item with missing `scheduleUrl`', item);
logWarn('JWEpgService', 'Tried requesting a schedule for an item with missing `scheduleUrl`', { item });
return undefined;
}

Expand All @@ -66,7 +66,7 @@ export default class JWEpgService extends EpgService {
return await getDataOrThrow(response);
} catch (error: unknown) {
if (error instanceof Error) {
logDev(`Fetch failed for EPG schedule: '${item.scheduleUrl}'`, error);
logError('JWEpgService', `Fetch failed for EPG schedule: '${item.scheduleUrl}'`, { error });
}
}
};
Expand Down
6 changes: 3 additions & 3 deletions packages/common/src/services/epg/ViewNexaEpgService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { injectable } from 'inversify';

import EpgService from '../EpgService';
import type { PlaylistItem } from '../../../types/playlist';
import { logDev } from '../../utils/common';
import type { EpgProgram } from '../../../types/epg';
import { logError, logWarn } from '../../logger';

const viewNexaEpgProgramSchema = object().shape({
'episode-num': object().shape({
Expand Down Expand Up @@ -48,7 +48,7 @@ export default class ViewNexaEpgService extends EpgService {
const scheduleUrl = item.scheduleUrl;

if (!scheduleUrl) {
logDev('Tried requesting a schedule for an item with missing `scheduleUrl`', item);
logWarn('ViewNexaEpgService', 'Tried requesting a schedule for an item with missing `scheduleUrl`', { item });
return undefined;
}

Expand All @@ -65,7 +65,7 @@ export default class ViewNexaEpgService extends EpgService {
return schedule?.tv?.programme || [];
} catch (error: unknown) {
if (error instanceof Error) {
logDev(`Fetch failed for View Nexa EPG schedule: '${scheduleUrl}'`, error);
logError('ViewNexaEpgService', `Fetch failed for View Nexa EPG schedule: '${scheduleUrl}'`, { error });
}
}
};
Expand Down
Loading
Loading