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 { Component, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 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 { AdvancedInputFilter } from '@app/shared/shared-forms'
import { Actor, DropdownAction } from '@app/shared/shared-main' import { Actor, DropdownAction } from '@app/shared/shared-main'
import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation'
import { UserAdminService } from '@app/shared/shared-users' import { UserAdminService } from '@app/shared/shared-users'
import { logger } from '@root-helpers/logger'
import { User, UserRole } from '@shared/models' import { User, UserRole } from '@shared/models'
type UserForList = User & { type UserForList = User & {
@ -149,7 +150,7 @@ export class UserListComponent extends RestTable implements OnInit {
this.selectedColumns = JSON.parse(result) this.selectedColumns = JSON.parse(result)
return return
} catch (err) { } 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 { ActivatedRoute, Router } from '@angular/router'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core' import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core'
import { logger } from '@root-helpers/logger'
import { PeerTubePluginIndex, PluginType } from '@shared/models' import { PeerTubePluginIndex, PluginType } from '@shared/models'
@Component({ @Component({
@ -94,7 +95,7 @@ export class PluginSearchComponent implements OnInit {
}, },
error: err => { error: err => {
console.error(err) logger.error(err)
const message = $localize`The plugin index is not available. Please retry later.` const message = $localize`The plugin index is not available. Please retry later.`
this.notifier.error(message) this.notifier.error(message)

View File

@ -1,10 +1,11 @@
import { LogLevel } from '@shared/models'
import omit from 'lodash-es/omit' import omit from 'lodash-es/omit'
import { logger } from '@root-helpers/logger'
import { ServerLogLevel } from '@shared/models'
export class LogRow { export class LogRow {
date: Date date: Date
localeDate: string localeDate: string
level: LogLevel level: ServerLogLevel
message: string message: string
meta: string meta: string
@ -33,7 +34,7 @@ export class LogRow {
this.meta = JSON.stringify(message, null, 2) this.meta = JSON.stringify(message, null, 2)
this.message = '' this.message = ''
} catch (err) { } 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 { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
import { LocalStorageService, Notifier } from '@app/core' import { LocalStorageService, Notifier } from '@app/core'
import { LogLevel } from '@shared/models' import { ServerLogLevel } from '@shared/models'
import { LogRow } from './log-row.model' import { LogRow } from './log-row.model'
import { LogsService } from './logs.service' import { LogsService } from './logs.service'
@ -17,11 +17,11 @@ export class LogsComponent implements OnInit {
logs: LogRow[] = [] logs: LogRow[] = []
timeChoices: { id: string, label: string, dateFormat: string }[] = [] timeChoices: { id: string, label: string, dateFormat: string }[] = []
levelChoices: { id: LogLevel, label: string }[] = [] levelChoices: { id: ServerLogLevel, label: string }[] = []
logTypeChoices: { id: 'audit' | 'standard', label: string }[] = [] logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
startDate: string startDate: string
level: LogLevel level: ServerLogLevel
logType: 'audit' | 'standard' logType: 'audit' | 'standard'
tagsOneOf: string[] = [] tagsOneOf: string[] = []

View File

@ -3,7 +3,7 @@ import { catchError, map } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestService } from '@app/core' import { RestExtractor, RestService } from '@app/core'
import { LogLevel } from '@shared/models' import { ServerLogLevel } from '@shared/models'
import { environment } from '../../../../environments/environment' import { environment } from '../../../../environments/environment'
import { LogRow } from './log-row.model' import { LogRow } from './log-row.model'
@ -22,7 +22,7 @@ export class LogsService {
isAuditLog: boolean isAuditLog: boolean
startDate: string startDate: string
tagsOneOf?: string[] tagsOneOf?: string[]
level?: LogLevel level?: ServerLogLevel
endDate?: string endDate?: string
}): Observable<any[]> { }): Observable<any[]> {
const { isAuditLog, startDate, endDate, tagsOneOf } = options const { isAuditLog, startDate, endDate, tagsOneOf } = options

View File

@ -1,6 +1,7 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core' import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { PluginService } from '@app/core' import { PluginService } from '@app/core'
import { logger } from '@root-helpers/logger'
@Component({ @Component({
templateUrl: './plugin-pages.component.html' templateUrl: './plugin-pages.component.html'
@ -26,7 +27,7 @@ export class PluginPagesComponent implements AfterViewInit {
const registered = this.pluginService.getRegisteredClientRoute(path) const registered = this.pluginService.getRegisteredClientRoute(path)
if (!registered) { 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 }) return this.router.navigate([ '/404' ], { skipLocationChange: true })
} }

View File

@ -1,6 +1,7 @@
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router' import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { ResultList } from '@shared/models' import { ResultList } from '@shared/models'
export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> { 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 const url = route.params.url
if (!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') return this.router.navigateByUrl('/404')
} }
@ -18,7 +19,7 @@ export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
.pipe( .pipe(
map(result => { map(result => {
if (result.data.length !== 1) { 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') 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 { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { VideoDetails } from '@app/shared/shared-main' import { VideoDetails } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { secondsToTime } from '@shared/core-utils' import { secondsToTime } from '@shared/core-utils'
import { VideoStudioTask, VideoStudioTaskCut } from '@shared/models' import { VideoStudioTask, VideoStudioTaskCut } from '@shared/models'
import { VideoStudioService } from '../shared' import { VideoStudioService } from '../shared'
@ -97,7 +98,7 @@ export class VideoStudioEditComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete() this.loadingBar.useRef().complete()
this.isRunningEdition = false this.isRunningEdition = false
this.notifier.error(err.message) 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 { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component'
import { VideoEditType } from './video-edit.type' import { VideoEditType } from './video-edit.type'
import { VideoSource } from '@shared/models/videos/video-source' import { VideoSource } from '@shared/models/videos/video-source'
import { logger } from '@root-helpers/logger'
type VideoLanguages = VideoConstant<string> & { group?: string } type VideoLanguages = VideoConstant<string> & { group?: string }
type PluginField = { type PluginField = {
@ -443,7 +444,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId) const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId)
if (!newChannel || !oldChannel) { if (!newChannel || !oldChannel) {
console.error('Cannot find new or old channel.') logger.error('Cannot find new or old channel.')
return 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 { Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LiveVideoService } from '@app/shared/shared-video-live' import { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models' import { LiveVideo, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -141,7 +142,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() 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 { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models' import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils' import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -139,7 +140,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() 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 { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { VideoUpdate } from '@shared/models' import { VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils' import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -128,7 +129,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() scrollToTop()
console.error(err) logger.error(err)
} }
}) })
} }

View File

@ -1,6 +1,5 @@
import { truncate } from 'lodash-es' import { truncate } from 'lodash-es'
import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx' import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx'
import { isIOS } from '@root-helpers/web-browser'
import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http' import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http'
import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
@ -9,6 +8,8 @@ import { genericUploadErrorHandler, scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms' import { FormValidatorService } from '@app/shared/shared-forms'
import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' 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 { HttpStatusCode, VideoCreateResult } from '@shared/models'
import { UploaderXFormData } from './uploaderx-form-data' import { UploaderXFormData } from './uploaderx-form-data'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -264,7 +265,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() 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 { Video, VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LiveVideoService } from '@app/shared/shared-video-live' import { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models' import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models'
import { hydrateFormFromVideo } from './shared/video-edit-utils'
import { VideoSource } from '@shared/models/videos/video-source' import { VideoSource } from '@shared/models/videos/video-source'
import { hydrateFormFromVideo } from './shared/video-edit-utils'
@Component({ @Component({
selector: 'my-videos-update', selector: 'my-videos-update',
@ -156,7 +157,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete() this.loadingBar.useRef().complete()
this.isUpdatingVideo = false this.isUpdatingVideo = false
this.notifier.error(err.message) 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 { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'
import { MarkdownService, Notifier } from '@app/core' import { MarkdownService, Notifier } from '@app/core'
import { VideoDetails, VideoService } from '@app/shared/shared-main' import { VideoDetails, VideoService } from '@app/shared/shared-main'
import { logger } from '@root-helpers/logger'
@Component({ @Component({
selector: 'my-video-description', selector: 'my-video-description',
@ -75,7 +76,7 @@ export class VideoDescriptionComponent implements OnChanges {
private updateVideoDescription (description: string) { private updateVideoDescription (description: string) {
this.video.description = description this.video.description = description
this.setVideoDescriptionHTML() this.setVideoDescriptionHTML()
.catch(err => console.error(err)) .catch(err => logger.error(err))
} }
private async setVideoDescriptionHTML () { 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 { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
import { LiveVideoService } from '@app/shared/shared-video-live' import { LiveVideoService } from '@app/shared/shared-video-live'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
import { logger } from '@root-helpers/logger'
import { isP2PEnabled } from '@root-helpers/video' import { isP2PEnabled } from '@root-helpers/video'
import { timeToInt } from '@shared/core-utils' import { timeToInt } from '@shared/core-utils'
import { import {
@ -225,7 +226,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
: parseInt(positionParam + '', 10) : parseInt(positionParam + '', 10)
if (isNaN(this.playlistPosition)) { if (isNaN(this.playlistPosition)) {
console.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`) logger.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`)
this.playlistPosition = 1 this.playlistPosition = 1
} }
@ -378,7 +379,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
} }
this.buildPlayer(urlOptions, loggedInOrAnonymousUser) this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
.catch(err => console.error('Cannot build the player', err)) .catch(err => logger.error('Cannot build the player', err))
this.setOpenGraphTags() this.setOpenGraphTags()
@ -550,7 +551,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.player.dispose() this.player.dispose()
this.player = undefined this.player = undefined
} catch (err) { } 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) { private handleLiveStateChange (newState: VideoState) {
if (newState !== VideoState.PUBLISHED) return if (newState !== VideoState.PUBLISHED) return
console.log('Loading video after live update.') logger.info('Loading video after live update.')
const videoUUID = this.video.uuid const videoUUID = this.video.uuid
@ -728,11 +729,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private handleLiveViewsChange (newViewers: number) { private handleLiveViewsChange (newViewers: number) {
if (!this.video) { 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 return
} }
console.log('Updating live views.') logger.info('Updating live views.')
this.video.viewers = newViewers this.video.viewers = newViewers
} }

View File

@ -1,5 +1,5 @@
import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Hotkey, HotkeysService } from 'angular2-hotkeys'
import { forkJoin, delay } from 'rxjs' import { delay, forkJoin } from 'rxjs'
import { filter, first, map } from 'rxjs/operators' import { filter, first, map } from 'rxjs/operators'
import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common' import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core' 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 { HooksService } from '@app/core/plugins/hooks.service'
import { PluginService } from '@app/core/plugins/plugin.service' import { PluginService } from '@app/core/plugins/plugin.service'
import { AccountSetupWarningModalComponent } from '@app/modal/account-setup-warning-modal.component' 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 { CustomModalComponent } from '@app/modal/custom-modal.component'
import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-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 { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { getShortLocale } from '@shared/core-utils/i18n' import { getShortLocale } from '@shared/core-utils/i18n'
import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/models' import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/models'
import { MenuService } from './core/menu/menu.service' import { MenuService } from './core/menu/menu.service'
import { POP_STATE_MODAL_DISMISS } from './helpers' import { POP_STATE_MODAL_DISMISS } from './helpers'
import { InstanceService } from './shared/shared-instance'
import { GlobalIconName } from './shared/shared-icons' import { GlobalIconName } from './shared/shared-icons'
import { InstanceService } from './shared/shared-instance'
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
@ -221,7 +222,7 @@ export class AppComponent implements OnInit, AfterViewInit {
/* eslint-disable no-eval */ /* eslint-disable no-eval */
eval(this.serverConfig.instance.customizations.javascript) eval(this.serverConfig.instance.customizations.javascript)
} catch (err) { } 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 { Injectable } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { Notifier } from '@app/core/notification/notifier.service' 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 { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { RestExtractor } from '../rest/rest-extractor.service' 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_ID, this.clientId)
peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret) peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret)
console.log('Client credentials loaded.') logger.info('Client credentials loaded.')
}, },
error: err => { error: err => {
@ -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 this.user = null
@ -190,7 +190,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
refreshAccessToken () { refreshAccessToken () {
if (this.refreshingTokenObservable) return this.refreshingTokenObservable if (this.refreshingTokenObservable) return this.refreshingTokenObservable
console.log('Refreshing token...') logger.info('Refreshing token...')
const refreshToken = this.getRefreshToken() const refreshToken = this.getRefreshToken()
@ -212,8 +212,8 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
catchError(err => { catchError(err => {
this.refreshingTokenObservable = null this.refreshingTokenObservable = null
console.error(err) logger.error(err)
console.log('Cannot refresh token -> logout...') logger.info('Cannot refresh token -> logout...')
this.logout() this.logout()
this.router.navigate([ '/login' ]) this.router.navigate([ '/login' ])

View File

@ -1,5 +1,6 @@
import { MessageService } from 'primeng/api' import { MessageService } from 'primeng/api'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { logger } from '@root-helpers/logger'
@Injectable() @Injectable()
export class Notifier { export class Notifier {
@ -10,21 +11,21 @@ export class Notifier {
info (text: string, title?: string, timeout?: number, sticky?: boolean) { info (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Info` if (!title) title = $localize`Info`
console.info(`${title}: ${text}`) logger.info(`${title}: ${text}`)
return this.notify('info', text, title, timeout, sticky) return this.notify('info', text, title, timeout, sticky)
} }
error (text: string, title?: string, timeout?: number, sticky?: boolean) { error (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Error` if (!title) title = $localize`Error`
console.error(`${title}: ${text}`) logger.error(`${title}: ${text}`)
return this.notify('error', text, title, timeout, sticky) return this.notify('error', text, title, timeout, sticky)
} }
success (text: string, title?: string, timeout?: number, sticky?: boolean) { success (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Success` if (!title) title = $localize`Success`
console.log(`${title}: ${text}`) logger.info(`${title}: ${text}`)
return this.notify('success', text, title, timeout, sticky) 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 { mergeMap, switchMap } from 'rxjs/operators'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { PluginService } from '@app/core/plugins/plugin.service' import { PluginService } from '@app/core/plugins/plugin.service'
import { logger } from '@root-helpers/logger'
import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models' import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models'
import { AuthService, AuthStatus } from '../auth' import { AuthService, AuthStatus } from '../auth'
@ -50,7 +51,7 @@ export class HooksService {
runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) { runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) {
this.pluginService.ensurePluginsAreLoaded(scope) this.pluginService.ensurePluginsAreLoaded(scope)
.then(() => this.pluginService.runHook(hookName, undefined, params)) .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) { 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 { Router } from '@angular/router'
import { dateToHuman } from '@app/helpers' import { dateToHuman } from '@app/helpers'
import { HttpStatusCode, ResultList } from '@shared/models' import { HttpStatusCode, ResultList } from '@shared/models'
import { logger } from '@root-helpers/logger'
@Injectable() @Injectable()
export class RestExtractor { export class RestExtractor {
@ -64,7 +65,7 @@ export class RestExtractor {
if (err.error instanceof Error) { if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly. // A client-side or network error occurred. Handle it accordingly.
const errorMessage = err.error.detail || err.error.title const errorMessage = err.error.detail || err.error.title
console.error('An error occurred:', errorMessage) logger.error('An error occurred:', errorMessage)
return errorMessage return errorMessage
} }
@ -75,12 +76,12 @@ export class RestExtractor {
if (err.status !== undefined) { if (err.status !== undefined) {
const errorMessage = this.buildServerErrorMessage(err) 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 return errorMessage
} }
console.error(err) logger.error(err)
return 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 { LazyLoadEvent, SortMeta } from 'primeng/api'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { RestPagination } from './rest-pagination' import { RestPagination } from './rest-pagination'
const logger = debug('peertube:tables:RestTable') const debugLogger = debug('peertube:tables:RestTable')
export abstract class RestTable { export abstract class RestTable {
@ -34,7 +35,7 @@ export abstract class RestTable {
try { try {
this.sort = JSON.parse(result) this.sort = JSON.parse(result)
} catch (err) { } 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) { loadLazy (event: LazyLoadEvent) {
logger('Load lazy %o.', event) debugLogger('Load lazy %o.', event)
this.sort = { this.sort = {
order: event.sortOrder, order: event.sortOrder,

View File

@ -5,7 +5,7 @@ import { Injectable } from '@angular/core'
import { ComponentPaginationLight } from './component-pagination.model' import { ComponentPaginationLight } from './component-pagination.model'
import { RestPagination } from './rest-pagination' import { RestPagination } from './rest-pagination'
const logger = debug('peertube:rest') const debugLogger = debug('peertube:rest')
interface QueryStringFilterPrefixes { interface QueryStringFilterPrefixes {
[key: string]: { [key: string]: {
@ -88,7 +88,7 @@ export class RestService {
const prefixeStrings = Object.values(prefixes) const prefixeStrings = Object.values(prefixes)
.map(p => p.prefix) .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 // Search is the querystring minus defined filters
const searchTokens = tokens.filter(t => { const searchTokens = tokens.filter(t => {
@ -127,7 +127,7 @@ export class RestService {
const search = searchTokens.join(' ') || undefined const search = searchTokens.join(' ') || undefined
logger('Built search: ' + search, additionalFilters) debugLogger('Built search: ' + search, additionalFilters)
return { return {
search, search,

View File

@ -1,5 +1,6 @@
import { ComponentRef, Injectable } from '@angular/core' import { ComponentRef, Injectable } from '@angular/core'
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router' import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { DisableForReuseHook } from './disable-for-reuse-hook' import { DisableForReuseHook } from './disable-for-reuse-hook'
import { PeerTubeRouterService, RouterSetting } from './peertube-router.service' import { PeerTubeRouterService, RouterSetting } from './peertube-router.service'
@ -22,7 +23,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
const key = this.generateKey(route) const key = this.generateKey(route)
this.recentlyUsed = key 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> const componentRef = (handle as any).componentRef as ComponentRef<DisableForReuseHook>
componentRef.instance.disableForReuse() componentRef.instance.disableForReuse()
@ -46,7 +47,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
const key = this.generateKey(route) const key = this.generateKey(route)
this.recentlyUsed = key this.recentlyUsed = key
console.log('Reusing component %s.', key) logger.info(`Reusing component ${key}.`)
const handle = this.storedRouteHandles.get(key) const handle = this.storedRouteHandles.get(key)
if (!handle) return handle; if (!handle) return handle;
@ -66,7 +67,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
this.storedRouteHandles.forEach((r, key) => { this.storedRouteHandles.forEach((r, key) => {
if (key === this.recentlyUsed) return if (key === this.recentlyUsed) return
console.log('Removing stored component %s.', key); logger.info(`Removing stored component ${key}`);
(r as any).componentRef.destroy() (r as any).componentRef.destroy()
this.storedRouteHandles.delete(key) this.storedRouteHandles.delete(key)

View File

@ -1,10 +1,11 @@
import * as debug from 'debug' import * as debug from 'debug'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { NavigationCancel, NavigationEnd, Router } from '@angular/router' import { NavigationCancel, NavigationEnd, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { ServerService } from '../server' import { ServerService } from '../server'
import { SessionStorageService } from '../wrappers/storage.service' import { SessionStorageService } from '../wrappers/storage.service'
const logger = debug('peertube:router:RedirectService') const debugLogger = debug('peertube:router:RedirectService')
@Injectable() @Injectable()
export class RedirectService { export class RedirectService {
@ -40,7 +41,7 @@ export class RedirectService {
this.latestSessionUrl = this.storage.getItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY) this.latestSessionUrl = this.storage.getItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY)
this.storage.removeItem(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 // Track previous url
this.currentUrl = this.router.url this.currentUrl = this.router.url
@ -51,8 +52,8 @@ export class RedirectService {
this.previousUrl = this.currentUrl this.previousUrl = this.currentUrl
this.currentUrl = event.url this.currentUrl = event.url
logger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl) debugLogger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl)
logger('Setting %s as latest URL in session storage.', this.currentUrl) debugLogger('Setting %s as latest URL in session storage.', this.currentUrl)
this.storage.setItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY, this.currentUrl) this.storage.setItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY, this.currentUrl)
} }
@ -84,18 +85,14 @@ export class RedirectService {
this.redirectingToHomepage = true this.redirectingToHomepage = true
console.log('Redirecting to %s...', this.defaultRoute) logger.info(`Redirecting to ${this.defaultRoute}...`)
this.router.navigateByUrl(this.defaultRoute, { skipLocationChange }) this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
.then(() => this.redirectingToHomepage = false) .then(() => this.redirectingToHomepage = false)
.catch(() => { .catch(() => {
this.redirectingToHomepage = false this.redirectingToHomepage = false
console.error( logger.error(`Cannot navigate to ${this.defaultRoute}, resetting default route to ${RedirectService.INIT_DEFAULT_ROUTE}`)
'Cannot navigate to %s, resetting default route to %s.',
this.defaultRoute,
RedirectService.INIT_DEFAULT_ROUTE
)
this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange }) return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
@ -104,18 +101,18 @@ export class RedirectService {
} }
private doRedirect (redirectUrl: string, fallbackRoute?: string) { private doRedirect (redirectUrl: string, fallbackRoute?: string) {
logger('Redirecting on %s', redirectUrl) debugLogger('Redirecting on %s', redirectUrl)
if (this.isValidRedirection(redirectUrl)) { if (this.isValidRedirection(redirectUrl)) {
return this.router.navigateByUrl(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) { if (fallbackRoute) {
return this.router.navigateByUrl(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() return this.redirectToHomepage()
} }

View File

@ -4,8 +4,9 @@ import { ViewportScroller } from '@angular/common'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RouterSetting } from '../' import { RouterSetting } from '../'
import { PeerTubeRouterService } from './peertube-router.service' 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() @Injectable()
export class ScrollService { export class ScrollService {
@ -57,8 +58,8 @@ export class ScrollService {
if (nextSearchParams.toString() !== previousSearchParams.toString()) { if (nextSearchParams.toString() !== previousSearchParams.toString()) {
this.resetScroll = true this.resetScroll = true
} }
} catch (e) { } catch (err) {
console.error('Cannot parse URL to check next scroll.', e) logger.error('Cannot parse URL to check next scroll.', err)
this.resetScroll = true this.resetScroll = true
} }
}) })
@ -67,7 +68,7 @@ export class ScrollService {
private consumeScroll () { private consumeScroll () {
// Handle anchors/restore position // Handle anchors/restore position
this.peertubeRouter.getScrollEvents().subscribe(e => { 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 // scrollToAnchor first to preserve anchor position when using history navigation
if (e.anchor) { 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 { HttpClient } from '@angular/common/http'
import { Inject, Injectable, LOCALE_ID } from '@angular/core' import { Inject, Injectable, LOCALE_ID } from '@angular/core'
import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers' import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
import { logger } from '@root-helpers/logger'
import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models' import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
@ -43,7 +44,7 @@ export class ServerService {
} catch (err) { } catch (err) {
// Expected in dev mode since we can't inject the config in the HTML // Expected in dev mode since we can't inject the config in the HTML
if (environment.production !== false) { 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() return this.getConfig()

View File

@ -1,4 +1,5 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { logger } from '@root-helpers/logger'
import { capitalizeFirstLetter } from '@root-helpers/string' import { capitalizeFirstLetter } from '@root-helpers/string'
import { UserLocalStorageKeys } from '@root-helpers/users' import { UserLocalStorageKeys } from '@root-helpers/users'
import { HTMLServerConfig, ServerConfigTheme } from '@shared/models' import { HTMLServerConfig, ServerConfigTheme } from '@shared/models'
@ -57,7 +58,7 @@ export class ThemeService {
private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) { private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) {
this.themes = themes this.themes = themes
console.log('Injecting %d themes.', this.themes.length) logger.info(`Injecting ${this.themes.length} themes.`)
const head = this.getHeadElement() const head = this.getHeadElement()
@ -117,13 +118,13 @@ export class ThemeService {
const currentTheme = this.getCurrentTheme() const currentTheme = this.getCurrentTheme()
console.log('Enabling %s theme.', currentTheme) logger.info(`Enabling ${currentTheme} theme.`)
this.loadTheme(currentTheme) this.loadTheme(currentTheme)
const theme = this.getTheme(currentTheme) const theme = this.getTheme(currentTheme)
if (theme) { if (theme) {
console.log('Adding scripts of theme %s.', currentTheme) logger.info(`Adding scripts of theme ${currentTheme}`)
this.pluginService.addPlugin(theme, true) this.pluginService.addPlugin(theme, true)
@ -165,7 +166,7 @@ export class ThemeService {
this.injectThemes([ lastActiveTheme ], true) this.injectThemes([ lastActiveTheme ], true)
this.updateCurrentTheme() this.updateCurrentTheme()
} catch (err) { } catch (err) {
console.error('Cannot parse last active theme.', err) logger.error('Cannot parse last active theme.', err)
return return
} }
} }
@ -173,7 +174,7 @@ export class ThemeService {
private removeThemePlugins (themeName: string) { private removeThemePlugins (themeName: string) {
const oldTheme = this.getTheme(themeName) const oldTheme = this.getTheme(themeName)
if (oldTheme) { if (oldTheme) {
console.log('Removing scripts of old theme %s.', themeName) logger.info(`Removing scripts of old theme ${themeName}.`)
this.pluginService.removePlugin(oldTheme) this.pluginService.removePlugin(oldTheme)
} }
} }

View File

@ -2,8 +2,9 @@
import { filter, throttleTime } from 'rxjs' import { filter, throttleTime } from 'rxjs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AuthService, AuthStatus } from '@app/core/auth' import { AuthService, AuthStatus } from '@app/core/auth'
import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
import { getBoolOrDefault } from '@root-helpers/local-storage-utils' 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 { UserRole, UserUpdateMe } from '@shared/models'
import { NSFWPolicyType } from '@shared/models/videos' import { NSFWPolicyType } from '@shared/models/videos'
import { ServerService } from '../server' import { ServerService } from '../server'
@ -95,7 +96,7 @@ export class UserLocalStorageService {
: null : null
} catch (err) { } catch (err) {
videoLanguages = null 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() const htmlConfig = this.server.getHTMLConfig()
@ -142,7 +143,7 @@ export class UserLocalStorageService {
this.localStorageService.setItem(key, localStorageValue) this.localStorageService.setItem(key, localStorageValue)
} catch (err) { } 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 { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'
import { ActivatedRoute, Params, Router } from '@angular/router' import { ActivatedRoute, Params, Router } from '@angular/router'
import { AuthService, ServerService } from '@app/core' import { AuthService, ServerService } from '@app/core'
import { logger } from '@root-helpers/logger'
import { HTMLServerConfig, SearchTargetType } from '@shared/models' import { HTMLServerConfig, SearchTargetType } from '@shared/models'
import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component' 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) const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true)
if (activeIndex === -1) { if (activeIndex === -1) {
console.error('Cannot find active index.', { suggestionItems: this.suggestionItems }) logger.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
} }
this.updateItemsState(activeIndex) this.updateItemsState(activeIndex)

View File

@ -1,5 +1,6 @@
import { environment } from '../../environments/environment'
import IntlMessageFormat from 'intl-messageformat' import IntlMessageFormat from 'intl-messageformat'
import { logger } from '@root-helpers/logger'
import { environment } from '../../environments/environment'
function isOnDevLocale () { function isOnDevLocale () {
return environment.production === false && window.location.search === '?lang=fr' return environment.production === false && window.location.search === '?lang=fr'
@ -19,14 +20,14 @@ function prepareIcu (icu: string) {
try { try {
return msg.format(context) as string return msg.format(context) as string
} catch (err) { } catch (err) {
if (!alreadyWarned) console.warn('Cannot format ICU %s.', icu, err) if (!alreadyWarned) logger.warn(`Cannot format ICU ${icu}.`, err)
alreadyWarned = true alreadyWarned = true
return fallback return fallback
} }
} }
} catch (err) { } 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 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 { PluginsManager } from '@root-helpers/plugins-manager'
import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models' import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models'
const logger = debug('peertube:menu:MenuComponent') const debugLogger = debug('peertube:menu:MenuComponent')
@Component({ @Component({
selector: 'my-menu', selector: 'my-menu',
@ -295,8 +295,8 @@ export class MenuComponent implements OnInit {
.pipe( .pipe(
switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed())) switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed()))
).subscribe(res => { ).subscribe(res => {
if (res === true) logger('User can see videos link.') if (res === true) debugLogger('User can see videos link.')
else logger('User cannot see videos link.') else debugLogger('User cannot see videos link.')
}) })
} }

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, ViewChild } from '@angular/core' import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, ServerService, User, UserService } from '@app/core' import { Notifier, ServerService, User, UserService } from '@app/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({ @Component({
@ -71,7 +72,7 @@ export class AccountSetupWarningModalComponent {
this.userService.updateMyProfile({ noAccountSetupWarningModal: true }) this.userService.updateMyProfile({ noAccountSetupWarningModal: true })
.subscribe({ .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) error: err => this.notifier.error(err.message)
}) })

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, ViewChild } from '@angular/core' import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core' import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({ @Component({
@ -42,7 +43,7 @@ export class AdminWelcomeModalComponent {
this.userService.updateMyProfile({ noWelcomeModal: true }) this.userService.updateMyProfile({ noWelcomeModal: true })
.subscribe({ .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) 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 { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
@Component({ @Component({
selector: 'my-custom-modal', selector: 'my-custom-modal',
@ -29,7 +30,7 @@ export class CustomModalComponent {
confirm?: { value: string, action?: () => void } confirm?: { value: string, action?: () => void }
}) { }) {
if (this.modalRef instanceof NgbModalRef && this.modalService.hasOpenModals()) { 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 return
} }

View File

@ -2,6 +2,7 @@ import { Location } from '@angular/common'
import { Component, ElementRef, ViewChild } from '@angular/core' import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core' import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { About, ServerConfig } from '@shared/models/server' import { About, ServerConfig } from '@shared/models/server'
@ -64,7 +65,7 @@ export class InstanceConfigWarningModalComponent {
this.userService.updateMyProfile({ noInstanceConfigWarningModal: true }) this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
.subscribe({ .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) 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 { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoCommentService } from '@app/shared/shared-video-comment' import { VideoCommentService } from '@app/shared/shared-video-comment'
import { logger } from '@root-helpers/logger'
import { AbuseState, AdminAbuse } from '@shared/models' import { AbuseState, AdminAbuse } from '@shared/models'
import { AdvancedInputFilter } from '../shared-forms' import { AdvancedInputFilter } from '../shared-forms'
import { AbuseMessageModalComponent } from './abuse-message-modal.component' import { AbuseMessageModalComponent } from './abuse-message-modal.component'
import { ModerationCommentModalComponent } from './moderation-comment-modal.component' import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
import { ProcessedAbuse } from './processed-abuse.model' import { ProcessedAbuse } from './processed-abuse.model'
const logger = debug('peertube:moderation:AbuseListTableComponent') const debugLogger = debug('peertube:moderation:AbuseListTableComponent')
@Component({ @Component({
selector: 'my-abuse-list-table', 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) const abuse = this.abuses.find(a => a.id === event.abuseId)
if (!abuse) { if (!abuse) {
console.error('Cannot find abuse %d.', event.abuseId) logger.error(`Cannot find abuse ${event.abuseId}`)
return return
} }
@ -177,7 +178,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
} }
protected reloadData () { protected reloadData () {
logger('Loading data.') debugLogger('Loading data.')
const options = { const options = {
pagination: this.pagination, 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 { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { logger } from '@root-helpers/logger'
import { AbuseMessage, UserAbuse } from '@shared/models' import { AbuseMessage, UserAbuse } from '@shared/models'
import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators' import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators'
import { AbuseService } from '../shared-moderation' import { AbuseService } from '../shared-moderation'
@ -72,7 +73,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
error: err => { error: err => {
this.sendingMessage = false this.sendingMessage = false
console.error(err) logger.error(err)
this.notifier.error('Sorry but you cannot send this message. Please retry later') this.notifier.error('Sorry but you cannot send this message. Please retry later')
} }
}) })

View File

@ -20,6 +20,7 @@ import {
VideosListMarkupComponent VideosListMarkupComponent
} from './peertube-custom-tags' } from './peertube-custom-tags'
import { CustomMarkupComponent } from './peertube-custom-tags/shared' import { CustomMarkupComponent } from './peertube-custom-tags/shared'
import { logger } from '@root-helpers/logger'
type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent> type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent>
type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
@ -70,7 +71,7 @@ export class CustomMarkupService {
// Insert as first child // Insert as first child
e.insertBefore(element, e.firstChild) e.insertBefore(element, e.firstChild)
} catch (err) { } 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) this.dynamicElementService.injectElement(e, component)
} catch (err) { } 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 value: string
} }
const logger = debug('peertube:AdvancedInputFilterComponent') const debugLogger = debug('peertube:AdvancedInputFilterComponent')
@Component({ @Component({
selector: 'my-advanced-input-filter', selector: 'my-advanced-input-filter',
@ -98,7 +98,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
.subscribe(params => { .subscribe(params => {
const search = params.search || '' const search = params.search || ''
logger('On route search change "%s".', search) debugLogger('On route search change "%s".', search)
if (this.searchValue === search) return if (this.searchValue === search) return
@ -132,7 +132,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
return return
} }
logger('On search "%s".', this.searchValue) debugLogger('On search "%s".', this.searchValue)
this.search.emit(this.searchValue) this.search.emit(this.searchValue)
} }

View File

@ -13,7 +13,7 @@ import {
ViewContainerRef ViewContainerRef
} from '@angular/core' } from '@angular/core'
const logger = debug('peertube:main:DeferLoadingDirective') const debugLogger = debug('peertube:main:DeferLoadingDirective')
@Directive({ @Directive({
selector: '[myDeferLoading]' selector: '[myDeferLoading]'
@ -52,7 +52,7 @@ export class DeferLoadingDirective implements AfterViewInit, OnDestroy {
load () { load () {
if (this.isLoaded()) return if (this.isLoaded()) return
logger('Loading component') debugLogger('Loading component')
this.viewContainer.clear() this.viewContainer.clear()
this.view = this.viewContainer.createEmbeddedView(this.template, {}, 0) 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 { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import * as debug from 'debug' import * as debug from 'debug'
const logger = debug('peertube:main:ListOverflowItem') const debugLogger = debug('peertube:main:ListOverflowItem')
export interface ListOverflowItem { export interface ListOverflowItem {
label: string label: string
@ -66,7 +66,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
let showItemsUntilIndexExcluded: number let showItemsUntilIndexExcluded: number
let accWidth = 0 let accWidth = 0
logger('Parent width is %d', parentWidth) debugLogger('Parent width is %d', parentWidth)
for (const [ index, el ] of this.itemsRendered.toArray().entries()) { for (const [ index, el ] of this.itemsRendered.toArray().entries()) {
accWidth += el.nativeElement.getBoundingClientRect().width accWidth += el.nativeElement.getBoundingClientRect().width
@ -79,7 +79,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden' 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.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded
this.cdr.markForCheck() 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 { Account } from '@app/shared/shared-main/account/account.model'
import { Actor } from '@app/shared/shared-main/account/actor.model' import { Actor } from '@app/shared/shared-main/account/actor.model'
import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model' import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
import { logger } from '@root-helpers/logger'
import { import {
AbuseState, AbuseState,
ActorInfo, ActorInfo,
@ -234,7 +235,7 @@ export class UserNotification implements UserNotificationServer {
} }
} catch (err) { } catch (err) {
this.type = null 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 { SearchService } from './search.service'
import { AdvancedSearch } from './advanced-search.model' 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> = { type BulkObservables <P extends number | string, R> = {
notifier: Subject<P> notifier: Subject<P>
@ -36,7 +36,7 @@ export class FindInBulkService {
} }
getVideo (uuid: string): Observable<Video> { getVideo (uuid: string): Observable<Video> {
logger('Schedule video fetch for uuid %s.', uuid) debugLogger('Schedule video fetch for uuid %s.', uuid)
return this.getData({ return this.getData({
observableObject: this.getVideoInBulk, observableObject: this.getVideoInBulk,
@ -46,7 +46,7 @@ export class FindInBulkService {
} }
getChannel (handle: string): Observable<VideoChannel> { getChannel (handle: string): Observable<VideoChannel> {
logger('Schedule channel fetch for handle %s.', handle) debugLogger('Schedule channel fetch for handle %s.', handle)
return this.getData({ return this.getData({
observableObject: this.getChannelInBulk, observableObject: this.getChannelInBulk,
@ -56,7 +56,7 @@ export class FindInBulkService {
} }
getPlaylist (uuid: string): Observable<VideoPlaylist> { getPlaylist (uuid: string): Observable<VideoPlaylist> {
logger('Schedule playlist fetch for uuid %s.', uuid) debugLogger('Schedule playlist fetch for uuid %s.', uuid)
return this.getData({ return this.getData({
observableObject: this.getPlaylistInBulk, observableObject: this.getPlaylistInBulk,
@ -94,7 +94,7 @@ export class FindInBulkService {
} }
private getVideosInBulk (uuids: string[]) { private getVideosInBulk (uuids: string[]) {
logger('Fetching videos %s.', uuids.join(', ')) debugLogger('Fetching videos %s.', uuids.join(', '))
return this.searchService.searchVideos({ return this.searchService.searchVideos({
uuids, uuids,
@ -104,7 +104,7 @@ export class FindInBulkService {
} }
private getChannelsInBulk (handles: string[]) { private getChannelsInBulk (handles: string[]) {
logger('Fetching channels %s.', handles.join(', ')) debugLogger('Fetching channels %s.', handles.join(', '))
return this.searchService.searchVideoChannels({ return this.searchService.searchVideoChannels({
handles, handles,
@ -114,7 +114,7 @@ export class FindInBulkService {
} }
private getPlaylistsInBulk (uuids: string[]) { private getPlaylistsInBulk (uuids: string[]) {
logger('Fetching playlists %s.', uuids.join(', ')) debugLogger('Fetching playlists %s.', uuids.join(', '))
return this.searchService.searchVideoPlaylists({ return this.searchService.searchVideoPlaylists({
uuids, uuids,

View File

@ -1,6 +1,7 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input, OnInit } from '@angular/core'
import { Notifier } from '@app/core' import { Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { logger } from '@root-helpers/logger'
import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators' import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators'
@Component({ @Component({
@ -59,7 +60,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
}) })
.then(window.open) .then(window.open)
.catch(err => { .catch(err => {
console.error(err) logger.error(err)
this.notifier.error($localize`Cannot fetch information of this remote account`) 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 { ActorFollow, ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
const logger = debug('peertube:subscriptions:UserSubscriptionService') const debugLogger = debug('peertube:subscriptions:UserSubscriptionService')
type SubscriptionExistResult = { [ uri: string ]: boolean } type SubscriptionExistResult = { [ uri: string ]: boolean }
type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> } type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> }
@ -176,17 +176,17 @@ export class UserSubscriptionService {
} }
doesSubscriptionExist (nameWithHost: string) { doesSubscriptionExist (nameWithHost: string) {
logger('Running subscription check for %d.', nameWithHost) debugLogger('Running subscription check for %d.', nameWithHost)
if (nameWithHost in this.myAccountSubscriptionCache) { if (nameWithHost in this.myAccountSubscriptionCache) {
logger('Found cache for %d.', nameWithHost) debugLogger('Found cache for %d.', nameWithHost)
return of(this.myAccountSubscriptionCache[nameWithHost]) return of(this.myAccountSubscriptionCache[nameWithHost])
} }
this.existsSubject.next(nameWithHost) this.existsSubject.next(nameWithHost)
logger('Fetching from network for %d.', nameWithHost) debugLogger('Fetching from network for %d.', nameWithHost)
return this.existsObservable.pipe( return this.existsObservable.pipe(
filter(existsResult => existsResult[nameWithHost] !== undefined), filter(existsResult => existsResult[nameWithHost] !== undefined),
map(existsResult => existsResult[nameWithHost]), 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 { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core'
import { AuthService, HooksService, Notifier } from '@app/core' import { AuthService, HooksService, Notifier } from '@app/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models' import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models'
import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main' import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main'
@ -142,7 +143,7 @@ export class VideoDownloadComponent {
.find(f => f.resolution.id === this.resolutionId) .find(f => f.resolution.id === this.resolutionId)
if (!file) { 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 return undefined
} }
@ -175,7 +176,7 @@ export class VideoDownloadComponent {
.find(c => c.language.id === this.subtitleLanguageId) .find(c => c.language.id === this.subtitleLanguageId)
if (!caption) { if (!caption) {
console.error('Cannot find caption %s.', this.subtitleLanguageId) logger.error(`Cannot find caption ${this.subtitleLanguageId}`)
return undefined return undefined
} }

View File

@ -8,7 +8,7 @@ import { UserRight } from '@shared/models'
import { PeertubeModalService } from '../shared-main' import { PeertubeModalService } from '../shared-main'
import { VideoFilters } from './video-filters.model' import { VideoFilters } from './video-filters.model'
const logger = debug('peertube:videos:VideoFiltersHeaderComponent') const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent')
@Component({ @Component({
selector: 'my-video-filters-header', selector: 'my-video-filters-header',
@ -54,7 +54,7 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
}) })
this.form.valueChanges.subscribe(values => { this.form.valueChanges.subscribe(values => {
logger('Loading values from form: %O', values) debugLogger('Loading values from form: %O', values)
this.filters.load(values) this.filters.load(values)
this.filtersChanged.emit() this.filtersChanged.emit()
@ -105,6 +105,6 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
const defaultValues = this.filters.toFormObject() const defaultValues = this.filters.toFormObject()
this.form.patchValue(defaultValues, { emitEvent }) this.form.patchValue(defaultValues, { emitEvent })
logger('Patched form: %O', defaultValues) debugLogger('Patched form: %O', defaultValues)
} }
} }

View File

@ -14,13 +14,14 @@ import {
UserService UserService
} from '@app/core' } from '@app/core'
import { GlobalIconName } from '@app/shared/shared-icons' import { GlobalIconName } from '@app/shared/shared-icons'
import { logger } from '@root-helpers/logger'
import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils' import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils'
import { ResultList, UserRight, VideoSortField } from '@shared/models' import { ResultList, UserRight, VideoSortField } from '@shared/models'
import { Syndication, Video } from '../shared-main' import { Syndication, Video } from '../shared-main'
import { VideoFilters, VideoFilterScope } from './video-filters.model' import { VideoFilters, VideoFilterScope } from './video-filters.model'
import { MiniatureDisplayOptions } from './video-miniature.component' import { MiniatureDisplayOptions } from './video-miniature.component'
const logger = debug('peertube:videos:VideosListComponent') const debugLogger = debug('peertube:videos:VideosListComponent')
export type HeaderAction = { export type HeaderAction = {
iconName: GlobalIconName iconName: GlobalIconName
@ -245,7 +246,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
error: err => { error: err => {
const message = $localize`Cannot load more videos. Try again later.` const message = $localize`Cannot load more videos. Try again later.`
console.error(message, { err }) logger.error(message, err)
this.notifier.error(message) this.notifier.error(message)
} }
}) })
@ -323,7 +324,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
} }
onFiltersChanged (customizedByUser: boolean) { onFiltersChanged (customizedByUser: boolean) {
logger('Running on filters changed') debugLogger('Running on filters changed')
this.updateUrl(customizedByUser) this.updateUrl(customizedByUser)
@ -364,7 +365,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
if (!items || items.length === 0) this.syndicationItems = undefined if (!items || items.length === 0) this.syndicationItems = undefined
else this.syndicationItems = items 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) { private updateUrl (customizedByUser: boolean) {
@ -375,7 +376,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
? { ...baseQuery, c: customizedByUser } ? { ...baseQuery, c: customizedByUser }
: baseQuery : baseQuery
logger('Will inject %O in URL query', queryParams) debugLogger('Will inject %O in URL query', queryParams)
const baseRoute = this.baseRouteBuilderFunction const baseRoute = this.baseRouteBuilderFunction
? this.baseRouteBuilderFunction(this.filters) ? this.baseRouteBuilderFunction(this.filters)

View File

@ -1,6 +1,7 @@
import { Observable, Subject } from 'rxjs' import { Observable, Subject } from 'rxjs'
import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core' import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core'
import { ComponentPagination, Notifier, User } from '@app/core' import { ComponentPagination, Notifier, User } from '@app/core'
import { logger } from '@root-helpers/logger'
import { ResultList, VideoSortField } from '@shared/models' import { ResultList, VideoSortField } from '@shared/models'
import { PeerTubeTemplateDirective, Video } from '../shared-main' import { PeerTubeTemplateDirective, Video } from '../shared-main'
import { MiniatureDisplayOptions } from './video-miniature.component' import { MiniatureDisplayOptions } from './video-miniature.component'
@ -128,7 +129,7 @@ export class VideosSelectionComponent implements AfterContentInit {
error: err => { error: err => {
const message = $localize`Cannot load more videos. Try again later.` const message = $localize`Cannot load more videos. Try again later.`
console.error(message, { err }) logger.error(message, err)
this.notifier.error(message) this.notifier.error(message)
} }
}) })

View File

@ -16,7 +16,7 @@ import {
import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators' import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators'
import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service' import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
const logger = debug('peertube:playlists:VideoAddToPlaylistComponent') const debugLogger = debug('peertube:playlists:VideoAddToPlaylistComponent')
type PlaylistElement = { type PlaylistElement = {
enabled: boolean enabled: boolean
@ -110,7 +110,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
} }
reload () { reload () {
logger('Reloading component') debugLogger('Reloading component')
this.videoPlaylists = [] this.videoPlaylists = []
this.videoPlaylistSearch = undefined this.videoPlaylistSearch = undefined
@ -121,7 +121,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
} }
load () { load () {
logger('Loading component') debugLogger('Loading component')
this.listenToVideoPlaylistChange() this.listenToVideoPlaylistChange()
@ -331,7 +331,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
} }
private rebuildPlaylists (existResult: VideoExistInPlaylist[]) { 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 const oldPlaylists = this.videoPlaylists
@ -359,7 +359,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
this.videoPlaylists.push(playlistSummary) 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() this.cd.markForCheck()
} }

View File

@ -23,7 +23,7 @@ import { environment } from '../../../environments/environment'
import { VideoPlaylistElement } from './video-playlist-element.model' import { VideoPlaylistElement } from './video-playlist-element.model'
import { VideoPlaylist } from './video-playlist.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 } export type CachedPlaylist = VideoPlaylist | { id: number, displayName: string }
@ -287,15 +287,15 @@ export class VideoPlaylistService {
} }
runPlaylistCheck (videoId: number) { runPlaylistCheck (videoId: number) {
logger('Running playlist check.') debugLogger('Running playlist check.')
if (this.videoExistsCache[videoId]) { if (this.videoExistsCache[videoId]) {
logger('Found cache for %d.', videoId) debugLogger('Found cache for %d.', videoId)
return this.videoExistsInPlaylistCacheSubject.next({ [videoId]: this.videoExistsCache[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) return this.videoExistsInPlaylistNotifier.next(videoId)
} }

View File

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

View File

@ -23,6 +23,7 @@ import './shared/mobile/peertube-mobile-plugin'
import './shared/mobile/peertube-mobile-buttons' import './shared/mobile/peertube-mobile-buttons'
import './shared/hotkeys/peertube-hotkeys-plugin' import './shared/hotkeys/peertube-hotkeys-plugin'
import videojs from 'video.js' import videojs from 'video.js'
import { logger } from '@root-helpers/logger'
import { PluginsManager } from '@root-helpers/plugins-manager' import { PluginsManager } from '@root-helpers/plugins-manager'
import { isMobile } from '@root-helpers/web-browser' import { isMobile } from '@root-helpers/web-browser'
import { saveAverageBandwidth } from './peertube-player-local-storage' import { saveAverageBandwidth } from './peertube-player-local-storage'
@ -145,7 +146,7 @@ export class PeertubePlayerManager {
return return
} }
console.log('Fast forwarding HLS to recover from an error.') logger.info('Fast forwarding HLS to recover from an error.')
this.videojsDecodeErrors++ this.videojsDecodeErrors++
@ -170,7 +171,7 @@ export class PeertubePlayerManager {
return return
} }
console.log('Fallback to webtorrent.') logger.info('Fallback to webtorrent.')
this.rebuildAndUpdateVideoElement(currentPlayer, options.common) this.rebuildAndUpdateVideoElement(currentPlayer, options.common)

View File

@ -1,5 +1,6 @@
import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core' import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs' import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
import { logger } from '@root-helpers/logger'
import { LiveVideoLatencyMode } from '@shared/models' import { LiveVideoLatencyMode } from '@shared/models'
import { getAverageBandwidthInStore } from '../../peertube-player-local-storage' import { getAverageBandwidthInStore } from '../../peertube-player-local-storage'
import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../../types' import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../../types'
@ -61,7 +62,7 @@ export class HLSOptionsBuilder {
private getP2PMediaLoaderOptions (redundancyUrlManager: RedundancyUrlManager): HlsJsEngineSettings { private getP2PMediaLoaderOptions (redundancyUrlManager: RedundancyUrlManager): HlsJsEngineSettings {
let consumeOnly = false let consumeOnly = false
if ((navigator as any)?.connection?.type === 'cellular') { 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 consumeOnly = true
} }

View File

@ -1,8 +1,9 @@
import { PeerTubeMobileButtons } from './peertube-mobile-buttons'
import videojs from 'video.js'
import debug from 'debug' 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') const Plugin = videojs.getPlugin('plugin')
@ -45,7 +46,7 @@ class PeerTubeMobilePlugin extends Plugin {
if (!this.player.isFullscreen() || this.isPortraitVideo()) return if (!this.player.isFullscreen() || this.isPortraitVideo()) return
screen.orientation.lock('landscape') 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) { 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.lastTapEvent = undefined
this.onDoubleTap(event) this.onDoubleTap(event)
@ -71,7 +72,7 @@ class PeerTubeMobilePlugin extends Plugin {
this.newActiveState = !this.player.userActive() this.newActiveState = !this.player.userActive()
this.tapTimeout = setTimeout(() => { 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) this.player.userActive(this.newActiveState)
}, PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS) }, PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS)
@ -100,19 +101,19 @@ class PeerTubeMobilePlugin extends Plugin {
const rect = this.findPlayerTarget((event.target as HTMLElement)).getBoundingClientRect() const rect = this.findPlayerTarget((event.target as HTMLElement)).getBoundingClientRect()
const offsetX = event.targetTouches[0].pageX - rect.left 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 (offsetX > 0.66 * playerWidth) {
if (this.seekAmount < 0) this.seekAmount = 0 if (this.seekAmount < 0) this.seekAmount = 0
this.seekAmount += 10 this.seekAmount += 10
logger('Will forward %d seconds', this.seekAmount) debugLogger('Will forward %d seconds', this.seekAmount)
} else if (offsetX < 0.33 * playerWidth) { } else if (offsetX < 0.33 * playerWidth) {
if (this.seekAmount > 0) this.seekAmount = 0 if (this.seekAmount > 0) this.seekAmount = 0
this.seekAmount -= 10 this.seekAmount -= 10
logger('Will rewind %d seconds', this.seekAmount) debugLogger('Will rewind %d seconds', this.seekAmount)
} }
this.peerTubeMobileButtons.displayFastSeek(this.seekAmount) this.peerTubeMobileButtons.displayFastSeek(this.seekAmount)

View File

@ -3,6 +3,7 @@
import Hlsjs, { ErrorData, HlsConfig, Level, LevelSwitchingData, ManifestParsedData } from 'hls.js' import Hlsjs, { ErrorData, HlsConfig, Level, LevelSwitchingData, ManifestParsedData } from 'hls.js'
import videojs from 'video.js' import videojs from 'video.js'
import { logger } from '@root-helpers/logger'
import { HlsjsConfigHandlerOptions, PeerTubeResolution, VideoJSTechHLS } from '../../types' import { HlsjsConfigHandlerOptions, PeerTubeResolution, VideoJSTechHLS } from '../../types'
type ErrorCounts = { type ErrorCounts = {
@ -17,14 +18,14 @@ type HookFn = (player: videojs.Player, hljs: Hlsjs) => void
const registerSourceHandler = function (vjs: typeof videojs) { const registerSourceHandler = function (vjs: typeof videojs) {
if (!Hlsjs.isSupported()) { if (!Hlsjs.isSupported()) {
console.warn('Hls.js is not supported in this browser!') logger.warn('Hls.js is not supported in this browser!')
return return
} }
const html5 = vjs.getTech('Html5') const html5 = vjs.getTech('Html5')
if (!html5) { if (!html5) {
console.error('No Hml5 tech found in videojs') logger.error('No Hml5 tech found in videojs')
return return
} }
@ -120,7 +121,7 @@ class Html5Hlsjs {
if (!mediaError) return if (!mediaError) return
console.log(mediaError) logger.info(mediaError)
switch (mediaError.code) { switch (mediaError.code) {
case mediaError.MEDIA_ERR_ABORTED: case mediaError.MEDIA_ERR_ABORTED:
errorTxt = 'You aborted the video playback' errorTxt = 'You aborted the video playback'
@ -141,7 +142,7 @@ class Html5Hlsjs {
errorTxt = mediaError.message errorTxt = mediaError.message
} }
console.error('MEDIA_ERROR: ', errorTxt) logger.error(`MEDIA_ERROR: ${errorTxt}`)
}) })
this.initialize() this.initialize()
@ -212,20 +213,20 @@ class Html5Hlsjs {
private _handleMediaError (error: any) { private _handleMediaError (error: any) {
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) { 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() this.hls.recoverMediaError()
return return
} }
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 2) { 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.swapAudioCodec()
this.hls.recoverMediaError() this.hls.recoverMediaError()
return return
} }
if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) { 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.hls.destroy()
this.tech.error = () => error this.tech.error = () => error
this.tech.trigger('error') this.tech.trigger('error')
@ -234,7 +235,7 @@ class Html5Hlsjs {
private _handleNetworkError (error: any) { private _handleNetworkError (error: any) {
if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= this.maxNetworkErrorRecovery) { 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 // Wait 1 second and retry
setTimeout(() => this.hls.startLoad(), 1000) setTimeout(() => this.hls.startLoad(), 1000)
@ -247,7 +248,7 @@ class Html5Hlsjs {
return return
} }
console.info('bubbling network error up to VIDEOJS') logger.info('bubbling network error up to VIDEOJS')
this.hls.destroy() this.hls.destroy()
this.tech.error = () => error this.tech.error = () => error
this.tech.trigger('error') this.tech.trigger('error')
@ -262,8 +263,8 @@ class Html5Hlsjs {
if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1 if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1
else this.errorCounts[data.type] = 1 else this.errorCounts[data.type] = 1
if (data.fatal) console.warn(error.message) if (data.fatal) logger.warn(error.message)
else console.error(error.message, data) else logger.error(error.message, { data })
if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) { if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) {
error.code = 2 error.code = 2
@ -273,7 +274,7 @@ class Html5Hlsjs {
this._handleMediaError(error) this._handleMediaError(error)
} else if (data.fatal) { } else if (data.fatal) {
this.hls.destroy() 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.error = () => error as any
this.tech.trigger('error') this.tech.trigger('error')
} }

View File

@ -5,6 +5,7 @@ import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertub
import { timeToInt } from '@shared/core-utils' import { timeToInt } from '@shared/core-utils'
import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types' import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types'
import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
import { logger } from '@root-helpers/logger'
registerConfigPlugin(videojs) registerConfigPlugin(videojs)
registerSourceHandler(videojs) registerSourceHandler(videojs)
@ -43,11 +44,11 @@ class P2pMediaLoaderPlugin extends Plugin {
// FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080 // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
if (!(videojs as any).Html5Hlsjs) { 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')) { if (!player.canPlayType('application/vnd.apple.mpegurl')) {
const message = 'Cannot fallback to built-in HLS' const message = 'Cannot fallback to built-in HLS'
console.warn(message) logger.warn(message)
player.ready(() => player.trigger('error', new Error(message))) player.ready(() => player.trigger('error', new Error(message)))
return return
@ -114,7 +115,7 @@ class P2pMediaLoaderPlugin extends Plugin {
this.p2pEngine = this.options.loader.getEngine() this.p2pEngine = this.options.loader.getEngine()
this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => { 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) this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl)
}) })

View File

@ -1,4 +1,5 @@
import { basename, dirname } from 'path' import { basename, dirname } from 'path'
import { logger } from '@root-helpers/logger'
class RedundancyUrlManager { class RedundancyUrlManager {
@ -7,7 +8,7 @@ class RedundancyUrlManager {
} }
removeBySegmentUrl (segmentUrl: string) { removeBySegmentUrl (segmentUrl: string) {
console.log('Removing redundancy of segment URL %s.', segmentUrl) logger.info(`Removing redundancy of segment URL ${segmentUrl}.`)
const baseUrl = dirname(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 { 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 } } type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } }
@ -23,7 +24,7 @@ function segmentValidatorFactory (segmentsSha256Url: string, isLive: boolean) {
} }
if (!segmentValue) { if (!segmentValue) {
console.log('Refetching sha segments for %s.', filename) logger.info(`Refetching sha segments for ${filename}`)
await wait(1000) await wait(1000)
@ -71,7 +72,7 @@ function fetchSha256Segments (url: string) {
return fetch(url) return fetch(url)
.then(res => res.json() as Promise<SegmentsJSON>) .then(res => res.json() as Promise<SegmentsJSON>)
.catch(err => { .catch(err => {
console.error('Cannot get sha256 segments', err) logger.error('Cannot get sha256 segments', err)
return {} return {}
}) })
} }

View File

@ -1,5 +1,6 @@
import debug from 'debug' import debug from 'debug'
import videojs from 'video.js' import videojs from 'video.js'
import { logger } from '@root-helpers/logger'
import { isMobile } from '@root-helpers/web-browser' import { isMobile } from '@root-helpers/web-browser'
import { timeToInt } from '@shared/core-utils' import { timeToInt } from '@shared/core-utils'
import { VideoView, VideoViewEvent } from '@shared/models/videos' import { VideoView, VideoViewEvent } from '@shared/models/videos'
@ -15,7 +16,7 @@ import {
import { PeerTubePluginOptions, VideoJSCaption } from '../../types' import { PeerTubePluginOptions, VideoJSCaption } from '../../types'
import { SettingsButton } from '../settings/settings-menu-button' import { SettingsButton } from '../settings/settings-menu-button'
const logger = debug('peertube:player:peertube') const debugLogger = debug('peertube:player:peertube')
const Plugin = videojs.getPlugin('plugin') const Plugin = videojs.getPlugin('plugin')
@ -176,7 +177,7 @@ class PeerTubePlugin extends Plugin {
lastCurrentTime = currentTime lastCurrentTime = currentTime
this.notifyUserIsWatching(currentTime, lastViewEvent) 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 lastViewEvent = undefined
@ -249,7 +250,7 @@ class PeerTubePlugin extends Plugin {
(this.player as any).cache_.inactivityTimeout = timeout (this.player as any).cache_.inactivityTimeout = timeout
this.player.options_.inactivityTimeout = timeout this.player.options_.inactivityTimeout = timeout
logger('Set player inactivity to ' + timeout) debugLogger('Set player inactivity to ' + timeout)
} }
private initCaptions () { private initCaptions () {

View File

@ -1,4 +1,5 @@
import videojs from 'video.js' import videojs from 'video.js'
import { logger } from '@root-helpers/logger'
import { secondsToTime } from '@shared/core-utils' import { secondsToTime } from '@shared/core-utils'
import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../../types' import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../../types'
import { bytes } from '../common' import { bytes } from '../common'
@ -125,7 +126,7 @@ class StatsCard extends Component {
this.populateInfoValues(options) this.populateInfoValues(options)
} catch (err) { } catch (err) {
console.error('Cannot update stats.', err) logger.error('Cannot update stats.', err)
clearInterval(this.updateInterval) clearInterval(this.updateInterval)
} }
}, this.intervalMs) }, this.intervalMs)

View File

@ -2,8 +2,9 @@
// We use temporary IndexDB (all data are removed on destroy) to avoid RAM issues // We use temporary IndexDB (all data are removed on destroy) to avoid RAM issues
// Thanks @santiagogil and @Feross // Thanks @santiagogil and @Feross
import { EventEmitter } from 'events'
import Dexie from 'dexie' import Dexie from 'dexie'
import { EventEmitter } from 'events'
import { logger } from '@root-helpers/logger'
class ChunkDatabase extends Dexie { class ChunkDatabase extends Dexie {
chunks: Dexie.Table<{ id: number, buf: Buffer }, number> 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 }))) return this.db.chunks.bulkPut(processing.map(p => ({ id: p.id, buf: p.buf })))
}) })
} catch (err) { } 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 => { processing.forEach(p => {
this.memoryChunks[p.id] = p.buf this.memoryChunks[p.id] = p.buf
@ -143,7 +144,7 @@ export class PeertubeChunkStore extends EventEmitter {
return cb(null, buf.slice(offset, len + offset)) return cb(null, buf.slice(offset, len + offset))
}) })
.catch(err => { .catch(err => {
console.error(err) logger.error(err)
return cb(err) return cb(err)
}) })
} }
@ -176,7 +177,7 @@ export class PeertubeChunkStore extends EventEmitter {
return cb() return cb()
} catch (err) { } catch (err) {
console.error('Cannot destroy peertube chunk store.', err) logger.error('Cannot destroy peertube chunk store.', err)
return cb(err) return cb(err)
} }
} }
@ -204,7 +205,7 @@ export class PeertubeChunkStore extends EventEmitter {
databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray() databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray()
}) })
} catch (err) { } 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) { for (const databaseToDeleteInfo of databasesToDeleteInfo) {
@ -214,7 +215,7 @@ export class PeertubeChunkStore extends EventEmitter {
private async dropDatabase (databaseName: string) { private async dropDatabase (databaseName: string) {
const dbToDelete = new ChunkDatabase(databaseName) const dbToDelete = new ChunkDatabase(databaseName)
console.log('Destroying IndexDB database %s.', databaseName) logger.info(`Destroying IndexDB database ${databaseName}`)
try { try {
await dbToDelete.delete() await dbToDelete.delete()
@ -223,7 +224,7 @@ export class PeertubeChunkStore extends EventEmitter {
return this.expirationDB.databases.where({ name: databaseName }).delete() return this.expirationDB.databases.where({ name: databaseName }).delete()
}) })
} catch (err) { } 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 // Thanks: https://github.com/feross/render-media
const MediaElementWrapper = require('mediasource') const MediaElementWrapper = require('mediasource')
import { logger } from '@root-helpers/logger'
import { extname } from 'path' import { extname } from 'path'
const Videostream = require('videostream') const Videostream = require('videostream')
@ -77,8 +78,8 @@ function renderMedia (file: any, elem: HTMLVideoElement, opts: RenderMediaOption
} }
function fallbackToMediaSource (useVP9 = false) { function fallbackToMediaSource (useVP9 = false) {
if (useVP9 === true) console.log('Falling back to media source with VP9 enabled.') if (useVP9 === true) logger.info('Falling back to media source with VP9 enabled.')
else console.log('Falling back to media source..') else logger.info('Falling back to media source..')
useMediaSource(useVP9) useMediaSource(useVP9)
} }

View File

@ -1,5 +1,6 @@
import videojs from 'video.js' import videojs from 'video.js'
import * as WebTorrent from 'webtorrent' import * as WebTorrent from 'webtorrent'
import { logger } from '@root-helpers/logger'
import { isIOS } from '@root-helpers/web-browser' import { isIOS } from '@root-helpers/web-browser'
import { timeToInt } from '@shared/core-utils' import { timeToInt } from '@shared/core-utils'
import { VideoFile } from '@shared/models' import { VideoFile } from '@shared/models'
@ -210,7 +211,7 @@ class WebTorrentPlugin extends Plugin {
if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy() if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy()
this.webtorrent.remove(videoFile.magnetUri) 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) if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done)
console.log('Adding ' + magnetOrTorrentUrl + '.') logger.info(`Adding ${magnetOrTorrentUrl}.`)
const oldTorrent = this.torrent const oldTorrent = this.torrent
const torrentOptions = { const torrentOptions = {
@ -269,7 +270,7 @@ class WebTorrentPlugin extends Plugin {
} }
this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => { this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => {
console.log('Added ' + magnetOrTorrentUrl + '.') logger.info(`Added ${magnetOrTorrentUrl}.`)
if (oldTorrent) { if (oldTorrent) {
// Pause the old torrent // Pause the old torrent
@ -309,7 +310,7 @@ class WebTorrentPlugin extends Plugin {
}, options.delay || 0) }, 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) => { this.torrent.on('warning', (err: any) => {
// We don't support HTTP tracker but we don't care -> we use the web socket tracker // 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 // 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) { if (err.message.indexOf('Ice connection failed') !== -1) {
console.log(err) logger.info(err)
return return
} }
// Magnet hash is not up to date with the torrent file, add directly the torrent file // Magnet hash is not up to date with the torrent file, add directly the torrent file
if (err.message.indexOf('incorrect info hash') !== -1) { 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 } const newOptions = { forcePlay: true, seek: options.seek }
return this.addTorrent(this.torrent['xs'], previousVideoFile, newOptions, done) return this.addTorrent(this.torrent['xs'], previousVideoFile, newOptions, done)
} }
@ -333,7 +334,7 @@ class WebTorrentPlugin extends Plugin {
this.handleError(err) this.handleError(err)
} }
console.warn(err) logger.warn(err)
}) })
} }
@ -348,7 +349,7 @@ class WebTorrentPlugin extends Plugin {
return return
} }
console.error(err) logger.error(err)
this.player.pause() this.player.pause()
this.player.posterImage.show() this.player.posterImage.show()
this.player.removeClass('vjs-has-autoplay') this.player.removeClass('vjs-has-autoplay')
@ -465,10 +466,10 @@ class WebTorrentPlugin extends Plugin {
// Lower resolution // Lower resolution
if (this.isPlayerWaiting() && file.resolution.id < this.currentVideoFile.resolution.id) { 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 changeResolution = true
} else if (file.resolution.id > this.currentVideoFile.resolution.id) { // Higher resolution } 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 changeResolution = true
changeResolutionDelay = this.CONSTANTS.AUTO_QUALITY_HIGHER_RESOLUTION_DELAY 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 // The renderer returns an error when we destroy it, so skip them
if (this.destroyingFakeRenderer === false && err) { 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) // Load the future file at the correct time (in delay MS - 2 seconds)
@ -593,7 +594,7 @@ class WebTorrentPlugin extends Plugin {
try { try {
this.fakeRenderer.destroy() this.fakeRenderer.destroy()
} catch (err) { } catch (err) {
console.log('Cannot destroy correctly fake renderer.', err) logger.info('Cannot destroy correctly fake renderer.', err)
} }
} }
this.fakeRenderer = undefined 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' import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '@shared/core-utils/i18n'
export class TranslationsManager { export class TranslationsManager {
@ -11,7 +12,7 @@ export class TranslationsManager {
return fetch(path + '/server.json') return fetch(path + '/server.json')
.then(res => res.json()) .then(res => res.json())
.catch(err => { .catch(err => {
console.error('Cannot get server translations', err) logger.error('Cannot get server translations', err)
return undefined return undefined
}) })
} }
@ -33,7 +34,7 @@ export class TranslationsManager {
return json return json
}) })
.catch(err => { .catch(err => {
console.error('Cannot get player translations', err) logger.error('Cannot get player translations', err)
return undefined return undefined
}) })
} }

View File

@ -3,11 +3,14 @@ import { enableDebugTools } from '@angular/platform-browser'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module' import { AppModule } from './app/app.module'
import { environment } from './environments/environment' import { environment } from './environments/environment'
import { logger } from './root-helpers'
if (environment.production) { if (environment.production) {
enableProdMode() enableProdMode()
} }
logger.registerServerSending(environment.apiUrl)
const bootstrap = () => platformBrowserDynamic() const bootstrap = () => platformBrowserDynamic()
.bootstrapModule(AppModule) .bootstrapModule(AppModule)
.then(bootstrapModule => { .then(bootstrapModule => {
@ -22,7 +25,7 @@ const bootstrap = () => platformBrowserDynamic()
return bootstrapModule return bootstrapModule
}) })
.catch(err => { .catch(err => {
console.error(err) logger.error(err)
return null return null
}) })

View File

@ -1,8 +1,10 @@
import { logger } from './logger'
function imageToDataURL (input: File | Blob) { function imageToDataURL (input: File | Blob) {
return new Promise<string>(res => { return new Promise<string>(res => {
const reader = new FileReader() 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.onloadend = () => res(reader.result as string)
reader.readAsDataURL(input) reader.readAsDataURL(input)
}) })

View File

@ -2,6 +2,7 @@ export * from './users'
export * from './bytes' export * from './bytes'
export * from './images' export * from './images'
export * from './local-storage-utils' export * from './local-storage-utils'
export * from './logger'
export * from './peertube-web-storage' export * from './peertube-web-storage'
export * from './plugins-manager' export * from './plugins-manager'
export * from './string' 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' } from '@shared/models'
import { environment } from '../environments/environment' import { environment } from '../environments/environment'
import { ClientScript } from '../types' import { ClientScript } from '../types'
import { logger } from './logger'
interface HookStructValue extends RegisterClientHookOptions { interface HookStructValue extends RegisterClientHookOptions {
plugin: ServerConfigPlugin plugin: ServerConfigPlugin
@ -48,7 +49,7 @@ type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSetting
type OnClientRoute = (options: RegisterClientRouteOptions) => void type OnClientRoute = (options: RegisterClientRouteOptions) => void
const logger = debug('peertube:plugins') const debugLogger = debug('peertube:plugins')
class PluginsManager { class PluginsManager {
private hooks: Hooks = {} private hooks: Hooks = {}
@ -109,10 +110,10 @@ class PluginsManager {
const hookType = getHookType(hookName) const hookType = getHookType(hookName)
for (const hook of this.hooks[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 => { 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 this.loadingScopes[scope] = true
logger('Loading scope %s', scope) debugLogger('Loading scope %s', scope)
try { try {
if (!isReload) this.loadedScopes.push(scope) if (!isReload) this.loadedScopes.push(scope)
@ -180,7 +181,7 @@ class PluginsManager {
this.loadingScopes[scope] = false this.loadingScopes[scope] = false
this.pluginsLoaded[scope].next(true) this.pluginsLoaded[scope].next(true)
logger('Nothing to load for scope %s', scope) debugLogger('Nothing to load for scope %s', scope)
return return
} }
@ -200,9 +201,9 @@ class PluginsManager {
this.pluginsLoaded[scope].next(true) this.pluginsLoaded[scope].next(true)
this.loadingScopes[scope] = false this.loadingScopes[scope] = false
logger('Scope %s loaded', scope) debugLogger('Scope %s loaded', scope)
} catch (err) { } 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) => { const registerHook = (options: RegisterClientHookOptions) => {
if (clientHookObject[options.target] !== true) { 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 return
} }
@ -252,7 +253,7 @@ class PluginsManager {
const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo) 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 const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
return dynamicImport(absURL) return dynamicImport(absURL)
@ -266,7 +267,7 @@ class PluginsManager {
}) })
}) })
.then(() => this.sortHooksByPriority()) .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 () { private sortHooksByPriority () {
@ -294,7 +295,7 @@ async function dynamicImport (url: string) {
// eslint-disable-next-line no-new-func // eslint-disable-next-line no-new-func
return new Function(`return import('${url}')`)() return new Function(`return import('${url}')`)()
} catch { } catch {
console.log('Fallback to import polyfill') logger.info('Fallback to import polyfill')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const vector = '$importModule$' + Math.random().toString(32).slice(2) const vector = '$importModule$' + Math.random().toString(32).slice(2)

View File

@ -1,6 +1,6 @@
import './embed.scss' import './embed.scss'
import * as Channel from 'jschannel' import * as Channel from 'jschannel'
import { logger } from '../../root-helpers'
import { PeerTubeResolution, PeerTubeTextTrack } from '../player/definitions' import { PeerTubeResolution, PeerTubeTextTrack } from '../player/definitions'
import { PeerTubeEmbed } from './embed' import { PeerTubeEmbed } from './embed'
@ -59,7 +59,7 @@ export class PeerTubeEmbedApi {
} }
private setResolution (resolutionId: number) { private setResolution (resolutionId: number) {
console.log('set resolution %d', resolutionId) logger.info(`Set resolution ${resolutionId}`)
if (this.isWebtorrent()) { if (this.isWebtorrent()) {
if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionPossible() === false) return 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 { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models'
import { PeertubePlayerManager } from '../../assets/player' import { PeertubePlayerManager } from '../../assets/player'
import { TranslationsManager } from '../../assets/player/translations-manager' import { TranslationsManager } from '../../assets/player/translations-manager'
import { getParamString } from '../../root-helpers' import { getParamString, logger } from '../../root-helpers'
import { PeerTubeEmbedApi } from './embed-api' import { PeerTubeEmbedApi } from './embed-api'
import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared' import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared'
import { PlayerHTML } from './shared/player-html' import { PlayerHTML } from './shared/player-html'
@ -31,6 +31,8 @@ export class PeerTubeEmbed {
private playlistTracker: PlaylistTracker private playlistTracker: PlaylistTracker
constructor (videoWrapperId: string) { constructor (videoWrapperId: string) {
logger.registerServerSending(window.location.origin)
this.http = new AuthHTTP() this.http = new AuthHTTP()
this.videoFetcher = new VideoFetcher(this.http) this.videoFetcher = new VideoFetcher(this.http)
@ -43,7 +45,7 @@ export class PeerTubeEmbed {
try { try {
this.config = JSON.parse(window['PeerTubeServerConfig']) this.config = JSON.parse(window['PeerTubeServerConfig'])
} catch (err) { } 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 () { async playNextPlaylistVideo () {
const next = this.playlistTracker.getNextPlaylistElement() const next = this.playlistTracker.getNextPlaylistElement()
if (!next) { if (!next) {
console.log('Next element not found in playlist.') logger.info('Next element not found in playlist.')
return return
} }
@ -137,7 +139,7 @@ export class PeerTubeEmbed {
async playPreviousPlaylistVideo () { async playPreviousPlaylistVideo () {
const previous = this.playlistTracker.getPreviousPlaylistElement() const previous = this.playlistTracker.getPreviousPlaylistElement()
if (!previous) { if (!previous) {
console.log('Previous element not found in playlist.') logger.info('Previous element not found in playlist.')
return return
} }
@ -343,5 +345,5 @@ PeerTubeEmbed.main()
.catch(err => { .catch(err => {
(window as any).displayIncompatibleBrowser() (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 { peertubeTranslate } from '../../../../../shared/core-utils/i18n'
import { VideoDetails } from '../../../../../shared/models' import { VideoDetails } from '../../../../../shared/models'
import { logger } from '../../../root-helpers'
import { Translations } from './translations' import { Translations } from './translations'
export class PlayerHTML { export class PlayerHTML {
@ -29,7 +30,7 @@ export class PlayerHTML {
} }
displayError (text: string, translations: Translations) { displayError (text: string, translations: Translations) {
console.error(text) logger.error(text)
// Remove video element // Remove video element
if (this.playerElement) { if (this.playerElement) {

View File

@ -14,6 +14,7 @@ import {
getParamString, getParamString,
getParamToggle, getParamToggle,
isP2PEnabled, isP2PEnabled,
logger,
peertubeLocalStorage, peertubeLocalStorage,
UserLocalStorageKeys UserLocalStorageKeys
} from '../../../root-helpers' } from '../../../root-helpers'
@ -137,7 +138,7 @@ export class PlayerManagerOptions {
else this.mode = 'webtorrent' else this.mode = 'webtorrent'
} }
} catch (err) { } 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 { HttpStatusCode, ResultList, VideoPlaylistElement } from '../../../../../shared/models'
import { logger } from '../../../root-helpers'
import { AuthHTTP } from './auth-http' import { AuthHTTP } from './auth-http'
export class PlaylistFetcher { export class PlaylistFetcher {
@ -18,7 +19,7 @@ export class PlaylistFetcher {
playlistResponse = await playlistPromise playlistResponse = await playlistPromise
isResponseOk = playlistResponse.status === HttpStatusCode.OK_200 isResponseOk = playlistResponse.status === HttpStatusCode.OK_200
} catch (err) { } catch (err) {
console.error(err) logger.error(err)
isResponseOk = false isResponseOk = false
} }
@ -49,7 +50,7 @@ export class PlaylistFetcher {
} }
if (i === 10) { 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 return elements

View File

@ -1,4 +1,5 @@
import { VideoPlaylist, VideoPlaylistElement } from '../../../../../shared/models' import { VideoPlaylist, VideoPlaylistElement } from '../../../../../shared/models'
import { logger } from '../../../root-helpers'
export class PlaylistTracker { export class PlaylistTracker {
private currentPlaylistElement: VideoPlaylistElement private currentPlaylistElement: VideoPlaylistElement
@ -68,7 +69,7 @@ export class PlaylistTracker {
setPosition (position: number) { setPosition (position: number) {
this.currentPlaylistElement = this.playlistElements.find(e => e.position === position) this.currentPlaylistElement = this.playlistElements.find(e => e.position === position)
if (!this.currentPlaylistElement || !this.currentPlaylistElement.video) { 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() this.currentPlaylistElement = this.getNextPlaylistElement()
} }

View File

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

View File

@ -1,6 +1,7 @@
import './test-embed.scss' import './test-embed.scss'
import { PeerTubeResolution, PlayerEventType } from '../player/definitions' import { PeerTubeResolution, PlayerEventType } from '../player/definitions'
import { PeerTubePlayer } from '../player/player' import { PeerTubePlayer } from '../player/player'
import { logger } from '../../root-helpers'
window.addEventListener('load', async () => { window.addEventListener('load', async () => {
const urlParts = window.location.href.split('/') const urlParts = window.location.href.split('/')
@ -20,14 +21,14 @@ window.addEventListener('load', async () => {
const mainElement = document.querySelector('#host') const mainElement = document.querySelector('#host')
mainElement.appendChild(iframe) mainElement.appendChild(iframe)
console.log('Document finished loading.') logger.info('Document finished loading.')
const player = new PeerTubePlayer(document.querySelector('iframe')) const player = new PeerTubePlayer(document.querySelector('iframe'))
window['player'] = player window['player'] = player
console.log('Awaiting player ready...') logger.info('Awaiting player ready...')
await player.ready await player.ready
console.log('Player is ready.') logger.info('Player is ready.')
const monitoredEvents = [ const monitoredEvents = [
'pause', 'pause',
@ -37,8 +38,8 @@ window.addEventListener('load', async () => {
] ]
monitoredEvents.forEach(e => { monitoredEvents.forEach(e => {
player.addEventListener(e as PlayerEventType, (param) => console.log(`PLAYER: event '${e}' received`, param)) player.addEventListener(e as PlayerEventType, (param) => logger.info(`PLAYER: event '${e}' received`, { param }))
console.log(`PLAYER: now listening for event '${e}'`) logger.info(`PLAYER: now listening for event '${e}'`)
player.getCurrentPosition() player.getCurrentPosition()
.then(position => { .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', loader: 'ts-loader',
options: { 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 # 3 attempts in 5 min
window: 5 minutes window: 5 minutes
max: 3 max: 3
receive_client_log:
# 10 attempts in 10 min
window: 10 minutes
max: 10
# Proxies to trust to get real client IP # Proxies to trust to get real client IP
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback' # If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
@ -168,15 +172,22 @@ object_storage:
log: log:
level: 'info' # 'debug' | 'info' | 'warn' | 'error' level: 'info' # 'debug' | 'info' | 'warn' | 'error'
rotation: rotation:
enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate 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_file_size: 12MB
max_files: 20 max_files: 20
anonymize_ip: false anonymize_ip: false
log_ping_requests: true log_ping_requests: true
log_tracker_unknown_infohash: true log_tracker_unknown_infohash: true
prettify_sql: false prettify_sql: false
# Accept warn/error logs coming from the client
accept_client_log: true
# Highly experimental support of Open Telemetry # Highly experimental support of Open Telemetry
open_telemetry: open_telemetry:
metrics: metrics:

View File

@ -25,6 +25,10 @@ rates_limit:
# 3 attempts in 5 min # 3 attempts in 5 min
window: 5 minutes window: 5 minutes
max: 3 max: 3
receive_client_log:
# 10 attempts in 10 min
window: 10 minutes
max: 10
# Proxies to trust to get real client IP # Proxies to trust to get real client IP
# If you run PeerTube just behind a local proxy (nginx), keep 'loopback' # If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
@ -166,15 +170,22 @@ object_storage:
log: log:
level: 'info' # 'debug' | 'info' | 'warn' | 'error' level: 'info' # 'debug' | 'info' | 'warn' | 'error'
rotation: rotation:
enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate 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_file_size: 12MB
max_files: 20 max_files: 20
anonymize_ip: false anonymize_ip: false
log_ping_requests: true log_ping_requests: true
log_tracker_unknown_infohash: true log_tracker_unknown_infohash: true
prettify_sql: false prettify_sql: false
# Accept warn/error logs coming from the client
accept_client_log: true
# Highly experimental support of Open Telemetry # Highly experimental support of Open Telemetry
open_telemetry: open_telemetry:
metrics: metrics:

View File

@ -3,15 +3,29 @@ import { readdir, readFile } from 'fs-extra'
import { join } from 'path' import { join } from 'path'
import { isArray } from '@server/helpers/custom-validators/misc' import { isArray } from '@server/helpers/custom-validators/misc'
import { logger, mtimeSortFilesDesc } from '@server/helpers/logger' 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 { UserRight } from '../../../../shared/models/users'
import { CONFIG } from '../../../initializers/config' import { CONFIG } from '../../../initializers/config'
import { AUDIT_LOG_FILENAME, LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants' import { AUDIT_LOG_FILENAME, LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants'
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' import { asyncMiddleware, authenticate, buildRateLimiter, ensureUserHasRight, optionalAuthenticate } from '../../../middlewares'
import { getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs' 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() const logsRouter = express.Router()
logsRouter.post('/logs/client',
createClientLogRateLimiter,
optionalAuthenticate,
createClientLogValidator,
createClientLog
)
logsRouter.get('/logs', logsRouter.get('/logs',
authenticate, authenticate,
ensureUserHasRight(UserRight.MANAGE_LOGS), 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) const auditLogNameFilter = generateLogNameFilter(AUDIT_LOG_FILENAME)
async function getAuditLogs (req: express.Request, res: express.Response) { async function getAuditLogs (req: express.Request, res: express.Response) {
const output = await generateOutput({ const output = await generateOutput({
@ -63,7 +92,7 @@ async function generateOutput (options: {
startDateQuery: string startDateQuery: string
endDateQuery?: string endDateQuery?: string
level: LogLevel level: ServerLogLevel
nameFilter: RegExp nameFilter: RegExp
tagsOneOf?: string[] tagsOneOf?: string[]
}) { }) {
@ -104,7 +133,7 @@ async function getOutputFromFile (options: {
path: string path: string
startDate: Date startDate: Date
endDate: Date endDate: Date
level: LogLevel level: ServerLogLevel
currentSize: number currentSize: number
tagsOneOf: Set<string> tagsOneOf: Set<string>
}) { }) {
@ -116,7 +145,7 @@ async function getOutputFromFile (options: {
let logTime: number let logTime: number
const logsLevel: { [ id in LogLevel ]: number } = { const logsLevel: { [ id in ServerLogLevel ]: number } = {
audit: -1, audit: -1,
debug: 0, debug: 0,
info: 1, 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 { 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) { 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 { 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')), WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.login.window')),
MAX: config.get<number>('rates_limit.login.max') 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: { ASK_SEND_EMAIL: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')), WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
MAX: config.get<number>('rates_limit.ask_send_email.max') MAX: config.get<number>('rates_limit.ask_send_email.max')
@ -165,7 +169,8 @@ const CONFIG = {
ANONYMIZE_IP: config.get<boolean>('log.anonymize_ip'), ANONYMIZE_IP: config.get<boolean>('log.anonymize_ip'),
LOG_PING_REQUESTS: config.get<boolean>('log.log_ping_requests'), LOG_PING_REQUESTS: config.get<boolean>('log.log_ping_requests'),
LOG_TRACKER_UNKNOWN_INFOHASH: config.get<boolean>('log.log_tracker_unknown_infohash'), 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: { OPEN_TELEMETRY: {
METRICS: { METRICS: {

View File

@ -365,6 +365,12 @@ const CONSTRAINTS_FIELDS = {
VIDEO_STUDIO: { VIDEO_STUDIO: {
TASKS: { min: 1, max: 10 }, // Number of tasks TASKS: { min: 1, max: 10 }, // Number of tasks
CUT_TIME: { min: 0 } // Value 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 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 { 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 { isDateValid, toArray } from '../../helpers/custom-validators/misc'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { areValidationErrors } from './shared' 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 = [ const getLogsValidator = [
query('startDate') query('startDate')
.custom(isDateValid).withMessage('Should have a start date that conforms to ISO 8601'), .custom(isDateValid).withMessage('Should have a start date that conforms to ISO 8601'),
@ -49,5 +94,6 @@ const getAuditLogsValidator = [
export { export {
getLogsValidator, getLogsValidator,
getAuditLogsValidator getAuditLogsValidator,
createClientLogValidator
} }

View File

@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import 'mocha' import 'mocha'
import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands' import { expect } from 'chai'
import { HttpStatusCode } from '@shared/models' import { HttpStatusCode } from '@shared/models'
import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
describe('Test logs API validators', function () { describe('Test logs API validators', function () {
const path = '/api/v1/server/logs' 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 () { after(async function () {
await cleanupTests([ server ]) await cleanupTests([ server ])
}) })

View File

@ -2,6 +2,7 @@
import 'mocha' import 'mocha'
import * as chai from 'chai' import * as chai from 'chai'
import { HttpStatusCode } from '@shared/models'
import { import {
cleanupTests, cleanupTests,
createSingleServer, 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 () { after(async function () {
await cleanupTests([ server ]) 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 './about.model'
export * from './broadcast-message-level.type' 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 './contact-form.model'
export * from './custom-config.model' export * from './custom-config.model'
export * from './debug.model' export * from './debug.model'
export * from './emailer.model' export * from './emailer.model'
export * from './job.model' export * from './job.model'
export * from './log-level.type'
export * from './peertube-problem-document.model' export * from './peertube-problem-document.model'
export * from './server-config.model' export * from './server-config.model'
export * from './server-debug.model' export * from './server-debug.model'
export * from './server-error-code.enum' export * from './server-error-code.enum'
export * from './server-follow-create.model' export * from './server-follow-create.model'
export * from './server-log-level.type'
export * from './server-stats.model' 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' import { AbstractCommand, OverrideCommandOptions } from '../shared'
export class LogsCommand extends AbstractCommand { 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 & { getLogs (options: OverrideCommandOptions & {
startDate: Date startDate: Date
endDate?: Date endDate?: Date
level?: LogLevel level?: ServerLogLevel
tagsOneOf?: string[] tagsOneOf?: string[]
}) { }) {
const { startDate, endDate, tagsOneOf, level } = options const { startDate, endDate, tagsOneOf, level } = options

View File

@ -330,26 +330,21 @@ x-tagGroups:
- name: Search - name: Search
tags: tags:
- Search - Search
- name: Custom pages
tags:
- Homepage
- name: Moderation - name: Moderation
tags: tags:
- Abuses - Abuses
- Video Blocks - Video Blocks
- Account Blocks - Account Blocks
- Server Blocks - Server Blocks
- name: Instance Configuration - name: Instance
tags: tags:
- Config - Config
- Homepage
- Instance Follows - Instance Follows
- Instance Redundancy - Instance Redundancy
- Plugins - Plugins
- name: Stats
tags:
- Stats - Stats
- name: Jobs - Logs
tags:
- Job - Job
paths: paths:
'/accounts/{name}': '/accounts/{name}':
@ -4316,6 +4311,58 @@ paths:
schema: schema:
$ref: '#/components/schemas/ServerStats' $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}': '/feeds/video-comments.{format}':
get: get:
@ -6526,6 +6573,31 @@ components:
enabled: enabled:
type: boolean 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: ServerStats:
properties: properties:
totalUsers: totalUsers: