Skip to content

Commit

Permalink
feat: add log service
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer authored Jul 4, 2024
1 parent bd71d9f commit 7717a2a
Show file tree
Hide file tree
Showing 37 changed files with 282 additions and 87 deletions.
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

0 comments on commit 7717a2a

Please sign in to comment.