]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add ability for client to create server logs
authorChocobozzz <me@florianbigard.com>
Fri, 15 Jul 2022 13:30:14 +0000 (15:30 +0200)
committerChocobozzz <me@florianbigard.com>
Mon, 18 Jul 2022 09:37:18 +0000 (11:37 +0200)
97 files changed:
client/src/app/+admin/overview/users/user-list/user-list.component.ts
client/src/app/+admin/plugins/plugin-search/plugin-search.component.ts
client/src/app/+admin/system/logs/log-row.model.ts
client/src/app/+admin/system/logs/logs.component.ts
client/src/app/+admin/system/logs/logs.service.ts
client/src/app/+plugin-pages/plugin-pages.component.ts
client/src/app/+search/shared/abstract-lazy-load.resolver.ts
client/src/app/+video-studio/edit/video-studio-edit.component.ts
client/src/app/+videos/+video-edit/shared/video-edit.component.ts
client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
client/src/app/+videos/+video-edit/video-add-components/video-import-torrent.component.ts
client/src/app/+videos/+video-edit/video-add-components/video-import-url.component.ts
client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
client/src/app/+videos/+video-edit/video-update.component.ts
client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts
client/src/app/+videos/+video-watch/video-watch.component.ts
client/src/app/app.component.ts
client/src/app/core/auth/auth.service.ts
client/src/app/core/notification/notifier.service.ts
client/src/app/core/plugins/hooks.service.ts
client/src/app/core/rest/rest-extractor.service.ts
client/src/app/core/rest/rest-table.ts
client/src/app/core/rest/rest.service.ts
client/src/app/core/routing/custom-reuse-strategy.ts
client/src/app/core/routing/redirect.service.ts
client/src/app/core/routing/scroll.service.ts
client/src/app/core/server/server.service.ts
client/src/app/core/theme/theme.service.ts
client/src/app/core/users/user-local-storage.service.ts
client/src/app/header/search-typeahead.component.ts
client/src/app/helpers/i18n-utils.ts
client/src/app/menu/menu.component.ts
client/src/app/modal/account-setup-warning-modal.component.ts
client/src/app/modal/admin-welcome-modal.component.ts
client/src/app/modal/custom-modal.component.ts
client/src/app/modal/instance-config-warning-modal.component.ts
client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
client/src/app/shared/shared-custom-markup/custom-markup.service.ts
client/src/app/shared/shared-forms/advanced-input-filter.component.ts
client/src/app/shared/shared-main/angular/defer-loading.directive.ts
client/src/app/shared/shared-main/misc/list-overflow.component.ts
client/src/app/shared/shared-main/users/user-notification.model.ts
client/src/app/shared/shared-search/find-in-bulk.service.ts
client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
client/src/app/shared/shared-user-subscription/user-subscription.service.ts
client/src/app/shared/shared-video-miniature/video-download.component.ts
client/src/app/shared/shared-video-miniature/video-filters-header.component.ts
client/src/app/shared/shared-video-miniature/videos-list.component.ts
client/src/app/shared/shared-video-miniature/videos-selection.component.ts
client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
client/src/app/shared/shared-video-playlist/video-playlist.service.ts
client/src/assets/player/peertube-player-local-storage.ts
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/shared/manager-options/hls-options-builder.ts
client/src/assets/player/shared/mobile/peertube-mobile-plugin.ts
client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts
client/src/assets/player/shared/p2p-media-loader/p2p-media-loader-plugin.ts
client/src/assets/player/shared/p2p-media-loader/redundancy-url-manager.ts
client/src/assets/player/shared/p2p-media-loader/segment-validator.ts
client/src/assets/player/shared/peertube/peertube-plugin.ts
client/src/assets/player/shared/stats/stats-card.ts
client/src/assets/player/shared/webtorrent/peertube-chunk-store.ts
client/src/assets/player/shared/webtorrent/video-renderer.ts
client/src/assets/player/shared/webtorrent/webtorrent-plugin.ts
client/src/assets/player/translations-manager.ts
client/src/main.ts
client/src/root-helpers/images.ts
client/src/root-helpers/index.ts
client/src/root-helpers/logger.ts [new file with mode: 0644]
client/src/root-helpers/plugins-manager.ts
client/src/standalone/videos/embed-api.ts
client/src/standalone/videos/embed.ts
client/src/standalone/videos/shared/player-html.ts
client/src/standalone/videos/shared/player-manager-options.ts
client/src/standalone/videos/shared/playlist-fetcher.ts
client/src/standalone/videos/shared/playlist-tracker.ts
client/src/standalone/videos/shared/video-fetcher.ts
client/src/standalone/videos/test-embed.ts
client/src/standalone/videos/tsconfig.json [new file with mode: 0644]
client/webpack/webpack.video-embed.js
config/default.yaml
config/production.yaml.example
server/controllers/api/server/logs.ts
server/helpers/custom-validators/logs.ts
server/initializers/config.ts
server/initializers/constants.ts
server/middlewares/validators/logs.ts
server/tests/api/check-params/logs.ts
server/tests/api/server/logs.ts
shared/models/server/client-log-create.model.ts [new file with mode: 0644]
shared/models/server/client-log-level.type.ts [new file with mode: 0644]
shared/models/server/index.ts
shared/models/server/log-level.type.ts [deleted file]
shared/models/server/server-log-level.type.ts [new file with mode: 0644]
shared/server-commands/logs/logs-command.ts
support/doc/api/openapi.yaml

index 3e1a5f6b80bf56e0b5a5bacd8c8be94be05c6bce..99987fdff4303565424d481568ee4a30887f5b6b 100644 (file)
@@ -2,11 +2,12 @@ import { SortMeta } from 'primeng/api'
 import { Component, OnInit, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
-import { prepareIcu, getAPIHost } from '@app/helpers'
+import { getAPIHost, prepareIcu } from '@app/helpers'
 import { AdvancedInputFilter } from '@app/shared/shared-forms'
 import { Actor, DropdownAction } from '@app/shared/shared-main'
 import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation'
 import { UserAdminService } from '@app/shared/shared-users'
+import { logger } from '@root-helpers/logger'
 import { User, UserRole } from '@shared/models'
 
 type UserForList = User & {
@@ -149,7 +150,7 @@ export class UserListComponent extends RestTable implements OnInit {
         this.selectedColumns = JSON.parse(result)
         return
       } catch (err) {
-        console.error('Cannot load selected columns.', err)
+        logger.error('Cannot load selected columns.', err)
       }
     }
 
index d39c2ea1cc8c2f26173476e330989ae2a01fcc59..b02c054a27a56c6098f9812a8e9c85ab404fdce0 100644 (file)
@@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
 import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core'
+import { logger } from '@root-helpers/logger'
 import { PeerTubePluginIndex, PluginType } from '@shared/models'
 
 @Component({
@@ -94,7 +95,7 @@ export class PluginSearchComponent implements OnInit {
           },
 
           error: err => {
-            console.error(err)
+            logger.error(err)
 
             const message = $localize`The plugin index is not available. Please retry later.`
             this.notifier.error(message)
index 615778210a4f0c6efe4fe371b96178863887fe52..e83c7b064f71c4952cff60a19e496c76c6b807b8 100644 (file)
@@ -1,10 +1,11 @@
-import { LogLevel } from '@shared/models'
 import omit from 'lodash-es/omit'
+import { logger } from '@root-helpers/logger'
+import { ServerLogLevel } from '@shared/models'
 
 export class LogRow {
   date: Date
   localeDate: string
-  level: LogLevel
+  level: ServerLogLevel
   message: string
   meta: string
 
@@ -33,7 +34,7 @@ export class LogRow {
         this.meta = JSON.stringify(message, null, 2)
         this.message = ''
       } catch (err) {
-        console.error('Cannot parse audit message.', err)
+        logger.error('Cannot parse audit message.', err)
       }
     }
   }
index 06237522a58b721ef7649d0d1696f7c11b216478..939e710d712384f42944d868714aab0bc0195d6b 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
 import { LocalStorageService, Notifier } from '@app/core'
-import { LogLevel } from '@shared/models'
+import { ServerLogLevel } from '@shared/models'
 import { LogRow } from './log-row.model'
 import { LogsService } from './logs.service'
 
@@ -17,11 +17,11 @@ export class LogsComponent implements OnInit {
 
   logs: LogRow[] = []
   timeChoices: { id: string, label: string, dateFormat: string }[] = []
-  levelChoices: { id: LogLevel, label: string }[] = []
+  levelChoices: { id: ServerLogLevel, label: string }[] = []
   logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
 
   startDate: string
-  level: LogLevel
+  level: ServerLogLevel
   logType: 'audit' | 'standard'
   tagsOneOf: string[] = []
 
index ea7e08b9bf32deb5bd8c97de34f9786e40a64ee7..933a074a89ef20cc18551a4dec4a3ed165b07d48 100644 (file)
@@ -3,7 +3,7 @@ import { catchError, map } from 'rxjs/operators'
 import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { RestExtractor, RestService } from '@app/core'
-import { LogLevel } from '@shared/models'
+import { ServerLogLevel } from '@shared/models'
 import { environment } from '../../../../environments/environment'
 import { LogRow } from './log-row.model'
 
@@ -22,7 +22,7 @@ export class LogsService {
     isAuditLog: boolean
     startDate: string
     tagsOneOf?: string[]
-    level?: LogLevel
+    level?: ServerLogLevel
     endDate?: string
   }): Observable<any[]> {
     const { isAuditLog, startDate, endDate, tagsOneOf } = options
index 973e4d021104bad1d7bb3c8649359a33f62cdf51..9fe4b413e8f3b6166e86a2ac2ef660294808fd5c 100644 (file)
@@ -1,6 +1,7 @@
 import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { PluginService } from '@app/core'
+import { logger } from '@root-helpers/logger'
 
 @Component({
   templateUrl: './plugin-pages.component.html'
@@ -26,7 +27,7 @@ export class PluginPagesComponent implements AfterViewInit {
 
     const registered = this.pluginService.getRegisteredClientRoute(path)
     if (!registered) {
-      console.log('Could not find registered route %s.', path, this.pluginService.getAllRegisteredClientRoutes())
+      logger.info(`Could not find registered route ${path}`, this.pluginService.getAllRegisteredClientRoutes())
 
       return this.router.navigate([ '/404' ], { skipLocationChange: true })
     }
index b18a5b64d51ee07030e667ace19f232d83837fcd..7551d977ddd91c83005848131ef686ebfcfd6536 100644 (file)
@@ -1,6 +1,7 @@
 import { Observable } from 'rxjs'
 import { map } from 'rxjs/operators'
 import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'
+import { logger } from '@root-helpers/logger'
 import { ResultList } from '@shared/models'
 
 export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
@@ -10,7 +11,7 @@ export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
     const url = route.params.url
 
     if (!url) {
-      console.error('Could not find url param.', { params: route.params })
+      logger.error('Could not find url param.', { params: route.params })
       return this.router.navigateByUrl('/404')
     }
 
@@ -18,7 +19,7 @@ export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
       .pipe(
         map(result => {
           if (result.data.length !== 1) {
-            console.error('Cannot find result for this URL')
+            logger.error('Cannot find result for this URL')
             return this.router.navigateByUrl('/404')
           }
 
index 392b6576731b7738d40b7becaade7523306298ad..bf91c237acaab531f245f1027d45ac61b92ce4a2 100644 (file)
@@ -4,6 +4,7 @@ import { ConfirmService, Notifier, ServerService } from '@app/core'
 import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
 import { VideoDetails } from '@app/shared/shared-main'
 import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
 import { secondsToTime } from '@shared/core-utils'
 import { VideoStudioTask, VideoStudioTaskCut } from '@shared/models'
 import { VideoStudioService } from '../shared'
@@ -97,7 +98,7 @@ export class VideoStudioEditComponent extends FormReactive implements OnInit {
           this.loadingBar.useRef().complete()
           this.isRunningEdition = false
           this.notifier.error(err.message)
-          console.error(err)
+          logger.error(err)
         }
       })
   }
index c74ef5731605aa311ead9b0cc6ac6066de5318c1..99f8c9034915c33cb8dcdcc49b2623da55929216 100644 (file)
@@ -38,6 +38,7 @@ import { VideoCaptionAddModalComponent } from './video-caption-add-modal.compone
 import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component'
 import { VideoEditType } from './video-edit.type'
 import { VideoSource } from '@shared/models/videos/video-source'
+import { logger } from '@root-helpers/logger'
 
 type VideoLanguages = VideoConstant<string> & { group?: string }
 type PluginField = {
@@ -443,7 +444,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
 
             const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId)
             if (!newChannel || !oldChannel) {
-              console.error('Cannot find new or old channel.')
+              logger.error('Cannot find new or old channel.')
               return
             }
 
index 80e5a73da5fa67511e118d8119e0dc16d673d3e0..91eb669311a54f1efff9ed43a771dfb924be51b4 100644 (file)
@@ -7,6 +7,7 @@ import { FormValidatorService } from '@app/shared/shared-forms'
 import { Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
 import { LiveVideoService } from '@app/shared/shared-video-live'
 import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
 import { LiveVideo, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
 import { VideoSend } from './video-send'
 
@@ -141,7 +142,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
       error: err => {
         this.error = err.message
         scrollToTop()
-        console.error(err)
+        logger.error(err)
       }
     })
   }
index da49969025d671270ed622af2a7fc0f29885238d..7b9531d27489e6ac1ecdb30c4f4f6c76a0b053f4 100644 (file)
@@ -6,6 +6,7 @@ import { scrollToTop } from '@app/helpers'
 import { FormValidatorService } from '@app/shared/shared-forms'
 import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
 import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
 import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models'
 import { hydrateFormFromVideo } from '../shared/video-edit-utils'
 import { VideoSend } from './video-send'
@@ -139,7 +140,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
           error: err => {
             this.error = err.message
             scrollToTop()
-            console.error(err)
+            logger.error(err)
           }
         })
   }
index 971a2a070edac6f3386aa91e79ffc16ad0a037bd..4ef7d1321de65895d61e1c560a724d4facf006cc 100644 (file)
@@ -7,6 +7,7 @@ import { scrollToTop } from '@app/helpers'
 import { FormValidatorService } from '@app/shared/shared-forms'
 import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
 import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
 import { VideoUpdate } from '@shared/models'
 import { hydrateFormFromVideo } from '../shared/video-edit-utils'
 import { VideoSend } from './video-send'
@@ -128,7 +129,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
           error: err => {
             this.error = err.message
             scrollToTop()
-            console.error(err)
+            logger.error(err)
           }
         })
   }
index 663955d2710dac27eaed81e50f8af4f104ae1bc3..66a3967c7173af1ca12d55272245f627748793da 100644 (file)
@@ -1,6 +1,5 @@
 import { truncate } from 'lodash-es'
 import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx'
-import { isIOS } from '@root-helpers/web-browser'
 import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http'
 import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
@@ -9,6 +8,8 @@ import { genericUploadErrorHandler, scrollToTop } from '@app/helpers'
 import { FormValidatorService } from '@app/shared/shared-forms'
 import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
 import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
+import { isIOS } from '@root-helpers/web-browser'
 import { HttpStatusCode, VideoCreateResult } from '@shared/models'
 import { UploaderXFormData } from './uploaderx-form-data'
 import { VideoSend } from './video-send'
@@ -264,7 +265,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
           error: err => {
             this.error = err.message
             scrollToTop()
-            console.error(err)
+            logger.error(err)
           }
         })
   }
index 13e786a8eed988b04cb9c7db2e41ccecf93130a6..ed17dff06ec1bb94383451a41e8a9ed289ec39a6 100644 (file)
@@ -8,9 +8,10 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
 import { Video, VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main'
 import { LiveVideoService } from '@app/shared/shared-video-live'
 import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
 import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models'
-import { hydrateFormFromVideo } from './shared/video-edit-utils'
 import { VideoSource } from '@shared/models/videos/video-source'
+import { hydrateFormFromVideo } from './shared/video-edit-utils'
 
 @Component({
   selector: 'my-videos-update',
@@ -156,7 +157,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
             this.loadingBar.useRef().complete()
             this.isUpdatingVideo = false
             this.notifier.error(err.message)
-            console.error(err)
+            logger.error(err)
           }
         })
   }
index e002b3c2212d36e4b090e9d68d44bc6b3d0188b9..b5444facb6003e654c2a8808c5c0b8e5ce849234 100644 (file)
@@ -1,6 +1,7 @@
 import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'
 import { MarkdownService, Notifier } from '@app/core'
 import { VideoDetails, VideoService } from '@app/shared/shared-main'
+import { logger } from '@root-helpers/logger'
 
 @Component({
   selector: 'my-video-description',
@@ -75,7 +76,7 @@ export class VideoDescriptionComponent implements OnChanges {
   private updateVideoDescription (description: string) {
     this.video.description = description
     this.setVideoDescriptionHTML()
-      .catch(err => console.error(err))
+      .catch(err => logger.error(err))
   }
 
   private async setVideoDescriptionHTML () {
index 6a3bd15222cc030f926f6684e2391ab087a48f6e..292ce644125fed4386160d65ce463fe864e38a21 100644 (file)
@@ -24,6 +24,7 @@ import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/sha
 import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
 import { LiveVideoService } from '@app/shared/shared-video-live'
 import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
+import { logger } from '@root-helpers/logger'
 import { isP2PEnabled } from '@root-helpers/video'
 import { timeToInt } from '@shared/core-utils'
 import {
@@ -225,7 +226,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         : parseInt(positionParam + '', 10)
 
       if (isNaN(this.playlistPosition)) {
-        console.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`)
+        logger.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`)
         this.playlistPosition = 1
       }
 
@@ -378,7 +379,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     }
 
     this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
-      .catch(err => console.error('Cannot build the player', err))
+      .catch(err => logger.error('Cannot build the player', err))
 
     this.setOpenGraphTags()
 
@@ -550,7 +551,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       this.player.dispose()
       this.player = undefined
     } catch (err) {
-      console.error('Cannot dispose player.', err)
+      logger.error('Cannot dispose player.', err)
     }
   }
 
@@ -717,7 +718,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   private handleLiveStateChange (newState: VideoState) {
     if (newState !== VideoState.PUBLISHED) return
 
-    console.log('Loading video after live update.')
+    logger.info('Loading video after live update.')
 
     const videoUUID = this.video.uuid
 
@@ -728,11 +729,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
   private handleLiveViewsChange (newViewers: number) {
     if (!this.video) {
-      console.error('Cannot update video live views because video is no defined.')
+      logger.error('Cannot update video live views because video is no defined.')
       return
     }
 
-    console.log('Updating live views.')
+    logger.info('Updating live views.')
 
     this.video.viewers = newViewers
   }
index 8fdab0c409478d62469444eb7ac76e55dccbc952..a2ad4806c782e863db054408bfb3a9518dcb0804 100644 (file)
@@ -1,5 +1,5 @@
 import { Hotkey, HotkeysService } from 'angular2-hotkeys'
-import { forkJoin, delay } from 'rxjs'
+import { delay, forkJoin } from 'rxjs'
 import { filter, first, map } from 'rxjs/operators'
 import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
 import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'
@@ -20,18 +20,19 @@ import {
 import { HooksService } from '@app/core/plugins/hooks.service'
 import { PluginService } from '@app/core/plugins/plugin.service'
 import { AccountSetupWarningModalComponent } from '@app/modal/account-setup-warning-modal.component'
+import { AdminWelcomeModalComponent } from '@app/modal/admin-welcome-modal.component'
 import { CustomModalComponent } from '@app/modal/custom-modal.component'
 import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
-import { AdminWelcomeModalComponent } from '@app/modal/admin-welcome-modal.component'
 import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { LoadingBarService } from '@ngx-loading-bar/core'
+import { logger } from '@root-helpers/logger'
 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 import { getShortLocale } from '@shared/core-utils/i18n'
 import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/models'
 import { MenuService } from './core/menu/menu.service'
 import { POP_STATE_MODAL_DISMISS } from './helpers'
-import { InstanceService } from './shared/shared-instance'
 import { GlobalIconName } from './shared/shared-icons'
+import { InstanceService } from './shared/shared-instance'
 
 @Component({
   selector: 'my-app',
@@ -221,7 +222,7 @@ export class AppComponent implements OnInit, AfterViewInit {
         /* eslint-disable no-eval */
         eval(this.serverConfig.instance.customizations.javascript)
       } catch (err) {
-        console.error('Cannot eval custom JavaScript.', err)
+        logger.error('Cannot eval custom JavaScript.', err)
       }
     }
   }
index 2ac88c18529b8ad071e004ccdf2a778d3407dc20..ece6bc5d168333d10cd91ed397ec1fc033d52304 100644 (file)
@@ -5,7 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { Router } from '@angular/router'
 import { Notifier } from '@app/core/notification/notifier.service'
-import { objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index'
+import { logger, objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index'
 import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
 import { environment } from '../../../environments/environment'
 import { RestExtractor } from '../rest/rest-extractor.service'
@@ -90,7 +90,7 @@ export class AuthService {
             peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID, this.clientId)
             peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret)
 
-            console.log('Client credentials loaded.')
+            logger.info('Client credentials loaded.')
           },
 
           error: err => {
@@ -177,7 +177,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
           }
         },
 
-        error: err => console.error(err)
+        error: err => logger.error(err)
       })
 
     this.user = null
@@ -190,7 +190,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
   refreshAccessToken () {
     if (this.refreshingTokenObservable) return this.refreshingTokenObservable
 
-    console.log('Refreshing token...')
+    logger.info('Refreshing token...')
 
     const refreshToken = this.getRefreshToken()
 
@@ -212,8 +212,8 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
                                            catchError(err => {
                                              this.refreshingTokenObservable = null
 
-                                             console.error(err)
-                                             console.log('Cannot refresh token -> logout...')
+                                             logger.error(err)
+                                             logger.info('Cannot refresh token -> logout...')
                                              this.logout()
                                              this.router.navigate([ '/login' ])
 
index 165bb0c76bd8b9afef404a3dee01a520aeb27995..15af5c1b65a375e8cb90fe9b5e418912673309e9 100644 (file)
@@ -1,5 +1,6 @@
 import { MessageService } from 'primeng/api'
 import { Injectable } from '@angular/core'
+import { logger } from '@root-helpers/logger'
 
 @Injectable()
 export class Notifier {
@@ -10,21 +11,21 @@ export class Notifier {
   info (text: string, title?: string, timeout?: number, sticky?: boolean) {
     if (!title) title = $localize`Info`
 
-    console.info(`${title}: ${text}`)
+    logger.info(`${title}: ${text}`)
     return this.notify('info', text, title, timeout, sticky)
   }
 
   error (text: string, title?: string, timeout?: number, sticky?: boolean) {
     if (!title) title = $localize`Error`
 
-    console.error(`${title}: ${text}`)
+    logger.error(`${title}: ${text}`)
     return this.notify('error', text, title, timeout, sticky)
   }
 
   success (text: string, title?: string, timeout?: number, sticky?: boolean) {
     if (!title) title = $localize`Success`
 
-    console.log(`${title}: ${text}`)
+    logger.info(`${title}: ${text}`)
     return this.notify('success', text, title, timeout, sticky)
   }
 
index 062083fd155db48a1749e4c4dafba77e521c2fee..7fd56d92e833a980eb3177fb2b59507eb0f6b536 100644 (file)
@@ -2,6 +2,7 @@ import { from, Observable } from 'rxjs'
 import { mergeMap, switchMap } from 'rxjs/operators'
 import { Injectable } from '@angular/core'
 import { PluginService } from '@app/core/plugins/plugin.service'
+import { logger } from '@root-helpers/logger'
 import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models'
 import { AuthService, AuthStatus } from '../auth'
 
@@ -50,7 +51,7 @@ export class HooksService {
   runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) {
     this.pluginService.ensurePluginsAreLoaded(scope)
         .then(() => this.pluginService.runHook(hookName, undefined, params))
-        .catch((err: any) => console.error('Fatal hook error.', { err }))
+        .catch((err: any) => logger.error('Fatal hook error.', err))
   }
 
   async wrapObject<T, U extends ClientFilterHookName> (result: T, scope: PluginClientScope, hookName: U) {
index 86c7484a5c0be56493868c1c518eff82d812daa9..8a297456334b1fae3ed37e27176ca7583e5d3f8c 100644 (file)
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core'
 import { Router } from '@angular/router'
 import { dateToHuman } from '@app/helpers'
 import { HttpStatusCode, ResultList } from '@shared/models'
+import { logger } from '@root-helpers/logger'
 
 @Injectable()
 export class RestExtractor {
@@ -64,7 +65,7 @@ export class RestExtractor {
     if (err.error instanceof Error) {
       // A client-side or network error occurred. Handle it accordingly.
       const errorMessage = err.error.detail || err.error.title
-      console.error('An error occurred:', errorMessage)
+      logger.error('An error occurred:', errorMessage)
 
       return errorMessage
     }
@@ -75,12 +76,12 @@ export class RestExtractor {
 
     if (err.status !== undefined) {
       const errorMessage = this.buildServerErrorMessage(err)
-      console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
+      logger.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
 
       return errorMessage
     }
 
-    console.error(err)
+    logger.error(err)
     return err
   }
 
index cb5bd0b892c13b6db270acf20855ef0926d39e10..ec5646b5df6320161d53ef7ee94dfb9d3a0369dd 100644 (file)
@@ -1,10 +1,11 @@
-import * as debug from 'debug'
+import debug from 'debug'
 import { LazyLoadEvent, SortMeta } from 'primeng/api'
 import { ActivatedRoute, Router } from '@angular/router'
+import { logger } from '@root-helpers/logger'
 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 import { RestPagination } from './rest-pagination'
 
-const logger = debug('peertube:tables:RestTable')
+const debugLogger = debug('peertube:tables:RestTable')
 
 export abstract class RestTable {
 
@@ -34,7 +35,7 @@ export abstract class RestTable {
       try {
         this.sort = JSON.parse(result)
       } catch (err) {
-        console.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err)
+        logger.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err)
       }
     }
   }
@@ -44,7 +45,7 @@ export abstract class RestTable {
   }
 
   loadLazy (event: LazyLoadEvent) {
-    logger('Load lazy %o.', event)
+    debugLogger('Load lazy %o.', event)
 
     this.sort = {
       order: event.sortOrder,
index fc729f0f68e1436a2ca125e53a844a14301aeb1a..d8b5ffb1851755aee06a8373a2c035c4cc5bc1d9 100644 (file)
@@ -5,7 +5,7 @@ import { Injectable } from '@angular/core'
 import { ComponentPaginationLight } from './component-pagination.model'
 import { RestPagination } from './rest-pagination'
 
-const logger = debug('peertube:rest')
+const debugLogger = debug('peertube:rest')
 
 interface QueryStringFilterPrefixes {
   [key: string]: {
@@ -88,7 +88,7 @@ export class RestService {
     const prefixeStrings = Object.values(prefixes)
                                  .map(p => p.prefix)
 
-    logger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`)
+    debugLogger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`)
 
     // Search is the querystring minus defined filters
     const searchTokens = tokens.filter(t => {
@@ -127,7 +127,7 @@ export class RestService {
 
     const search = searchTokens.join(' ') || undefined
 
-    logger('Built search: ' + search, additionalFilters)
+    debugLogger('Built search: ' + search, additionalFilters)
 
     return {
       search,
index 5d3ad2e6751b44739123850322965d8fca35979a..269b9d193ae6557fea70680ddbd014c1ce861479 100644 (file)
@@ -1,5 +1,6 @@
 import { ComponentRef, Injectable } from '@angular/core'
 import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
+import { logger } from '@root-helpers/logger'
 import { DisableForReuseHook } from './disable-for-reuse-hook'
 import { PeerTubeRouterService, RouterSetting } from './peertube-router.service'
 
@@ -22,7 +23,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
     const key = this.generateKey(route)
     this.recentlyUsed = key
 
-    console.log('Storing component %s to reuse later.', key)
+    logger.info(`Storing component ${key} to reuse later.`)
 
     const componentRef = (handle as any).componentRef as ComponentRef<DisableForReuseHook>
     componentRef.instance.disableForReuse()
@@ -46,7 +47,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
     const key = this.generateKey(route)
     this.recentlyUsed = key
 
-    console.log('Reusing component %s.', key)
+    logger.info(`Reusing component ${key}.`)
 
     const handle = this.storedRouteHandles.get(key)
     if (!handle) return handle;
@@ -66,7 +67,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
       this.storedRouteHandles.forEach((r, key) => {
         if (key === this.recentlyUsed) return
 
-        console.log('Removing stored component %s.', key);
+        logger.info(`Removing stored component ${key}`);
 
         (r as any).componentRef.destroy()
         this.storedRouteHandles.delete(key)
index 567fd432b04e34336ab0898711f260b673ab4201..575b3b2a18322c9da5d10d3ec9a71a91cad410c7 100644 (file)
@@ -1,10 +1,11 @@
 import * as debug from 'debug'
 import { Injectable } from '@angular/core'
 import { NavigationCancel, NavigationEnd, Router } from '@angular/router'
+import { logger } from '@root-helpers/logger'
 import { ServerService } from '../server'
 import { SessionStorageService } from '../wrappers/storage.service'
 
-const logger = debug('peertube:router:RedirectService')
+const debugLogger = debug('peertube:router:RedirectService')
 
 @Injectable()
 export class RedirectService {
@@ -40,7 +41,7 @@ export class RedirectService {
     this.latestSessionUrl = this.storage.getItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY)
     this.storage.removeItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY)
 
-    logger('Loaded latest session URL %s', this.latestSessionUrl)
+    debugLogger('Loaded latest session URL %s', this.latestSessionUrl)
 
     // Track previous url
     this.currentUrl = this.router.url
@@ -51,8 +52,8 @@ export class RedirectService {
         this.previousUrl = this.currentUrl
         this.currentUrl = event.url
 
-        logger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl)
-        logger('Setting %s as latest URL in session storage.', this.currentUrl)
+        debugLogger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl)
+        debugLogger('Setting %s as latest URL in session storage.', this.currentUrl)
 
         this.storage.setItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY, this.currentUrl)
       }
@@ -84,18 +85,14 @@ export class RedirectService {
 
     this.redirectingToHomepage = true
 
-    console.log('Redirecting to %s...', this.defaultRoute)
+    logger.info(`Redirecting to ${this.defaultRoute}...`)
 
     this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
         .then(() => this.redirectingToHomepage = false)
         .catch(() => {
           this.redirectingToHomepage = false
 
-          console.error(
-            'Cannot navigate to %s, resetting default route to %s.',
-            this.defaultRoute,
-            RedirectService.INIT_DEFAULT_ROUTE
-          )
+          logger.error(`Cannot navigate to ${this.defaultRoute}, resetting default route to ${RedirectService.INIT_DEFAULT_ROUTE}`)
 
           this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
           return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
@@ -104,18 +101,18 @@ export class RedirectService {
   }
 
   private doRedirect (redirectUrl: string, fallbackRoute?: string) {
-    logger('Redirecting on %s', redirectUrl)
+    debugLogger('Redirecting on %s', redirectUrl)
 
     if (this.isValidRedirection(redirectUrl)) {
       return this.router.navigateByUrl(redirectUrl)
     }
 
-    logger('%s is not a valid redirection, try fallback route %s', redirectUrl, fallbackRoute)
+    debugLogger('%s is not a valid redirection, try fallback route %s', redirectUrl, fallbackRoute)
     if (fallbackRoute) {
       return this.router.navigateByUrl(fallbackRoute)
     }
 
-    logger('There was no fallback route, redirecting to homepage')
+    debugLogger('There was no fallback route, redirecting to homepage')
     return this.redirectToHomepage()
   }
 
index 6d37fde71cb7c2b9d64be72a3000e7c95db2cef4..0966255b3b42337fd1e25317f6670547a6d22214 100644 (file)
@@ -4,8 +4,9 @@ import { ViewportScroller } from '@angular/common'
 import { Injectable } from '@angular/core'
 import { RouterSetting } from '../'
 import { PeerTubeRouterService } from './peertube-router.service'
+import { logger } from '@root-helpers/logger'
 
-const logger = debug('peertube:main:ScrollService')
+const debugLogger = debug('peertube:main:ScrollService')
 
 @Injectable()
 export class ScrollService {
@@ -57,8 +58,8 @@ export class ScrollService {
                           if (nextSearchParams.toString() !== previousSearchParams.toString()) {
                             this.resetScroll = true
                           }
-                        } catch (e) {
-                          console.error('Cannot parse URL to check next scroll.', e)
+                        } catch (err) {
+                          logger.error('Cannot parse URL to check next scroll.', err)
                           this.resetScroll = true
                         }
                       })
@@ -67,7 +68,7 @@ export class ScrollService {
   private consumeScroll () {
     // Handle anchors/restore position
     this.peertubeRouter.getScrollEvents().subscribe(e => {
-      logger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll })
+      debugLogger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll })
 
       // scrollToAnchor first to preserve anchor position when using history navigation
       if (e.anchor) {
index d019421390ee6686b183c1487138f0b7f1bacf0b..9db455cb86e20da0afc72e8fe6a52c73f8996821 100644 (file)
@@ -3,6 +3,7 @@ import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
 import { HttpClient } from '@angular/common/http'
 import { Inject, Injectable, LOCALE_ID } from '@angular/core'
 import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
+import { logger } from '@root-helpers/logger'
 import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
 import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
 import { environment } from '../../../environments/environment'
@@ -43,7 +44,7 @@ export class ServerService {
     } catch (err) {
       // Expected in dev mode since we can't inject the config in the HTML
       if (environment.production !== false) {
-        console.error('Cannot load config locally. Fallback to API.')
+        logger.error('Cannot load config locally. Fallback to API.')
       }
 
       return this.getConfig()
index 40939ecb85ebb01cafe9ac04bfb2ff3665a914e3..ead1770ba1b30bd87fe0e5ddeb617f3b53885439 100644 (file)
@@ -1,4 +1,5 @@
 import { Injectable } from '@angular/core'
+import { logger } from '@root-helpers/logger'
 import { capitalizeFirstLetter } from '@root-helpers/string'
 import { UserLocalStorageKeys } from '@root-helpers/users'
 import { HTMLServerConfig, ServerConfigTheme } from '@shared/models'
@@ -57,7 +58,7 @@ export class ThemeService {
   private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) {
     this.themes = themes
 
-    console.log('Injecting %d themes.', this.themes.length)
+    logger.info(`Injecting ${this.themes.length} themes.`)
 
     const head = this.getHeadElement()
 
@@ -117,13 +118,13 @@ export class ThemeService {
 
     const currentTheme = this.getCurrentTheme()
 
-    console.log('Enabling %s theme.', currentTheme)
+    logger.info(`Enabling ${currentTheme} theme.`)
 
     this.loadTheme(currentTheme)
 
     const theme = this.getTheme(currentTheme)
     if (theme) {
-      console.log('Adding scripts of theme %s.', currentTheme)
+      logger.info(`Adding scripts of theme ${currentTheme}`)
 
       this.pluginService.addPlugin(theme, true)
 
@@ -165,7 +166,7 @@ export class ThemeService {
       this.injectThemes([ lastActiveTheme ], true)
       this.updateCurrentTheme()
     } catch (err) {
-      console.error('Cannot parse last active theme.', err)
+      logger.error('Cannot parse last active theme.', err)
       return
     }
   }
@@ -173,7 +174,7 @@ export class ThemeService {
   private removeThemePlugins (themeName: string) {
     const oldTheme = this.getTheme(themeName)
     if (oldTheme) {
-      console.log('Removing scripts of old theme %s.', themeName)
+      logger.info(`Removing scripts of old theme ${themeName}.`)
       this.pluginService.removePlugin(oldTheme)
     }
   }
index d15bf735ba3005a2a83a03d43d0117a6844f4c98..fff649eef41c117db282b870ceac290cf5cb95cf 100644 (file)
@@ -2,8 +2,9 @@
 import { filter, throttleTime } from 'rxjs'
 import { Injectable } from '@angular/core'
 import { AuthService, AuthStatus } from '@app/core/auth'
-import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
 import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
+import { logger } from '@root-helpers/logger'
+import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
 import { UserRole, UserUpdateMe } from '@shared/models'
 import { NSFWPolicyType } from '@shared/models/videos'
 import { ServerService } from '../server'
@@ -95,7 +96,7 @@ export class UserLocalStorageService {
         : null
     } catch (err) {
       videoLanguages = null
-      console.error('Cannot parse desired video languages from localStorage.', err)
+      logger.error('Cannot parse desired video languages from localStorage.', err)
     }
 
     const htmlConfig = this.server.getHTMLConfig()
@@ -142,7 +143,7 @@ export class UserLocalStorageService {
 
         this.localStorageService.setItem(key, localStorageValue)
       } catch (err) {
-        console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
+        logger.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
       }
     }
   }
index 0794ec8f4774e7f3d71b70cf1bd7d593927b2786..d2549315cd4f9db83cea26c84d9bca20c7844043 100644 (file)
@@ -4,6 +4,7 @@ import { ListKeyManager } from '@angular/cdk/a11y'
 import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'
 import { ActivatedRoute, Params, Router } from '@angular/router'
 import { AuthService, ServerService } from '@app/core'
+import { logger } from '@root-helpers/logger'
 import { HTMLServerConfig, SearchTargetType } from '@shared/models'
 import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component'
 
@@ -91,7 +92,7 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDes
 
     const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true)
     if (activeIndex === -1) {
-      console.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
+      logger.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
     }
 
     this.updateItemsState(activeIndex)
index 2017a31ea3dc253feeddf9823dab95dc4fa1f74d..b7d73d16bda40e3aa9b8d28d7bfd52fdc05ca6cd 100644 (file)
@@ -1,5 +1,6 @@
-import { environment } from '../../environments/environment'
 import IntlMessageFormat from 'intl-messageformat'
+import { logger } from '@root-helpers/logger'
+import { environment } from '../../environments/environment'
 
 function isOnDevLocale () {
   return environment.production === false && window.location.search === '?lang=fr'
@@ -19,14 +20,14 @@ function prepareIcu (icu: string) {
       try {
         return msg.format(context) as string
       } catch (err) {
-        if (!alreadyWarned) console.warn('Cannot format ICU %s.', icu, err)
+        if (!alreadyWarned) logger.warn(`Cannot format ICU ${icu}.`, err)
 
         alreadyWarned = true
         return fallback
       }
     }
   } catch (err) {
-    console.warn('Cannot build intl message %s.', icu, err)
+    logger.warn(`Cannot build intl message ${icu}.`, err)
 
     return (_context: unknown, fallback: string) => fallback
   }
index 983f0a938388814e6585a7d31e075b2fb26d299a..63f01df92f560b891a8b10bbbc4feab2b6bde463 100644 (file)
@@ -24,7 +24,7 @@ import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
 import { PluginsManager } from '@root-helpers/plugins-manager'
 import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models'
 
-const logger = debug('peertube:menu:MenuComponent')
+const debugLogger = debug('peertube:menu:MenuComponent')
 
 @Component({
   selector: 'my-menu',
@@ -295,8 +295,8 @@ export class MenuComponent implements OnInit {
       .pipe(
         switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed()))
       ).subscribe(res => {
-        if (res === true) logger('User can see videos link.')
-        else logger('User cannot see videos link.')
+        if (res === true) debugLogger('User can see videos link.')
+        else debugLogger('User cannot see videos link.')
       })
   }
 
index c4cbf92b69bca3c060a471dd39651a06a504cb86..b999a3407d54935155d61928938bc4acec236835 100644 (file)
@@ -1,6 +1,7 @@
 import { Component, ElementRef, ViewChild } from '@angular/core'
 import { Notifier, ServerService, User, UserService } from '@app/core'
 import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 
 @Component({
@@ -71,7 +72,7 @@ export class AccountSetupWarningModalComponent {
 
     this.userService.updateMyProfile({ noAccountSetupWarningModal: true })
         .subscribe({
-          next: () => console.log('We will not open the account setup modal again.'),
+          next: () => logger.info('We will not open the account setup modal again.'),
 
           error: err => this.notifier.error(err.message)
         })
index 3679f08472840e88ab34dc5c7194ae603bfdcbd0..7a9b89d3d0044ced06dbb3c7c940f5c9d27866c2 100644 (file)
@@ -1,6 +1,7 @@
 import { Component, ElementRef, ViewChild } from '@angular/core'
 import { Notifier, User, UserService } from '@app/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 
 @Component({
@@ -42,7 +43,7 @@ export class AdminWelcomeModalComponent {
 
     this.userService.updateMyProfile({ noWelcomeModal: true })
       .subscribe({
-        next: () => console.log('We will not open the welcome modal again.'),
+        next: () => logger.info('We will not open the welcome modal again.'),
 
         error: err => this.notifier.error(err.message)
       })
index 559230e04b19b884c7a67d9705105362159c5dd5..49343ae71cbf0a429cccba5b5e37f8a8558bd4d4 100644 (file)
@@ -1,5 +1,6 @@
-import { Component, ElementRef, ViewChild, Input } from '@angular/core'
+import { Component, ElementRef, Input, ViewChild } from '@angular/core'
 import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
 
 @Component({
   selector: 'my-custom-modal',
@@ -29,7 +30,7 @@ export class CustomModalComponent {
     confirm?: { value: string, action?: () => void }
   }) {
     if (this.modalRef instanceof NgbModalRef && this.modalService.hasOpenModals()) {
-      console.error('Cannot open another custom modal, one is already opened.')
+      logger.error('Cannot open another custom modal, one is already opened.')
       return
     }
 
index 300738a41f79321cd143fb74555bb7ba50fbcfad..23c2c777e98b96c19bb23eeab1bded6a0d8160ca 100644 (file)
@@ -2,6 +2,7 @@ import { Location } from '@angular/common'
 import { Component, ElementRef, ViewChild } from '@angular/core'
 import { Notifier, User, UserService } from '@app/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
 import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
 import { About, ServerConfig } from '@shared/models/server'
 
@@ -64,7 +65,7 @@ export class InstanceConfigWarningModalComponent {
 
     this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
         .subscribe({
-          next: () => console.log('We will not open the instance config warning modal again.'),
+          next: () => logger.info('We will not open the instance config warning modal again.'),
 
           error: err => this.notifier.error(err.message)
         })
index 8781c16f5c3d99ca763370cd4e7d213e71e99b56..32d3b0093bdde9191164c7f314c62cc01038dcfa 100644 (file)
@@ -8,13 +8,14 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable }
 import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
 import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
 import { VideoCommentService } from '@app/shared/shared-video-comment'
+import { logger } from '@root-helpers/logger'
 import { AbuseState, AdminAbuse } from '@shared/models'
 import { AdvancedInputFilter } from '../shared-forms'
 import { AbuseMessageModalComponent } from './abuse-message-modal.component'
 import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
 import { ProcessedAbuse } from './processed-abuse.model'
 
-const logger = debug('peertube:moderation:AbuseListTableComponent')
+const debugLogger = debug('peertube:moderation:AbuseListTableComponent')
 
 @Component({
   selector: 'my-abuse-list-table',
@@ -158,7 +159,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
     const abuse = this.abuses.find(a => a.id === event.abuseId)
 
     if (!abuse) {
-      console.error('Cannot find abuse %d.', event.abuseId)
+      logger.error(`Cannot find abuse ${event.abuseId}`)
       return
     }
 
@@ -177,7 +178,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
   }
 
   protected reloadData () {
-    logger('Loading data.')
+    debugLogger('Loading data.')
 
     const options = {
       pagination: this.pagination,
index 6c8dc6d35e102777a2276377ee0291a5b5f08db6..d24a5d58da6c031e5aafeaba44ec40ef609dfd1f 100644 (file)
@@ -3,6 +3,7 @@ import { AuthService, HtmlRendererService, Notifier } from '@app/core'
 import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
+import { logger } from '@root-helpers/logger'
 import { AbuseMessage, UserAbuse } from '@shared/models'
 import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators'
 import { AbuseService } from '../shared-moderation'
@@ -72,7 +73,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
 
         error: err => {
           this.sendingMessage = false
-          console.error(err)
+          logger.error(err)
           this.notifier.error('Sorry but you cannot send this message. Please retry later')
         }
       })
index a959b336d18b555619dbdddbf9b29b85cacefbd1..d738a644ec0f90498271eb62b2f2cdd6736e20e4 100644 (file)
@@ -20,6 +20,7 @@ import {
   VideosListMarkupComponent
 } from './peertube-custom-tags'
 import { CustomMarkupComponent } from './peertube-custom-tags/shared'
+import { logger } from '@root-helpers/logger'
 
 type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent>
 type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
@@ -70,7 +71,7 @@ export class CustomMarkupService {
             // Insert as first child
             e.insertBefore(element, e.firstChild)
           } catch (err) {
-            console.error('Cannot inject component %s.', selector, err)
+            logger.error(`Cannot inject component ${selector}`, err)
           }
         })
     }
@@ -90,7 +91,7 @@ export class CustomMarkupService {
 
             this.dynamicElementService.injectElement(e, component)
           } catch (err) {
-            console.error('Cannot inject component %s.', selector, err)
+            logger.error(`Cannot inject component ${selector}`, err)
           }
         })
     }
index d8aeaa0fa3fceb133d2176192a87c8913cdbfb6f..92943874902163800f6f07c7b688d71e0d0bd63a 100644 (file)
@@ -16,7 +16,7 @@ export type AdvancedInputFilterChild = {
   value: string
 }
 
-const logger = debug('peertube:AdvancedInputFilterComponent')
+const debugLogger = debug('peertube:AdvancedInputFilterComponent')
 
 @Component({
   selector: 'my-advanced-input-filter',
@@ -98,7 +98,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
       .subscribe(params => {
         const search = params.search || ''
 
-        logger('On route search change "%s".', search)
+        debugLogger('On route search change "%s".', search)
 
         if (this.searchValue === search) return
 
@@ -132,7 +132,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
       return
     }
 
-    logger('On search "%s".', this.searchValue)
+    debugLogger('On search "%s".', this.searchValue)
 
     this.search.emit(this.searchValue)
   }
index 9a10e90e3c4be8c1f4d59ad45e99176f07c9cd89..53d6e70ba07fbc5bca6f252e8fb573ab751e4a0c 100644 (file)
@@ -13,7 +13,7 @@ import {
   ViewContainerRef
 } from '@angular/core'
 
-const logger = debug('peertube:main:DeferLoadingDirective')
+const debugLogger = debug('peertube:main:DeferLoadingDirective')
 
 @Directive({
   selector: '[myDeferLoading]'
@@ -52,7 +52,7 @@ export class DeferLoadingDirective implements AfterViewInit, OnDestroy {
   load () {
     if (this.isLoaded()) return
 
-    logger('Loading component')
+    debugLogger('Loading component')
 
     this.viewContainer.clear()
     this.view = this.viewContainer.createEmbeddedView(this.template, {}, 0)
index 541991f7438f34a8b098e4d8edc5eb6880ab7c23..7e4e1b1d1b1d73332402ec01cc4484d4d8cac463 100644 (file)
@@ -17,7 +17,7 @@ import { ScreenService } from '@app/core'
 import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import * as debug from 'debug'
 
-const logger = debug('peertube:main:ListOverflowItem')
+const debugLogger = debug('peertube:main:ListOverflowItem')
 
 export interface ListOverflowItem {
   label: string
@@ -66,7 +66,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
     let showItemsUntilIndexExcluded: number
     let accWidth = 0
 
-    logger('Parent width is %d', parentWidth)
+    debugLogger('Parent width is %d', parentWidth)
 
     for (const [ index, el ] of this.itemsRendered.toArray().entries()) {
       accWidth += el.nativeElement.getBoundingClientRect().width
@@ -79,7 +79,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
       e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden'
     }
 
-    logger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded)
+    debugLogger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded)
 
     this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded
     this.cdr.markForCheck()
index a2367166eb3b1be43d0f0cdfab87fbf5a4714f66..bf8870a7965712d8f719abf51512e6b356c65d84 100644 (file)
@@ -2,6 +2,7 @@ import { AuthUser } from '@app/core'
 import { Account } from '@app/shared/shared-main/account/account.model'
 import { Actor } from '@app/shared/shared-main/account/actor.model'
 import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
+import { logger } from '@root-helpers/logger'
 import {
   AbuseState,
   ActorInfo,
@@ -234,7 +235,7 @@ export class UserNotification implements UserNotificationServer {
       }
     } catch (err) {
       this.type = null
-      console.error(err)
+      logger.error(err)
     }
   }
 
index 117685cc64424f34c78ec4c6235873596e95f81c..d2f8c321369ea31a9a7b0960416ec8c8401b584f 100644 (file)
@@ -9,7 +9,7 @@ import { VideoPlaylist } from '../shared-video-playlist'
 import { SearchService } from './search.service'
 import { AdvancedSearch } from './advanced-search.model'
 
-const logger = debug('peertube:search:FindInBulkService')
+const debugLogger = debug('peertube:search:FindInBulkService')
 
 type BulkObservables <P extends number | string, R> = {
   notifier: Subject<P>
@@ -36,7 +36,7 @@ export class FindInBulkService {
   }
 
   getVideo (uuid: string): Observable<Video> {
-    logger('Schedule video fetch for uuid %s.', uuid)
+    debugLogger('Schedule video fetch for uuid %s.', uuid)
 
     return this.getData({
       observableObject: this.getVideoInBulk,
@@ -46,7 +46,7 @@ export class FindInBulkService {
   }
 
   getChannel (handle: string): Observable<VideoChannel> {
-    logger('Schedule channel fetch for handle %s.', handle)
+    debugLogger('Schedule channel fetch for handle %s.', handle)
 
     return this.getData({
       observableObject: this.getChannelInBulk,
@@ -56,7 +56,7 @@ export class FindInBulkService {
   }
 
   getPlaylist (uuid: string): Observable<VideoPlaylist> {
-    logger('Schedule playlist fetch for uuid %s.', uuid)
+    debugLogger('Schedule playlist fetch for uuid %s.', uuid)
 
     return this.getData({
       observableObject: this.getPlaylistInBulk,
@@ -94,7 +94,7 @@ export class FindInBulkService {
   }
 
   private getVideosInBulk (uuids: string[]) {
-    logger('Fetching videos %s.', uuids.join(', '))
+    debugLogger('Fetching videos %s.', uuids.join(', '))
 
     return this.searchService.searchVideos({
       uuids,
@@ -104,7 +104,7 @@ export class FindInBulkService {
   }
 
   private getChannelsInBulk (handles: string[]) {
-    logger('Fetching channels %s.', handles.join(', '))
+    debugLogger('Fetching channels %s.', handles.join(', '))
 
     return this.searchService.searchVideoChannels({
       handles,
@@ -114,7 +114,7 @@ export class FindInBulkService {
   }
 
   private getPlaylistsInBulk (uuids: string[]) {
-    logger('Fetching playlists %s.', uuids.join(', '))
+    debugLogger('Fetching playlists %s.', uuids.join(', '))
 
     return this.searchService.searchVideoPlaylists({
       uuids,
index 36969271530da72394ee8e52931bab9a2cf43ded..7bcfdd8aae43e20d1b301b36f190da33336dab14 100644 (file)
@@ -1,6 +1,7 @@
 import { Component, Input, OnInit } from '@angular/core'
 import { Notifier } from '@app/core'
 import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
+import { logger } from '@root-helpers/logger'
 import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators'
 
 @Component({
@@ -59,7 +60,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
       })
       .then(window.open)
       .catch(err => {
-        console.error(err)
+        logger.error(err)
 
         this.notifier.error($localize`Cannot fetch information of this remote account`)
       })
index 33a2d04fd743a118fcdb2fe1ed7dd67bb0d912ae..9cf6b4d165f2777157a2b3756308e58c237271a8 100644 (file)
@@ -9,7 +9,7 @@ import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/sha
 import { ActorFollow, ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models'
 import { environment } from '../../../environments/environment'
 
-const logger = debug('peertube:subscriptions:UserSubscriptionService')
+const debugLogger = debug('peertube:subscriptions:UserSubscriptionService')
 
 type SubscriptionExistResult = { [ uri: string ]: boolean }
 type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> }
@@ -176,17 +176,17 @@ export class UserSubscriptionService {
   }
 
   doesSubscriptionExist (nameWithHost: string) {
-    logger('Running subscription check for %d.', nameWithHost)
+    debugLogger('Running subscription check for %d.', nameWithHost)
 
     if (nameWithHost in this.myAccountSubscriptionCache) {
-      logger('Found cache for %d.', nameWithHost)
+      debugLogger('Found cache for %d.', nameWithHost)
 
       return of(this.myAccountSubscriptionCache[nameWithHost])
     }
 
     this.existsSubject.next(nameWithHost)
 
-    logger('Fetching from network for %d.', nameWithHost)
+    debugLogger('Fetching from network for %d.', nameWithHost)
     return this.existsObservable.pipe(
       filter(existsResult => existsResult[nameWithHost] !== undefined),
       map(existsResult => existsResult[nameWithHost]),
index bbda39c2dfb84a679b4adaa6220c2fa977f71a56..47482caaa786e909b4def947e44c28adef7119b8 100644 (file)
@@ -4,6 +4,7 @@ import { tap } from 'rxjs/operators'
 import { Component, ElementRef, Inject, LOCALE_ID, ViewChild } from '@angular/core'
 import { AuthService, HooksService, Notifier } from '@app/core'
 import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
+import { logger } from '@root-helpers/logger'
 import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models'
 import { BytesPipe, NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main'
 
@@ -142,7 +143,7 @@ export class VideoDownloadComponent {
                      .find(f => f.resolution.id === this.resolutionId)
 
     if (!file) {
-      console.error('Could not find file with resolution %d.', this.resolutionId)
+      logger.error(`Could not find file with resolution ${this.resolutionId}`)
       return undefined
     }
 
@@ -175,7 +176,7 @@ export class VideoDownloadComponent {
                         .find(c => c.language.id === this.subtitleLanguageId)
 
     if (!caption) {
-      console.error('Cannot find caption %s.', this.subtitleLanguageId)
+      logger.error(`Cannot find caption ${this.subtitleLanguageId}`)
       return undefined
     }
 
index 7b806248b139269fb3636b39048196e04d435c9f..a5da9ebf318fadfffb09a68018e1610518ffdeb6 100644 (file)
@@ -8,7 +8,7 @@ import { UserRight } from '@shared/models'
 import { PeertubeModalService } from '../shared-main'
 import { VideoFilters } from './video-filters.model'
 
-const logger = debug('peertube:videos:VideoFiltersHeaderComponent')
+const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent')
 
 @Component({
   selector: 'my-video-filters-header',
@@ -54,7 +54,7 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
     })
 
     this.form.valueChanges.subscribe(values => {
-      logger('Loading values from form: %O', values)
+      debugLogger('Loading values from form: %O', values)
 
       this.filters.load(values)
       this.filtersChanged.emit()
@@ -105,6 +105,6 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
     const defaultValues = this.filters.toFormObject()
     this.form.patchValue(defaultValues, { emitEvent })
 
-    logger('Patched form: %O', defaultValues)
+    debugLogger('Patched form: %O', defaultValues)
   }
 }
index 38a80b97385d6323af01562724c9ed4d04fc6ad7..508a189fdaaf5fa6ccdff4fa49040b02481f96a9 100644 (file)
@@ -14,13 +14,14 @@ import {
   UserService
 } from '@app/core'
 import { GlobalIconName } from '@app/shared/shared-icons'
+import { logger } from '@root-helpers/logger'
 import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils'
 import { ResultList, UserRight, VideoSortField } from '@shared/models'
 import { Syndication, Video } from '../shared-main'
 import { VideoFilters, VideoFilterScope } from './video-filters.model'
 import { MiniatureDisplayOptions } from './video-miniature.component'
 
-const logger = debug('peertube:videos:VideosListComponent')
+const debugLogger = debug('peertube:videos:VideosListComponent')
 
 export type HeaderAction = {
   iconName: GlobalIconName
@@ -245,7 +246,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
         error: err => {
           const message = $localize`Cannot load more videos. Try again later.`
 
-          console.error(message, { err })
+          logger.error(message, err)
           this.notifier.error(message)
         }
       })
@@ -323,7 +324,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
   }
 
   onFiltersChanged (customizedByUser: boolean) {
-    logger('Running on filters changed')
+    debugLogger('Running on filters changed')
 
     this.updateUrl(customizedByUser)
 
@@ -364,7 +365,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
         if (!items || items.length === 0) this.syndicationItems = undefined
         else this.syndicationItems = items
       })
-      .catch(err => console.error('Cannot get syndication items.', err))
+      .catch(err => logger.error('Cannot get syndication items.', err))
   }
 
   private updateUrl (customizedByUser: boolean) {
@@ -375,7 +376,7 @@ export class VideosListComponent implements OnInit, OnChanges, OnDestroy {
       ? { ...baseQuery, c: customizedByUser }
       : baseQuery
 
-    logger('Will inject %O in URL query', queryParams)
+    debugLogger('Will inject %O in URL query', queryParams)
 
     const baseRoute = this.baseRouteBuilderFunction
       ? this.baseRouteBuilderFunction(this.filters)
index bac828fbaedc679da3bbd32ef933fd7f281b475c..fa3c79bbb737d183a2474b377552f0520e6a753b 100644 (file)
@@ -1,6 +1,7 @@
 import { Observable, Subject } from 'rxjs'
 import { AfterContentInit, Component, ContentChildren, EventEmitter, Input, Output, QueryList, TemplateRef } from '@angular/core'
 import { ComponentPagination, Notifier, User } from '@app/core'
+import { logger } from '@root-helpers/logger'
 import { ResultList, VideoSortField } from '@shared/models'
 import { PeerTubeTemplateDirective, Video } from '../shared-main'
 import { MiniatureDisplayOptions } from './video-miniature.component'
@@ -128,7 +129,7 @@ export class VideosSelectionComponent implements AfterContentInit {
         error: err => {
           const message = $localize`Cannot load more videos. Try again later.`
 
-          console.error(message, { err })
+          logger.error(message, err)
           this.notifier.error(message)
         }
       })
index e4972ec10ab7dd6376a20dfa2462f56e7d585b93..e019fdd265cf4728ca8c3f8946a334fcc69a2c83 100644 (file)
@@ -16,7 +16,7 @@ import {
 import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators'
 import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
 
-const logger = debug('peertube:playlists:VideoAddToPlaylistComponent')
+const debugLogger = debug('peertube:playlists:VideoAddToPlaylistComponent')
 
 type PlaylistElement = {
   enabled: boolean
@@ -110,7 +110,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
   }
 
   reload () {
-    logger('Reloading component')
+    debugLogger('Reloading component')
 
     this.videoPlaylists = []
     this.videoPlaylistSearch = undefined
@@ -121,7 +121,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
   }
 
   load () {
-    logger('Loading component')
+    debugLogger('Loading component')
 
     this.listenToVideoPlaylistChange()
 
@@ -331,7 +331,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
   }
 
   private rebuildPlaylists (existResult: VideoExistInPlaylist[]) {
-    logger('Got existing results for %d.', this.video.id, existResult)
+    debugLogger('Got existing results for %d.', this.video.id, existResult)
 
     const oldPlaylists = this.videoPlaylists
 
@@ -359,7 +359,7 @@ export class VideoAddToPlaylistComponent extends FormReactive implements OnInit,
       this.videoPlaylists.push(playlistSummary)
     }
 
-    logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
+    debugLogger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
 
     this.cd.markForCheck()
   }
index db9f78a7ab3e267b1c36f8e9cde778524788346e..81ae0f2924c8dd9759105890c22fa87c8fcdb599 100644 (file)
@@ -23,7 +23,7 @@ import { environment } from '../../../environments/environment'
 import { VideoPlaylistElement } from './video-playlist-element.model'
 import { VideoPlaylist } from './video-playlist.model'
 
-const logger = debug('peertube:playlists:VideoPlaylistService')
+const debugLogger = debug('peertube:playlists:VideoPlaylistService')
 
 export type CachedPlaylist = VideoPlaylist | { id: number, displayName: string }
 
@@ -287,15 +287,15 @@ export class VideoPlaylistService {
   }
 
   runPlaylistCheck (videoId: number) {
-    logger('Running playlist check.')
+    debugLogger('Running playlist check.')
 
     if (this.videoExistsCache[videoId]) {
-      logger('Found cache for %d.', videoId)
+      debugLogger('Found cache for %d.', videoId)
 
       return this.videoExistsInPlaylistCacheSubject.next({ [videoId]: this.videoExistsCache[videoId] })
     }
 
-    logger('Fetching from network for %d.', videoId)
+    debugLogger('Fetching from network for %d.', videoId)
     return this.videoExistsInPlaylistNotifier.next(videoId)
   }
 
index d9dacfba533ef1efdea82df24de2df7eece4327d..64040abf1d5ae4736bf71144fc99e757e68744de 100644 (file)
@@ -1,3 +1,5 @@
+import { logger } from '@root-helpers/logger'
+
 function getStoredVolume () {
   const value = getLocalStorage('volume')
   if (value !== null && value !== undefined) {
@@ -81,7 +83,7 @@ function getStoredVideoWatchHistory (videoUUID?: string) {
 
     data = JSON.parse(value)
   } catch (error) {
-    console.error('Cannot parse video watch history from local storage: ', error)
+    logger.error('Cannot parse video watch history from local storage/', error)
   }
 
   data = data || {}
index b24b6966eb6272defb5617b47211edc0e27efd89..b9077dcae520ccd6264502b99a0f5713890c3af5 100644 (file)
@@ -23,6 +23,7 @@ import './shared/mobile/peertube-mobile-plugin'
 import './shared/mobile/peertube-mobile-buttons'
 import './shared/hotkeys/peertube-hotkeys-plugin'
 import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
 import { PluginsManager } from '@root-helpers/plugins-manager'
 import { isMobile } from '@root-helpers/web-browser'
 import { saveAverageBandwidth } from './peertube-player-local-storage'
@@ -145,7 +146,7 @@ export class PeertubePlayerManager {
         return
       }
 
-      console.log('Fast forwarding HLS to recover from an error.')
+      logger.info('Fast forwarding HLS to recover from an error.')
 
       this.videojsDecodeErrors++
 
@@ -170,7 +171,7 @@ export class PeertubePlayerManager {
       return
     }
 
-    console.log('Fallback to webtorrent.')
+    logger.info('Fallback to webtorrent.')
 
     this.rebuildAndUpdateVideoElement(currentPlayer, options.common)
 
index cdfad0f4c08cbb1834a98522cf67381b294f908c..ed12f6e8b2b79dcde6fd4f01023f2bc4a2b26e7c 100644 (file)
@@ -1,5 +1,6 @@
 import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
 import { HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
+import { logger } from '@root-helpers/logger'
 import { LiveVideoLatencyMode } from '@shared/models'
 import { getAverageBandwidthInStore } from '../../peertube-player-local-storage'
 import { P2PMediaLoader, P2PMediaLoaderPluginOptions } from '../../types'
@@ -61,7 +62,7 @@ export class HLSOptionsBuilder {
   private getP2PMediaLoaderOptions (redundancyUrlManager: RedundancyUrlManager): HlsJsEngineSettings {
     let consumeOnly = false
     if ((navigator as any)?.connection?.type === 'cellular') {
-      console.log('We are on a cellular connection: disabling seeding.')
+      logger.info('We are on a cellular connection: disabling seeding.')
       consumeOnly = true
     }
 
index 91dda7f948341857f8fc56f8d9f8eebc6029488a..646e9f8c63f728930db40739aaadc6d46c36517e 100644 (file)
@@ -1,8 +1,9 @@
-import { PeerTubeMobileButtons } from './peertube-mobile-buttons'
-import videojs from 'video.js'
 import debug from 'debug'
+import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
+import { PeerTubeMobileButtons } from './peertube-mobile-buttons'
 
-const logger = debug('peertube:player:mobile')
+const debugLogger = debug('peertube:player:mobile')
 
 const Plugin = videojs.getPlugin('plugin')
 
@@ -45,7 +46,7 @@ class PeerTubeMobilePlugin extends Plugin {
       if (!this.player.isFullscreen() || this.isPortraitVideo()) return
 
       screen.orientation.lock('landscape')
-        .catch(err => console.error('Cannot lock screen to landscape.', err))
+        .catch(err => logger.error('Cannot lock screen to landscape.', err))
     })
   }
 
@@ -61,7 +62,7 @@ class PeerTubeMobilePlugin extends Plugin {
       }
 
       if (this.lastTapEvent && event.timeStamp - this.lastTapEvent.timeStamp < PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS) {
-        logger('Detected double tap')
+        debugLogger('Detected double tap')
 
         this.lastTapEvent = undefined
         this.onDoubleTap(event)
@@ -71,7 +72,7 @@ class PeerTubeMobilePlugin extends Plugin {
       this.newActiveState = !this.player.userActive()
 
       this.tapTimeout = setTimeout(() => {
-        logger('No double tap detected, set user active state to %s.', this.newActiveState)
+        debugLogger('No double tap detected, set user active state to %s.', this.newActiveState)
 
         this.player.userActive(this.newActiveState)
       }, PeerTubeMobilePlugin.DOUBLE_TAP_DELAY_MS)
@@ -100,19 +101,19 @@ class PeerTubeMobilePlugin extends Plugin {
     const rect = this.findPlayerTarget((event.target as HTMLElement)).getBoundingClientRect()
     const offsetX = event.targetTouches[0].pageX - rect.left
 
-    logger('Calculating double tap zone (player width: %d, offset X: %d)', playerWidth, offsetX)
+    debugLogger('Calculating double tap zone (player width: %d, offset X: %d)', playerWidth, offsetX)
 
     if (offsetX > 0.66 * playerWidth) {
       if (this.seekAmount < 0) this.seekAmount = 0
 
       this.seekAmount += 10
 
-      logger('Will forward %d seconds', this.seekAmount)
+      debugLogger('Will forward %d seconds', this.seekAmount)
     } else if (offsetX < 0.33 * playerWidth) {
       if (this.seekAmount > 0) this.seekAmount = 0
 
       this.seekAmount -= 10
-      logger('Will rewind %d seconds', this.seekAmount)
+      debugLogger('Will rewind %d seconds', this.seekAmount)
     }
 
     this.peerTubeMobileButtons.displayFastSeek(this.seekAmount)
index d0105fa36f22176b6194036a848d48c917f3f797..e49e5c6943348aa4841e045b87b1f82c6401635e 100644 (file)
@@ -3,6 +3,7 @@
 
 import Hlsjs, { ErrorData, HlsConfig, Level, LevelSwitchingData, ManifestParsedData } from 'hls.js'
 import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
 import { HlsjsConfigHandlerOptions, PeerTubeResolution, VideoJSTechHLS } from '../../types'
 
 type ErrorCounts = {
@@ -17,14 +18,14 @@ type HookFn = (player: videojs.Player, hljs: Hlsjs) => void
 
 const registerSourceHandler = function (vjs: typeof videojs) {
   if (!Hlsjs.isSupported()) {
-    console.warn('Hls.js is not supported in this browser!')
+    logger.warn('Hls.js is not supported in this browser!')
     return
   }
 
   const html5 = vjs.getTech('Html5')
 
   if (!html5) {
-    console.error('No Hml5 tech found in videojs')
+    logger.error('No Hml5 tech found in videojs')
     return
   }
 
@@ -120,7 +121,7 @@ class Html5Hlsjs {
 
       if (!mediaError) return
 
-      console.log(mediaError)
+      logger.info(mediaError)
       switch (mediaError.code) {
         case mediaError.MEDIA_ERR_ABORTED:
           errorTxt = 'You aborted the video playback'
@@ -141,7 +142,7 @@ class Html5Hlsjs {
           errorTxt = mediaError.message
       }
 
-      console.error('MEDIA_ERROR: ', errorTxt)
+      logger.error(`MEDIA_ERROR: ${errorTxt}`)
     })
 
     this.initialize()
@@ -212,20 +213,20 @@ class Html5Hlsjs {
 
   private _handleMediaError (error: any) {
     if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) {
-      console.info('trying to recover media error')
+      logger.info('trying to recover media error')
       this.hls.recoverMediaError()
       return
     }
 
     if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 2) {
-      console.info('2nd try to recover media error (by swapping audio codec')
+      logger.info('2nd try to recover media error (by swapping audio codec')
       this.hls.swapAudioCodec()
       this.hls.recoverMediaError()
       return
     }
 
     if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) {
-      console.info('bubbling media error up to VIDEOJS')
+      logger.info('bubbling media error up to VIDEOJS')
       this.hls.destroy()
       this.tech.error = () => error
       this.tech.trigger('error')
@@ -234,7 +235,7 @@ class Html5Hlsjs {
 
   private _handleNetworkError (error: any) {
     if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= this.maxNetworkErrorRecovery) {
-      console.info('trying to recover network error')
+      logger.info('trying to recover network error')
 
       // Wait 1 second and retry
       setTimeout(() => this.hls.startLoad(), 1000)
@@ -247,7 +248,7 @@ class Html5Hlsjs {
       return
     }
 
-    console.info('bubbling network error up to VIDEOJS')
+    logger.info('bubbling network error up to VIDEOJS')
     this.hls.destroy()
     this.tech.error = () => error
     this.tech.trigger('error')
@@ -262,8 +263,8 @@ class Html5Hlsjs {
     if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1
     else this.errorCounts[data.type] = 1
 
-    if (data.fatal) console.warn(error.message)
-    else console.error(error.message, data)
+    if (data.fatal) logger.warn(error.message)
+    else logger.error(error.message, { data })
 
     if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) {
       error.code = 2
@@ -273,7 +274,7 @@ class Html5Hlsjs {
       this._handleMediaError(error)
     } else if (data.fatal) {
       this.hls.destroy()
-      console.info('bubbling error up to VIDEOJS')
+      logger.info('bubbling error up to VIDEOJS')
       this.tech.error = () => error as any
       this.tech.trigger('error')
     }
index 5c0f0021f37de04551298732e1fd5ccff5bcce23..e5f099deadce7bb14ed57fa5e05012e055aa2ae8 100644 (file)
@@ -5,6 +5,7 @@ import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from '@peertub
 import { timeToInt } from '@shared/core-utils'
 import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../../types'
 import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
+import { logger } from '@root-helpers/logger'
 
 registerConfigPlugin(videojs)
 registerSourceHandler(videojs)
@@ -43,11 +44,11 @@ class P2pMediaLoaderPlugin extends Plugin {
 
     // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
     if (!(videojs as any).Html5Hlsjs) {
-      console.warn('HLS.js does not seem to be supported. Try to fallback to built in HLS.')
+      logger.warn('HLS.js does not seem to be supported. Try to fallback to built in HLS.')
 
       if (!player.canPlayType('application/vnd.apple.mpegurl')) {
         const message = 'Cannot fallback to built-in HLS'
-        console.warn(message)
+        logger.warn(message)
 
         player.ready(() => player.trigger('error', new Error(message)))
         return
@@ -114,7 +115,7 @@ class P2pMediaLoaderPlugin extends Plugin {
     this.p2pEngine = this.options.loader.getEngine()
 
     this.p2pEngine.on(Events.SegmentError, (segment: Segment, err) => {
-      console.error('Segment error.', segment, err)
+      logger.error(`Segment ${segment.id} error.`, err)
 
       this.options.redundancyUrlManager.removeBySegmentUrl(segment.requestUrl)
     })
index abab8aa99e3524c739c39d3a780cd5c2f5bbe191..376efb83572ab968ac240f3967142c701d44488d 100644 (file)
@@ -1,4 +1,5 @@
 import { basename, dirname } from 'path'
+import { logger } from '@root-helpers/logger'
 
 class RedundancyUrlManager {
 
@@ -7,7 +8,7 @@ class RedundancyUrlManager {
   }
 
   removeBySegmentUrl (segmentUrl: string) {
-    console.log('Removing redundancy of segment URL %s.', segmentUrl)
+    logger.info(`Removing redundancy of segment URL ${segmentUrl}.`)
 
     const baseUrl = dirname(segmentUrl)
 
index f7f83a8a4f531a46c6377d3aa63db4f7161bed2d..18cb6750f2b0289e2ef4b605bb9f189a3fd13208 100644 (file)
@@ -1,6 +1,7 @@
-import { wait } from '@root-helpers/utils'
-import { Segment } from '@peertube/p2p-media-loader-core'
 import { basename } from 'path'
+import { Segment } from '@peertube/p2p-media-loader-core'
+import { logger } from '@root-helpers/logger'
+import { wait } from '@root-helpers/utils'
 
 type SegmentsJSON = { [filename: string]: string | { [byterange: string]: string } }
 
@@ -23,7 +24,7 @@ function segmentValidatorFactory (segmentsSha256Url: string, isLive: boolean) {
     }
 
     if (!segmentValue) {
-      console.log('Refetching sha segments for %s.', filename)
+      logger.info(`Refetching sha segments for ${filename}`)
 
       await wait(1000)
 
@@ -71,7 +72,7 @@ function fetchSha256Segments (url: string) {
   return fetch(url)
     .then(res => res.json() as Promise<SegmentsJSON>)
     .catch(err => {
-      console.error('Cannot get sha256 segments', err)
+      logger.error('Cannot get sha256 segments', err)
       return {}
     })
 }
index a29a0921f755923501608406335fe07486dc8b54..69a7b2d650347cb960571b6ef74027d7893d1153 100644 (file)
@@ -1,5 +1,6 @@
 import debug from 'debug'
 import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
 import { isMobile } from '@root-helpers/web-browser'
 import { timeToInt } from '@shared/core-utils'
 import { VideoView, VideoViewEvent } from '@shared/models/videos'
@@ -15,7 +16,7 @@ import {
 import { PeerTubePluginOptions, VideoJSCaption } from '../../types'
 import { SettingsButton } from '../settings/settings-menu-button'
 
-const logger = debug('peertube:player:peertube')
+const debugLogger = debug('peertube:player:peertube')
 
 const Plugin = videojs.getPlugin('plugin')
 
@@ -176,7 +177,7 @@ class PeerTubePlugin extends Plugin {
       lastCurrentTime = currentTime
 
       this.notifyUserIsWatching(currentTime, lastViewEvent)
-        .catch(err => console.error('Cannot notify user is watching.', err))
+        .catch(err => logger.error('Cannot notify user is watching.', err))
 
       lastViewEvent = undefined
 
@@ -249,7 +250,7 @@ class PeerTubePlugin extends Plugin {
     (this.player as any).cache_.inactivityTimeout = timeout
     this.player.options_.inactivityTimeout = timeout
 
-    logger('Set player inactivity to ' + timeout)
+    debugLogger('Set player inactivity to ' + timeout)
   }
 
   private initCaptions () {
index e9f9b6bd2c9fbd477169d983ac97d618d4893172..b65adcfca381f19938b3c50cfcc17d8d0489b12c 100644 (file)
@@ -1,4 +1,5 @@
 import videojs from 'video.js'
+import { logger } from '@root-helpers/logger'
 import { secondsToTime } from '@shared/core-utils'
 import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../../types'
 import { bytes } from '../common'
@@ -125,7 +126,7 @@ class StatsCard extends Component {
 
         this.populateInfoValues(options)
       } catch (err) {
-        console.error('Cannot update stats.', err)
+        logger.error('Cannot update stats.', err)
         clearInterval(this.updateInterval)
       }
     }, this.intervalMs)
index 81378c2777ccac5debb6d46ad2fbf3e1005f0f99..74ae17704ca28dbe7685eb41fb9219fbd1d057f6 100644 (file)
@@ -2,8 +2,9 @@
 // We use temporary IndexDB (all data are removed on destroy) to avoid RAM issues
 // Thanks @santiagogil and @Feross
 
-import { EventEmitter } from 'events'
 import Dexie from 'dexie'
+import { EventEmitter } from 'events'
+import { logger } from '@root-helpers/logger'
 
 class ChunkDatabase extends Dexie {
   chunks: Dexie.Table<{ id: number, buf: Buffer }, number>
@@ -104,7 +105,7 @@ export class PeertubeChunkStore extends EventEmitter {
           return this.db.chunks.bulkPut(processing.map(p => ({ id: p.id, buf: p.buf })))
         })
       } catch (err) {
-        console.log('Cannot bulk insert chunks. Store them in memory.', { err })
+        logger.info('Cannot bulk insert chunks. Store them in memory.', err)
 
         processing.forEach(p => {
           this.memoryChunks[p.id] = p.buf
@@ -143,7 +144,7 @@ export class PeertubeChunkStore extends EventEmitter {
       return cb(null, buf.slice(offset, len + offset))
     })
     .catch(err => {
-      console.error(err)
+      logger.error(err)
       return cb(err)
     })
   }
@@ -176,7 +177,7 @@ export class PeertubeChunkStore extends EventEmitter {
 
       return cb()
     } catch (err) {
-      console.error('Cannot destroy peertube chunk store.', err)
+      logger.error('Cannot destroy peertube chunk store.', err)
       return cb(err)
     }
   }
@@ -204,7 +205,7 @@ export class PeertubeChunkStore extends EventEmitter {
         databasesToDeleteInfo = await this.expirationDB.databases.where('expiration').below(now).toArray()
       })
     } catch (err) {
-      console.error('Cannot update expiration of fetch expired databases.', err)
+      logger.error('Cannot update expiration of fetch expired databases.', err)
     }
 
     for (const databaseToDeleteInfo of databasesToDeleteInfo) {
@@ -214,7 +215,7 @@ export class PeertubeChunkStore extends EventEmitter {
 
   private async dropDatabase (databaseName: string) {
     const dbToDelete = new ChunkDatabase(databaseName)
-    console.log('Destroying IndexDB database %s.', databaseName)
+    logger.info(`Destroying IndexDB database ${databaseName}`)
 
     try {
       await dbToDelete.delete()
@@ -223,7 +224,7 @@ export class PeertubeChunkStore extends EventEmitter {
         return this.expirationDB.databases.where({ name: databaseName }).delete()
       })
     } catch (err) {
-      console.error('Cannot delete %s.', databaseName, err)
+      logger.error(`Cannot delete ${databaseName}.`, err)
     }
   }
 
index 9b80fea2c196e40a109a1e54e949def57ccfba0c..a85d7a83812e0b0168d9b56f9a033c4a2082e931 100644 (file)
@@ -1,6 +1,7 @@
 // Thanks: https://github.com/feross/render-media
 
 const MediaElementWrapper = require('mediasource')
+import { logger } from '@root-helpers/logger'
 import { extname } from 'path'
 const Videostream = require('videostream')
 
@@ -77,8 +78,8 @@ function renderMedia (file: any, elem: HTMLVideoElement, opts: RenderMediaOption
   }
 
   function fallbackToMediaSource (useVP9 = false) {
-    if (useVP9 === true) console.log('Falling back to media source with VP9 enabled.')
-    else console.log('Falling back to media source..')
+    if (useVP9 === true) logger.info('Falling back to media source with VP9 enabled.')
+    else logger.info('Falling back to media source..')
 
     useMediaSource(useVP9)
   }
index 83b483d876eef4942fd6e5005476c91f97997aa1..ab9ab56acddeeba175e36ebeeb6dbb2f4c3b47ff 100644 (file)
@@ -1,5 +1,6 @@
 import videojs from 'video.js'
 import * as WebTorrent from 'webtorrent'
+import { logger } from '@root-helpers/logger'
 import { isIOS } from '@root-helpers/web-browser'
 import { timeToInt } from '@shared/core-utils'
 import { VideoFile } from '@shared/models'
@@ -210,7 +211,7 @@ class WebTorrentPlugin extends Plugin {
       if (destroyRenderer === true && this.renderer && this.renderer.destroy) this.renderer.destroy()
 
       this.webtorrent.remove(videoFile.magnetUri)
-      console.log('Removed ' + videoFile.magnetUri)
+      logger.info(`Removed ${videoFile.magnetUri}`)
     }
   }
 
@@ -256,7 +257,7 @@ class WebTorrentPlugin extends Plugin {
   ) {
     if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done)
 
-    console.log('Adding ' + magnetOrTorrentUrl + '.')
+    logger.info(`Adding ${magnetOrTorrentUrl}.`)
 
     const oldTorrent = this.torrent
     const torrentOptions = {
@@ -269,7 +270,7 @@ class WebTorrentPlugin extends Plugin {
     }
 
     this.torrent = this.webtorrent.add(magnetOrTorrentUrl, torrentOptions, torrent => {
-      console.log('Added ' + magnetOrTorrentUrl + '.')
+      logger.info(`Added ${magnetOrTorrentUrl}.`)
 
       if (oldTorrent) {
         // Pause the old torrent
@@ -309,7 +310,7 @@ class WebTorrentPlugin extends Plugin {
       }, options.delay || 0)
     })
 
-    this.torrent.on('error', (err: any) => console.error(err))
+    this.torrent.on('error', (err: any) => logger.error(err))
 
     this.torrent.on('warning', (err: any) => {
       // We don't support HTTP tracker but we don't care -> we use the web socket tracker
@@ -317,13 +318,13 @@ class WebTorrentPlugin extends Plugin {
 
       // Users don't care about issues with WebRTC, but developers do so log it in the console
       if (err.message.indexOf('Ice connection failed') !== -1) {
-        console.log(err)
+        logger.info(err)
         return
       }
 
       // Magnet hash is not up to date with the torrent file, add directly the torrent file
       if (err.message.indexOf('incorrect info hash') !== -1) {
-        console.error('Incorrect info hash detected, falling back to torrent file.')
+        logger.error('Incorrect info hash detected, falling back to torrent file.')
         const newOptions = { forcePlay: true, seek: options.seek }
         return this.addTorrent(this.torrent['xs'], previousVideoFile, newOptions, done)
       }
@@ -333,7 +334,7 @@ class WebTorrentPlugin extends Plugin {
         this.handleError(err)
       }
 
-      console.warn(err)
+      logger.warn(err)
     })
   }
 
@@ -348,7 +349,7 @@ class WebTorrentPlugin extends Plugin {
                             return
                           }
 
-                          console.error(err)
+                          logger.error(err)
                           this.player.pause()
                           this.player.posterImage.show()
                           this.player.removeClass('vjs-has-autoplay')
@@ -465,10 +466,10 @@ class WebTorrentPlugin extends Plugin {
 
       // Lower resolution
       if (this.isPlayerWaiting() && file.resolution.id < this.currentVideoFile.resolution.id) {
-        console.log('Downgrading automatically the resolution to: %s', file.resolution.label)
+        logger.info(`Downgrading automatically the resolution to: ${file.resolution.label}`)
         changeResolution = true
       } else if (file.resolution.id > this.currentVideoFile.resolution.id) { // Higher resolution
-        console.log('Upgrading automatically the resolution to: %s', file.resolution.label)
+        logger.info(`Upgrading automatically the resolution to: ${file.resolution.label}`)
         changeResolution = true
         changeResolutionDelay = this.CONSTANTS.AUTO_QUALITY_HIGHER_RESOLUTION_DELAY
       }
@@ -577,7 +578,7 @@ class WebTorrentPlugin extends Plugin {
 
       // The renderer returns an error when we destroy it, so skip them
       if (this.destroyingFakeRenderer === false && err) {
-        console.error('Cannot render new torrent in fake video element.', err)
+        logger.error('Cannot render new torrent in fake video element.', err)
       }
 
       // Load the future file at the correct time (in delay MS - 2 seconds)
@@ -593,7 +594,7 @@ class WebTorrentPlugin extends Plugin {
         try {
           this.fakeRenderer.destroy()
         } catch (err) {
-          console.log('Cannot destroy correctly fake renderer.', err)
+          logger.info('Cannot destroy correctly fake renderer.', err)
         }
       }
       this.fakeRenderer = undefined
index 8a6e67dda088355a027148f23863861d378605f6..bf9c2d471a41e6273b00f4d59a82278619578134 100644 (file)
@@ -1,3 +1,4 @@
+import { logger } from '@root-helpers/logger'
 import { getCompleteLocale, getShortLocale, is18nLocale, isDefaultLocale } from '@shared/core-utils/i18n'
 
 export class TranslationsManager {
@@ -11,7 +12,7 @@ export class TranslationsManager {
     return fetch(path + '/server.json')
       .then(res => res.json())
       .catch(err => {
-        console.error('Cannot get server translations', err)
+        logger.error('Cannot get server translations', err)
         return undefined
       })
   }
@@ -33,7 +34,7 @@ export class TranslationsManager {
           return json
         })
         .catch(err => {
-          console.error('Cannot get player translations', err)
+          logger.error('Cannot get player translations', err)
           return undefined
         })
     }
index 84c82203d2e678fbacd7e111e05c318d6f3051d3..432db0eacb92557bae45f4958efdd7f416854916 100644 (file)
@@ -3,11 +3,14 @@ import { enableDebugTools } from '@angular/platform-browser'
 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
 import { AppModule } from './app/app.module'
 import { environment } from './environments/environment'
+import { logger } from './root-helpers'
 
 if (environment.production) {
   enableProdMode()
 }
 
+logger.registerServerSending(environment.apiUrl)
+
 const bootstrap = () => platformBrowserDynamic()
   .bootstrapModule(AppModule)
   .then(bootstrapModule => {
@@ -22,7 +25,7 @@ const bootstrap = () => platformBrowserDynamic()
     return bootstrapModule
   })
   .catch(err => {
-    console.error(err)
+    logger.error(err)
     return null
   })
 
index fb229ce6dab2f6b1e91efc52fa9594f32d1348fa..c4b09ec3cfcdd9b83e97f406ecdf8d03a0ac784f 100644 (file)
@@ -1,8 +1,10 @@
+import { logger } from './logger'
+
 function imageToDataURL (input: File | Blob) {
   return new Promise<string>(res => {
     const reader = new FileReader()
 
-    reader.onerror = err => console.error('Cannot read input file.', err)
+    reader.onerror = err => logger.error('Cannot read input file.', err)
     reader.onloadend = () => res(reader.result as string)
     reader.readAsDataURL(input)
   })
index a198557614e00bcaa52f5cb78d9906159735bb3e..86301eafa3f35c9918e6564e351957bb190af5b5 100644 (file)
@@ -2,6 +2,7 @@ export * from './users'
 export * from './bytes'
 export * from './images'
 export * from './local-storage-utils'
+export * from './logger'
 export * from './peertube-web-storage'
 export * from './plugins-manager'
 export * from './string'
diff --git a/client/src/root-helpers/logger.ts b/client/src/root-helpers/logger.ts
new file mode 100644 (file)
index 0000000..cd559cf
--- /dev/null
@@ -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
+}
index 49a19781b7be970e87e4451f707d9eb128b248c5..37a52be722b3c70f4574fabbbc78c8076a37aae1 100644 (file)
@@ -21,6 +21,7 @@ import {
 } from '@shared/models'
 import { environment } from '../environments/environment'
 import { ClientScript } from '../types'
+import { logger } from './logger'
 
 interface HookStructValue extends RegisterClientHookOptions {
   plugin: ServerConfigPlugin
@@ -48,7 +49,7 @@ type OnSettingsScripts = (pluginInfo: PluginInfo, options: RegisterClientSetting
 
 type OnClientRoute = (options: RegisterClientRouteOptions) => void
 
-const logger = debug('peertube:plugins')
+const debugLogger = debug('peertube:plugins')
 
 class PluginsManager {
   private hooks: Hooks = {}
@@ -109,10 +110,10 @@ class PluginsManager {
     const hookType = getHookType(hookName)
 
     for (const hook of this.hooks[hookName]) {
-      console.log('Running hook %s of plugin %s.', hookName, hook.plugin.name)
+      logger.info(`Running hook ${hookName} of plugin ${hook.plugin.name}`)
 
       result = await internalRunHook(hook.handler, hookType, result, params, err => {
-        console.error('Cannot run hook %s of script %s of plugin %s.', hookName, hook.clientScript.script, hook.plugin.name, err)
+        logger.error(`Cannot run hook ${hookName} of script ${hook.clientScript.script} of plugin ${hook.plugin.name}`, err)
       })
     }
 
@@ -170,7 +171,7 @@ class PluginsManager {
 
     this.loadingScopes[scope] = true
 
-    logger('Loading scope %s', scope)
+    debugLogger('Loading scope %s', scope)
 
     try {
       if (!isReload) this.loadedScopes.push(scope)
@@ -180,7 +181,7 @@ class PluginsManager {
         this.loadingScopes[scope] = false
         this.pluginsLoaded[scope].next(true)
 
-        logger('Nothing to load for scope %s', scope)
+        debugLogger('Nothing to load for scope %s', scope)
         return
       }
 
@@ -200,9 +201,9 @@ class PluginsManager {
       this.pluginsLoaded[scope].next(true)
       this.loadingScopes[scope] = false
 
-      logger('Scope %s loaded', scope)
+      debugLogger('Scope %s loaded', scope)
     } catch (err) {
-      console.error('Cannot load plugins by scope %s.', scope, err)
+      logger.error(`Cannot load plugins by scope ${scope}`, err)
     }
   }
 
@@ -211,7 +212,7 @@ class PluginsManager {
 
     const registerHook = (options: RegisterClientHookOptions) => {
       if (clientHookObject[options.target] !== true) {
-        console.error('Unknown hook %s of plugin %s. Skipping.', options.target, plugin.name)
+        logger.error(`Unknown hook ${options.target} of plugin ${plugin.name}. Skipping.`)
         return
       }
 
@@ -252,7 +253,7 @@ class PluginsManager {
 
     const peertubeHelpers = this.peertubeHelpersFactory(pluginInfo)
 
-    console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
+    logger.info(`Loading script ${clientScript.script} of plugin ${plugin.name}`)
 
     const absURL = (environment.apiUrl || window.location.origin) + clientScript.script
     return dynamicImport(absURL)
@@ -266,7 +267,7 @@ class PluginsManager {
         })
       })
       .then(() => this.sortHooksByPriority())
-      .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
+      .catch(err => logger.error(`Cannot import or register plugin ${pluginInfo.plugin.name}`, err))
   }
 
   private sortHooksByPriority () {
@@ -294,7 +295,7 @@ async function dynamicImport (url: string) {
     // eslint-disable-next-line no-new-func
     return new Function(`return import('${url}')`)()
   } catch {
-    console.log('Fallback to import polyfill')
+    logger.info('Fallback to import polyfill')
 
     return new Promise((resolve, reject) => {
       const vector = '$importModule$' + Math.random().toString(32).slice(2)
index 84d664654ad0a8b266757e791c8f7c5a13a03ad7..2124b471176b6ebd62c6791963b6176106ce9122 100644 (file)
@@ -1,6 +1,6 @@
 import './embed.scss'
-
 import * as Channel from 'jschannel'
+import { logger } from '../../root-helpers'
 import { PeerTubeResolution, PeerTubeTextTrack } from '../player/definitions'
 import { PeerTubeEmbed } from './embed'
 
@@ -59,7 +59,7 @@ export class PeerTubeEmbedApi {
   }
 
   private setResolution (resolutionId: number) {
-    console.log('set resolution %d', resolutionId)
+    logger.info(`Set resolution ${resolutionId}`)
 
     if (this.isWebtorrent()) {
       if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionPossible() === false) return
index c15d4db177be8aba397b61fa843304eac21ac212..5384ada1cd23e5f11607ce68bea316dff641ef00 100644 (file)
@@ -6,7 +6,7 @@ import { peertubeTranslate } from '../../../../shared/core-utils/i18n'
 import { HTMLServerConfig, LiveVideo, ResultList, VideoDetails, VideoPlaylist, VideoPlaylistElement } from '../../../../shared/models'
 import { PeertubePlayerManager } from '../../assets/player'
 import { TranslationsManager } from '../../assets/player/translations-manager'
-import { getParamString } from '../../root-helpers'
+import { getParamString, logger } from '../../root-helpers'
 import { PeerTubeEmbedApi } from './embed-api'
 import { AuthHTTP, LiveManager, PeerTubePlugin, PlayerManagerOptions, PlaylistFetcher, PlaylistTracker, VideoFetcher } from './shared'
 import { PlayerHTML } from './shared/player-html'
@@ -31,6 +31,8 @@ export class PeerTubeEmbed {
   private playlistTracker: PlaylistTracker
 
   constructor (videoWrapperId: string) {
+    logger.registerServerSending(window.location.origin)
+
     this.http = new AuthHTTP()
 
     this.videoFetcher = new VideoFetcher(this.http)
@@ -43,7 +45,7 @@ export class PeerTubeEmbed {
     try {
       this.config = JSON.parse(window['PeerTubeServerConfig'])
     } catch (err) {
-      console.error('Cannot parse HTML config.', err)
+      logger.error('Cannot parse HTML config.', err)
     }
   }
 
@@ -125,7 +127,7 @@ export class PeerTubeEmbed {
   async playNextPlaylistVideo () {
     const next = this.playlistTracker.getNextPlaylistElement()
     if (!next) {
-      console.log('Next element not found in playlist.')
+      logger.info('Next element not found in playlist.')
       return
     }
 
@@ -137,7 +139,7 @@ export class PeerTubeEmbed {
   async playPreviousPlaylistVideo () {
     const previous = this.playlistTracker.getPreviousPlaylistElement()
     if (!previous) {
-      console.log('Previous element not found in playlist.')
+      logger.info('Previous element not found in playlist.')
       return
     }
 
@@ -343,5 +345,5 @@ PeerTubeEmbed.main()
   .catch(err => {
     (window as any).displayIncompatibleBrowser()
 
-    console.error('Cannot init embed.', err)
+    logger.error('Cannot init embed.', err)
   })
index eb6324ac70b970752ae4e2a098354b23a989d7a9..61231d2cbc544deddccd3564cf564ab12a6871df 100644 (file)
@@ -1,5 +1,6 @@
 import { peertubeTranslate } from '../../../../../shared/core-utils/i18n'
 import { VideoDetails } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
 import { Translations } from './translations'
 
 export class PlayerHTML {
@@ -29,7 +30,7 @@ export class PlayerHTML {
   }
 
   displayError (text: string, translations: Translations) {
-    console.error(text)
+    logger.error(text)
 
     // Remove video element
     if (this.playerElement) {
index f3bd46a6989fc8088a75e08af29a6b019ce07809..2eeb5ecac1efbadc9cb87ac6c20bf07cc7b562c5 100644 (file)
@@ -14,6 +14,7 @@ import {
   getParamString,
   getParamToggle,
   isP2PEnabled,
+  logger,
   peertubeLocalStorage,
   UserLocalStorageKeys
 } from '../../../root-helpers'
@@ -137,7 +138,7 @@ export class PlayerManagerOptions {
         else this.mode = 'webtorrent'
       }
     } catch (err) {
-      console.error('Cannot get params from URL.', err)
+      logger.error('Cannot get params from URL.', err)
     }
   }
 
index a7e72c177c901699fd744831e0cbf709af36b3e6..713d82e3afc61a1c0f1482258cf70a71a23a9180 100644 (file)
@@ -1,4 +1,5 @@
 import { HttpStatusCode, ResultList, VideoPlaylistElement } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
 import { AuthHTTP } from './auth-http'
 
 export class PlaylistFetcher {
@@ -18,7 +19,7 @@ export class PlaylistFetcher {
       playlistResponse = await playlistPromise
       isResponseOk = playlistResponse.status === HttpStatusCode.OK_200
     } catch (err) {
-      console.error(err)
+      logger.error(err)
       isResponseOk = false
     }
 
@@ -49,7 +50,7 @@ export class PlaylistFetcher {
     }
 
     if (i === 10) {
-      console.error('Cannot fetch all playlists elements, there are too many!')
+      logger.error('Cannot fetch all playlists elements, there are too many!')
     }
 
     return elements
index 75d10b4e277851afd696b62e0b449ee1c643102d..9ea4be83f466c00bbfd6fdf23b8413602117438e 100644 (file)
@@ -1,4 +1,5 @@
 import { VideoPlaylist, VideoPlaylistElement } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
 
 export class PlaylistTracker {
   private currentPlaylistElement: VideoPlaylistElement
@@ -68,7 +69,7 @@ export class PlaylistTracker {
   setPosition (position: number) {
     this.currentPlaylistElement = this.playlistElements.find(e => e.position === position)
     if (!this.currentPlaylistElement || !this.currentPlaylistElement.video) {
-      console.error('Current playlist element is not valid.', this.currentPlaylistElement)
+      logger.error('Current playlist element is not valid.', this.currentPlaylistElement)
       this.currentPlaylistElement = this.getNextPlaylistElement()
     }
 
index e78d38536f7128c20aa92f64572393750424f03d..b42d622f9e898ca30cb52792f93f35882c9e3a3d 100644 (file)
@@ -1,4 +1,5 @@
 import { HttpStatusCode, LiveVideo, VideoDetails } from '../../../../../shared/models'
+import { logger } from '../../../root-helpers'
 import { AuthHTTP } from './auth-http'
 
 export class VideoFetcher {
@@ -17,7 +18,7 @@ export class VideoFetcher {
       videoResponse = await videoPromise
       isResponseOk = videoResponse.status === HttpStatusCode.OK_200
     } catch (err) {
-      console.error(err)
+      logger.error(err)
 
       isResponseOk = false
     }
index 18c338a2da583d303c259fe2cb49a8b89e2a3d6f..ab52629025a3197b4d0845eb6a628b06926e71a5 100644 (file)
@@ -1,6 +1,7 @@
 import './test-embed.scss'
 import { PeerTubeResolution, PlayerEventType } from '../player/definitions'
 import { PeerTubePlayer } from '../player/player'
+import { logger } from '../../root-helpers'
 
 window.addEventListener('load', async () => {
   const urlParts = window.location.href.split('/')
@@ -20,14 +21,14 @@ window.addEventListener('load', async () => {
   const mainElement = document.querySelector('#host')
   mainElement.appendChild(iframe)
 
-  console.log('Document finished loading.')
+  logger.info('Document finished loading.')
   const player = new PeerTubePlayer(document.querySelector('iframe'))
 
   window['player'] = player
 
-  console.log('Awaiting player ready...')
+  logger.info('Awaiting player ready...')
   await player.ready
-  console.log('Player is ready.')
+  logger.info('Player is ready.')
 
   const monitoredEvents = [
     'pause',
@@ -37,8 +38,8 @@ window.addEventListener('load', async () => {
   ]
 
   monitoredEvents.forEach(e => {
-    player.addEventListener(e as PlayerEventType, (param) => console.log(`PLAYER: event '${e}' received`, param))
-    console.log(`PLAYER: now listening for event '${e}'`)
+    player.addEventListener(e as PlayerEventType, (param) => logger.info(`PLAYER: event '${e}' received`, { param }))
+    logger.info(`PLAYER: now listening for event '${e}'`)
 
     player.getCurrentPosition()
       .then(position => {
diff --git a/client/src/standalone/videos/tsconfig.json b/client/src/standalone/videos/tsconfig.json
new file mode 100644 (file)
index 0000000..e0cab7c
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "extends": "../../../tsconfig.json",
+  "include": [
+    "src/standalone/videos/embed.ts",
+    "src/standalone/videos/test-embed.ts"
+  ]
+}
index f5c75dd47597c1cfa357b7ec9ef9a8ecc4d12a17..547e8aa63440f488452879eb1c7d15bbece3defb 100644 (file)
@@ -69,7 +69,7 @@ module.exports = function () {
             {
               loader: 'ts-loader',
               options: {
-                configFile: helpers.root('tsconfig.json')
+                configFile: helpers.root('src/standalone/videos/tsconfig.json')
               }
             }
           ]
index 2c1b9c64f208e10f13fe35a0cdfd520d6fce214c..7e07165b9328db6993fb0f895b852ab2d7449026 100644 (file)
@@ -27,6 +27,10 @@ rates_limit:
     # 3 attempts in 5 min
     window: 5 minutes
     max: 3
+  receive_client_log:
+    # 10 attempts in 10 min
+    window: 10 minutes
+    max: 10
 
 # Proxies to trust to get real client IP
 # If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
@@ -168,15 +172,22 @@ object_storage:
 
 log:
   level: 'info' # 'debug' | 'info' | 'warn' | 'error'
+
   rotation:
     enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
     max_file_size: 12MB
     max_files: 20
+
   anonymize_ip: false
+
   log_ping_requests: true
   log_tracker_unknown_infohash: true
+
   prettify_sql: false
 
+  # Accept warn/error logs coming from the client
+  accept_client_log: true
+
 # Highly experimental support of Open Telemetry
 open_telemetry:
   metrics:
index 3e4035eaa95b20899ca4ae8f504b5d8aef7b0e94..042f5a641c5e1c31290d04d18a654f7583483147 100644 (file)
@@ -25,6 +25,10 @@ rates_limit:
     # 3 attempts in 5 min
     window: 5 minutes
     max: 3
+  receive_client_log:
+    # 10 attempts in 10 min
+    window: 10 minutes
+    max: 10
 
 # Proxies to trust to get real client IP
 # If you run PeerTube just behind a local proxy (nginx), keep 'loopback'
@@ -166,15 +170,22 @@ object_storage:
 
 log:
   level: 'info' # 'debug' | 'info' | 'warn' | 'error'
+
   rotation:
     enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
     max_file_size: 12MB
     max_files: 20
+
   anonymize_ip: false
+
   log_ping_requests: true
   log_tracker_unknown_infohash: true
+
   prettify_sql: false
 
+  # Accept warn/error logs coming from the client
+  accept_client_log: true
+
 # Highly experimental support of Open Telemetry
 open_telemetry:
   metrics:
index 8aa4b71907fa5e450a2cdef3f5e9619f4e64989d..ed0aa6e8e78f1435649afab62ebf32768bcc4943 100644 (file)
@@ -3,15 +3,29 @@ import { readdir, readFile } from 'fs-extra'
 import { join } from 'path'
 import { isArray } from '@server/helpers/custom-validators/misc'
 import { logger, mtimeSortFilesDesc } from '@server/helpers/logger'
-import { LogLevel } from '../../../../shared/models/server/log-level.type'
+import { pick } from '@shared/core-utils'
+import { ClientLogCreate, HttpStatusCode } from '@shared/models'
+import { ServerLogLevel } from '../../../../shared/models/server/server-log-level.type'
 import { UserRight } from '../../../../shared/models/users'
 import { CONFIG } from '../../../initializers/config'
 import { AUDIT_LOG_FILENAME, LOG_FILENAME, MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants'
-import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
-import { getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
+import { asyncMiddleware, authenticate, buildRateLimiter, ensureUserHasRight, optionalAuthenticate } from '../../../middlewares'
+import { createClientLogValidator, getAuditLogsValidator, getLogsValidator } from '../../../middlewares/validators/logs'
+
+const createClientLogRateLimiter = buildRateLimiter({
+  windowMs: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.WINDOW_MS,
+  max: CONFIG.RATES_LIMIT.RECEIVE_CLIENT_LOG.MAX
+})
 
 const logsRouter = express.Router()
 
+logsRouter.post('/logs/client',
+  createClientLogRateLimiter,
+  optionalAuthenticate,
+  createClientLogValidator,
+  createClientLog
+)
+
 logsRouter.get('/logs',
   authenticate,
   ensureUserHasRight(UserRight.MANAGE_LOGS),
@@ -34,6 +48,21 @@ export {
 
 // ---------------------------------------------------------------------------
 
+function createClientLog (req: express.Request, res: express.Response) {
+  const logInfo = req.body as ClientLogCreate
+
+  const meta = {
+    tags: [ 'client' ],
+    username: res.locals.oauth?.token?.User?.username,
+
+    ...pick(logInfo, [ 'userAgent', 'stackTrace', 'meta', 'url' ])
+  }
+
+  logger.log(logInfo.level, `Client log: ${logInfo.message}`, meta)
+
+  return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
+}
+
 const auditLogNameFilter = generateLogNameFilter(AUDIT_LOG_FILENAME)
 async function getAuditLogs (req: express.Request, res: express.Response) {
   const output = await generateOutput({
@@ -63,7 +92,7 @@ async function generateOutput (options: {
   startDateQuery: string
   endDateQuery?: string
 
-  level: LogLevel
+  level: ServerLogLevel
   nameFilter: RegExp
   tagsOneOf?: string[]
 }) {
@@ -104,7 +133,7 @@ async function getOutputFromFile (options: {
   path: string
   startDate: Date
   endDate: Date
-  level: LogLevel
+  level: ServerLogLevel
   currentSize: number
   tagsOneOf: Set<string>
 }) {
@@ -116,7 +145,7 @@ async function getOutputFromFile (options: {
 
   let logTime: number
 
-  const logsLevel: { [ id in LogLevel ]: number } = {
+  const logsLevel: { [ id in ServerLogLevel ]: number } = {
     audit: -1,
     debug: 0,
     info: 1,
index 0f266ed3b8e4c8dfaf3790ec22280c1eb6fb864f..41d45cbb23f8510fa1fb70d5e11d86b5852d3b6c 100644 (file)
@@ -1,14 +1,42 @@
+import validator from 'validator'
+import { CONSTRAINTS_FIELDS } from '@server/initializers/constants'
+import { ClientLogLevel, ServerLogLevel } from '@shared/models'
 import { exists } from './misc'
-import { LogLevel } from '../../../shared/models/server/log-level.type'
 
-const logLevels: LogLevel[] = [ 'debug', 'info', 'warn', 'error' ]
+const serverLogLevels: Set<ServerLogLevel> = new Set([ 'debug', 'info', 'warn', 'error' ])
+const clientLogLevels: Set<ClientLogLevel> = new Set([ 'warn', 'error' ])
 
 function isValidLogLevel (value: any) {
-  return exists(value) && logLevels.includes(value)
+  return exists(value) && serverLogLevels.has(value)
+}
+
+function isValidClientLogMessage (value: any) {
+  return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_MESSAGE)
+}
+
+function isValidClientLogLevel (value: any) {
+  return exists(value) && clientLogLevels.has(value)
+}
+
+function isValidClientLogStackTrace (value: any) {
+  return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_STACK_TRACE)
+}
+
+function isValidClientLogMeta (value: any) {
+  return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_META)
+}
+
+function isValidClientLogUserAgent (value: any) {
+  return typeof value === 'string' && validator.isLength(value, CONSTRAINTS_FIELDS.LOGS.CLIENT_USER_AGENT)
 }
 
 // ---------------------------------------------------------------------------
 
 export {
-  isValidLogLevel
+  isValidLogLevel,
+  isValidClientLogMessage,
+  isValidClientLogStackTrace,
+  isValidClientLogMeta,
+  isValidClientLogLevel,
+  isValidClientLogUserAgent
 }
index 0943ffe2d7152b504ad111f30ce9064416a30d61..ba0f756ef5cf4f33d696e7cc695a0aecfb02ed91 100644 (file)
@@ -149,6 +149,10 @@ const CONFIG = {
       WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.login.window')),
       MAX: config.get<number>('rates_limit.login.max')
     },
+    RECEIVE_CLIENT_LOG: {
+      WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.receive_client_log.window')),
+      MAX: config.get<number>('rates_limit.receive_client_log.max')
+    },
     ASK_SEND_EMAIL: {
       WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
       MAX: config.get<number>('rates_limit.ask_send_email.max')
@@ -165,7 +169,8 @@ const CONFIG = {
     ANONYMIZE_IP: config.get<boolean>('log.anonymize_ip'),
     LOG_PING_REQUESTS: config.get<boolean>('log.log_ping_requests'),
     LOG_TRACKER_UNKNOWN_INFOHASH: config.get<boolean>('log.log_tracker_unknown_infohash'),
-    PRETTIFY_SQL: config.get<boolean>('log.prettify_sql')
+    PRETTIFY_SQL: config.get<boolean>('log.prettify_sql'),
+    ACCEPT_CLIENT_LOG: config.get<boolean>('log.accept_client_log')
   },
   OPEN_TELEMETRY: {
     METRICS: {
index 009f878fcc4c1f4d6a312e9d9eba5a3a50479641..8cb4d5f4a4c5ee7a842babce0854f3f5beac5852 100644 (file)
@@ -365,6 +365,12 @@ const CONSTRAINTS_FIELDS = {
   VIDEO_STUDIO: {
     TASKS: { min: 1, max: 10 }, // Number of tasks
     CUT_TIME: { min: 0 } // Value
+  },
+  LOGS: {
+    CLIENT_MESSAGE: { min: 1, max: 1000 }, // Length
+    CLIENT_STACK_TRACE: { min: 1, max: 5000 }, // Length
+    CLIENT_META: { min: 1, max: 5000 }, // Length
+    CLIENT_USER_AGENT: { min: 1, max: 200 } // Length
   }
 }
 
index 901d8ca64f684032b7be6ba3de65b12419d01c9d..324ba6915e59e35976340134d7a1b0148811cce2 100644 (file)
@@ -1,11 +1,56 @@
 import express from 'express'
-import { query } from 'express-validator'
+import { body, query } from 'express-validator'
+import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
 import { isStringArray } from '@server/helpers/custom-validators/search'
-import { isValidLogLevel } from '../../helpers/custom-validators/logs'
+import { CONFIG } from '@server/initializers/config'
+import { HttpStatusCode } from '@shared/models'
+import {
+  isValidClientLogLevel,
+  isValidClientLogMessage,
+  isValidClientLogMeta,
+  isValidClientLogStackTrace,
+  isValidClientLogUserAgent,
+  isValidLogLevel
+} from '../../helpers/custom-validators/logs'
 import { isDateValid, toArray } from '../../helpers/custom-validators/misc'
 import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './shared'
 
+const createClientLogValidator = [
+  body('message')
+    .custom(isValidClientLogMessage).withMessage('Should have a valid log message'),
+
+  body('url')
+    .custom(isUrlValid).withMessage('Should have a valid log url'),
+
+  body('level')
+    .custom(isValidClientLogLevel).withMessage('Should have a valid log message'),
+
+  body('stackTrace')
+    .optional()
+    .custom(isValidClientLogStackTrace).withMessage('Should have a valid log stack trace'),
+
+  body('meta')
+    .optional()
+    .custom(isValidClientLogMeta).withMessage('Should have a valid log meta'),
+
+  body('userAgent')
+    .optional()
+    .custom(isValidClientLogUserAgent).withMessage('Should have a valid log user agent'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking createClientLogValidator parameters.', { parameters: req.query })
+
+    if (CONFIG.LOG.ACCEPT_CLIENT_LOG !== true) {
+      return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
+    }
+
+    if (areValidationErrors(req, res)) return
+
+    return next()
+  }
+]
+
 const getLogsValidator = [
   query('startDate')
     .custom(isDateValid).withMessage('Should have a start date that conforms to ISO 8601'),
@@ -49,5 +94,6 @@ const getAuditLogsValidator = [
 
 export {
   getLogsValidator,
-  getAuditLogsValidator
+  getAuditLogsValidator,
+  createClientLogValidator
 }
index 970671c15ec0c3101d247db09f525ba0d4a11b4c..fa67408b7e6946711a22bc664f7a3dccafe1728d 100644 (file)
@@ -1,8 +1,9 @@
 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
 
 import 'mocha'
-import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
+import { expect } from 'chai'
 import { HttpStatusCode } from '@shared/models'
+import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
 
 describe('Test logs API validators', function () {
   const path = '/api/v1/server/logs'
@@ -95,6 +96,62 @@ describe('Test logs API validators', function () {
     })
   })
 
+  describe('When creating client logs', function () {
+    const base = {
+      level: 'warn' as 'warn',
+      message: 'my super message',
+      url: 'https://example.com/toto'
+    }
+    const expectedStatus = HttpStatusCode.BAD_REQUEST_400
+
+    it('Should fail with an invalid level', async function () {
+      await server.logs.createLogClient({ payload: { ...base, level: '' as any }, expectedStatus })
+      await server.logs.createLogClient({ payload: { ...base, level: undefined }, expectedStatus })
+      await server.logs.createLogClient({ payload: { ...base, level: 'toto' as any }, expectedStatus })
+    })
+
+    it('Should fail with an invalid message', async function () {
+      await server.logs.createLogClient({ payload: { ...base, message: undefined }, expectedStatus })
+      await server.logs.createLogClient({ payload: { ...base, message: '' }, expectedStatus })
+      await server.logs.createLogClient({ payload: { ...base, message: 'm'.repeat(2500) }, expectedStatus })
+    })
+
+    it('Should fail with an invalid url', async function () {
+      await server.logs.createLogClient({ payload: { ...base, url: undefined }, expectedStatus })
+      await server.logs.createLogClient({ payload: { ...base, url: 'toto' }, expectedStatus })
+    })
+
+    it('Should fail with an invalid stackTrace', async function () {
+      await server.logs.createLogClient({ payload: { ...base, stackTrace: 's'.repeat(10000) }, expectedStatus })
+    })
+
+    it('Should fail with an invalid userAgent', async function () {
+      await server.logs.createLogClient({ payload: { ...base, userAgent: 's'.repeat(500) }, expectedStatus })
+    })
+
+    it('Should fail with an invalid meta', async function () {
+      await server.logs.createLogClient({ payload: { ...base, meta: 's'.repeat(10000) }, expectedStatus })
+    })
+
+    it('Should succeed with the correct params', async function () {
+      await server.logs.createLogClient({ payload: { ...base, stackTrace: 'stackTrace', meta: '{toto}', userAgent: 'userAgent' } })
+    })
+
+    it('Should rate limit log creation', async function () {
+      let fail = false
+
+      for (let i = 0; i < 10; i++) {
+        try {
+          await server.logs.createLogClient({ token: null, payload: base })
+        } catch {
+          fail = true
+        }
+      }
+
+      expect(fail).to.be.true
+    })
+  })
+
   after(async function () {
     await cleanupTests([ server ])
   })
index 697f10337980a27822c0df4eb1fd0b4e841b5e24..ed7555fd76691023f3825c9df40acc4a7ed42674 100644 (file)
@@ -2,6 +2,7 @@
 
 import 'mocha'
 import * as chai from 'chai'
+import { HttpStatusCode } from '@shared/models'
 import {
   cleanupTests,
   createSingleServer,
@@ -198,6 +199,70 @@ describe('Test logs', function () {
     })
   })
 
+  describe('When creating log from the client', function () {
+
+    it('Should create a warn client log', async function () {
+      const now = new Date()
+
+      await server.logs.createLogClient({
+        payload: {
+          level: 'warn',
+          url: 'http://example.com',
+          message: 'my super client message'
+        },
+        token: null
+      })
+
+      const body = await logsCommand.getLogs({ startDate: now })
+      const logsString = JSON.stringify(body)
+
+      expect(logsString.includes('my super client message')).to.be.true
+    })
+
+    it('Should create an error authenticated client log', async function () {
+      const now = new Date()
+
+      await server.logs.createLogClient({
+        payload: {
+          url: 'https://example.com/page1',
+          level: 'error',
+          message: 'my super client message 2',
+          userAgent: 'super user agent',
+          meta: '{hello}',
+          stackTrace: 'super stack trace'
+        }
+      })
+
+      const body = await logsCommand.getLogs({ startDate: now })
+      const logsString = JSON.stringify(body)
+
+      expect(logsString.includes('my super client message 2')).to.be.true
+      expect(logsString.includes('super user agent')).to.be.true
+      expect(logsString.includes('super stack trace')).to.be.true
+      expect(logsString.includes('{hello}')).to.be.true
+      expect(logsString.includes('https://example.com/page1')).to.be.true
+    })
+
+    it('Should refuse to create client logs', async function () {
+      await server.kill()
+
+      await server.run({
+        log: {
+          accept_client_log: false
+        }
+      })
+
+      await server.logs.createLogClient({
+        payload: {
+          level: 'warn',
+          url: 'http://example.com',
+          message: 'my super client message'
+        },
+        expectedStatus: HttpStatusCode.FORBIDDEN_403
+      })
+    })
+  })
+
   after(async function () {
     await cleanupTests([ server ])
   })
diff --git a/shared/models/server/client-log-create.model.ts b/shared/models/server/client-log-create.model.ts
new file mode 100644 (file)
index 0000000..c9dc655
--- /dev/null
@@ -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
+}
diff --git a/shared/models/server/client-log-level.type.ts b/shared/models/server/client-log-level.type.ts
new file mode 100644 (file)
index 0000000..18dea27
--- /dev/null
@@ -0,0 +1 @@
+export type ClientLogLevel = 'warn' | 'error'
index 0f7646c7afcfbb0899fc34a7b7149754e6cfd01a..a9136f3d4b8a4174a0ffd8f4105d3926e109a02c 100644 (file)
@@ -1,14 +1,16 @@
 export * from './about.model'
 export * from './broadcast-message-level.type'
+export * from './client-log-create.model'
+export * from './client-log-level.type'
 export * from './contact-form.model'
 export * from './custom-config.model'
 export * from './debug.model'
 export * from './emailer.model'
 export * from './job.model'
-export * from './log-level.type'
 export * from './peertube-problem-document.model'
 export * from './server-config.model'
 export * from './server-debug.model'
 export * from './server-error-code.enum'
 export * from './server-follow-create.model'
+export * from './server-log-level.type'
 export * from './server-stats.model'
diff --git a/shared/models/server/log-level.type.ts b/shared/models/server/log-level.type.ts
deleted file mode 100644 (file)
index 4afb92d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'audit'
diff --git a/shared/models/server/server-log-level.type.ts b/shared/models/server/server-log-level.type.ts
new file mode 100644 (file)
index 0000000..f0f31a4
--- /dev/null
@@ -0,0 +1 @@
+export type ServerLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'audit'
index 8f63383ea21a58fb50b62fa8bdc6a0ce76b7debe..1c5de7f59332845f77357d27f63b201c50ddc5ae 100644 (file)
@@ -1,12 +1,25 @@
-import { HttpStatusCode, LogLevel } from '@shared/models'
+import { ClientLogCreate, HttpStatusCode, ServerLogLevel } from '@shared/models'
 import { AbstractCommand, OverrideCommandOptions } from '../shared'
 
 export class LogsCommand extends AbstractCommand {
 
+  createLogClient (options: OverrideCommandOptions & { payload: ClientLogCreate }) {
+    const path = '/api/v1/server/logs/client'
+
+    return this.postBodyRequest({
+      ...options,
+
+      path,
+      fields: options.payload,
+      implicitToken: true,
+      defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+    })
+  }
+
   getLogs (options: OverrideCommandOptions & {
     startDate: Date
     endDate?: Date
-    level?: LogLevel
+    level?: ServerLogLevel
     tagsOneOf?: string[]
   }) {
     const { startDate, endDate, tagsOneOf, level } = options
index 93c545e36ec709b5a9be8db311952125e225dce3..9745f68e98d104471e022a394fcb38d8a5b78138 100644 (file)
@@ -330,26 +330,21 @@ x-tagGroups:
   - name: Search
     tags:
       - Search
-  - name: Custom pages
-    tags:
-      - Homepage
   - name: Moderation
     tags:
       - Abuses
       - Video Blocks
       - Account Blocks
       - Server Blocks
-  - name: Instance Configuration
+  - name: Instance
     tags:
       - Config
+      - Homepage
       - Instance Follows
       - Instance Redundancy
       - Plugins
-  - name: Stats
-    tags:
       - Stats
-  - name: Jobs
-    tags:
+      - Logs
       - Job
 paths:
   '/accounts/{name}':
@@ -4316,6 +4311,58 @@ paths:
               schema:
                 $ref: '#/components/schemas/ServerStats'
 
+  /server/logs/client:
+    post:
+      tags:
+        - Logs
+      summary: Send client log
+      operationId: sendClientLog
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/SendClientLog'
+      responses:
+        '204':
+          description: successful operation
+
+  /server/logs:
+    get:
+      tags:
+        - Logs
+      summary: Get instance logs
+      operationId: getInstanceLogs
+      security:
+        - OAuth2:
+          - admin
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  type: string
+
+  /server/audit-logs:
+    get:
+      tags:
+        - Logs
+      summary: Get instance audit logs
+      operationId: getInstanceAuditLogs
+      security:
+        - OAuth2:
+          - admin
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  type: string
 
   '/feeds/video-comments.{format}':
     get:
@@ -6526,6 +6573,31 @@ components:
             enabled:
               type: boolean
 
+    SendClientLog:
+      properties:
+        message:
+          type: string
+        url:
+          type: string
+          description: URL of the current user page
+        level:
+          enum:
+            - error
+            - warn
+        stackTrace:
+          type: string
+          description: Stack trace of the error if there is one
+        userAgent:
+          type: string
+          description: User agent of the web browser that sends the message
+        meta:
+          type: string
+          description: Additional information regarding this log
+      required:
+        - message
+        - url
+        - level
+
     ServerStats:
       properties:
         totalUsers: