1
0
Fork 0

Add ability for client to create server logs

This commit is contained in:
Chocobozzz 2022-07-15 15:30:14 +02:00
parent 654d4ede7f
commit 42b4063699
No known key found for this signature in database
GPG key ID: 583A612D890159BE
97 changed files with 828 additions and 261 deletions

View file

@ -2,11 +2,12 @@ import { SortMeta } from 'primeng/api'
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 & {
@ -149,7 +150,7 @@ export class UserListComponent extends RestTable implements OnInit {
this.selectedColumns = JSON.parse(result)
return
} catch (err) {
console.error('Cannot load selected columns.', err)
logger.error('Cannot load selected columns.', err)
}
}

View file

@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core'
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({
@ -94,7 +95,7 @@ export class PluginSearchComponent implements OnInit {
},
error: err => {
console.error(err)
logger.error(err)
const message = $localize`The plugin index is not available. Please retry later.`
this.notifier.error(message)

View file

@ -1,10 +1,11 @@
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
@ -33,7 +34,7 @@ export class LogRow {
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)
}
}
}

View file

@ -1,6 +1,6 @@
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'
@ -17,11 +17,11 @@ export class LogsComponent implements OnInit {
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[] = []

View file

@ -3,7 +3,7 @@ import { catchError, map } from 'rxjs/operators'
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'
@ -22,7 +22,7 @@ export class LogsService {
isAuditLog: boolean
startDate: string
tagsOneOf?: string[]
level?: LogLevel
level?: ServerLogLevel
endDate?: string
}): Observable<any[]> {
const { isAuditLog, startDate, endDate, tagsOneOf } = options

View file

@ -1,6 +1,7 @@
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'
@ -26,7 +27,7 @@ export class PluginPagesComponent implements AfterViewInit {
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 })
}

View file

@ -1,6 +1,7 @@
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> {
@ -10,7 +11,7 @@ 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')
}
@ -18,7 +19,7 @@ export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
.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')
}

View file

@ -4,6 +4,7 @@ import { ConfirmService, Notifier, ServerService } from '@app/core'
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'
@ -97,7 +98,7 @@ export class VideoStudioEditComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete()
this.isRunningEdition = false
this.notifier.error(err.message)
console.error(err)
logger.error(err)
}
})
}

View file

@ -38,6 +38,7 @@ import { VideoCaptionAddModalComponent } from './video-caption-add-modal.compone
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 = {
@ -443,7 +444,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
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
}

View file

@ -7,6 +7,7 @@ import { FormValidatorService } from '@app/shared/shared-forms'
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'
@ -141,7 +142,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
error: err => {
this.error = err.message
scrollToTop()
console.error(err)
logger.error(err)
}
})
}

View file

@ -6,6 +6,7 @@ import { scrollToTop } from '@app/helpers'
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'
@ -139,7 +140,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
error: err => {
this.error = err.message
scrollToTop()
console.error(err)
logger.error(err)
}
})
}

View file

@ -7,6 +7,7 @@ import { scrollToTop } from '@app/helpers'
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'
@ -128,7 +129,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
error: err => {
this.error = err.message
scrollToTop()
console.error(err)
logger.error(err)
}
})
}

View file

@ -1,6 +1,5 @@
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'
@ -9,6 +8,8 @@ import { genericUploadErrorHandler, scrollToTop } from '@app/helpers'
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'
@ -264,7 +265,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
error: err => {
this.error = err.message
scrollToTop()
console.error(err)
logger.error(err)
}
})
}

View file

@ -8,9 +8,10 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
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',
@ -156,7 +157,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete()
this.isUpdatingVideo = false
this.notifier.error(err.message)
console.error(err)
logger.error(err)
}
})
}

View file

@ -1,6 +1,7 @@
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',
@ -75,7 +76,7 @@ export class VideoDescriptionComponent implements OnChanges {
private updateVideoDescription (description: string) {
this.video.description = description
this.setVideoDescriptionHTML()
.catch(err => console.error(err))
.catch(err => logger.error(err))
}
private async setVideoDescriptionHTML () {

View file

@ -24,6 +24,7 @@ import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/sha
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 {
@ -225,7 +226,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
: 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
}
@ -378,7 +379,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
}
this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
.catch(err => console.error('Cannot build the player', err))
.catch(err => logger.error('Cannot build the player', err))
this.setOpenGraphTags()
@ -550,7 +551,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.player.dispose()
this.player = undefined
} catch (err) {
console.error('Cannot dispose player.', err)
logger.error('Cannot dispose player.', err)
}
}
@ -717,7 +718,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
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
@ -728,11 +729,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
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
}

View file

@ -1,5 +1,5 @@
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'
@ -20,18 +20,19 @@ import {
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',
@ -221,7 +222,7 @@ export class AppComponent implements OnInit, AfterViewInit {
/* 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)
}
}
}

View file

@ -5,7 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
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'
@ -90,7 +90,7 @@ export class AuthService {
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 => {
@ -177,7 +177,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
}
},
error: err => console.error(err)
error: err => logger.error(err)
})
this.user = null
@ -190,7 +190,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
refreshAccessToken () {
if (this.refreshingTokenObservable) return this.refreshingTokenObservable
console.log('Refreshing token...')
logger.info('Refreshing token...')
const refreshToken = this.getRefreshToken()
@ -212,8 +212,8 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
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' ])

View file

@ -1,5 +1,6 @@
import { MessageService } from 'primeng/api'
import { Injectable } from '@angular/core'
import { logger } from '@root-helpers/logger'
@Injectable()
export class Notifier {
@ -10,21 +11,21 @@ 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)
}

View file

@ -2,6 +2,7 @@ import { from, Observable } from 'rxjs'
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'
@ -50,7 +51,7 @@ export class HooksService {
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) {

View file

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core'
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 {
@ -64,7 +65,7 @@ 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
}
@ -75,12 +76,12 @@ export class RestExtractor {
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
}

View file

@ -1,10 +1,11 @@
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 {
@ -34,7 +35,7 @@ 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)
}
}
}
@ -44,7 +45,7 @@ export abstract class RestTable {
}
loadLazy (event: LazyLoadEvent) {
logger('Load lazy %o.', event)
debugLogger('Load lazy %o.', event)
this.sort = {
order: event.sortOrder,

View file

@ -5,7 +5,7 @@ import { Injectable } from '@angular/core'
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]: {
@ -88,7 +88,7 @@ export class RestService {
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 => {
@ -127,7 +127,7 @@ export class RestService {
const search = searchTokens.join(' ') || undefined
logger('Built search: ' + search, additionalFilters)
debugLogger('Built search: ' + search, additionalFilters)
return {
search,

View file

@ -1,5 +1,6 @@
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'
@ -22,7 +23,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
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()
@ -46,7 +47,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
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;
@ -66,7 +67,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
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)

View file

@ -1,10 +1,11 @@
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 {
@ -40,7 +41,7 @@ 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
@ -51,8 +52,8 @@ export class RedirectService {
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)
}
@ -84,18 +85,14 @@ export class RedirectService {
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 })
@ -104,18 +101,18 @@ export class RedirectService {
}
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()
}

View file

@ -4,8 +4,9 @@ import { ViewportScroller } from '@angular/common'
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 {
@ -57,8 +58,8 @@ 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
}
})
@ -67,7 +68,7 @@ export class ScrollService {
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) {

View file

@ -3,6 +3,7 @@ import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
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'
@ -43,7 +44,7 @@ export class ServerService {
} 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()

View file

@ -1,4 +1,5 @@
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'
@ -57,7 +58,7 @@ export class ThemeService {
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()
@ -117,13 +118,13 @@ export class ThemeService {
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)
@ -165,7 +166,7 @@ export class ThemeService {
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
}
}
@ -173,7 +174,7 @@ export class ThemeService {
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)
}
}

View file

@ -2,8 +2,9 @@
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'
@ -95,7 +96,7 @@ export class UserLocalStorageService {
: 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()
@ -142,7 +143,7 @@ export class UserLocalStorageService {
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)
}
}
}

View file

@ -4,6 +4,7 @@ import { ListKeyManager } from '@angular/cdk/a11y'
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'
@ -91,7 +92,7 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDes
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)

View file

@ -1,5 +1,6 @@
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'
@ -19,14 +20,14 @@ function prepareIcu (icu: string) {
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
}

View file

@ -24,7 +24,7 @@ import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
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',
@ -295,8 +295,8 @@ export class MenuComponent implements OnInit {
.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.')
})
}

View file

@ -1,6 +1,7 @@
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({
@ -71,7 +72,7 @@ export class AccountSetupWarningModalComponent {
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)
})

View file

@ -1,6 +1,7 @@
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({
@ -42,7 +43,7 @@ export class AdminWelcomeModalComponent {
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)
})

View file

@ -1,5 +1,6 @@
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',
@ -29,7 +30,7 @@ export class CustomModalComponent {
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
}

View file

@ -2,6 +2,7 @@ import { Location } from '@angular/common'
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'
@ -64,7 +65,7 @@ export class InstanceConfigWarningModalComponent {
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)
})

View file

@ -8,13 +8,14 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable }
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',
@ -158,7 +159,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
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
}
@ -177,7 +178,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
}
protected reloadData () {
logger('Loading data.')
debugLogger('Loading data.')
const options = {
pagination: this.pagination,

View file

@ -3,6 +3,7 @@ import { AuthService, HtmlRendererService, Notifier } from '@app/core'
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'
@ -72,7 +73,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
error: err => {
this.sendingMessage = false
console.error(err)
logger.error(err)
this.notifier.error('Sorry but you cannot send this message. Please retry later')
}
})

View file

@ -20,6 +20,7 @@ import {
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
@ -70,7 +71,7 @@ export class CustomMarkupService {
// 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)
}
})
}
@ -90,7 +91,7 @@ export class CustomMarkupService {
this.dynamicElementService.injectElement(e, component)
} catch (err) {
console.error('Cannot inject component %s.', selector, err)
logger.error(`Cannot inject component ${selector}`, err)
}
})
}

View file

@ -16,7 +16,7 @@ export type AdvancedInputFilterChild = {
value: string
}
const logger = debug('peertube:AdvancedInputFilterComponent')
const debugLogger = debug('peertube:AdvancedInputFilterComponent')
@Component({
selector: 'my-advanced-input-filter',
@ -98,7 +98,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
.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
@ -132,7 +132,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
return
}
logger('On search "%s".', this.searchValue)
debugLogger('On search "%s".', this.searchValue)
this.search.emit(this.searchValue)
}

View file

@ -13,7 +13,7 @@ import {
ViewContainerRef
} from '@angular/core'
const logger = debug('peertube:main:DeferLoadingDirective')
const debugLogger = debug('peertube:main:DeferLoadingDirective')
@Directive({
selector: '[myDeferLoading]'
@ -52,7 +52,7 @@ export class DeferLoadingDirective implements AfterViewInit, OnDestroy {
load () {
if (this.isLoaded()) return
logger('Loading component')
debugLogger('Loading component')
this.viewContainer.clear()
this.view = this.viewContainer.createEmbeddedView(this.template, {}, 0)

View file

@ -17,7 +17,7 @@ import { ScreenService } from '@app/core'
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
@ -66,7 +66,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
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
@ -79,7 +79,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
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()

View file

@ -2,6 +2,7 @@ import { AuthUser } from '@app/core'
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,
@ -234,7 +235,7 @@ export class UserNotification implements UserNotificationServer {
}
} catch (err) {
this.type = null
console.error(err)
logger.error(err)
}
}

View file

@ -9,7 +9,7 @@ import { VideoPlaylist } from '../shared-video-playlist'
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>
@ -36,7 +36,7 @@ export class FindInBulkService {
}
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,
@ -46,7 +46,7 @@ export class FindInBulkService {
}
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,
@ -56,7 +56,7 @@ export class FindInBulkService {
}
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,
@ -94,7 +94,7 @@ export class FindInBulkService {
}
private getVideosInBulk (uuids: string[]) {
logger('Fetching videos %s.', uuids.join(', '))
debugLogger('Fetching videos %s.', uuids.join(', '))
return this.searchService.searchVideos({
uuids,
@ -104,7 +104,7 @@ export class FindInBulkService {
}
private getChannelsInBulk (handles: string[]) {
logger('Fetching channels %s.', handles.join(', '))
debugLogger('Fetching channels %s.', handles.join(', '))
return this.searchService.searchVideoChannels({
handles,
@ -114,7 +114,7 @@ export class FindInBulkService {
}
private getPlaylistsInBulk (uuids: string[]) {
logger('Fetching playlists %s.', uuids.join(', '))
debugLogger('Fetching playlists %s.', uuids.join(', '))
return this.searchService.searchVideoPlaylists({
uuids,

View file

@ -1,6 +1,7 @@
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({
@ -59,7 +60,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
})
.then(window.open)
.catch(err => {
console.error(err)
logger.error(err)
this.notifier.error($localize`Cannot fetch information of this remote account`)
})

View file

@ -9,7 +9,7 @@ import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/sha
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> }
@ -176,17 +176,17 @@ export class UserSubscriptionService {
}
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]),

View file

@ -4,6 +4,7 @@ import { tap } from 'rxjs/operators'
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'
@ -142,7 +143,7 @@ export class VideoDownloadComponent {
.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
}
@ -175,7 +176,7 @@ export class VideoDownloadComponent {
.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
}

View file

@ -8,7 +8,7 @@ import { UserRight } from '@shared/models'
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',
@ -54,7 +54,7 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
})
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()
@ -105,6 +105,6 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
const defaultValues = this.filters.toFormObject()
this.form.patchValue(defaultValues, { emitEvent })
logger('Patched form: %O', defaultValues)
debugLogger('Patched form: %O', defaultValues)
}
}

View file

@ -14,13 +14,14 @@ import {
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
@ -245,7 +246,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
error: err => {
const message = $localize`Cannot load more videos. Try again later.`
console.error(message, { err })
logger.error(message, err)
this.notifier.error(message)
}
})
@ -323,7 +324,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
}
onFiltersChanged (customizedByUser: boolean) {
logger('Running on filters changed')
debugLogger('Running on filters changed')
this.updateUrl(customizedByUser)
@ -364,7 +365,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
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) {
@ -375,7 +376,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
? { ...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)

View file

@ -1,6 +1,7 @@
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'
@ -128,7 +129,7 @@ export class VideosSelectionComponent implements AfterContentInit {
error: err => {
const message = $localize`Cannot load more videos. Try again later.`
console.error(message, { err })
logger.error(message, err)
this.notifier.error(message)
}
})

View file

@ -16,7 +16,7 @@ import {
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
@ -110,7 +110,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
}
reload () {
logger('Reloading component')
debugLogger('Reloading component')
this.videoPlaylists = []
this.videoPlaylistSearch = undefined
@ -121,7 +121,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
}
load () {
logger('Loading component')
debugLogger('Loading component')
this.listenToVideoPlaylistChange()
@ -331,7 +331,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
}
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
@ -359,7 +359,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
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()
}

View file

@ -23,7 +23,7 @@ import { environment } from '../../../environments/environment'
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 }
@ -287,15 +287,15 @@ export class VideoPlaylistService {
}
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)
}

View file

@ -1,3 +1,5 @@
import { logger } from '@root-helpers/logger'
function getStoredVolume () {
const value = getLocalStorage('volume')
if (value !== null && value !== undefined) {
@ -81,7 +83,7 @@ function getStoredVideoWatchHistory (videoUUID?: string) {
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 || {}

View file

@ -23,6 +23,7 @@ import './shared/mobile/peertube-mobile-plugin'
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'
@ -145,7 +146,7 @@ export class PeertubePlayerManager {
return
}
console.log('Fast forwarding HLS to recover from an error.')
logger.info('Fast forwarding HLS to recover from an error.')
this.videojsDecodeErrors++
@ -170,7 +171,7 @@ export class PeertubePlayerManager {
return
}
console.log('Fallback to webtorrent.')
logger.info('Fallback to webtorrent.')
this.rebuildAndUpdateVideoElement(currentPlayer, options.common)

View file

@ -1,5 +1,6 @@
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'
@ -61,7 +62,7 @@ export class HLSOptionsBuilder {
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
}

View file

@ -1,8 +1,9 @@
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')
@ -45,7 +46,7 @@ class PeerTubeMobilePlugin extends 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))
})
}
@ -61,7 +62,7 @@ class PeerTubeMobilePlugin extends Plugin {
}
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)
@ -71,7 +72,7 @@ class PeerTubeMobilePlugin extends Plugin {
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)
@ -100,19 +101,19 @@ class PeerTubeMobilePlugin extends Plugin {
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)

View file

@ -3,6 +3,7 @@
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 = {
@ -17,14 +18,14 @@ type HookFn = (player: videojs.Player, hljs: Hlsjs) => void
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
}
@ -120,7 +121,7 @@ class Html5Hlsjs {
if (!mediaError) return
console.log(mediaError)
logger.info(mediaError)
switch (mediaError.code) {
case mediaError.MEDIA_ERR_ABORTED:
errorTxt = 'You aborted the video playback'
@ -141,7 +142,7 @@ class Html5Hlsjs {
errorTxt = mediaError.message
}
console.error('MEDIA_ERROR: ', errorTxt)
logger.error(`MEDIA_ERROR: ${errorTxt}`)
})
this.initialize()
@ -212,20 +213,20 @@ class Html5Hlsjs {
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')
@ -234,7 +235,7 @@ class Html5Hlsjs {
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)
@ -247,7 +248,7 @@ class Html5Hlsjs {
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')
@ -262,8 +263,8 @@ class Html5Hlsjs {
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
@ -273,7 +274,7 @@ class Html5Hlsjs {
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')
}

View file

@ -5,6 +5,7 @@ import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertub
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)
@ -43,11 +44,11 @@ class P2pMediaLoaderPlugin extends Plugin {
// 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
@ -114,7 +115,7 @@ class P2pMediaLoaderPlugin extends Plugin {
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)
})

View file

@ -1,4 +1,5 @@
import { basename, dirname } from 'path'
import { logger } from '@root-helpers/logger'
class RedundancyUrlManager {
@ -7,7 +8,7 @@ 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)

View file

@ -1,6 +1,7 @@
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 } }
@ -23,7 +24,7 @@ function segmentValidatorFactory (segmentsSha256Url: string, isLive: boolean) {
}
if (!segmentValue) {
console.log('Refetching sha segments for %s.', filename)
logger.info(`Refetching sha segments for ${filename}`)
await wait(1000)
@ -71,7 +72,7 @@ function fetchSha256Segments (url: string) {
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 {}
})
}

View file

@ -1,5 +1,6 @@
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'
@ -15,7 +16,7 @@ import {
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')
@ -176,7 +177,7 @@ class PeerTubePlugin extends 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
@ -249,7 +250,7 @@ class PeerTubePlugin extends Plugin {
(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 () {

View file

@ -1,4 +1,5 @@
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'
@ -125,7 +126,7 @@ class StatsCard extends Component {
this.populateInfoValues(options)
} catch (err) {
console.error('Cannot update stats.', err)
logger.error('Cannot update stats.', err)
clearInterval(this.updateInterval)
}
}, this.intervalMs)

View file

@ -2,8 +2,9 @@
// 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>
@ -104,7 +105,7 @@ export class PeertubeChunkStore extends EventEmitter {
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
@ -143,7 +144,7 @@ export class PeertubeChunkStore extends EventEmitter {
return cb(null, buf.slice(offset, len + offset))
})
.catch(err => {
console.error(err)
logger.error(err)
return cb(err)
})
}
@ -176,7 +177,7 @@ export class PeertubeChunkStore extends EventEmitter {
return cb()
} catch (err) {
console.error('Cannot destroy peertube chunk store.', err)
logger.error('Cannot destroy peertube chunk store.', err)
return cb(err)
}
}
@ -204,7 +205,7 @@ export class PeertubeChunkStore extends EventEmitter {
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) {
@ -214,7 +215,7 @@ export class PeertubeChunkStore extends EventEmitter {
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()
@ -223,7 +224,7 @@ export class PeertubeChunkStore extends EventEmitter {
return this.expirationDB.databases.where({ name: databaseName }).delete()
})
} catch (err) {
console.error('Cannot delete %s.', databaseName, err)
logger.error(`Cannot delete ${databaseName}.`, err)
}
}

View file

@ -1,6 +1,7 @@
// 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')
@ -77,8 +78,8 @@ function renderMedia (file: any, elem: HTMLVideoElement, opts: RenderMediaOption
}
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)
}

View file

@ -1,5 +1,6 @@
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'
@ -210,7 +211,7 @@ class WebTorrentPlugin extends Plugin {
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}`)
}
}
@ -256,7 +257,7 @@ class WebTorrentPlugin extends Plugin {
) {
if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done)
console.log('Adding ' + magnetOrTorrentUrl + '.')
logger.info(`Adding ${magnetOrTorrentUrl}.`)
const oldTorrent = this.torrent
const torrentOptions = {
@ -269,7 +270,7 @@ class WebTorrentPlugin extends Plugin {
}
this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => {
console.log('Added ' + magnetOrTorrentUrl + '.')
logger.info(`Added ${magnetOrTorrentUrl}.`)
if (oldTorrent) {
// Pause the old torrent
@ -309,7 +310,7 @@ class WebTorrentPlugin extends Plugin {
}, 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
@ -317,13 +318,13 @@ class WebTorrentPlugin extends Plugin {
// 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)
}
@ -333,7 +334,7 @@ class WebTorrentPlugin extends Plugin {
this.handleError(err)
}
console.warn(err)
logger.warn(err)
})
}
@ -348,7 +349,7 @@ class WebTorrentPlugin extends Plugin {
return
}
console.error(err)
logger.error(err)
this.player.pause()
this.player.posterImage.show()
this.player.removeClass('vjs-has-autoplay')
@ -465,10 +466,10 @@ class WebTorrentPlugin extends Plugin {
// 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
}
@ -577,7 +578,7 @@ class WebTorrentPlugin extends Plugin {
// 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)
@ -593,7 +594,7 @@ class WebTorrentPlugin extends Plugin {
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

View file

@ -1,3 +1,4 @@
import { logger } from '@root-helpers/logger'
import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '@shared/core-utils/i18n'
export class TranslationsManager {
@ -11,7 +12,7 @@ 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
})
}
@ -33,7 +34,7 @@ export class TranslationsManager {
return json
})
.catch(err => {
console.error('Cannot get player translations', err)
logger.error('Cannot get player translations', err)
return undefined
})
}

View file

@ -3,11 +3,14 @@ import { enableDebugTools } from '@angular/platform-browser'
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 => {
@ -22,7 +25,7 @@ const bootstrap = () => platformBrowserDynamic()
return bootstrapModule
})
.catch(err => {
console.error(err)
logger.error(err)
return null
})

View file

@ -1,8 +1,10 @@
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)
})

View file

@ -2,6 +2,7 @@ export * from './users'
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'

View file

@ -0,0 +1,138 @@
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
}

View file

@ -21,6 +21,7 @@ import {
} from '@shared/models'
import { environment } from '../environments/environment'
import { ClientScript } from '../types'
import { logger } from './logger'
interface HookStructValue extends RegisterClientHookOptions {
plugin: ServerConfigPlugin
@ -48,7 +49,7 @@ type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSetting
type OnClientRoute = (options: RegisterClientRouteOptions) => void
const logger = debug('peertube:plugins')
const debugLogger = debug('peertube:plugins')
class PluginsManager {
private hooks: Hooks = {}
@ -109,10 +110,10 @@ class PluginsManager {
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)
})
}
@ -170,7 +171,7 @@ class PluginsManager {
this.loadingScopes[scope] = true
logger('Loading scope %s', scope)
debugLogger('Loading scope %s', scope)
try {
if (!isReload) this.loadedScopes.push(scope)
@ -180,7 +181,7 @@ class PluginsManager {
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
}
@ -200,9 +201,9 @@ class PluginsManager {
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)
}
}
@ -211,7 +212,7 @@ class PluginsManager {
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
}
@ -252,7 +253,7 @@ class PluginsManager {
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)
@ -266,7 +267,7 @@ class PluginsManager {
})
})
.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 () {
@ -294,7 +295,7 @@ async function dynamicImport (url: string) {
// 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)

View file

@ -1,6 +1,6 @@
import './embed.scss'
import * as Channel from 'jschannel'
import { logger } from '../../root-helpers'
import { PeerTubeResolution, PeerTubeTextTrack } from '../player/definitions'
import { PeerTubeEmbed } from './embed'
@ -59,7 +59,7 @@ export class PeerTubeEmbedApi {
}
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

View file

@ -6,7 +6,7 @@ import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
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'
@ -31,6 +31,8 @@ export class PeerTubeEmbed {
private playlistTracker: PlaylistTracker
constructor (videoWrapperId: string) {
logger.registerServerSending(window.location.origin)
this.http = new AuthHTTP()
this.videoFetcher = new VideoFetcher(this.http)
@ -43,7 +45,7 @@ export class PeerTubeEmbed {
try {
this.config = JSON.parse(window['PeerTubeServerConfig'])
} catch (err) {
console.error('Cannot parse HTML config.', err)
logger.error('Cannot parse HTML config.', err)
}
}
@ -125,7 +127,7 @@ export class PeerTubeEmbed {
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
}
@ -137,7 +139,7 @@ export class PeerTubeEmbed {
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
}
@ -343,5 +345,5 @@ PeerTubeEmbed.main()
.catch(err => {
(window as any).displayIncompatibleBrowser()
console.error('Cannot init embed.', err)
logger.error('Cannot init embed.', err)
})

View file

@ -1,5 +1,6 @@
import { peertubeTranslate } from '../../../../../shared/core-utils/i18n'
import { VideoDetails } from '../../../../../shared/models'
import { logger } from '../../../root-helpers'
import { Translations } from './translations'
export class PlayerHTML {
@ -29,7 +30,7 @@ export class PlayerHTML {
}
displayError (text: string, translations: Translations) {
console.error(text)
logger.error(text)
// Remove video element
if (this.playerElement) {

View file

@ -14,6 +14,7 @@ import {
getParamString,
getParamToggle,
isP2PEnabled,
logger,
peertubeLocalStorage,
UserLocalStorageKeys
} from '../../../root-helpers'
@ -137,7 +138,7 @@ export class PlayerManagerOptions {
else this.mode = 'webtorrent'
}
} catch (err) {
console.error('Cannot get params from URL.', err)
logger.error('Cannot get params from URL.', err)
}
}

View file

@ -1,4 +1,5 @@
import { HttpStatusCode, ResultList, VideoPlaylistElement } from '../../../../../shared/models'
import { logger } from '../../../root-helpers'
import { AuthHTTP } from './auth-http'
export class PlaylistFetcher {
@ -18,7 +19,7 @@ export class PlaylistFetcher {
playlistResponse = await playlistPromise
isResponseOk = playlistResponse.status === HttpStatusCode.OK_200
} catch (err) {
console.error(err)
logger.error(err)
isResponseOk = false
}
@ -49,7 +50,7 @@ export class PlaylistFetcher {
}
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

View file

@ -1,4 +1,5 @@
import { VideoPlaylist, VideoPlaylistElement } from '../../../../../shared/models'
import { logger } from '../../../root-helpers'
export class PlaylistTracker {
private currentPlaylistElement: VideoPlaylistElement
@ -68,7 +69,7 @@ export class PlaylistTracker {
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()
}

View file

@ -1,4 +1,5 @@
import { HttpStatusCode, LiveVideo, VideoDetails } from '../../../../../shared/models'
import { logger } from '../../../root-helpers'
import { AuthHTTP } from './auth-http'
export class VideoFetcher {
@ -17,7 +18,7 @@ export class VideoFetcher {
videoResponse = await videoPromise
isResponseOk = videoResponse.status === HttpStatusCode.OK_200
} catch (err) {
console.error(err)
logger.error(err)
isResponseOk = false
}

View file

@ -1,6 +1,7 @@
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('/')
@ -20,14 +21,14 @@ window.addEventListener('load', async () => {
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',
@ -37,8 +38,8 @@ window.addEventListener('load', async () => {
]
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 => {

View file

@ -0,0 +1,7 @@
{
"extends": "../../../tsconfig.json",
"include": [
"src/standalone/videos/embed.ts",
"src/standalone/videos/test-embed.ts"
]
}

View file

@ -69,7 +69,7 @@ module.exports = function () {
{
loader: 'ts-loader',
options: {
configFile: helpers.root('tsconfig.json')
configFile: helpers.root('src/standalone/videos/tsconfig.json')
}
}
]

View file

@ -27,6 +27,10 @@ rates_limit:
# 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'
@ -168,15 +172,22 @@ object_storage:
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:

View file

@ -25,6 +25,10 @@ rates_limit:
# 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'
@ -166,15 +170,22 @@ object_storage:
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:

View file

@ -3,15 +3,29 @@ import { readdir, readFile } from 'fs-extra'
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),
@ -34,6 +48,21 @@ export {
// ---------------------------------------------------------------------------
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({
@ -63,7 +92,7 @@ async function generateOutput (options: {
startDateQuery: string
endDateQuery?: string
level: LogLevel
level: ServerLogLevel
nameFilter: RegExp
tagsOneOf?: string[]
}) {
@ -104,7 +133,7 @@ async function getOutputFromFile (options: {
path: string
startDate: Date
endDate: Date
level: LogLevel
level: ServerLogLevel
currentSize: number
tagsOneOf: Set<string>
}) {
@ -116,7 +145,7 @@ async function getOutputFromFile (options: {
let logTime: number
const logsLevel: { [ id in LogLevel ]: number } = {
const logsLevel: { [ id in ServerLogLevel ]: number } = {
audit: -1,
debug: 0,
info: 1,

View file

@ -1,14 +1,42 @@
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
}

View file

@ -149,6 +149,10 @@ const CONFIG = {
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')
@ -165,7 +169,8 @@ const CONFIG = {
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: {

View file

@ -365,6 +365,12 @@ const CONSTRAINTS_FIELDS = {
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
}
}

View file

@ -1,11 +1,56 @@
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'),
@ -49,5 +94,6 @@ const getAuditLogsValidator = [
export {
getLogsValidator,
getAuditLogsValidator
getAuditLogsValidator,
createClientLogValidator
}

View file

@ -1,8 +1,9 @@
/* 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'
@ -95,6 +96,62 @@ describe('Test logs API validators', function () {
})
})
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 ])
})

View file

@ -2,6 +2,7 @@
import 'mocha'
import * as chai from 'chai'
import { HttpStatusCode } from '@shared/models'
import {
cleanupTests,
createSingleServer,
@ -198,6 +199,70 @@ describe('Test logs', function () {
})
})
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 ])
})

View file

@ -0,0 +1,11 @@
import { ClientLogLevel } from './client-log-level.type'
export interface ClientLogCreate {
message: string
url: string
level: ClientLogLevel
stackTrace?: string
userAgent?: string
meta?: string
}

View file

@ -0,0 +1 @@
export type ClientLogLevel = 'warn' | 'error'

View file

@ -1,14 +1,16 @@
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'

View file

@ -1 +0,0 @@
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'audit'

View file

@ -0,0 +1 @@
export type ServerLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'audit'

View file

@ -1,12 +1,25 @@
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

View file

@ -330,26 +330,21 @@ x-tagGroups:
- 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}':
@ -4316,6 +4311,58 @@ paths:
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:
@ -6526,6 +6573,31 @@ components:
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: