log_tracker_unknown_infohash: true
prettify_sql: false
+# Highly experimental support of Open Telemetry
+open_telemetry:
+ metrics:
+ enabled: false
+
+ # Create a prometheus exporter server on this port so prometheus server can scrape PeerTube metrics
+ prometheus_exporter:
+ port: 9091
+
+ tracing:
+ enabled: false
+
+ # Send traces to a Jaeger compatible endpoint
+ jaeger_exporter:
+ endpoint: ''
+
trending:
videos:
interval_days: 7 # Compute trending videos for the last x days
log_tracker_unknown_infohash: true
prettify_sql: false
+# Highly experimental support of Open Telemetry
+open_telemetry:
+ metrics:
+ enabled: false
+
+ # Create a prometheus exporter server on this port so prometheus server can scrape PeerTube metrics
+ prometheus_exporter:
+ port: 9091
+
+ tracing:
+ enabled: false
+
+ # Send traces to a Jaeger compatible endpoint
+ jaeger_exporter:
+ endpoint: ''
+
trending:
videos:
interval_days: 7 # Compute trending videos for the last x days
"@aws-sdk/node-http-handler": "^3.82.0",
"@babel/parser": "7.17.8",
"@node-oauth/oauth2-server": "^4.2.0",
+ "@opentelemetry/api": "^1.1.0",
+ "@opentelemetry/api-metrics": "^0.29.2",
+ "@opentelemetry/exporter-jaeger": "^1.3.1",
+ "@opentelemetry/exporter-prometheus": "~0.29.2",
+ "@opentelemetry/instrumentation": "^0.29.2",
+ "@opentelemetry/instrumentation-dns": "^0.29.0",
+ "@opentelemetry/instrumentation-express": "^0.30.0",
+ "@opentelemetry/instrumentation-fs": "^0.4.0",
+ "@opentelemetry/instrumentation-http": "^0.29.2",
+ "@opentelemetry/instrumentation-pg": "^0.30.0",
+ "@opentelemetry/instrumentation-redis-4": "^0.31.0",
+ "@opentelemetry/resources": "^1.3.1",
+ "@opentelemetry/sdk-metrics-base": "~0.29.2",
+ "@opentelemetry/sdk-trace-base": "^1.3.1",
+ "@opentelemetry/sdk-trace-node": "^1.3.1",
+ "@opentelemetry/semantic-conventions": "^1.3.1",
"@peertube/feed": "^5.0.1",
"@peertube/http-signature": "^1.6.0",
"@uploadx/core": "^5.1.2",
// ----------- Node modules -----------
+import { registerOpentelemetryTracing } from './server/lib/opentelemetry/tracing'
+registerOpentelemetryTracing()
+
import express from 'express'
import morgan, { token } from 'morgan'
import cors from 'cors'
// Trust our proxy (IP forwarding...)
app.set('trust proxy', CONFIG.TRUST_PROXY)
+app.use((_req, res, next) => {
+ res.locals.requestStart = Date.now()
+
+ return next()
+})
+
// Security middleware
import { baseCSP } from './server/middlewares/csp'
import { ServerConfigManager } from '@server/lib/server-config-manager'
import { VideoViewsManager } from '@server/lib/views/video-views-manager'
import { isTestInstance } from './server/helpers/core-utils'
+import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
// ----------- Command line -----------
// W3C DNT Tracking Status
app.use(advertiseDoNotTrack)
+// ----------- Open Telemetry -----------
+
+OpenTelemetryMetrics.Instance.init(app)
+
// ----------- Views, routes and static files -----------
// API
RemoveDanglingResumableUploadsScheduler.Instance.enable()
VideoViewsBufferScheduler.Instance.enable()
GeoIPUpdateScheduler.Instance.enable()
+ OpenTelemetryMetrics.Instance.registerMetrics()
Redis.Instance.init()
PeerTubeSocket.Instance.init(server)
-// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
import { stat } from 'fs-extra'
import { omit } from 'lodash'
import { join } from 'path'
import { format as sqlFormat } from 'sql-formatter'
import { createLogger, format, transports } from 'winston'
import { FileTransportOptions } from 'winston/lib/winston/transports'
+import { context } from '@opentelemetry/api'
+import { getSpanContext } from '@opentelemetry/api/build/src/trace/context-utils'
import { CONFIG } from '../initializers/config'
import { LOG_FILENAME } from '../initializers/constants'
const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
-function getLoggerReplacer () {
- const seen = new WeakSet()
-
- // Thanks: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#Examples
- return (key: string, value: any) => {
- if (key === 'cert') return 'Replaced by the logger to avoid large log message'
-
- if (typeof value === 'object' && value !== null) {
- if (seen.has(value)) return
-
- seen.add(value)
- }
-
- if (value instanceof Set) {
- return Array.from(value)
- }
-
- if (value instanceof Map) {
- return Array.from(value.entries())
- }
-
- if (value instanceof Error) {
- const error = {}
-
- Object.getOwnPropertyNames(value).forEach(key => { error[key] = value[key] })
-
- return error
- }
-
- return value
- }
-}
-
const consoleLoggerFormat = format.printf(info => {
- const toOmit = [ 'label', 'timestamp', 'level', 'message', 'sql', 'tags' ]
-
- const obj = omit(info, ...toOmit)
-
- let additionalInfos = JSON.stringify(obj, getLoggerReplacer(), 2)
+ let additionalInfos = JSON.stringify(getAdditionalInfo(info), removeCyclicValues(), 2)
if (additionalInfos === undefined || additionalInfos === '{}') additionalInfos = ''
else additionalInfos = ' ' + additionalInfos
})
const jsonLoggerFormat = format.printf(info => {
- return JSON.stringify(info, getLoggerReplacer())
+ return JSON.stringify(info, removeCyclicValues())
})
const timestampFormatter = format.timestamp({
fileLoggerOptions.maxFiles = CONFIG.LOG.ROTATION.MAX_FILES
}
-const logger = buildLogger()
-
function buildLogger (labelSuffix?: string) {
return createLogger({
level: CONFIG.LOG.LEVEL,
+ defaultMeta: {
+ get traceId () { return getSpanContext(context.active())?.traceId },
+ get spanId () { return getSpanContext(context.active())?.spanId },
+ get traceFlags () { return getSpanContext(context.active())?.traceFlags }
+ },
format: format.combine(
labelFormatter(labelSuffix),
format.splat()
})
}
+const logger = buildLogger()
+
+// ---------------------------------------------------------------------------
+
function bunyanLogFactory (level: string) {
return function (...params: any[]) {
let meta = null
level: () => { },
trace: bunyanLogFactory('debug'),
debug: bunyanLogFactory('debug'),
+ verbose: bunyanLogFactory('debug'),
info: bunyanLogFactory('info'),
warn: bunyanLogFactory('warn'),
error: bunyanLogFactory('error'),
fatal: bunyanLogFactory('error')
}
+// ---------------------------------------------------------------------------
+
type LoggerTagsFn = (...tags: string[]) => { tags: string[] }
function loggerTagsFactory (...defaultTags: string[]): LoggerTagsFn {
return (...tags: string[]) => {
}
}
+// ---------------------------------------------------------------------------
+
async function mtimeSortFilesDesc (files: string[], basePath: string) {
const promises = []
const out: { file: string, mtime: number }[] = []
loggerTagsFactory,
bunyanLogger
}
+
+// ---------------------------------------------------------------------------
+
+function removeCyclicValues () {
+ const seen = new WeakSet()
+
+ // Thanks: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#Examples
+ return (key: string, value: any) => {
+ if (key === 'cert') return 'Replaced by the logger to avoid large log message'
+
+ if (typeof value === 'object' && value !== null) {
+ if (seen.has(value)) return
+
+ seen.add(value)
+ }
+
+ if (value instanceof Set) {
+ return Array.from(value)
+ }
+
+ if (value instanceof Map) {
+ return Array.from(value.entries())
+ }
+
+ if (value instanceof Error) {
+ const error = {}
+
+ Object.getOwnPropertyNames(value).forEach(key => { error[key] = value[key] })
+
+ return error
+ }
+
+ return value
+ }
+}
+
+function getAdditionalInfo (info: any) {
+ const toOmit = [ 'label', 'timestamp', 'level', 'message', 'sql', 'tags' ]
+
+ return omit(info, ...toOmit)
+}
LOG_TRACKER_UNKNOWN_INFOHASH: config.get<boolean>('log.log_tracker_unknown_infohash'),
PRETTIFY_SQL: config.get<boolean>('log.prettify_sql')
},
+ OPEN_TELEMETRY: {
+ METRICS: {
+ ENABLED: config.get<boolean>('open_telemetry.metrics.enabled'),
+
+ PROMETHEUS_EXPORTER: {
+ PORT: config.get<number>('open_telemetry.metrics.prometheus_exporter.port')
+ }
+ },
+ TRACING: {
+ ENABLED: config.get<boolean>('open_telemetry.tracing.enabled'),
+
+ JAEGER_EXPORTER: {
+ ENDPOINT: config.get<string>('open_telemetry.tracing.jaeger_exporter.endpoint')
+ }
+ }
+ },
TRENDING: {
VIDEOS: {
INTERVAL_DAYS: config.get<number>('trending.videos.interval_days'),
INFO_HASH_EXISTS: 1000 * 3600 * 12, // 12 hours
VIDEO_DURATION: 1000 * 10, // 10 seconds
LIVE_ABLE_TO_UPLOAD: 1000 * 60, // 1 minute
- LIVE_CHECK_SOCKET_HEALTH: 1000 * 60 // 1 minute
+ LIVE_CHECK_SOCKET_HEALTH: 1000 * 60, // 1 minute
+ GET_STATS_FOR_OPEN_TELEMETRY_METRICS: 1000 * 60 // 1 minute
}
const MEMOIZE_LENGTH = {
+import { ActivityType } from "@shared/models"
+
function getAPId (object: string | { id: string }) {
if (typeof object === 'string') return object
return parseInt(duration.replace(/[^\d]+/, ''))
}
+function buildAvailableActivities (): ActivityType[] {
+ return [
+ 'Create',
+ 'Update',
+ 'Delete',
+ 'Follow',
+ 'Accept',
+ 'Announce',
+ 'Undo',
+ 'Like',
+ 'Reject',
+ 'View',
+ 'Dislike',
+ 'Flag'
+ ]
+}
+
export {
getAPId,
getActivityStreamDuration,
+ buildAvailableActivities,
getDurationFromActivityStream
}
return total
}
+ async getStats () {
+ const promises = jobTypes.map(async t => ({ jobType: t, counts: await this.queues[t].getJobCounts() }))
+
+ return Promise.all(promises)
+ }
+
async removeOldJobs () {
for (const key of Object.keys(this.queues)) {
const queue = this.queues[key]
--- /dev/null
+export * from './stats-observers-builder'
--- /dev/null
+import memoizee from 'memoizee'
+import { Meter } from '@opentelemetry/api-metrics'
+import { MEMOIZE_TTL } from '@server/initializers/constants'
+import { buildAvailableActivities } from '@server/lib/activitypub/activity'
+import { StatsManager } from '@server/lib/stat-manager'
+
+export class StatsObserverBuilder {
+
+ private readonly getInstanceStats = memoizee(() => {
+ return StatsManager.Instance.getStats()
+ }, { maxAge: MEMOIZE_TTL.GET_STATS_FOR_OPEN_TELEMETRY_METRICS })
+
+ constructor (private readonly meter: Meter) {
+
+ }
+
+ buildObservers () {
+ this.buildUserStatsObserver()
+ this.buildVideoStatsObserver()
+ this.buildCommentStatsObserver()
+ this.buildPlaylistStatsObserver()
+ this.buildChannelStatsObserver()
+ this.buildInstanceFollowsStatsObserver()
+ this.buildRedundancyStatsObserver()
+ this.buildActivityPubStatsObserver()
+ }
+
+ private buildUserStatsObserver () {
+ this.meter.createObservableGauge('peertube_users_total', {
+ description: 'Total users on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalUsers)
+ })
+
+ this.meter.createObservableGauge('peertube_active_users_total', {
+ description: 'Total active users on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalDailyActiveUsers, { activeInterval: 'daily' })
+ observableResult.observe(stats.totalWeeklyActiveUsers, { activeInterval: 'weekly' })
+ observableResult.observe(stats.totalMonthlyActiveUsers, { activeInterval: 'monthly' })
+ })
+ }
+
+ private buildChannelStatsObserver () {
+ this.meter.createObservableGauge('peertube_channels_total', {
+ description: 'Total channels on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalLocalVideoChannels, { channelOrigin: 'local' })
+ })
+
+ this.meter.createObservableGauge('peertube_active_channels_total', {
+ description: 'Total active channels on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalLocalDailyActiveVideoChannels, { channelOrigin: 'local', activeInterval: 'daily' })
+ observableResult.observe(stats.totalLocalWeeklyActiveVideoChannels, { channelOrigin: 'local', activeInterval: 'weekly' })
+ observableResult.observe(stats.totalLocalMonthlyActiveVideoChannels, { channelOrigin: 'local', activeInterval: 'monthly' })
+ })
+ }
+
+ private buildVideoStatsObserver () {
+ this.meter.createObservableGauge('peertube_videos_total', {
+ description: 'Total videos on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalLocalVideos, { videoOrigin: 'local' })
+ observableResult.observe(stats.totalVideos - stats.totalLocalVideos, { videoOrigin: 'remote' })
+ })
+
+ this.meter.createObservableGauge('peertube_video_views_total', {
+ description: 'Total video views made on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalLocalVideoViews, { viewOrigin: 'local' })
+ })
+
+ this.meter.createObservableGauge('peertube_video_bytes_total', {
+ description: 'Total bytes of videos'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalLocalVideoFilesSize, { videoOrigin: 'local' })
+ })
+ }
+
+ private buildCommentStatsObserver () {
+ this.meter.createObservableGauge('peertube_comments_total', {
+ description: 'Total comments on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalLocalVideoComments, { accountOrigin: 'local' })
+ })
+ }
+
+ private buildPlaylistStatsObserver () {
+ this.meter.createObservableGauge('peertube_playlists_total', {
+ description: 'Total playlists on the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalLocalPlaylists, { playlistOrigin: 'local' })
+ })
+ }
+
+ private buildInstanceFollowsStatsObserver () {
+ this.meter.createObservableGauge('peertube_instance_followers_total', {
+ description: 'Total followers of the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalInstanceFollowers)
+ })
+
+ this.meter.createObservableGauge('peertube_instance_following_total', {
+ description: 'Total following of the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalInstanceFollowing)
+ })
+ }
+
+ private buildRedundancyStatsObserver () {
+ this.meter.createObservableGauge('peertube_redundancy_used_bytes_total', {
+ description: 'Total redundancy used of the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ for (const r of stats.videosRedundancy) {
+ observableResult.observe(r.totalUsed, { strategy: r.strategy })
+ }
+ })
+
+ this.meter.createObservableGauge('peertube_redundancy_available_bytes_total', {
+ description: 'Total redundancy available of the instance'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ for (const r of stats.videosRedundancy) {
+ observableResult.observe(r.totalSize, { strategy: r.strategy })
+ }
+ })
+ }
+
+ private buildActivityPubStatsObserver () {
+ const availableActivities = buildAvailableActivities()
+
+ this.meter.createObservableGauge('peertube_ap_inbox_success_total', {
+ description: 'Total inbox messages processed with success'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ for (const type of availableActivities) {
+ observableResult.observe(stats[`totalActivityPub${type}MessagesSuccesses`], { activityType: type })
+ }
+ })
+
+ this.meter.createObservableGauge('peertube_ap_inbox_error_total', {
+ description: 'Total inbox messages processed with error'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ for (const type of availableActivities) {
+ observableResult.observe(stats[`totalActivityPub${type}MessagesErrors`], { activityType: type })
+ }
+ })
+
+ this.meter.createObservableGauge('peertube_ap_inbox_waiting_total', {
+ description: 'Total inbox messages waiting for being processed'
+ }).addCallback(async observableResult => {
+ const stats = await this.getInstanceStats()
+
+ observableResult.observe(stats.totalActivityPubMessagesWaiting)
+ })
+ }
+}
--- /dev/null
+import { Application, Request, Response } from 'express'
+import { Meter, metrics } from '@opentelemetry/api-metrics'
+import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
+import { MeterProvider } from '@opentelemetry/sdk-metrics-base'
+import { logger } from '@server/helpers/logger'
+import { CONFIG } from '@server/initializers/config'
+import { JobQueue } from '../job-queue'
+import { StatsObserverBuilder } from './metric-helpers'
+
+class OpenTelemetryMetrics {
+
+ private static instance: OpenTelemetryMetrics
+
+ private meter: Meter
+
+ private onRequestDuration: (req: Request, res: Response) => void
+
+ private constructor () {}
+
+ init (app: Application) {
+ if (CONFIG.OPEN_TELEMETRY.METRICS.ENABLED !== true) return
+
+ app.use((req, res, next) => {
+ res.once('finish', () => {
+ if (!this.onRequestDuration) return
+
+ this.onRequestDuration(req as Request, res as Response)
+ })
+
+ next()
+ })
+ }
+
+ registerMetrics () {
+ if (CONFIG.OPEN_TELEMETRY.METRICS.ENABLED !== true) return
+
+ logger.info('Registering Open Telemetry metrics')
+
+ const provider = new MeterProvider()
+
+ provider.addMetricReader(new PrometheusExporter({ port: CONFIG.OPEN_TELEMETRY.METRICS.PROMETHEUS_EXPORTER.PORT }))
+
+ metrics.setGlobalMeterProvider(provider)
+
+ this.meter = metrics.getMeter('default')
+
+ this.buildMemoryObserver()
+ this.buildRequestObserver()
+ this.buildJobQueueObserver()
+
+ const statsObserverBuilder = new StatsObserverBuilder(this.meter)
+ statsObserverBuilder.buildObservers()
+ }
+
+ private buildMemoryObserver () {
+ this.meter.createObservableGauge('nodejs_memory_usage_bytes', {
+ description: 'Memory'
+ }).addCallback(observableResult => {
+ const current = process.memoryUsage()
+
+ observableResult.observe(current.heapTotal, { memoryType: 'heapTotal' })
+ observableResult.observe(current.heapUsed, { memoryType: 'heapUsed' })
+ observableResult.observe(current.arrayBuffers, { memoryType: 'arrayBuffers' })
+ observableResult.observe(current.external, { memoryType: 'external' })
+ observableResult.observe(current.rss, { memoryType: 'rss' })
+ })
+ }
+
+ private buildJobQueueObserver () {
+ this.meter.createObservableGauge('peertube_job_queue_total', {
+ description: 'Total jobs in the PeerTube job queue'
+ }).addCallback(async observableResult => {
+ const stats = await JobQueue.Instance.getStats()
+
+ for (const { jobType, counts } of stats) {
+ for (const state of Object.keys(counts)) {
+ observableResult.observe(counts[state], { jobType, state })
+ }
+ }
+ })
+ }
+
+ private buildRequestObserver () {
+ const requestDuration = this.meter.createHistogram('http_request_duration_ms', {
+ unit: 'milliseconds',
+ description: 'Duration of HTTP requests in ms'
+ })
+
+ this.onRequestDuration = (req: Request, res: Response) => {
+ const duration = Date.now() - res.locals.requestStart
+
+ requestDuration.record(duration, {
+ path: this.buildRequestPath(req.originalUrl),
+ method: req.method,
+ statusCode: res.statusCode + ''
+ })
+ }
+ }
+
+ private buildRequestPath (path: string) {
+ return path.split('?')[0]
+ }
+
+ static get Instance () {
+ return this.instance || (this.instance = new this())
+ }
+}
+
+export {
+ OpenTelemetryMetrics
+}
--- /dev/null
+import { diag, DiagLogLevel, trace } from '@opentelemetry/api'
+import { JaegerExporter } from '@opentelemetry/exporter-jaeger'
+import { registerInstrumentations } from '@opentelemetry/instrumentation'
+import { DnsInstrumentation } from '@opentelemetry/instrumentation-dns'
+import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'
+import FsInstrumentation from '@opentelemetry/instrumentation-fs'
+import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'
+import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'
+import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis-4'
+import { Resource } from '@opentelemetry/resources'
+import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
+import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
+import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
+import { logger } from '@server/helpers/logger'
+import { CONFIG } from '@server/initializers/config'
+
+function registerOpentelemetryTracing () {
+ if (CONFIG.OPEN_TELEMETRY.TRACING.ENABLED !== true) return
+
+ logger.info('Registering Open Telemetry tracing')
+
+ const customLogger = (level: string) => {
+ return (message: string, ...args: unknown[]) => {
+ let fullMessage = message
+
+ for (const arg of args) {
+ if (typeof arg === 'string') fullMessage += arg
+ else break
+ }
+
+ logger[level](fullMessage)
+ }
+ }
+
+ diag.setLogger({
+ error: customLogger('error'),
+ warn: customLogger('warn'),
+ info: customLogger('info'),
+ debug: customLogger('debug'),
+ verbose: customLogger('verbose')
+ }, DiagLogLevel.INFO)
+
+ const tracerProvider = new NodeTracerProvider({
+ resource: new Resource({
+ [SemanticResourceAttributes.SERVICE_NAME]: 'peertube'
+ })
+ })
+
+ registerInstrumentations({
+ tracerProvider: tracerProvider,
+ instrumentations: [
+ new PgInstrumentation({
+ enhancedDatabaseReporting: true
+ }),
+ new DnsInstrumentation(),
+ new HttpInstrumentation(),
+ new ExpressInstrumentation(),
+ new RedisInstrumentation({
+ dbStatementSerializer: function (cmdName, cmdArgs) {
+ return [ cmdName, ...cmdArgs ].join(' ')
+ }
+ }),
+ new FsInstrumentation()
+ ]
+ })
+
+ tracerProvider.addSpanProcessor(
+ new BatchSpanProcessor(
+ new JaegerExporter({ endpoint: CONFIG.OPEN_TELEMETRY.TRACING.JAEGER_EXPORTER.ENDPOINT })
+ )
+ )
+
+ tracerProvider.register()
+}
+
+const tracer = trace.getTracer('peertube')
+
+export {
+ registerOpentelemetryTracing,
+ tracer
+}
Table,
UpdatedAt
} from 'sequelize-typescript'
-import { buildNSFWFilter } from '@server/helpers/express-utils'
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
import { LiveManager } from '@server/lib/live/live-manager'
import { removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage'
import { VideoLiveModel } from './video-live'
import { VideoPlaylistElementModel } from './video-playlist-element'
import { VideoShareModel } from './video-share'
+import { VideoSourceModel } from './video-source'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { VideoTagModel } from './video-tag'
-import { VideoSourceModel } from './video-source'
export enum ScopeNames {
FOR_API = 'FOR_API',
}
static async getStats () {
- const totalLocalVideos = await VideoModel.count({
- where: {
- remote: false
- }
- })
+ const serverActor = await getServerActor()
let totalLocalVideoViews = await VideoModel.sum('views', {
where: {
// Sequelize could return null...
if (!totalLocalVideoViews) totalLocalVideoViews = 0
- const serverActor = await getServerActor()
-
- const { total: totalVideos } = await VideoModel.listForApi({
+ const baseOptions = {
start: 0,
count: 0,
sort: '-publishedAt',
- nsfw: buildNSFWFilter(),
+ nsfw: null,
+ isLocal: true,
displayOnlyForFollower: {
actorId: serverActor.id,
orLocalVideos: true
}
+ }
+
+ const { total: totalLocalVideos } = await VideoModel.listForApi({
+ ...baseOptions,
+
+ isLocal: true
})
+ const { total: totalVideos } = await VideoModel.listForApi(baseOptions)
+
return {
totalLocalVideos,
totalLocalVideoViews,
import './stats'
import './tracker'
import './no-client'
+import './open-telemetry'
import './plugins'
import './proxy'
-import 'mocha'
import request from 'supertest'
-import { cleanupTests, createSingleServer, PeerTubeServer } from '@shared/server-commands'
import { HttpStatusCode } from '@shared/models'
+import { cleanupTests, createSingleServer, PeerTubeServer } from '@shared/server-commands'
describe('Start and stop server without web client routes', function () {
let server: PeerTubeServer
--- /dev/null
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import { expect } from 'chai'
+import { expectLogContain, expectLogDoesNotContain, MockHTTP } from '@server/tests/shared'
+import { HttpStatusCode, VideoPrivacy } from '@shared/models'
+import { cleanupTests, createSingleServer, makeRawRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
+
+describe('Open Telemetry', function () {
+ let server: PeerTubeServer
+
+ describe('Metrics', function () {
+ const metricsUrl = 'http://localhost:9091/metrics'
+
+ it('Should not enable open telemetry metrics', async function () {
+ server = await createSingleServer(1)
+
+ let hasError = false
+ try {
+ await makeRawRequest(metricsUrl, HttpStatusCode.NOT_FOUND_404)
+ } catch (err) {
+ hasError = err.message.includes('ECONNREFUSED')
+ }
+
+ expect(hasError).to.be.true
+
+ await server.kill()
+ })
+
+ it('Should enable open telemetry metrics', async function () {
+ server = await createSingleServer(1, {
+ open_telemetry: {
+ metrics: {
+ enabled: true
+ }
+ }
+ })
+
+ const res = await makeRawRequest(metricsUrl, HttpStatusCode.OK_200)
+ expect(res.text).to.contain('peertube_job_queue_total')
+
+ await server.kill()
+ })
+ })
+
+ describe('Tracing', function () {
+ let mockHTTP: MockHTTP
+ let mockPort: number
+
+ before(async function () {
+ mockHTTP = new MockHTTP()
+ mockPort = await mockHTTP.initialize()
+ })
+
+ it('Should enable open telemetry tracing', async function () {
+ server = await createSingleServer(1)
+
+ await expectLogDoesNotContain(server, 'Registering Open Telemetry tracing')
+
+ await server.kill()
+ })
+
+ it('Should enable open telemetry metrics', async function () {
+ server = await createSingleServer(1, {
+ open_telemetry: {
+ tracing: {
+ enabled: true,
+ jaeger_exporter: {
+ endpoint: 'http://localhost:' + mockPort
+ }
+ }
+ }
+ })
+
+ await expectLogContain(server, 'Registering Open Telemetry tracing')
+ })
+
+ it('Should upload a video and correctly works', async function () {
+ await setAccessTokensToServers([ server ])
+
+ const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PUBLIC })
+
+ const video = await server.videos.get({ id: uuid })
+
+ expect(video.name).to.equal('video')
+ })
+
+ after(async function () {
+ await mockHTTP.terminate()
+ })
+ })
+
+ after(async function () {
+ await cleanupTests([ server ])
+ })
+})
expect(content.toString()).to.not.contain(str)
}
+async function expectLogContain (server: PeerTubeServer, str: string) {
+ const content = await server.servers.getLogContent()
+
+ expect(content.toString()).to.contain(str)
+}
+
async function testImage (url: string, imageName: string, imageHTTPPath: string, extension = '.jpg') {
const res = await makeGetRequest({
url,
expectNotStartWith,
checkBadStartPagination,
checkBadCountPagination,
- checkBadSortPagination
+ checkBadSortPagination,
+ expectLogContain
}
export * from './mock-429'
export * from './mock-email'
+export * from './mock-http'
export * from './mock-instances-index'
export * from './mock-joinpeertube-versions'
export * from './mock-object-storage'
--- /dev/null
+import express from 'express'
+import { Server } from 'http'
+import { getPort, randomListen, terminateServer } from './shared'
+
+export class MockHTTP {
+ private server: Server
+
+ async initialize () {
+ const app = express()
+
+ app.get('/*', (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ return res.sendStatus(200)
+ })
+
+ this.server = await randomListen(app)
+
+ return getPort(this.server)
+ }
+
+ terminate () {
+ return terminateServer(this.server)
+ }
+}
}) => void
locals: {
+ requestStart: number
+
apicache: {
content: string | Buffer
write: Writable['write']
+import { ActivityType } from '../activitypub'
import { VideoRedundancyStrategyWithManual } from '../redundancy'
-export interface ServerStats {
+
+type ActivityPubMessagesSuccess = Record<`totalActivityPub${ActivityType}MessagesSuccesses`, number>
+type ActivityPubMessagesErrors = Record<`totalActivityPub${ActivityType}MessagesErrors`, number>
+
+export interface ServerStats extends ActivityPubMessagesSuccess, ActivityPubMessagesErrors {
totalUsers: number
totalDailyActiveUsers: number
totalWeeklyActiveUsers: number
totalActivityPubMessagesSuccesses: number
totalActivityPubMessagesErrors: number
- totalActivityPubCreateMessagesSuccesses: number
- totalActivityPubUpdateMessagesSuccesses: number
- totalActivityPubDeleteMessagesSuccesses: number
- totalActivityPubFollowMessagesSuccesses: number
- totalActivityPubAcceptMessagesSuccesses: number
- totalActivityPubRejectMessagesSuccesses: number
- totalActivityPubAnnounceMessagesSuccesses: number
- totalActivityPubUndoMessagesSuccesses: number
- totalActivityPubLikeMessagesSuccesses: number
- totalActivityPubDislikeMessagesSuccesses: number
- totalActivityPubFlagMessagesSuccesses: number
- totalActivityPubViewMessagesSuccesses: number
-
- totalActivityPubCreateMessagesErrors: number
- totalActivityPubUpdateMessagesErrors: number
- totalActivityPubDeleteMessagesErrors: number
- totalActivityPubFollowMessagesErrors: number
- totalActivityPubAcceptMessagesErrors: number
- totalActivityPubRejectMessagesErrors: number
- totalActivityPubAnnounceMessagesErrors: number
- totalActivityPubUndoMessagesErrors: number
- totalActivityPubLikeMessagesErrors: number
- totalActivityPubDislikeMessagesErrors: number
- totalActivityPubFlagMessagesErrors: number
- totalActivityPubViewMessagesErrors: number
-
activityPubMessagesProcessedPerSecond: number
totalActivityPubMessagesWaiting: number
}
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@opentelemetry/api-metrics@0.29.2", "@opentelemetry/api-metrics@^0.29.2":
+ version "0.29.2"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/api-metrics/-/api-metrics-0.29.2.tgz#daa823e0965754222b49a6ae6133df8b39ff8fd2"
+ integrity sha512-yRdF5beqKuEdsPNoO7ijWCQ9HcyN0Tlgicf8RS6gzGOI54d6Hj7yKquJ6+X9XV+CSRbRWJYb+lOsXyso7uyX2g==
+ dependencies:
+ "@opentelemetry/api" "^1.0.0"
+
+"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.1.0.tgz#563539048255bbe1a5f4f586a4a10a1bb737f44a"
+ integrity sha512-hf+3bwuBwtXsugA2ULBc95qxrOqP2pOekLz34BJhcAKawt94vfeNyUKpYc0lZQ/3sCP6LqRa7UAdHA7i5UODzQ==
+
+"@opentelemetry/context-async-hooks@1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.3.1.tgz#6b5288b535178fb8e3053c00c30eb38d3fdff60b"
+ integrity sha512-NKUY3SGiEEIOD3EpB8erpEF4K1iyXkWald1vJMaa973+EPTASNSXvzf8hZa7nhnUVxYbxtTJqbSRsZFfbZpw4g==
+
+"@opentelemetry/core@1.3.1", "@opentelemetry/core@^1.0.0":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.3.1.tgz#6eef5c5efca9a4cd7daa0cd4c7ff28ca2317c8d7"
+ integrity sha512-k7lOC86N7WIyUZsUuSKZfFIrUtINtlauMGQsC1r7jNmcr0vVJGqK1ROBvt7WWMxLbpMnt1q2pXJO8tKu0b9auA==
+ dependencies:
+ "@opentelemetry/semantic-conventions" "1.3.1"
+
+"@opentelemetry/exporter-jaeger@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.3.1.tgz#00070e54abea51d5001acfaeb9fcfd537bf0d1d5"
+ integrity sha512-uJ9811zn5TTdazyTNc4xmcDnKC8H63VRGp23ujGTxBOCFUnFzfI/kUGUJ8/O7Xok9Ulop7wuuBW3onL1WedfjA==
+ dependencies:
+ "@opentelemetry/core" "1.3.1"
+ "@opentelemetry/sdk-trace-base" "1.3.1"
+ "@opentelemetry/semantic-conventions" "1.3.1"
+ jaeger-client "^3.15.0"
+
+"@opentelemetry/exporter-prometheus@~0.29.2":
+ version "0.29.2"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.29.2.tgz#70ca7fb37655ca57a580387607d5465b47e27ac3"
+ integrity sha512-E5sRfUM4rzbvjxdpL1H6YRtjr8wY8+/2R4NjfxPEwrENLeeQk87V1E+YFLqAS7TfFLW7Zr4lmmamunwn5THvQA==
+ dependencies:
+ "@opentelemetry/api-metrics" "0.29.2"
+ "@opentelemetry/core" "1.3.1"
+ "@opentelemetry/sdk-metrics-base" "0.29.2"
+
+"@opentelemetry/instrumentation-dns@^0.29.0":
+ version "0.29.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.29.0.tgz#efd21eb9d8938de97e225b52f8a432d6072b4096"
+ integrity sha512-3WTC4m6JKviaABiR3a+56WUMvrUp9WW9EYC0+LRpqm7RK/1a5bYq2Cozc2SlFYX9ZfWKMqGS39/fU24mKQ5toA==
+ dependencies:
+ "@opentelemetry/instrumentation" "^0.29.2"
+ "@opentelemetry/semantic-conventions" "^1.0.0"
+ semver "^7.3.2"
+
+"@opentelemetry/instrumentation-express@^0.30.0":
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.30.0.tgz#4f5f45ce47c8f1ac75741284d3e59c861ced4269"
+ integrity sha512-OsCfM+ThAXh3wzsyHgXyA5HUoLMdLd6Asix2Jx8yxniruU/Gq8y4Cz7aLy/vXNckXHWO3fwwL5gb7K3dykTnAQ==
+ dependencies:
+ "@opentelemetry/core" "^1.0.0"
+ "@opentelemetry/instrumentation" "^0.29.2"
+ "@opentelemetry/semantic-conventions" "^1.0.0"
+ "@types/express" "4.17.13"
+
+"@opentelemetry/instrumentation-fs@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.4.0.tgz#8d689f741c8cb706fd28306f5f70dab756d2f02c"
+ integrity sha512-AINjLsYifpBC5R0YrkS0aaWBcql5WYr+UGste6HLJlHiselA22hBE3zP2WX4y+24eIlvDFKybwlodiNrITV16Q==
+ dependencies:
+ "@opentelemetry/core" "^1.0.0"
+ "@opentelemetry/instrumentation" "^0.29.2"
+ "@opentelemetry/semantic-conventions" "^1.0.0"
+
+"@opentelemetry/instrumentation-http@^0.29.2":
+ version "0.29.2"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.29.2.tgz#c4750c33929d476c2a656f457c83d2144c5dd844"
+ integrity sha512-XIF9WCH03rp3vQjwXXVdTxlsXT2AG6LYfFKO8r2QC+w4F4KFuZa4J3VPYJ0L/a/6dWt34DA67eBh3l6Z1rMZrg==
+ dependencies:
+ "@opentelemetry/core" "1.3.1"
+ "@opentelemetry/instrumentation" "0.29.2"
+ "@opentelemetry/semantic-conventions" "1.3.1"
+ semver "^7.3.5"
+
+"@opentelemetry/instrumentation-pg@^0.30.0":
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.30.0.tgz#5941dd2817a846e7b5bc283112a9cf46728d1f44"
+ integrity sha512-RQ3cTTJnCBE/9GagjSpaM+yzxN25MvEwOxDFes3y8c1cqrMgqxukQLm3MbcqCQ8e1g/8d18+oyiEeBUjZJ5jnw==
+ dependencies:
+ "@opentelemetry/instrumentation" "^0.29.2"
+ "@opentelemetry/semantic-conventions" "^1.0.0"
+ "@types/pg" "8.6.1"
+ "@types/pg-pool" "2.0.3"
+
+"@opentelemetry/instrumentation-redis-4@^0.31.0":
+ version "0.31.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.31.0.tgz#3db1cfc177857fc208b73565df2d54065c69a67e"
+ integrity sha512-3DY6bkqKnVlPc2WWHelb6DnU78ryYLQFqv0lqnVsoSkr7b6hnmw1Bzuwo/5YmS4C3XuTAD4/6dZVrQJ23g8HNA==
+ dependencies:
+ "@opentelemetry/instrumentation" "^0.29.2"
+ "@opentelemetry/semantic-conventions" "^1.0.0"
+
+"@opentelemetry/instrumentation@0.29.2", "@opentelemetry/instrumentation@^0.29.2":
+ version "0.29.2"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.29.2.tgz#70e6d4e1a84508f5e9d8c7c426adcd7b0dba6c95"
+ integrity sha512-LXx5V0ONNATQFCE8C5uqnxWSm4rcXLssdLHdXjtGdxRmURqj/JO8jYefqXCD0LzsqEQ6yxOx2GZ0dgXvhBVdTw==
+ dependencies:
+ "@opentelemetry/api-metrics" "0.29.2"
+ require-in-the-middle "^5.0.3"
+ semver "^7.3.2"
+ shimmer "^1.2.1"
+
+"@opentelemetry/propagator-b3@1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-1.3.1.tgz#39208de42afef5635e74f4bedca5961d6ce25004"
+ integrity sha512-tEAtHsRr6l3glsmKaJiJ/7HDw/isPv+f8OBsWJqkSlfLicKes8T/1D7nEDC6jPACiEbD3f6oK1KQSpMijC9/UQ==
+ dependencies:
+ "@opentelemetry/core" "1.3.1"
+
+"@opentelemetry/propagator-jaeger@1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.3.1.tgz#ad02cf5e63f7adb6986418dac916e7b89c34df5b"
+ integrity sha512-H6swQcjZ8aMCS5caZaEBaadfn205IqLlB3ZyY+tCWDf5YPwJgPpjw3qgYgWulHVSEzK7VQTle/mZG7u9MAe6Pw==
+ dependencies:
+ "@opentelemetry/core" "1.3.1"
+
+"@opentelemetry/resources@1.3.1", "@opentelemetry/resources@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.3.1.tgz#9fd85ac4ffeefc35441404b384d5c1db8b243121"
+ integrity sha512-X8bl3X0YjlsHWy0Iv0KUETtZuRUznX4yr1iScKCtfy8AoRfZFc2xxWKMDJ0TrqYwSapgeg4YwpmRzUKmmnrbeA==
+ dependencies:
+ "@opentelemetry/core" "1.3.1"
+ "@opentelemetry/semantic-conventions" "1.3.1"
+
+"@opentelemetry/sdk-metrics-base@0.29.2", "@opentelemetry/sdk-metrics-base@~0.29.2":
+ version "0.29.2"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics-base/-/sdk-metrics-base-0.29.2.tgz#bd515455f1d90e211458dcf957f0ae937772b155"
+ integrity sha512-7hhhZ/6YRRgAXOUTeCsbe6SIk3wZAdAHnEwGGp7aiVH5AOyioHyHInw4EHtowlD6dbLxUWURjh6k+Geht2zbxg==
+ dependencies:
+ "@opentelemetry/api-metrics" "0.29.2"
+ "@opentelemetry/core" "1.3.1"
+ "@opentelemetry/resources" "1.3.1"
+ lodash.merge "4.6.2"
+
+"@opentelemetry/sdk-trace-base@1.3.1", "@opentelemetry/sdk-trace-base@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.3.1.tgz#958083dbab928eefd17848959ac8810c787bec7f"
+ integrity sha512-Or95QZ+9QyvAiwqj+K68z8bDDuyWF50c37w17D10GV1dWzg4Ezcectsu/GB61QcBxm3Y4br0EN5F5TpIFfFliQ==
+ dependencies:
+ "@opentelemetry/core" "1.3.1"
+ "@opentelemetry/resources" "1.3.1"
+ "@opentelemetry/semantic-conventions" "1.3.1"
+
+"@opentelemetry/sdk-trace-node@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.3.1.tgz#ef6598aef93b24bfaae1ddc0321d3cf00d44c304"
+ integrity sha512-4sn/pYhaVaEI8WY0arivM77858IM5BjUKvymjJ+HmRNWBocCJKCCCY4P9cL8w8iCGGmst5yxecMyvM7OOFBnmg==
+ dependencies:
+ "@opentelemetry/context-async-hooks" "1.3.1"
+ "@opentelemetry/core" "1.3.1"
+ "@opentelemetry/propagator-b3" "1.3.1"
+ "@opentelemetry/propagator-jaeger" "1.3.1"
+ "@opentelemetry/sdk-trace-base" "1.3.1"
+ semver "^7.3.5"
+
+"@opentelemetry/semantic-conventions@1.3.1", "@opentelemetry/semantic-conventions@^1.0.0", "@opentelemetry/semantic-conventions@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.3.1.tgz#ba07b864a3c955f061aa30ea3ef7f4ae4449794a"
+ integrity sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA==
+
"@peertube/feed@^5.0.1":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@peertube/feed/-/feed-5.0.2.tgz#d9ae7f38f1ccc75d353a5e24ad335a982bc4df74"
"@types/qs" "*"
"@types/range-parser" "*"
-"@types/express@*":
+"@types/express@*", "@types/express@4.17.13":
version "4.17.13"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
dependencies:
"@types/node" "*"
+"@types/pg-pool@2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.3.tgz#3eb8df2933f617f219a53091ad4080c94ba1c959"
+ integrity sha512-fwK5WtG42Yb5RxAwxm3Cc2dJ39FlgcaNiXKvtTLAwtCn642X7dgel+w1+cLWwpSOFImR3YjsZtbkfjxbHtFAeg==
+ dependencies:
+ "@types/pg" "*"
+
+"@types/pg@*":
+ version "8.6.5"
+ resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.5.tgz#2dce9cb468a6a5e0f1296a59aea3ac75dd27b702"
+ integrity sha512-tOkGtAqRVkHa/PVZicq67zuujI4Oorfglsr2IbKofDwBSysnaqSx7W1mDqFqdkGE6Fbgh+PZAl0r/BWON/mozw==
+ dependencies:
+ "@types/node" "*"
+ pg-protocol "*"
+ pg-types "^2.2.0"
+
+"@types/pg@8.6.1":
+ version "8.6.1"
+ resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9"
+ integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==
+ dependencies:
+ "@types/node" "*"
+ pg-protocol "*"
+ pg-types "^2.2.0"
+
"@types/qs@*":
version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
require-from-string "^2.0.2"
uri-js "^4.2.2"
+ansi-color@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-color/-/ansi-color-0.2.1.tgz#3e75c037475217544ed763a8db5709fa9ae5bf9a"
+ integrity sha512-bF6xLaZBLpOQzgYUtYEhJx090nPSZk1BQ/q2oyBK9aMMcJHzx9uXGCjI2Y+LebsN4Jwoykr0V9whbPiogdyHoQ==
+
ansi-colors@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
dependencies:
node-gyp-build "^4.3.0"
+bufrw@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/bufrw/-/bufrw-1.3.0.tgz#28d6cfdaf34300376836310f5c31d57eeb40c8fa"
+ integrity sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ==
+ dependencies:
+ ansi-color "^0.2.1"
+ error "^7.0.0"
+ hexer "^1.5.0"
+ xtend "^4.0.0"
+
bull@^4.1.0:
version "4.8.4"
resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.4.tgz#c538610492050d5160dbd9180704145f135a0aa9"
dependencies:
is-arrayish "^0.2.1"
+error@7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02"
+ integrity sha512-UtVv4l5MhijsYUxPJo4390gzfZvAnTHreNnDjnTZaKIiZ/SemXxAhBkYSKtWa5RtBXbLP8tMgn/n0RUa/H7jXw==
+ dependencies:
+ string-template "~0.2.1"
+ xtend "~4.0.0"
+
+error@^7.0.0:
+ version "7.2.1"
+ resolved "https://registry.yarnpkg.com/error/-/error-7.2.1.tgz#eab21a4689b5f684fc83da84a0e390de82d94894"
+ integrity sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==
+ dependencies:
+ string-template "~0.2.1"
+
es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5:
version "1.20.1"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-5.1.0.tgz#e98a5d4bf89ab8119c856018a3bcc82addadcd47"
integrity sha512-klsunXs8rgNSZoaUrNeuCiWUxyc+wzucnEnFejUg3/A+CaF589k9qepLZZ1Jehnzig7YbD4hEuscGXuBY3fq+g==
+hexer@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/hexer/-/hexer-1.5.0.tgz#b86ce808598e8a9d1892c571f3cedd86fc9f0653"
+ integrity sha512-dyrPC8KzBzUJ19QTIo1gXNqIISRXQ0NwteW6OeQHRN4ZuZeHkdODfj0zHBdOlHbRY8GqbqK57C9oWSvQZizFsg==
+ dependencies:
+ ansi-color "^0.2.1"
+ minimist "^1.1.0"
+ process "^0.10.0"
+ xtend "^4.0.0"
+
hexoid@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
+jaeger-client@^3.15.0:
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/jaeger-client/-/jaeger-client-3.19.0.tgz#9b5bd818ebd24e818616ee0f5cffe1722a53ae6e"
+ integrity sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw==
+ dependencies:
+ node-int64 "^0.4.0"
+ opentracing "^0.14.4"
+ thriftrw "^3.5.0"
+ uuid "^8.3.2"
+ xorshift "^1.1.1"
+
jimp@^0.16.0:
version "0.16.1"
resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.16.1.tgz#192f851a30e5ca11112a3d0aa53137659a78ca7a"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
-lodash.merge@^4.6.2:
+lodash.merge@4.6.2, lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
safe-stable-stringify "^2.3.1"
triple-beam "^1.3.0"
+long@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f"
+ integrity sha512-ijUtjmO/n2A5PaosNG9ZGDsQ3vxJg7ZW8vsY8Kp0f2yIZWhSJvjmegV7t+9RPQKxKrvj8yKGehhS+po14hPLGQ==
+
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
+module-details-from-path@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b"
+ integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==
+
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.4.0.tgz#42e99687ce87ddeaf3a10b99dc06abc11021f3f4"
integrity sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==
+node-int64@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
+ integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
+
node-media-server@^2.1.4:
version "2.3.12"
resolved "https://registry.yarnpkg.com/node-media-server/-/node-media-server-2.3.12.tgz#5380a60a26545144fa1e7954f08ece6f255d28b4"
is-docker "^2.0.0"
is-wsl "^2.1.1"
+opentracing@^0.14.4:
+ version "0.14.7"
+ resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.7.tgz#25d472bd0296dc0b64d7b94cbc995219031428f5"
+ integrity sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==
+
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.5.1.tgz#f499ce76f9bf5097488b3b83b19861f28e4ed905"
integrity sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==
-pg-protocol@^1.5.0:
+pg-protocol@*, pg-protocol@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0"
integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==
-pg-types@^2.1.0:
+pg-types@^2.1.0, pg-types@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+process@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/process/-/process-0.10.1.tgz#842457cc51cfed72dc775afeeafb8c6034372725"
+ integrity sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA==
+
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+require-in-the-middle@^5.0.3:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz#b768f800377b47526d026bbf5a7f727f16eb412f"
+ integrity sha512-M2rLKVupQfJ5lf9OvqFGIT+9iVLnTmjgbOmpil12hiSQNn5zJTKGPoIisETNjfK+09vP3rpm1zJajmErpr2sEQ==
+ dependencies:
+ debug "^4.1.1"
+ module-details-from-path "^1.0.3"
+ resolve "^1.12.0"
+
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
commander "9.2.0"
fast-glob "3.2.11"
-resolve@^1.10.1, resolve@^1.15.1, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.22.0:
+resolve@^1.10.1, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.22.0:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123"
integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==
+shimmer@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
+ integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
+
short-uuid@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/short-uuid/-/short-uuid-4.2.0.tgz#3706d9e7287ac589dc5ffe324d3e34817a07540b"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.1.2.tgz#c5b7bc03fb2b11983ba3a72333dd0559e77e4738"
integrity sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==
+string-template@~0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
+ integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
+
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a"
integrity sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==
+thriftrw@^3.5.0:
+ version "3.12.0"
+ resolved "https://registry.yarnpkg.com/thriftrw/-/thriftrw-3.12.0.tgz#30857847755e7f036b2e0a79d11c9f55075539d9"
+ integrity sha512-4YZvR4DPEI41n4Opwr4jmrLGG4hndxr7387kzRFIIzxHQjarPusH4lGXrugvgb7TtPrfZVTpZCVe44/xUxowEw==
+ dependencies:
+ bufrw "^1.3.0"
+ error "7.0.2"
+ long "^2.4.0"
+
through2@^0.6.3, through2@~0.6.1:
version "0.6.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
-"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1:
+xorshift@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/xorshift/-/xorshift-1.2.0.tgz#30a4cdd8e9f8d09d959ed2a88c42a09c660e8148"
+ integrity sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g==
+
+"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==