import { Component, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
-import { prepareIcu, getAPIHost } from '@app/helpers'
+import { getAPIHost, prepareIcu } from '@app/helpers'
import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { Actor, DropdownAction } from '@app/shared/shared-main'
import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation'
import { UserAdminService } from '@app/shared/shared-users'
+import { logger } from '@root-helpers/logger'
import { User, UserRole } from '@shared/models'
type UserForList = User & {
this.selectedColumns = JSON.parse(result)
return
} catch (err) {
- console.error('Cannot load selected columns.', err)
+ logger.error('Cannot load selected columns.', err)
}
}
import { ActivatedRoute, Router } from '@angular/router'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core'
+import { logger } from '@root-helpers/logger'
import { PeerTubePluginIndex, PluginType } from '@shared/models'
@Component({
},
error: err => {
- console.error(err)
+ logger.error(err)
const message = $localize`The plugin index is not available. Please retry later.`
this.notifier.error(message)
-import { LogLevel } from '@shared/models'
import omit from 'lodash-es/omit'
+import { logger } from '@root-helpers/logger'
+import { ServerLogLevel } from '@shared/models'
export class LogRow {
date: Date
localeDate: string
- level: LogLevel
+ level: ServerLogLevel
message: string
meta: string
this.meta = JSON.stringify(message, null, 2)
this.message = ''
} catch (err) {
- console.error('Cannot parse audit message.', err)
+ logger.error('Cannot parse audit message.', err)
}
}
}
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
import { LocalStorageService, Notifier } from '@app/core'
-import { LogLevel } from '@shared/models'
+import { ServerLogLevel } from '@shared/models'
import { LogRow } from './log-row.model'
import { LogsService } from './logs.service'
logs: LogRow[] = []
timeChoices: { id: string, label: string, dateFormat: string }[] = []
- levelChoices: { id: LogLevel, label: string }[] = []
+ levelChoices: { id: ServerLogLevel, label: string }[] = []
logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
startDate: string
- level: LogLevel
+ level: ServerLogLevel
logType: 'audit' | 'standard'
tagsOneOf: string[] = []
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { RestExtractor, RestService } from '@app/core'
-import { LogLevel } from '@shared/models'
+import { ServerLogLevel } from '@shared/models'
import { environment } from '../../../../environments/environment'
import { LogRow } from './log-row.model'
isAuditLog: boolean
startDate: string
tagsOneOf?: string[]
- level?: LogLevel
+ level?: ServerLogLevel
endDate?: string
}): Observable<any[]> {
const { isAuditLog, startDate, endDate, tagsOneOf } = options
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { PluginService } from '@app/core'
+import { logger } from '@root-helpers/logger'
@Component({
templateUrl: './plugin-pages.component.html'
const registered = this.pluginService.getRegisteredClientRoute(path)
if (!registered) {
- console.log('Could not find registered route %s.', path, this.pluginService.getAllRegisteredClientRoutes())
+ logger.info(`Could not find registered route ${path}`, this.pluginService.getAllRegisteredClientRoutes())
return this.router.navigate([ '/404' ], { skipLocationChange: true })
}
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'
+import { logger } from '@root-helpers/logger'
import { ResultList } from '@shared/models'
export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
const url = route.params.url
if (!url) {
- console.error('Could not find url param.', { params: route.params })
+ logger.error('Could not find url param.', { params: route.params })
return this.router.navigateByUrl('/404')
}
.pipe(
map(result => {
if (result.data.length !== 1) {
- console.error('Cannot find result for this URL')
+ logger.error('Cannot find result for this URL')
return this.router.navigateByUrl('/404')
}
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { VideoDetails } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
import { secondsToTime } from '@shared/core-utils'
import { VideoStudioTask, VideoStudioTaskCut } from '@shared/models'
import { VideoStudioService } from '../shared'
this.loadingBar.useRef().complete()
this.isRunningEdition = false
this.notifier.error(err.message)
- console.error(err)
+ logger.error(err)
}
})
}
import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component'
import { VideoEditType } from './video-edit.type'
import { VideoSource } from '@shared/models/videos/video-source'
+import { logger } from '@root-helpers/logger'
type VideoLanguages = VideoConstant<string> & { group?: string }
type PluginField = {
const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId)
if (!newChannel || !oldChannel) {
- console.error('Cannot find new or old channel.')
+ logger.error('Cannot find new or old channel.')
return
}
import { Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
import { VideoSend } from './video-send'
error: err => {
this.error = err.message
scrollToTop()
- console.error(err)
+ logger.error(err)
}
})
}
import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send'
error: err => {
this.error = err.message
scrollToTop()
- console.error(err)
+ logger.error(err)
}
})
}
import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
import { VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send'
error: err => {
this.error = err.message
scrollToTop()
- console.error(err)
+ logger.error(err)
}
})
}
import { truncate } from 'lodash-es'
import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx'
-import { isIOS } from '@root-helpers/web-browser'
import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http'
import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { FormValidatorService } from '@app/shared/shared-forms'
import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
+import { isIOS } from '@root-helpers/web-browser'
import { HttpStatusCode, VideoCreateResult } from '@shared/models'
import { UploaderXFormData } from './uploaderx-form-data'
import { VideoSend } from './video-send'
error: err => {
this.error = err.message
scrollToTop()
- console.error(err)
+ logger.error(err)
}
})
}
import { Video, VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models'
-import { hydrateFormFromVideo } from './shared/video-edit-utils'
import { VideoSource } from '@shared/models/videos/video-source'
+import { hydrateFormFromVideo } from './shared/video-edit-utils'
@Component({
selector: 'my-videos-update',
this.loadingBar.useRef().complete()
this.isUpdatingVideo = false
this.notifier.error(err.message)
- console.error(err)
+ logger.error(err)
}
})
}
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'
import { MarkdownService, Notifier } from '@app/core'
import { VideoDetails, VideoService } from '@app/shared/shared-main'
+import { logger } from '@root-helpers/logger'
@Component({
selector: 'my-video-description',
private updateVideoDescription (description: string) {
this.video.description = description
this.setVideoDescriptionHTML()
- .catch(err => console.error(err))
+ .catch(err => logger.error(err))
}
private async setVideoDescriptionHTML () {
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
import { LiveVideoService } from '@app/shared/shared-video-live'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
+import { logger } from '@root-helpers/logger'
import { isP2PEnabled } from '@root-helpers/video'
import { timeToInt } from '@shared/core-utils'
import {
: parseInt(positionParam + '', 10)
if (isNaN(this.playlistPosition)) {
- console.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`)
+ logger.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`)
this.playlistPosition = 1
}
}
this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
- .catch(err => console.error('Cannot build the player', err))
+ .catch(err => logger.error('Cannot build the player', err))
this.setOpenGraphTags()
this.player.dispose()
this.player = undefined
} catch (err) {
- console.error('Cannot dispose player.', err)
+ logger.error('Cannot dispose player.', err)
}
}
private handleLiveStateChange (newState: VideoState) {
if (newState !== VideoState.PUBLISHED) return
- console.log('Loading video after live update.')
+ logger.info('Loading video after live update.')
const videoUUID = this.video.uuid
private handleLiveViewsChange (newViewers: number) {
if (!this.video) {
- console.error('Cannot update video live views because video is no defined.')
+ logger.error('Cannot update video live views because video is no defined.')
return
}
- console.log('Updating live views.')
+ logger.info('Updating live views.')
this.video.viewers = newViewers
}
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
-import { forkJoin, delay } from 'rxjs'
+import { delay, forkJoin } from 'rxjs'
import { filter, first, map } from 'rxjs/operators'
import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'
import { HooksService } from '@app/core/plugins/hooks.service'
import { PluginService } from '@app/core/plugins/plugin.service'
import { AccountSetupWarningModalComponent } from '@app/modal/account-setup-warning-modal.component'
+import { AdminWelcomeModalComponent } from '@app/modal/admin-welcome-modal.component'
import { CustomModalComponent } from '@app/modal/custom-modal.component'
import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
-import { AdminWelcomeModalComponent } from '@app/modal/admin-welcome-modal.component'
import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { getShortLocale } from '@shared/core-utils/i18n'
import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/models'
import { MenuService } from './core/menu/menu.service'
import { POP_STATE_MODAL_DISMISS } from './helpers'
-import { InstanceService } from './shared/shared-instance'
import { GlobalIconName } from './shared/shared-icons'
+import { InstanceService } from './shared/shared-instance'
@Component({
selector: 'my-app',
/* eslint-disable no-eval */
eval(this.serverConfig.instance.customizations.javascript)
} catch (err) {
- console.error('Cannot eval custom JavaScript.', err)
+ logger.error('Cannot eval custom JavaScript.', err)
}
}
}
import { Injectable } from '@angular/core'
import { Router } from '@angular/router'
import { Notifier } from '@app/core/notification/notifier.service'
-import { objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index'
+import { logger, objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index'
import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
import { environment } from '../../../environments/environment'
import { RestExtractor } from '../rest/rest-extractor.service'
peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID, this.clientId)
peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret)
- console.log('Client credentials loaded.')
+ logger.info('Client credentials loaded.')
},
error: err => {
}
},
- error: err => console.error(err)
+ error: err => logger.error(err)
})
this.user = null
refreshAccessToken () {
if (this.refreshingTokenObservable) return this.refreshingTokenObservable
- console.log('Refreshing token...')
+ logger.info('Refreshing token...')
const refreshToken = this.getRefreshToken()
catchError(err => {
this.refreshingTokenObservable = null
- console.error(err)
- console.log('Cannot refresh token -> logout...')
+ logger.error(err)
+ logger.info('Cannot refresh token -> logout...')
this.logout()
this.router.navigate([ '/login' ])
import { MessageService } from 'primeng/api'
import { Injectable } from '@angular/core'
+import { logger } from '@root-helpers/logger'
@Injectable()
export class Notifier {
info (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Info`
- console.info(`${title}: ${text}`)
+ logger.info(`${title}: ${text}`)
return this.notify('info', text, title, timeout, sticky)
}
error (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Error`
- console.error(`${title}: ${text}`)
+ logger.error(`${title}: ${text}`)
return this.notify('error', text, title, timeout, sticky)
}
success (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Success`
- console.log(`${title}: ${text}`)
+ logger.info(`${title}: ${text}`)
return this.notify('success', text, title, timeout, sticky)
}
import { mergeMap, switchMap } from 'rxjs/operators'
import { Injectable } from '@angular/core'
import { PluginService } from '@app/core/plugins/plugin.service'
+import { logger } from '@root-helpers/logger'
import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models'
import { AuthService, AuthStatus } from '../auth'
runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) {
this.pluginService.ensurePluginsAreLoaded(scope)
.then(() => this.pluginService.runHook(hookName, undefined, params))
- .catch((err: any) => console.error('Fatal hook error.', { err }))
+ .catch((err: any) => logger.error('Fatal hook error.', err))
}
async wrapObject<T, U extends ClientFilterHookName> (result: T, scope: PluginClientScope, hookName: U) {
import { Router } from '@angular/router'
import { dateToHuman } from '@app/helpers'
import { HttpStatusCode, ResultList } from '@shared/models'
+import { logger } from '@root-helpers/logger'
@Injectable()
export class RestExtractor {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
const errorMessage = err.error.detail || err.error.title
- console.error('An error occurred:', errorMessage)
+ logger.error('An error occurred:', errorMessage)
return errorMessage
}
if (err.status !== undefined) {
const errorMessage = this.buildServerErrorMessage(err)
- console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
+ logger.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
return errorMessage
}
- console.error(err)
+ logger.error(err)
return err
}
-import * as debug from 'debug'
+import debug from 'debug'
import { LazyLoadEvent, SortMeta } from 'primeng/api'
import { ActivatedRoute, Router } from '@angular/router'
+import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { RestPagination } from './rest-pagination'
-const logger = debug('peertube:tables:RestTable')
+const debugLogger = debug('peertube:tables:RestTable')
export abstract class RestTable {
try {
this.sort = JSON.parse(result)
} catch (err) {
- console.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err)
+ logger.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err)
}
}
}
}
loadLazy (event: LazyLoadEvent) {
- logger('Load lazy %o.', event)
+ debugLogger('Load lazy %o.', event)
this.sort = {
order: event.sortOrder,
import { ComponentPaginationLight } from './component-pagination.model'
import { RestPagination } from './rest-pagination'
-const logger = debug('peertube:rest')
+const debugLogger = debug('peertube:rest')
interface QueryStringFilterPrefixes {
[key: string]: {
const prefixeStrings = Object.values(prefixes)
.map(p => p.prefix)
- logger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`)
+ debugLogger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`)
// Search is the querystring minus defined filters
const searchTokens = tokens.filter(t => {
const search = searchTokens.join(' ') || undefined
- logger('Built search: ' + search, additionalFilters)
+ debugLogger('Built search: ' + search, additionalFilters)
return {
search,
import { ComponentRef, Injectable } from '@angular/core'
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
+import { logger } from '@root-helpers/logger'
import { DisableForReuseHook } from './disable-for-reuse-hook'
import { PeerTubeRouterService, RouterSetting } from './peertube-router.service'
const key = this.generateKey(route)
this.recentlyUsed = key
- console.log('Storing component %s to reuse later.', key)
+ logger.info(`Storing component ${key} to reuse later.`)
const componentRef = (handle as any).componentRef as ComponentRef<DisableForReuseHook>
componentRef.instance.disableForReuse()
const key = this.generateKey(route)
this.recentlyUsed = key
- console.log('Reusing component %s.', key)
+ logger.info(`Reusing component ${key}.`)
const handle = this.storedRouteHandles.get(key)
if (!handle) return handle;
this.storedRouteHandles.forEach((r, key) => {
if (key === this.recentlyUsed) return
- console.log('Removing stored component %s.', key);
+ logger.info(`Removing stored component ${key}`);
(r as any).componentRef.destroy()
this.storedRouteHandles.delete(key)
import * as debug from 'debug'
import { Injectable } from '@angular/core'
import { NavigationCancel, NavigationEnd, Router } from '@angular/router'
+import { logger } from '@root-helpers/logger'
import { ServerService } from '../server'
import { SessionStorageService } from '../wrappers/storage.service'
-const logger = debug('peertube:router:RedirectService')
+const debugLogger = debug('peertube:router:RedirectService')
@Injectable()
export class RedirectService {
this.latestSessionUrl = this.storage.getItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY)
this.storage.removeItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY)
- logger('Loaded latest session URL %s', this.latestSessionUrl)
+ debugLogger('Loaded latest session URL %s', this.latestSessionUrl)
// Track previous url
this.currentUrl = this.router.url
this.previousUrl = this.currentUrl
this.currentUrl = event.url
- logger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl)
- logger('Setting %s as latest URL in session storage.', this.currentUrl)
+ debugLogger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl)
+ debugLogger('Setting %s as latest URL in session storage.', this.currentUrl)
this.storage.setItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY, this.currentUrl)
}
this.redirectingToHomepage = true
- console.log('Redirecting to %s...', this.defaultRoute)
+ logger.info(`Redirecting to ${this.defaultRoute}...`)
this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
.then(() => this.redirectingToHomepage = false)
.catch(() => {
this.redirectingToHomepage = false
- console.error(
- 'Cannot navigate to %s, resetting default route to %s.',
- this.defaultRoute,
- RedirectService.INIT_DEFAULT_ROUTE
- )
+ logger.error(`Cannot navigate to ${this.defaultRoute}, resetting default route to ${RedirectService.INIT_DEFAULT_ROUTE}`)
this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
}
private doRedirect (redirectUrl: string, fallbackRoute?: string) {
- logger('Redirecting on %s', redirectUrl)
+ debugLogger('Redirecting on %s', redirectUrl)
if (this.isValidRedirection(redirectUrl)) {
return this.router.navigateByUrl(redirectUrl)
}
- logger('%s is not a valid redirection, try fallback route %s', redirectUrl, fallbackRoute)
+ debugLogger('%s is not a valid redirection, try fallback route %s', redirectUrl, fallbackRoute)
if (fallbackRoute) {
return this.router.navigateByUrl(fallbackRoute)
}
- logger('There was no fallback route, redirecting to homepage')
+ debugLogger('There was no fallback route, redirecting to homepage')
return this.redirectToHomepage()
}
import { Injectable } from '@angular/core'
import { RouterSetting } from '../'
import { PeerTubeRouterService } from './peertube-router.service'
+import { logger } from '@root-helpers/logger'
-const logger = debug('peertube:main:ScrollService')
+const debugLogger = debug('peertube:main:ScrollService')
@Injectable()
export class ScrollService {
if (nextSearchParams.toString() !== previousSearchParams.toString()) {
this.resetScroll = true
}
- } catch (e) {
- console.error('Cannot parse URL to check next scroll.', e)
+ } catch (err) {
+ logger.error('Cannot parse URL to check next scroll.', err)
this.resetScroll = true
}
})
private consumeScroll () {
// Handle anchors/restore position
this.peertubeRouter.getScrollEvents().subscribe(e => {
- logger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll })
+ debugLogger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll })
// scrollToAnchor first to preserve anchor position when using history navigation
if (e.anchor) {
import { HttpClient } from '@angular/common/http'
import { Inject, Injectable, LOCALE_ID } from '@angular/core'
import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
+import { logger } from '@root-helpers/logger'
import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
import { environment } from '../../../environments/environment'
} catch (err) {
// Expected in dev mode since we can't inject the config in the HTML
if (environment.production !== false) {
- console.error('Cannot load config locally. Fallback to API.')
+ logger.error('Cannot load config locally. Fallback to API.')
}
return this.getConfig()
import { Injectable } from '@angular/core'
+import { logger } from '@root-helpers/logger'
import { capitalizeFirstLetter } from '@root-helpers/string'
import { UserLocalStorageKeys } from '@root-helpers/users'
import { HTMLServerConfig, ServerConfigTheme } from '@shared/models'
private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) {
this.themes = themes
- console.log('Injecting %d themes.', this.themes.length)
+ logger.info(`Injecting ${this.themes.length} themes.`)
const head = this.getHeadElement()
const currentTheme = this.getCurrentTheme()
- console.log('Enabling %s theme.', currentTheme)
+ logger.info(`Enabling ${currentTheme} theme.`)
this.loadTheme(currentTheme)
const theme = this.getTheme(currentTheme)
if (theme) {
- console.log('Adding scripts of theme %s.', currentTheme)
+ logger.info(`Adding scripts of theme ${currentTheme}`)
this.pluginService.addPlugin(theme, true)
this.injectThemes([ lastActiveTheme ], true)
this.updateCurrentTheme()
} catch (err) {
- console.error('Cannot parse last active theme.', err)
+ logger.error('Cannot parse last active theme.', err)
return
}
}
private removeThemePlugins (themeName: string) {
const oldTheme = this.getTheme(themeName)
if (oldTheme) {
- console.log('Removing scripts of old theme %s.', themeName)
+ logger.info(`Removing scripts of old theme ${themeName}.`)
this.pluginService.removePlugin(oldTheme)
}
}
import { filter, throttleTime } from 'rxjs'
import { Injectable } from '@angular/core'
import { AuthService, AuthStatus } from '@app/core/auth'
-import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
+import { logger } from '@root-helpers/logger'
+import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
import { UserRole, UserUpdateMe } from '@shared/models'
import { NSFWPolicyType } from '@shared/models/videos'
import { ServerService } from '../server'
: null
} catch (err) {
videoLanguages = null
- console.error('Cannot parse desired video languages from localStorage.', err)
+ logger.error('Cannot parse desired video languages from localStorage.', err)
}
const htmlConfig = this.server.getHTMLConfig()
this.localStorageService.setItem(key, localStorageValue)
} catch (err) {
- console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
+ logger.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
}
}
}
import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'
import { ActivatedRoute, Params, Router } from '@angular/router'
import { AuthService, ServerService } from '@app/core'
+import { logger } from '@root-helpers/logger'
import { HTMLServerConfig, SearchTargetType } from '@shared/models'
import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component'
const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true)
if (activeIndex === -1) {
- console.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
+ logger.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
}
this.updateItemsState(activeIndex)
-import { environment } from '../../environments/environment'
import IntlMessageFormat from 'intl-messageformat'
+import { logger } from '@root-helpers/logger'
+import { environment } from '../../environments/environment'
function isOnDevLocale () {
return environment.production === false && window.location.search === '?lang=fr'
try {
return msg.format(context) as string
} catch (err) {
- if (!alreadyWarned) console.warn('Cannot format ICU %s.', icu, err)
+ if (!alreadyWarned) logger.warn(`Cannot format ICU ${icu}.`, err)
alreadyWarned = true
return fallback
}
}
} catch (err) {
- console.warn('Cannot build intl message %s.', icu, err)
+ logger.warn(`Cannot build intl message ${icu}.`, err)
return (_context: unknown, fallback: string) => fallback
}
import { PluginsManager } from '@root-helpers/plugins-manager'
import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models'
-const logger = debug('peertube:menu:MenuComponent')
+const debugLogger = debug('peertube:menu:MenuComponent')
@Component({
selector: 'my-menu',
.pipe(
switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed()))
).subscribe(res => {
- if (res === true) logger('User can see videos link.')
- else logger('User cannot see videos link.')
+ if (res === true) debugLogger('User can see videos link.')
+ else debugLogger('User cannot see videos link.')
})
}
import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, ServerService, User, UserService } from '@app/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({
this.userService.updateMyProfile({ noAccountSetupWarningModal: true })
.subscribe({
- next: () => console.log('We will not open the account setup modal again.'),
+ next: () => logger.info('We will not open the account setup modal again.'),
error: err => this.notifier.error(err.message)
})
import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({
this.userService.updateMyProfile({ noWelcomeModal: true })
.subscribe({
- next: () => console.log('We will not open the welcome modal again.'),
+ next: () => logger.info('We will not open the welcome modal again.'),
error: err => this.notifier.error(err.message)
})
-import { Component, ElementRef, ViewChild, Input } from '@angular/core'
+import { Component, ElementRef, Input, ViewChild } from '@angular/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
@Component({
selector: 'my-custom-modal',
confirm?: { value: string, action?: () => void }
}) {
if (this.modalRef instanceof NgbModalRef && this.modalService.hasOpenModals()) {
- console.error('Cannot open another custom modal, one is already opened.')
+ logger.error('Cannot open another custom modal, one is already opened.')
return
}
import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { About, ServerConfig } from '@shared/models/server'
this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
.subscribe({
- next: () => console.log('We will not open the instance config warning modal again.'),
+ next: () => logger.info('We will not open the instance config warning modal again.'),
error: err => this.notifier.error(err.message)
})
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoCommentService } from '@app/shared/shared-video-comment'
+import { logger } from '@root-helpers/logger'
import { AbuseState, AdminAbuse } from '@shared/models'
import { AdvancedInputFilter } from '../shared-forms'
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
import { ProcessedAbuse } from './processed-abuse.model'
-const logger = debug('peertube:moderation:AbuseListTableComponent')
+const debugLogger = debug('peertube:moderation:AbuseListTableComponent')
@Component({
selector: 'my-abuse-list-table',
const abuse = this.abuses.find(a => a.id === event.abuseId)
if (!abuse) {
- console.error('Cannot find abuse %d.', event.abuseId)
+ logger.error(`Cannot find abuse ${event.abuseId}`)
return
}
}
protected reloadData () {
- logger('Loading data.')
+ debugLogger('Loading data.')
const options = {
pagination: this.pagination,
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
+import { logger } from '@root-helpers/logger'
import { AbuseMessage, UserAbuse } from '@shared/models'
import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators'
import { AbuseService } from '../shared-moderation'
error: err => {
this.sendingMessage = false
- console.error(err)
+ logger.error(err)
this.notifier.error('Sorry but you cannot send this message. Please retry later')
}
})
VideosListMarkupComponent
} from './peertube-custom-tags'
import { CustomMarkupComponent } from './peertube-custom-tags/shared'
+import { logger } from '@root-helpers/logger'
type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent>
type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
// Insert as first child
e.insertBefore(element, e.firstChild)
} catch (err) {
- console.error('Cannot inject component %s.', selector, err)
+ logger.error(`Cannot inject component ${selector}`, err)
}
})
}
this.dynamicElementService.injectElement(e, component)
} catch (err) {
- console.error('Cannot inject component %s.', selector, err)
+ logger.error(`Cannot inject component ${selector}`, err)
}
})
}
value: string
}
-const logger = debug('peertube:AdvancedInputFilterComponent')
+const debugLogger = debug('peertube:AdvancedInputFilterComponent')
@Component({
selector: 'my-advanced-input-filter',
.subscribe(params => {
const search = params.search || ''
- logger('On route search change "%s".', search)
+ debugLogger('On route search change "%s".', search)
if (this.searchValue === search) return
return
}
- logger('On search "%s".', this.searchValue)
+ debugLogger('On search "%s".', this.searchValue)
this.search.emit(this.searchValue)
}
ViewContainerRef
} from '@angular/core'
-const logger = debug('peertube:main:DeferLoadingDirective')
+const debugLogger = debug('peertube:main:DeferLoadingDirective')
@Directive({
selector: '[myDeferLoading]'
load () {
if (this.isLoaded()) return
- logger('Loading component')
+ debugLogger('Loading component')
this.viewContainer.clear()
this.view = this.viewContainer.createEmbeddedView(this.template, {}, 0)
import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import * as debug from 'debug'
-const logger = debug('peertube:main:ListOverflowItem')
+const debugLogger = debug('peertube:main:ListOverflowItem')
export interface ListOverflowItem {
label: string
let showItemsUntilIndexExcluded: number
let accWidth = 0
- logger('Parent width is %d', parentWidth)
+ debugLogger('Parent width is %d', parentWidth)
for (const [ index, el ] of this.itemsRendered.toArray().entries()) {
accWidth += el.nativeElement.getBoundingClientRect().width
e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden'
}
- logger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded)
+ debugLogger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded)
this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded
this.cdr.markForCheck()
import { Account } from '@app/shared/shared-main/account/account.model'
import { Actor } from '@app/shared/shared-main/account/actor.model'
import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
+import { logger } from '@root-helpers/logger'
import {
AbuseState,
ActorInfo,
}
} catch (err) {
this.type = null
- console.error(err)
+ logger.error(err)
}
}
import { SearchService } from './search.service'
import { AdvancedSearch } from './advanced-search.model'
-const logger = debug('peertube:search:FindInBulkService')
+const debugLogger = debug('peertube:search:FindInBulkService')
type BulkObservables <P extends number | string, R> = {
notifier: Subject<P>
}
getVideo (uuid: string): Observable<Video> {
- logger('Schedule video fetch for uuid %s.', uuid)
+ debugLogger('Schedule video fetch for uuid %s.', uuid)
return this.getData({
observableObject: this.getVideoInBulk,
}
getChannel (handle: string): Observable<VideoChannel> {
- logger('Schedule channel fetch for handle %s.', handle)
+ debugLogger('Schedule channel fetch for handle %s.', handle)
return this.getData({
observableObject: this.getChannelInBulk,
}
getPlaylist (uuid: string): Observable<VideoPlaylist> {
- logger('Schedule playlist fetch for uuid %s.', uuid)
+ debugLogger('Schedule playlist fetch for uuid %s.', uuid)
return this.getData({
observableObject: this.getPlaylistInBulk,
}
private getVideosInBulk (uuids: string[]) {
- logger('Fetching videos %s.', uuids.join(', '))
+ debugLogger('Fetching videos %s.', uuids.join(', '))
return this.searchService.searchVideos({
uuids,
}
private getChannelsInBulk (handles: string[]) {
- logger('Fetching channels %s.', handles.join(', '))
+ debugLogger('Fetching channels %s.', handles.join(', '))
return this.searchService.searchVideoChannels({
handles,
}
private getPlaylistsInBulk (uuids: string[]) {
- logger('Fetching playlists %s.', uuids.join(', '))
+ debugLogger('Fetching playlists %s.', uuids.join(', '))
return this.searchService.searchVideoPlaylists({
uuids,
import { Component, Input, OnInit } from '@angular/core'
import { Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
+import { logger } from '@root-helpers/logger'
import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators'
@Component({
})
.then(window.open)
.catch(err => {
- console.error(err)
+ logger.error(err)
this.notifier.error($localize`Cannot fetch information of this remote account`)
})
import { ActorFollow, ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models'
import { environment } from '../../../environments/environment'
-const logger = debug('peertube:subscriptions:UserSubscriptionService')
+const debugLogger = debug('peertube:subscriptions:UserSubscriptionService')
type SubscriptionExistResult = { [ uri: string ]: boolean }
type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> }
}
doesSubscriptionExist (nameWithHost: string) {
- logger('Running subscription check for %d.', nameWithHost)
+ debugLogger('Running subscription check for %d.', nameWithHost)
if (nameWithHost in this.myAccountSubscriptionCache) {
- logger('Found cache for %d.', nameWithHost)
+ debugLogger('Found cache for %d.', nameWithHost)
return of(this.myAccountSubscriptionCache[nameWithHost])
}
this.existsSubject.next(nameWithHost)
- logger('Fetching from network for %d.', nameWithHost)
+ debugLogger('Fetching from network for %d.', nameWithHost)
return this.existsObservable.pipe(
filter(existsResult => existsResult[nameWithHost] !== undefined),
map(existsResult => existsResult[nameWithHost]),
import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core'
import { AuthService, HooksService, Notifier } from '@app/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models'
import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main'
.find(f => f.resolution.id === this.resolutionId)
if (!file) {
- console.error('Could not find file with resolution %d.', this.resolutionId)
+ logger.error(`Could not find file with resolution ${this.resolutionId}`)
return undefined
}
.find(c => c.language.id === this.subtitleLanguageId)
if (!caption) {
- console.error('Cannot find caption %s.', this.subtitleLanguageId)
+ logger.error(`Cannot find caption ${this.subtitleLanguageId}`)
return undefined
}
import { PeertubeModalService } from '../shared-main'
import { VideoFilters } from './video-filters.model'
-const logger = debug('peertube:videos:VideoFiltersHeaderComponent')
+const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent')
@Component({
selector: 'my-video-filters-header',
})
this.form.valueChanges.subscribe(values => {
- logger('Loading values from form: %O', values)
+ debugLogger('Loading values from form: %O', values)
this.filters.load(values)
this.filtersChanged.emit()
const defaultValues = this.filters.toFormObject()
this.form.patchValue(defaultValues, { emitEvent })
- logger('Patched form: %O', defaultValues)
+ debugLogger('Patched form: %O', defaultValues)
}
}
UserService
} from '@app/core'
import { GlobalIconName } from '@app/shared/shared-icons'
+import { logger } from '@root-helpers/logger'
import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils'
import { ResultList, UserRight, VideoSortField } from '@shared/models'
import { Syndication, Video } from '../shared-main'
import { VideoFilters, VideoFilterScope } from './video-filters.model'
import { MiniatureDisplayOptions } from './video-miniature.component'
-const logger = debug('peertube:videos:VideosListComponent')
+const debugLogger = debug('peertube:videos:VideosListComponent')
export type HeaderAction = {
iconName: GlobalIconName
error: err => {
const message = $localize`Cannot load more videos. Try again later.`
- console.error(message, { err })
+ logger.error(message, err)
this.notifier.error(message)
}
})
}
onFiltersChanged (customizedByUser: boolean) {
- logger('Running on filters changed')
+ debugLogger('Running on filters changed')
this.updateUrl(customizedByUser)
if (!items || items.length === 0) this.syndicationItems = undefined
else this.syndicationItems = items
})
- .catch(err => console.error('Cannot get syndication items.', err))
+ .catch(err => logger.error('Cannot get syndication items.', err))
}
private updateUrl (customizedByUser: boolean) {
? { ...baseQuery, c: customizedByUser }
: baseQuery
- logger('Will inject %O in URL query', queryParams)
+ debugLogger('Will inject %O in URL query', queryParams)
const baseRoute = this.baseRouteBuilderFunction
? this.baseRouteBuilderFunction(this.filters)
import { Observable, Subject } from 'rxjs'
import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core'
import { ComponentPagination, Notifier, User } from '@app/core'
+import { logger } from '@root-helpers/logger'
import { ResultList, VideoSortField } from '@shared/models'
import { PeerTubeTemplateDirective, Video } from '../shared-main'
import { MiniatureDisplayOptions } from './video-miniature.component'
error: err => {
const message = $localize`Cannot load more videos. Try again later.`
- console.error(message, { err })
+ logger.error(message, err)
this.notifier.error(message)
}
})
import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators'
import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
-const logger = debug('peertube:playlists:VideoAddToPlaylistComponent')
+const debugLogger = debug('peertube:playlists:VideoAddToPlaylistComponent')
type PlaylistElement = {
enabled: boolean
}
reload () {
- logger('Reloading component')
+ debugLogger('Reloading component')
this.videoPlaylists = []
this.videoPlaylistSearch = undefined
}
load () {
- logger('Loading component')
+ debugLogger('Loading component')
this.listenToVideoPlaylistChange()
}
private rebuildPlaylists (existResult: VideoExistInPlaylist[]) {
- logger('Got existing results for %d.', this.video.id, existResult)
+ debugLogger('Got existing results for %d.', this.video.id, existResult)
const oldPlaylists = this.videoPlaylists
this.videoPlaylists.push(playlistSummary)
}
- logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
+ debugLogger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
this.cd.markForCheck()
}
import { VideoPlaylistElement } from './video-playlist-element.model'
import { VideoPlaylist } from './video-playlist.model'
-const logger = debug('peertube:playlists:VideoPlaylistService')
+const debugLogger = debug('peertube:playlists:VideoPlaylistService')
export type CachedPlaylist = VideoPlaylist | { id: number, displayName: string }
}
runPlaylistCheck (videoId: number) {
- logger('Running playlist check.')
+ debugLogger('Running playlist check.')
if (this.videoExistsCache[videoId]) {
- logger('Found cache for %d.', videoId)
+ debugLogger('Found cache for %d.', videoId)
return this.videoExistsInPlaylistCacheSubject.next({ [videoId]: this.videoExistsCache[videoId] })
}
- logger('Fetching from network for %d.', videoId)
+ debugLogger('Fetching from network for %d.', videoId)
return this.videoExistsInPlaylistNotifier.next(videoId)
}
+import { logger } from '@root-helpers/logger'
+
function getStoredVolume () {
const value = getLocalStorage('volume')
if (value !== null && value !== undefined) {
data = JSON.parse(value)
} catch (error) {
- console.error('Cannot parse video watch history from local storage: ', error)
+ logger.error('Cannot parse video watch history from local storage/', error)
}
data = data || {}
import './shared/mobile/peertube-mobile-buttons'
import './shared/hotkeys/peertube-hotkeys-plugin'
import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
import { PluginsManager } from '@root-helpers/plugins-manager'
import { isMobile } from '@root-helpers/web-browser'
import { saveAverageBandwidth } from './peertube-player-local-storage'
return
}
- console.log('Fast forwarding HLS to recover from an error.')
+ logger.info('Fast forwarding HLS to recover from an error.')
this.videojsDecodeErrors++
return
}
- console.log('Fallback to webtorrent.')
+ logger.info('Fallback to webtorrent.')
this.rebuildAndUpdateVideoElement(currentPlayer, options.common)
import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
+import { logger } from '@root-helpers/logger'
import { LiveVideoLatencyMode } from '@shared/models'
import { getAverageBandwidthInStore } from '../../peertube-player-local-storage'
import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../../types'
private getP2PMediaLoaderOptions (redundancyUrlManager: RedundancyUrlManager): HlsJsEngineSettings {
let consumeOnly = false
if ((navigator as any)?.connection?.type === 'cellular') {
- console.log('We are on a cellular connection: disabling seeding.')
+ logger.info('We are on a cellular connection: disabling seeding.')
consumeOnly = true
}
-import { PeerTubeMobileButtons } from './peertube-mobile-buttons'
-import videojs from 'video.js'
import debug from 'debug'
+import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
+import { PeerTubeMobileButtons } from './peertube-mobile-buttons'
-const logger = debug('peertube:player:mobile')
+const debugLogger = debug('peertube:player:mobile')
const Plugin = videojs.getPlugin('plugin')
if (!this.player.isFullscreen() || this.isPortraitVideo()) return
screen.orientation.lock('landscape')
- .catch(err => console.error('Cannot lock screen to landscape.', err))
+ .catch(err => logger.error('Cannot lock screen to landscape.', err))
})
}
}
if (this.lastTapEvent && event.timeStamp - this.lastTapEvent.timeStamp < PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS) {
- logger('Detected double tap')
+ debugLogger('Detected double tap')
this.lastTapEvent = undefined
this.onDoubleTap(event)
this.newActiveState = !this.player.userActive()
this.tapTimeout = setTimeout(() => {
- logger('No double tap detected, set user active state to %s.', this.newActiveState)
+ debugLogger('No double tap detected, set user active state to %s.', this.newActiveState)
this.player.userActive(this.newActiveState)
}, PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS)
const rect = this.findPlayerTarget((event.target as HTMLElement)).getBoundingClientRect()
const offsetX = event.targetTouches[0].pageX - rect.left
- logger('Calculating double tap zone (player width: %d, offset X: %d)', playerWidth, offsetX)
+ debugLogger('Calculating double tap zone (player width: %d, offset X: %d)', playerWidth, offsetX)
if (offsetX > 0.66 * playerWidth) {
if (this.seekAmount < 0) this.seekAmount = 0
this.seekAmount += 10
- logger('Will forward %d seconds', this.seekAmount)
+ debugLogger('Will forward %d seconds', this.seekAmount)
} else if (offsetX < 0.33 * playerWidth) {
if (this.seekAmount > 0) this.seekAmount = 0
this.seekAmount -= 10
- logger('Will rewind %d seconds', this.seekAmount)
+ debugLogger('Will rewind %d seconds', this.seekAmount)
}
this.peerTubeMobileButtons.displayFastSeek(this.seekAmount)
import Hlsjs, { ErrorData, HlsConfig, Level, LevelSwitchingData, ManifestParsedData } from 'hls.js'
import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
import { HlsjsConfigHandlerOptions, PeerTubeResolution, VideoJSTechHLS } from '../../types'
type ErrorCounts = {
const registerSourceHandler = function (vjs: typeof videojs) {
if (!Hlsjs.isSupported()) {
- console.warn('Hls.js is not supported in this browser!')
+ logger.warn('Hls.js is not supported in this browser!')
return
}
const html5 = vjs.getTech('Html5')
if (!html5) {
- console.error('No Hml5 tech found in videojs')
+ logger.error('No Hml5 tech found in videojs')
return
}
if (!mediaError) return
- console.log(mediaError)
+ logger.info(mediaError)
switch (mediaError.code) {
case mediaError.MEDIA_ERR_ABORTED:
errorTxt = 'You aborted the video playback'
errorTxt = mediaError.message
}
- console.error('MEDIA_ERROR: ', errorTxt)
+ logger.error(`MEDIA_ERROR: ${errorTxt}`)
})
this.initialize()
private _handleMediaError (error: any) {
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) {
- console.info('trying to recover media error')
+ logger.info('trying to recover media error')
this.hls.recoverMediaError()
return
}
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 2) {
- console.info('2nd try to recover media error (by swapping audio codec')
+ logger.info('2nd try to recover media error (by swapping audio codec')
this.hls.swapAudioCodec()
this.hls.recoverMediaError()
return
}
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) {
- console.info('bubbling media error up to VIDEOJS')
+ logger.info('bubbling media error up to VIDEOJS')
this.hls.destroy()
this.tech.error = () => error
this.tech.trigger('error')
private _handleNetworkError (error: any) {
if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= this.maxNetworkErrorRecovery) {
- console.info('trying to recover network error')
+ logger.info('trying to recover network error')
// Wait 1 second and retry
setTimeout(() => this.hls.startLoad(), 1000)
return
}
- console.info('bubbling network error up to VIDEOJS')
+ logger.info('bubbling network error up to VIDEOJS')
this.hls.destroy()
this.tech.error = () => error
this.tech.trigger('error')
if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1
else this.errorCounts[data.type] = 1
- if (data.fatal) console.warn(error.message)
- else console.error(error.message, data)
+ if (data.fatal) logger.warn(error.message)
+ else logger.error(error.message, { data })
if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) {
error.code = 2
this._handleMediaError(error)
} else if (data.fatal) {
this.hls.destroy()
- console.info('bubbling error up to VIDEOJS')
+ logger.info('bubbling error up to VIDEOJS')
this.tech.error = () => error as any
this.tech.trigger('error')
}
import { timeToInt } from '@shared/core-utils'
import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types'
import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
+import { logger } from '@root-helpers/logger'
registerConfigPlugin(videojs)
registerSourceHandler(videojs)
// FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
if (!(videojs as any).Html5Hlsjs) {
- console.warn('HLS.js does not seem to be supported. Try to fallback to built in HLS.')
+ logger.warn('HLS.js does not seem to be supported. Try to fallback to built in HLS.')
if (!player.canPlayType('application/vnd.apple.mpegurl')) {
const message = 'Cannot fallback to built-in HLS'
- console.warn(message)
+ logger.warn(message)
player.ready(() => player.trigger('error', new Error(message)))
return
this.p2pEngine = this.options.loader.getEngine()
this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => {
- console.error('Segment error.', segment, err)
+ logger.error(`Segment ${segment.id} error.`, err)
this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl)
})
import { basename, dirname } from 'path'
+import { logger } from '@root-helpers/logger'
class RedundancyUrlManager {
}
removeBySegmentUrl (segmentUrl: string) {
- console.log('Removing redundancy of segment URL %s.', segmentUrl)
+ logger.info(`Removing redundancy of segment URL ${segmentUrl}.`)
const baseUrl = dirname(segmentUrl)
-import { wait } from '@root-helpers/utils'
-import { Segment } from '@peertube/p2p-media-loader-core'
import { basename } from 'path'
+import { Segment } from '@peertube/p2p-media-loader-core'
+import { logger } from '@root-helpers/logger'
+import { wait } from '@root-helpers/utils'
type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } }
}
if (!segmentValue) {
- console.log('Refetching sha segments for %s.', filename)
+ logger.info(`Refetching sha segments for ${filename}`)
await wait(1000)
return fetch(url)
.then(res => res.json() as Promise<SegmentsJSON>)
.catch(err => {
- console.error('Cannot get sha256 segments', err)
+ logger.error('Cannot get sha256 segments', err)
return {}
})
}
import debug from 'debug'
import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
import { isMobile } from '@root-helpers/web-browser'
import { timeToInt } from '@shared/core-utils'
import { VideoView, VideoViewEvent } from '@shared/models/videos'
import { PeerTubePluginOptions, VideoJSCaption } from '../../types'
import { SettingsButton } from '../settings/settings-menu-button'
-const logger = debug('peertube:player:peertube')
+const debugLogger = debug('peertube:player:peertube')
const Plugin = videojs.getPlugin('plugin')
lastCurrentTime = currentTime
this.notifyUserIsWatching(currentTime, lastViewEvent)
- .catch(err => console.error('Cannot notify user is watching.', err))
+ .catch(err => logger.error('Cannot notify user is watching.', err))
lastViewEvent = undefined
(this.player as any).cache_.inactivityTimeout = timeout
this.player.options_.inactivityTimeout = timeout
- logger('Set player inactivity to ' + timeout)
+ debugLogger('Set player inactivity to ' + timeout)
}
private initCaptions () {
import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
import { secondsToTime } from '@shared/core-utils'
import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../../types'
import { bytes } from '../common'
this.populateInfoValues(options)
} catch (err) {
- console.error('Cannot update stats.', err)
+ logger.error('Cannot update stats.', err)
clearInterval(this.updateInterval)
}
}, this.intervalMs)
// We use temporary IndexDB (all data are removed on destroy) to avoid RAM issues
// Thanks @santiagogil and @Feross
-import { EventEmitter } from 'events'
import Dexie from 'dexie'
+import { EventEmitter } from 'events'
+import { logger } from '@root-helpers/logger'
class ChunkDatabase extends Dexie {
chunks: Dexie.Table<{ id: number, buf: Buffer }, number>
return this.db.chunks.bulkPut(processing.map(p => ({ id: p.id, buf: p.buf })))
})
} catch (err) {
- console.log('Cannot bulk insert chunks. Store them in memory.', { err })
+ logger.info('Cannot bulk insert chunks. Store them in memory.', err)
processing.forEach(p => {
this.memoryChunks[p.id] = p.buf
return cb(null, buf.slice(offset, len + offset))
})
.catch(err => {
- console.error(err)
+ logger.error(err)
return cb(err)
})
}
return cb()
} catch (err) {
- console.error('Cannot destroy peertube chunk store.', err)
+ logger.error('Cannot destroy peertube chunk store.', err)
return cb(err)
}
}
databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray()
})
} catch (err) {
- console.error('Cannot update expiration of fetch expired databases.', err)
+ logger.error('Cannot update expiration of fetch expired databases.', err)
}
for (const databaseToDeleteInfo of databasesToDeleteInfo) {
private async dropDatabase (databaseName: string) {
const dbToDelete = new ChunkDatabase(databaseName)
- console.log('Destroying IndexDB database %s.', databaseName)
+ logger.info(`Destroying IndexDB database ${databaseName}`)
try {
await dbToDelete.delete()
return this.expirationDB.databases.where({ name: databaseName }).delete()
})
} catch (err) {
- console.error('Cannot delete %s.', databaseName, err)
+ logger.error(`Cannot delete ${databaseName}.`, err)
}
}
// Thanks: https://github.com/feross/render-media
const MediaElementWrapper = require('mediasource')
+import { logger } from '@root-helpers/logger'
import { extname } from 'path'
const Videostream = require('videostream')
}
function fallbackToMediaSource (useVP9 = false) {
- if (useVP9 === true) console.log('Falling back to media source with VP9 enabled.')
- else console.log('Falling back to media source..')
+ if (useVP9 === true) logger.info('Falling back to media source with VP9 enabled.')
+ else logger.info('Falling back to media source..')
useMediaSource(useVP9)
}
import videojs from 'video.js'
import * as WebTorrent from 'webtorrent'
+import { logger } from '@root-helpers/logger'
import { isIOS } from '@root-helpers/web-browser'
import { timeToInt } from '@shared/core-utils'
import { VideoFile } from '@shared/models'
if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy()
this.webtorrent.remove(videoFile.magnetUri)
- console.log('Removed ' + videoFile.magnetUri)
+ logger.info(`Removed ${videoFile.magnetUri}`)
}
}
) {
if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done)
- console.log('Adding ' + magnetOrTorrentUrl + '.')
+ logger.info(`Adding ${magnetOrTorrentUrl}.`)
const oldTorrent = this.torrent
const torrentOptions = {
}
this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => {
- console.log('Added ' + magnetOrTorrentUrl + '.')
+ logger.info(`Added ${magnetOrTorrentUrl}.`)
if (oldTorrent) {
// Pause the old torrent
}, options.delay || 0)
})
- this.torrent.on('error', (err: any) => console.error(err))
+ this.torrent.on('error', (err: any) => logger.error(err))
this.torrent.on('warning', (err: any) => {
// We don't support HTTP tracker but we don't care -> we use the web socket tracker
// Users don't care about issues with WebRTC, but developers do so log it in the console
if (err.message.indexOf('Ice connection failed') !== -1) {
- console.log(err)
+ logger.info(err)
return
}
// Magnet hash is not up to date with the torrent file, add directly the torrent file
if (err.message.indexOf('incorrect info hash') !== -1) {
- console.error('Incorrect info hash detected, falling back to torrent file.')
+ logger.error('Incorrect info hash detected, falling back to torrent file.')
const newOptions = { forcePlay: true, seek: options.seek }
return this.addTorrent(this.torrent['xs'], previousVideoFile, newOptions, done)
}
this.handleError(err)
}
- console.warn(err)
+ logger.warn(err)
})
}
return
}
- console.error(err)
+ logger.error(err)
this.player.pause()
this.player.posterImage.show()
this.player.removeClass('vjs-has-autoplay')
// Lower resolution
if (this.isPlayerWaiting() && file.resolution.id < this.currentVideoFile.resolution.id) {
- console.log('Downgrading automatically the resolution to: %s', file.resolution.label)
+ logger.info(`Downgrading automatically the resolution to: ${file.resolution.label}`)
changeResolution = true
} else if (file.resolution.id > this.currentVideoFile.resolution.id) { // Higher resolution
- console.log('Upgrading automatically the resolution to: %s', file.resolution.label)
+ logger.info(`Upgrading automatically the resolution to: ${file.resolution.label}`)
changeResolution = true
changeResolutionDelay = this.CONSTANTS.AUTO_QUALITY_HIGHER_RESOLUTION_DELAY
}
// The renderer returns an error when we destroy it, so skip them
if (this.destroyingFakeRenderer === false && err) {
- console.error('Cannot render new torrent in fake video element.', err)
+ logger.error('Cannot render new torrent in fake video element.', err)
}
// Load the future file at the correct time (in delay MS - 2 seconds)
try {
this.fakeRenderer.destroy()
} catch (err) {
- console.log('Cannot destroy correctly fake renderer.', err)
+ logger.info('Cannot destroy correctly fake renderer.', err)
}
}
this.fakeRenderer = undefined
+import { logger } from '@root-helpers/logger'
import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '@shared/core-utils/i18n'
export class TranslationsManager {
return fetch(path + '/server.json')
.then(res => res.json())
.catch(err => {
- console.error('Cannot get server translations', err)
+ logger.error('Cannot get server translations', err)
return undefined
})
}
return json
})
.catch(err => {
- console.error('Cannot get player translations', err)
+ logger.error('Cannot get player translations', err)
return undefined
})
}
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module'
import { environment } from './environments/environment'
+import { logger } from './root-helpers'
if (environment.production) {
enableProdMode()
}
+logger.registerServerSending(environment.apiUrl)
+
const bootstrap = () => platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(bootstrapModule => {
return bootstrapModule
})
.catch(err => {
- console.error(err)
+ logger.error(err)
return null
})
+import { logger } from './logger'
+
function imageToDataURL (input: File | Blob) {
return new Promise<string>(res => {
const reader = new FileReader()
- reader.onerror = err => console.error('Cannot read input file.', err)
+ reader.onerror = err => logger.error('Cannot read input file.', err)
reader.onloadend = () => res(reader.result as string)
reader.readAsDataURL(input)
})
export * from './bytes'
export * from './images'
export * from './local-storage-utils'
+export * from './logger'
export * from './peertube-web-storage'
export * from './plugins-manager'
export * from './string'
--- /dev/null
+import { ClientLogCreate } from '@shared/models/server'
+import { peertubeLocalStorage } from './peertube-web-storage'
+import { UserTokens } from './users'
+
+export type LoggerHook = (message: LoggerMessage, meta?: LoggerMeta) => void
+export type LoggerLevel = 'info' | 'warn' | 'error'
+
+export type LoggerMessage = string | Error | object
+export type LoggerMeta = Error | { [ id: string ]: any, err?: Error }
+
+declare global {
+ interface Window {
+ logger: Logger
+ }
+}
+
+class Logger {
+ private readonly hooks: { level: LoggerLevel, hook: LoggerHook }[] = []
+
+ info (message: LoggerMessage, meta?: LoggerMeta) {
+ this.runHooks('info', message, meta)
+
+ if (meta) console.log(message, meta)
+ else console.log(message)
+ }
+
+ warn (message: LoggerMessage, meta?: LoggerMeta) {
+ this.runHooks('warn', message, meta)
+
+ if (meta) console.warn(message, meta)
+ else console.warn(message)
+ }
+
+ error (message: LoggerMessage, meta?: LoggerMeta) {
+ this.runHooks('error', message, meta)
+
+ if (meta) console.error(message, meta)
+ else console.error(message)
+ }
+
+ addHook (level: LoggerLevel, hook: LoggerHook) {
+ this.hooks.push({ level, hook })
+ }
+
+ registerServerSending (serverUrl: string) {
+ this.addHook('warn', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('warn', message, meta)))
+ this.addHook('error', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('error', message, meta)))
+ }
+
+ sendClientLog (serverUrl: string, payload: ClientLogCreate | null) {
+ if (!payload) return
+
+ const headers = new Headers({
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ })
+
+ try {
+ const tokens = UserTokens.getUserTokens(peertubeLocalStorage)
+
+ if (tokens) headers.set('Authorization', `${tokens.tokenType} ${tokens.accessToken}`)
+ } catch (err) {
+ console.error('Cannot set tokens to client log sender.', { err })
+ }
+
+ try {
+ fetch(serverUrl + '/api/v1/server/logs/client', {
+ headers,
+ method: 'POST',
+ body: JSON.stringify(payload)
+ })
+ } catch (err) {
+ console.error('Cannot send client warn/error to server.', err)
+ }
+ }
+
+ private buildServerLogPayload (level: Extract<LoggerLevel, 'warn' | 'error'>, message: LoggerMessage, meta?: LoggerMeta) {
+ if (!message) return null
+
+ return {
+ message: this.buildMessageServerLogPayload(message),
+ userAgent: navigator.userAgent,
+ url: window.location.href,
+ level,
+ stackTrace: this.buildStackServerLogPayload(message, meta),
+ meta: this.buildMetaServerLogPayload(meta)
+ }
+ }
+
+ private buildMessageServerLogPayload (message: LoggerMessage) {
+ if (typeof message === 'string') return message
+ if (message instanceof Error) return message.message
+
+ return JSON.stringify(message)
+ }
+
+ private buildStackServerLogPayload (message: LoggerMessage, meta?: LoggerMeta) {
+ if (message instanceof Error) return message.stack
+ if (meta instanceof Error) return meta.stack
+ if (meta?.err instanceof Error) return meta.err.stack
+
+ return undefined
+ }
+
+ private buildMetaServerLogPayload (meta?: LoggerMeta) {
+ if (!meta) return undefined
+ if (meta instanceof Error) return undefined
+
+ let result: string
+
+ try {
+ result = JSON.stringify(meta, (key, value) => {
+ if (key === 'err') return undefined
+
+ return value
+ })
+ } catch (err) {
+ console.error('Cannot stringify meta.', err)
+ }
+
+ return result
+ }
+
+ private runHooks (level: LoggerLevel, message: LoggerMessage, meta?: LoggerMeta) {
+ for (const hookObj of this.hooks) {
+ if (hookObj.level !== level) continue
+
+ hookObj.hook(message, meta)
+ }
+ }
+}
+
+const logger = window.logger || new Logger()
+window.logger = logger
+
+export {
+ logger
+}
} from '@shared/models'
import { environment } from '../environments/environment'
import { ClientScript } from '../types'
+import { logger } from './logger'
interface HookStructValue extends RegisterClientHookOptions {
plugin: ServerConfigPlugin
type OnClientRoute = (options: RegisterClientRouteOptions) => void
-const logger = debug('peertube:plugins')
+const debugLogger = debug('peertube:plugins')
class PluginsManager {
private hooks: Hooks = {}
const hookType = getHookType(hookName)
for (const hook of this.hooks[hookName]) {
- console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name)
+ logger.info(`Running hook ${hookName} of plugin ${hook.plugin.name}`)
result = await internalRunHook(hook.handler, hookType, result, params, err => {
- console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err)
+ logger.error(`Cannot run hook ${hookName} of script ${hook.clientScript.script} of plugin ${hook.plugin.name}`, err)
})
}
this.loadingScopes[scope] = true
- logger('Loading scope %s', scope)
+ debugLogger('Loading scope %s', scope)
try {
if (!isReload) this.loadedScopes.push(scope)
this.loadingScopes[scope] = false
this.pluginsLoaded[scope].next(true)
- logger('Nothing to load for scope %s', scope)
+ debugLogger('Nothing to load for scope %s', scope)
return
}
this.pluginsLoaded[scope].next(true)
this.loadingScopes[scope] = false
- logger('Scope %s loaded', scope)
+ debugLogger('Scope %s loaded', scope)
} catch (err) {
- console.error('Cannot load plugins by scope %s.', scope, err)
+ logger.error(`Cannot load plugins by scope ${scope}`, err)
}
}
const registerHook = (options: RegisterClientHookOptions) => {
if (clientHookObject[options.target] !== true) {
- console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name)
+ logger.error(`Unknown hook ${options.target} of plugin ${plugin.name}. Skipping.`)
return
}
const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo)
- console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
+ logger.info(`Loading script ${clientScript.script} of plugin ${plugin.name}`)
const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
return dynamicImport(absURL)
})
})
.then(() => this.sortHooksByPriority())
- .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
+ .catch(err => logger.error(`Cannot import or register plugin ${pluginInfo.plugin.name}`, err))
}
private sortHooksByPriority () {
// eslint-disable-next-line no-new-func
return new Function(`return import('${url}')`)()
} catch {
- console.log('Fallback to import polyfill')
+ logger.info('Fallback to import polyfill')
return new Promise((resolve, reject) => {
const vector = '$importModule$' + Math.random().toString(32).slice(2)
import './embed.scss'
-
import * as Channel from 'jschannel'
+import { logger } from '../../root-helpers'
import { PeerTubeResolution, PeerTubeTextTrack } from '../player/definitions'
import { PeerTubeEmbed } from './embed'
}
private setResolution (resolutionId: number) {
- console.log('set resolution %d', resolutionId)
+ logger.info(`Set resolution ${resolutionId}`)
if (this.isWebtorrent()) {
if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionPossible() === false) return
import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models'
import { PeertubePlayerManager } from '../../assets/player'
import { TranslationsManager } from '../../assets/player/translations-manager'
-import { getParamString } from '../../root-helpers'
+import { getParamString, logger } from '../../root-helpers'
import { PeerTubeEmbedApi } from './embed-api'
import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared'
import { PlayerHTML } from './shared/player-html'
private playlistTracker: PlaylistTracker
constructor (videoWrapperId: string) {
+ logger.registerServerSending(window.location.origin)
+
this.http = new AuthHTTP()
this.videoFetcher = new VideoFetcher(this.http)
try {
this.config = JSON.parse(window['PeerTubeServerConfig'])
} catch (err) {
- console.error('Cannot parse HTML config.', err)
+ logger.error('Cannot parse HTML config.', err)
}
}
async playNextPlaylistVideo () {
const next = this.playlistTracker.getNextPlaylistElement()
if (!next) {
- console.log('Next element not found in playlist.')
+ logger.info('Next element not found in playlist.')
return
}
async playPreviousPlaylistVideo () {
const previous = this.playlistTracker.getPreviousPlaylistElement()
if (!previous) {
- console.log('Previous element not found in playlist.')
+ logger.info('Previous element not found in playlist.')
return
}
.catch(err => {
(window as any).displayIncompatibleBrowser()
- console.error('Cannot init embed.', err)
+ logger.error('Cannot init embed.', err)
})
import { peertubeTranslate } from '../../../../../shared/core-utils/i18n'
import { VideoDetails } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
import { Translations } from './translations'
export class PlayerHTML {
}
displayError (text: string, translations: Translations) {
- console.error(text)
+ logger.error(text)
// Remove video element
if (this.playerElement) {
getParamString,
getParamToggle,
isP2PEnabled,
+ logger,
peertubeLocalStorage,
UserLocalStorageKeys
} from '../../../root-helpers'
else this.mode = 'webtorrent'
}
} catch (err) {
- console.error('Cannot get params from URL.', err)
+ logger.error('Cannot get params from URL.', err)
}
}
import { HttpStatusCode, ResultList, VideoPlaylistElement } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
import { AuthHTTP } from './auth-http'
export class PlaylistFetcher {
playlistResponse = await playlistPromise
isResponseOk = playlistResponse.status === HttpStatusCode.OK_200
} catch (err) {
- console.error(err)
+ logger.error(err)
isResponseOk = false
}
}
if (i === 10) {
- console.error('Cannot fetch all playlists elements, there are too many!')
+ logger.error('Cannot fetch all playlists elements, there are too many!')
}
return elements
import { VideoPlaylist, VideoPlaylistElement } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
export class PlaylistTracker {
private currentPlaylistElement: VideoPlaylistElement
setPosition (position: number) {
this.currentPlaylistElement = this.playlistElements.find(e => e.position === position)
if (!this.currentPlaylistElement || !this.currentPlaylistElement.video) {
- console.error('Current playlist element is not valid.', this.currentPlaylistElement)
+ logger.error('Current playlist element is not valid.', this.currentPlaylistElement)
this.currentPlaylistElement = this.getNextPlaylistElement()
}
import { HttpStatusCode, LiveVideo, VideoDetails } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
import { AuthHTTP } from './auth-http'
export class VideoFetcher {
videoResponse = await videoPromise
isResponseOk = videoResponse.status === HttpStatusCode.OK_200
} catch (err) {
- console.error(err)
+ logger.error(err)
isResponseOk = false
}
import './test-embed.scss'
import { PeerTubeResolution, PlayerEventType } from '../player/definitions'
import { PeerTubePlayer } from '../player/player'
+import { logger } from '../../root-helpers'
window.addEventListener('load', async () => {
const urlParts = window.location.href.split('/')
const mainElement = document.querySelector('#host')
mainElement.appendChild(iframe)
- console.log('Document finished loading.')
+ logger.info('Document finished loading.')
const player = new PeerTubePlayer(document.querySelector('iframe'))
window['player'] = player
- console.log('Awaiting player ready...')
+ logger.info('Awaiting player ready...')
await player.ready
- console.log('Player is ready.')
+ logger.info('Player is ready.')
const monitoredEvents = [
'pause',
]
monitoredEvents.forEach(e => {
- player.addEventListener(e as PlayerEventType, (param) => console.log(`PLAYER: event '${e}' received`, param))
- console.log(`PLAYER: now listening for event '${e}'`)
+ player.addEventListener(e as PlayerEventType, (param) => logger.info(`PLAYER: event '${e}' received`, { param }))
+ logger.info(`PLAYER: now listening for event '${e}'`)
player.getCurrentPosition()
.then(position => {
--- /dev/null
+{
+ "extends": "../../../tsconfig.json",
+ "include": [
+ "src/standalone/videos/embed.ts",
+ "src/standalone/videos/test-embed.ts"
+ ]
+}
{
loader: 'ts-loader',
options: {
- configFile: helpers.root('tsconfig.json')
+ configFile: helpers.root('src/standalone/videos/tsconfig.json')
}
}
]
# 3 attempts in 5 min
window: 5 minutes
max: 3
+ receive_client_log:
+ # 10 attempts in 10 min
+ window: 10 minutes
+ max: 10
# Proxies to trust to get real client IP
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
log:
level: 'info' # 'debug' | 'info' | 'warn' | 'error'
+
rotation:
enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
max_file_size: 12MB
max_files: 20
+
anonymize_ip: false
+
log_ping_requests: true
log_tracker_unknown_infohash: true
+
prettify_sql: false
+ # Accept warn/error logs coming from the client
+ accept_client_log: true
+
# Highly experimental support of Open Telemetry
open_telemetry:
metrics:
# 3 attempts in 5 min
window: 5 minutes
max: 3
+ receive_client_log:
+ # 10 attempts in 10 min
+ window: 10 minutes
+ max: 10
# Proxies to trust to get real client IP
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
log:
level: 'info' # 'debug' | 'info' | 'warn' | 'error'
+
rotation:
enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
max_file_size: 12MB
max_files: 20
+
anonymize_ip: false
+
log_ping_requests: true
log_tracker_unknown_infohash: true
+
prettify_sql: false
+ # Accept warn/error logs coming from the client
+ accept_client_log: true
+
# Highly experimental support of Open Telemetry
open_telemetry:
metrics:
import { join } from 'path'
import { isArray } from '@server/helpers/custom-validators/misc'
import { logger, mtimeSortFilesDesc } from '@server/helpers/logger'
-import { LogLevel } from '../../../../shared/models/server/log-level.type'
+import { pick } from '@shared/core-utils'
+import { ClientLogCreate, HttpStatusCode } from '@shared/models'
+import { ServerLogLevel } from '../../../../shared/models/server/server-log-level.type'
import { UserRight } from '../../../../shared/models/users'
import { CONFIG } from '../../../initializers/config'
import { AUDIT_LOG_FILENAME, LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants'
-import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
-import { getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
+import { asyncMiddleware, authenticate, buildRateLimiter, ensureUserHasRight, optionalAuthenticate } from '../../../middlewares'
+import { createClientLogValidator, getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
+
+const createClientLogRateLimiter = buildRateLimiter({
+ windowMs: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.WINDOW_MS,
+ max: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.MAX
+})
const logsRouter = express.Router()
+logsRouter.post('/logs/client',
+ createClientLogRateLimiter,
+ optionalAuthenticate,
+ createClientLogValidator,
+ createClientLog
+)
+
logsRouter.get('/logs',
authenticate,
ensureUserHasRight(UserRight.MANAGE_LOGS),
// ---------------------------------------------------------------------------
+function createClientLog (req: express.Request, res: express.Response) {
+ const logInfo = req.body as ClientLogCreate
+
+ const meta = {
+ tags: [ 'client' ],
+ username: res.locals.oauth?.token?.User?.username,
+
+ ...pick(logInfo, [ 'userAgent', 'stackTrace', 'meta', 'url' ])
+ }
+
+ logger.log(logInfo.level, `Client log: ${logInfo.message}`, meta)
+
+ return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
+}
+
const auditLogNameFilter = generateLogNameFilter(AUDIT_LOG_FILENAME)
async function getAuditLogs (req: express.Request, res: express.Response) {
const output = await generateOutput({
startDateQuery: string
endDateQuery?: string
- level: LogLevel
+ level: ServerLogLevel
nameFilter: RegExp
tagsOneOf?: string[]
}) {
path: string
startDate: Date
endDate: Date
- level: LogLevel
+ level: ServerLogLevel
currentSize: number
tagsOneOf: Set<string>
}) {
let logTime: number
- const logsLevel: { [ id in LogLevel ]: number } = {
+ const logsLevel: { [ id in ServerLogLevel ]: number } = {
audit: -1,
debug: 0,
info: 1,
+import validator from 'validator'
+import { CONSTRAINTS_FIELDS } from '@server/initializers/constants'
+import { ClientLogLevel, ServerLogLevel } from '@shared/models'
import { exists } from './misc'
-import { LogLevel } from '../../../shared/models/server/log-level.type'
-const logLevels: LogLevel[] = [ 'debug', 'info', 'warn', 'error' ]
+const serverLogLevels: Set<ServerLogLevel> = new Set([ 'debug', 'info', 'warn', 'error' ])
+const clientLogLevels: Set<ClientLogLevel> = new Set([ 'warn', 'error' ])
function isValidLogLevel (value: any) {
- return exists(value) && logLevels.includes(value)
+ return exists(value) && serverLogLevels.has(value)
+}
+
+function isValidClientLogMessage (value: any) {
+ return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_MESSAGE)
+}
+
+function isValidClientLogLevel (value: any) {
+ return exists(value) && clientLogLevels.has(value)
+}
+
+function isValidClientLogStackTrace (value: any) {
+ return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_STACK_TRACE)
+}
+
+function isValidClientLogMeta (value: any) {
+ return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_META)
+}
+
+function isValidClientLogUserAgent (value: any) {
+ return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_USER_AGENT)
}
// ---------------------------------------------------------------------------
export {
- isValidLogLevel
+ isValidLogLevel,
+ isValidClientLogMessage,
+ isValidClientLogStackTrace,
+ isValidClientLogMeta,
+ isValidClientLogLevel,
+ isValidClientLogUserAgent
}
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.login.window')),
MAX: config.get<number>('rates_limit.login.max')
},
+ RECEIVE_CLIENT_LOG: {
+ WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.receive_client_log.window')),
+ MAX: config.get<number>('rates_limit.receive_client_log.max')
+ },
ASK_SEND_EMAIL: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
MAX: config.get<number>('rates_limit.ask_send_email.max')
ANONYMIZE_IP: config.get<boolean>('log.anonymize_ip'),
LOG_PING_REQUESTS: config.get<boolean>('log.log_ping_requests'),
LOG_TRACKER_UNKNOWN_INFOHASH: config.get<boolean>('log.log_tracker_unknown_infohash'),
- PRETTIFY_SQL: config.get<boolean>('log.prettify_sql')
+ PRETTIFY_SQL: config.get<boolean>('log.prettify_sql'),
+ ACCEPT_CLIENT_LOG: config.get<boolean>('log.accept_client_log')
},
OPEN_TELEMETRY: {
METRICS: {
VIDEO_STUDIO: {
TASKS: { min: 1, max: 10 }, // Number of tasks
CUT_TIME: { min: 0 } // Value
+ },
+ LOGS: {
+ CLIENT_MESSAGE: { min: 1, max: 1000 }, // Length
+ CLIENT_STACK_TRACE: { min: 1, max: 5000 }, // Length
+ CLIENT_META: { min: 1, max: 5000 }, // Length
+ CLIENT_USER_AGENT: { min: 1, max: 200 } // Length
}
}
import express from 'express'
-import { query } from 'express-validator'
+import { body, query } from 'express-validator'
+import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
import { isStringArray } from '@server/helpers/custom-validators/search'
-import { isValidLogLevel } from '../../helpers/custom-validators/logs'
+import { CONFIG } from '@server/initializers/config'
+import { HttpStatusCode } from '@shared/models'
+import {
+ isValidClientLogLevel,
+ isValidClientLogMessage,
+ isValidClientLogMeta,
+ isValidClientLogStackTrace,
+ isValidClientLogUserAgent,
+ isValidLogLevel
+} from '../../helpers/custom-validators/logs'
import { isDateValid, toArray } from '../../helpers/custom-validators/misc'
import { logger } from '../../helpers/logger'
import { areValidationErrors } from './shared'
+const createClientLogValidator = [
+ body('message')
+ .custom(isValidClientLogMessage).withMessage('Should have a valid log message'),
+
+ body('url')
+ .custom(isUrlValid).withMessage('Should have a valid log url'),
+
+ body('level')
+ .custom(isValidClientLogLevel).withMessage('Should have a valid log message'),
+
+ body('stackTrace')
+ .optional()
+ .custom(isValidClientLogStackTrace).withMessage('Should have a valid log stack trace'),
+
+ body('meta')
+ .optional()
+ .custom(isValidClientLogMeta).withMessage('Should have a valid log meta'),
+
+ body('userAgent')
+ .optional()
+ .custom(isValidClientLogUserAgent).withMessage('Should have a valid log user agent'),
+
+ (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking createClientLogValidator parameters.', { parameters: req.query })
+
+ if (CONFIG.LOG.ACCEPT_CLIENT_LOG !== true) {
+ return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
+ }
+
+ if (areValidationErrors(req, res)) return
+
+ return next()
+ }
+]
+
const getLogsValidator = [
query('startDate')
.custom(isDateValid).withMessage('Should have a start date that conforms to ISO 8601'),
export {
getLogsValidator,
- getAuditLogsValidator
+ getAuditLogsValidator,
+ createClientLogValidator
}
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import 'mocha'
-import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
+import { expect } from 'chai'
import { HttpStatusCode } from '@shared/models'
+import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
describe('Test logs API validators', function () {
const path = '/api/v1/server/logs'
})
})
+ describe('When creating client logs', function () {
+ const base = {
+ level: 'warn' as 'warn',
+ message: 'my super message',
+ url: 'https://example.com/toto'
+ }
+ const expectedStatus = HttpStatusCode.BAD_REQUEST_400
+
+ it('Should fail with an invalid level', async function () {
+ await server.logs.createLogClient({ payload: { ...base, level: '' as any }, expectedStatus })
+ await server.logs.createLogClient({ payload: { ...base, level: undefined }, expectedStatus })
+ await server.logs.createLogClient({ payload: { ...base, level: 'toto' as any }, expectedStatus })
+ })
+
+ it('Should fail with an invalid message', async function () {
+ await server.logs.createLogClient({ payload: { ...base, message: undefined }, expectedStatus })
+ await server.logs.createLogClient({ payload: { ...base, message: '' }, expectedStatus })
+ await server.logs.createLogClient({ payload: { ...base, message: 'm'.repeat(2500) }, expectedStatus })
+ })
+
+ it('Should fail with an invalid url', async function () {
+ await server.logs.createLogClient({ payload: { ...base, url: undefined }, expectedStatus })
+ await server.logs.createLogClient({ payload: { ...base, url: 'toto' }, expectedStatus })
+ })
+
+ it('Should fail with an invalid stackTrace', async function () {
+ await server.logs.createLogClient({ payload: { ...base, stackTrace: 's'.repeat(10000) }, expectedStatus })
+ })
+
+ it('Should fail with an invalid userAgent', async function () {
+ await server.logs.createLogClient({ payload: { ...base, userAgent: 's'.repeat(500) }, expectedStatus })
+ })
+
+ it('Should fail with an invalid meta', async function () {
+ await server.logs.createLogClient({ payload: { ...base, meta: 's'.repeat(10000) }, expectedStatus })
+ })
+
+ it('Should succeed with the correct params', async function () {
+ await server.logs.createLogClient({ payload: { ...base, stackTrace: 'stackTrace', meta: '{toto}', userAgent: 'userAgent' } })
+ })
+
+ it('Should rate limit log creation', async function () {
+ let fail = false
+
+ for (let i = 0; i < 10; i++) {
+ try {
+ await server.logs.createLogClient({ token: null, payload: base })
+ } catch {
+ fail = true
+ }
+ }
+
+ expect(fail).to.be.true
+ })
+ })
+
after(async function () {
await cleanupTests([ server ])
})
import 'mocha'
import * as chai from 'chai'
+import { HttpStatusCode } from '@shared/models'
import {
cleanupTests,
createSingleServer,
})
})
+ describe('When creating log from the client', function () {
+
+ it('Should create a warn client log', async function () {
+ const now = new Date()
+
+ await server.logs.createLogClient({
+ payload: {
+ level: 'warn',
+ url: 'http://example.com',
+ message: 'my super client message'
+ },
+ token: null
+ })
+
+ const body = await logsCommand.getLogs({ startDate: now })
+ const logsString = JSON.stringify(body)
+
+ expect(logsString.includes('my super client message')).to.be.true
+ })
+
+ it('Should create an error authenticated client log', async function () {
+ const now = new Date()
+
+ await server.logs.createLogClient({
+ payload: {
+ url: 'https://example.com/page1',
+ level: 'error',
+ message: 'my super client message 2',
+ userAgent: 'super user agent',
+ meta: '{hello}',
+ stackTrace: 'super stack trace'
+ }
+ })
+
+ const body = await logsCommand.getLogs({ startDate: now })
+ const logsString = JSON.stringify(body)
+
+ expect(logsString.includes('my super client message 2')).to.be.true
+ expect(logsString.includes('super user agent')).to.be.true
+ expect(logsString.includes('super stack trace')).to.be.true
+ expect(logsString.includes('{hello}')).to.be.true
+ expect(logsString.includes('https://example.com/page1')).to.be.true
+ })
+
+ it('Should refuse to create client logs', async function () {
+ await server.kill()
+
+ await server.run({
+ log: {
+ accept_client_log: false
+ }
+ })
+
+ await server.logs.createLogClient({
+ payload: {
+ level: 'warn',
+ url: 'http://example.com',
+ message: 'my super client message'
+ },
+ expectedStatus: HttpStatusCode.FORBIDDEN_403
+ })
+ })
+ })
+
after(async function () {
await cleanupTests([ server ])
})
--- /dev/null
+import { ClientLogLevel } from './client-log-level.type'
+
+export interface ClientLogCreate {
+ message: string
+ url: string
+ level: ClientLogLevel
+
+ stackTrace?: string
+ userAgent?: string
+ meta?: string
+}
--- /dev/null
+export type ClientLogLevel = 'warn' | 'error'
export * from './about.model'
export * from './broadcast-message-level.type'
+export * from './client-log-create.model'
+export * from './client-log-level.type'
export * from './contact-form.model'
export * from './custom-config.model'
export * from './debug.model'
export * from './emailer.model'
export * from './job.model'
-export * from './log-level.type'
export * from './peertube-problem-document.model'
export * from './server-config.model'
export * from './server-debug.model'
export * from './server-error-code.enum'
export * from './server-follow-create.model'
+export * from './server-log-level.type'
export * from './server-stats.model'
+++ /dev/null
-export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'audit'
--- /dev/null
+export type ServerLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'audit'
-import { HttpStatusCode, LogLevel } from '@shared/models'
+import { ClientLogCreate, HttpStatusCode, ServerLogLevel } from '@shared/models'
import { AbstractCommand, OverrideCommandOptions } from '../shared'
export class LogsCommand extends AbstractCommand {
+ createLogClient (options: OverrideCommandOptions & { payload: ClientLogCreate }) {
+ const path = '/api/v1/server/logs/client'
+
+ return this.postBodyRequest({
+ ...options,
+
+ path,
+ fields: options.payload,
+ implicitToken: true,
+ defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+ })
+ }
+
getLogs (options: OverrideCommandOptions & {
startDate: Date
endDate?: Date
- level?: LogLevel
+ level?: ServerLogLevel
tagsOneOf?: string[]
}) {
const { startDate, endDate, tagsOneOf, level } = options
- name: Search
tags:
- Search
- - name: Custom pages
- tags:
- - Homepage
- name: Moderation
tags:
- Abuses
- Video Blocks
- Account Blocks
- Server Blocks
- - name: Instance Configuration
+ - name: Instance
tags:
- Config
+ - Homepage
- Instance Follows
- Instance Redundancy
- Plugins
- - name: Stats
- tags:
- Stats
- - name: Jobs
- tags:
+ - Logs
- Job
paths:
'/accounts/{name}':
schema:
$ref: '#/components/schemas/ServerStats'
+ /server/logs/client:
+ post:
+ tags:
+ - Logs
+ summary: Send client log
+ operationId: sendClientLog
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SendClientLog'
+ responses:
+ '204':
+ description: successful operation
+
+ /server/logs:
+ get:
+ tags:
+ - Logs
+ summary: Get instance logs
+ operationId: getInstanceLogs
+ security:
+ - OAuth2:
+ - admin
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+
+ /server/audit-logs:
+ get:
+ tags:
+ - Logs
+ summary: Get instance audit logs
+ operationId: getInstanceAuditLogs
+ security:
+ - OAuth2:
+ - admin
+ responses:
+ '200':
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
'/feeds/video-comments.{format}':
get:
enabled:
type: boolean
+ SendClientLog:
+ properties:
+ message:
+ type: string
+ url:
+ type: string
+ description: URL of the current user page
+ level:
+ enum:
+ - error
+ - warn
+ stackTrace:
+ type: string
+ description: Stack trace of the error if there is one
+ userAgent:
+ type: string
+ description: User agent of the web browser that sends the message
+ meta:
+ type: string
+ description: Additional information regarding this log
+ required:
+ - message
+ - url
+ - level
+
ServerStats:
properties:
totalUsers: