Explorar o código

Add endpoint to log client events

crobibero %!s(int64=4) %!d(string=hai) anos
pai
achega
1d6224c9c6

+ 3 - 0
Emby.Server.Implementations/ApplicationHost.cs

@@ -55,6 +55,7 @@ using MediaBrowser.Common.Updates;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Chapters;
+using MediaBrowser.Controller.ClientEvent;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
@@ -680,6 +681,8 @@ namespace Emby.Server.Implementations
             ServiceCollection.AddScoped<AudioHelper>();
             ServiceCollection.AddScoped<DynamicHlsHelper>();
 
+            ServiceCollection.AddScoped<IClientEventLogger, ClientEventLogger>();
+
             ServiceCollection.AddSingleton<IDirectoryService, DirectoryService>();
         }
 

+ 69 - 0
Jellyfin.Api/Controllers/ClientLogController.cs

@@ -0,0 +1,69 @@
+using Jellyfin.Api.Models.ClientLogDtos;
+using MediaBrowser.Controller.ClientEvent;
+using MediaBrowser.Model.ClientLog;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Controllers
+{
+    /// <summary>
+    /// Client log controller.
+    /// </summary>
+    public class ClientLogController : BaseJellyfinApiController
+    {
+        private readonly IClientEventLogger _clientEventLogger;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ClientLogController"/> class.
+        /// </summary>
+        /// <param name="clientEventLogger">Instance of the <see cref="IClientEventLogger"/> interface.</param>
+        public ClientLogController(IClientEventLogger clientEventLogger)
+        {
+            _clientEventLogger = clientEventLogger;
+        }
+
+        /// <summary>
+        /// Post event from client.
+        /// </summary>
+        /// <param name="clientLogEventDto">The client log dto.</param>
+        /// <response code="204">Event logged.</response>
+        /// <returns>Submission status.</returns>
+        [HttpPost]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult LogEvent([FromBody] ClientLogEventDto clientLogEventDto)
+        {
+            Log(clientLogEventDto);
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Bulk post events from client.
+        /// </summary>
+        /// <param name="clientLogEventDtos">The list of client log dtos.</param>
+        /// <response code="204">All events logged.</response>
+        /// <returns>Submission status.</returns>
+        [HttpPost("Bulk")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult LogEvents([FromBody] ClientLogEventDto[] clientLogEventDtos)
+        {
+            foreach (var dto in clientLogEventDtos)
+            {
+                Log(dto);
+            }
+
+            return NoContent();
+        }
+
+        private void Log(ClientLogEventDto dto)
+        {
+            _clientEventLogger.Log(new ClientLogEvent(
+                dto.Timestamp,
+                dto.Level,
+                dto.UserId,
+                dto.ClientName,
+                dto.ClientVersion,
+                dto.DeviceId,
+                dto.Message));
+        }
+    }
+}

+ 54 - 0
Jellyfin.Api/Models/ClientLogDtos/ClientLogEventDto.cs

@@ -0,0 +1,54 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Models.ClientLogDtos
+{
+    /// <summary>
+    /// The client log dto.
+    /// </summary>
+    public class ClientLogEventDto
+    {
+        /// <summary>
+        /// Gets or sets the event timestamp.
+        /// </summary>
+        [Required]
+        public DateTime Timestamp { get; set; }
+
+        /// <summary>
+        /// Gets or sets the log level.
+        /// </summary>
+        [Required]
+        public LogLevel Level { get; set; }
+
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
+        public Guid? UserId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the client name.
+        /// </summary>
+        [Required]
+        public string ClientName { get; set; } = string.Empty;
+
+        /// <summary>
+        /// Gets or sets the client version.
+        /// </summary>
+        [Required]
+        public string ClientVersion { get; set; } = string.Empty;
+
+        ///
+        /// <summary>
+        /// Gets or sets the device id.
+        /// </summary>
+        [Required]
+        public string DeviceId { get; set; } = string.Empty;
+
+        /// <summary>
+        /// Gets or sets the log message.
+        /// </summary>
+        [Required]
+        public string Message { get; set; } = string.Empty;
+    }
+}

+ 1 - 0
Jellyfin.Server/Jellyfin.Server.csproj

@@ -49,6 +49,7 @@
     <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
     <PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
     <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
+    <PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
     <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
   </ItemGroup>
 

+ 38 - 11
Jellyfin.Server/Program.cs

@@ -14,6 +14,7 @@ using Emby.Server.Implementations;
 using Emby.Server.Implementations.IO;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.ClientEvent;
 using MediaBrowser.Controller.Extensions;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Configuration;
@@ -24,6 +25,7 @@ using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Abstractions;
 using Serilog;
 using Serilog.Extensions.Logging;
+using Serilog.Filters;
 using SQLitePCL;
 using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 using ILogger = Microsoft.Extensions.Logging.ILogger;
@@ -585,22 +587,47 @@ namespace Jellyfin.Server
             {
                 // Serilog.Log is used by SerilogLoggerFactory when no logger is specified
                 Serilog.Log.Logger = new LoggerConfiguration()
-                    .ReadFrom.Configuration(configuration)
-                    .Enrich.FromLogContext()
-                    .Enrich.WithThreadId()
+                    .WriteTo.Logger(lc =>
+                        lc.ReadFrom.Configuration(configuration)
+                            .Enrich.FromLogContext()
+                            .Enrich.WithThreadId()
+                            .Filter.ByExcluding(Matching.FromSource<ClientEventLogger>()))
+                    .WriteTo.Logger(lc =>
+                        lc
+                            .WriteTo.Map(
+                                "ClientName",
+                                (clientName, wt)
+                                    => wt.File(
+                                        Path.Combine(appPaths.LogDirectoryPath, $"log_{clientName}_.log"),
+                                        rollingInterval: RollingInterval.Day,
+                                        outputTemplate: "{Message:l}{NewLine}{Exception}",
+                                        encoding: Encoding.UTF8))
+                            .Filter.ByIncludingOnly(Matching.FromSource<ClientEventLogger>()))
                     .CreateLogger();
             }
             catch (Exception ex)
             {
                 Serilog.Log.Logger = new LoggerConfiguration()
-                    .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
-                    .WriteTo.Async(x => x.File(
-                        Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
-                        rollingInterval: RollingInterval.Day,
-                        outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}",
-                        encoding: Encoding.UTF8))
-                    .Enrich.FromLogContext()
-                    .Enrich.WithThreadId()
+                    .WriteTo.Logger(lc =>
+                        lc.WriteTo.Async(x => x.File(
+                                Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
+                                rollingInterval: RollingInterval.Day,
+                                outputTemplate: "{Message:l}{NewLine}{Exception}",
+                                encoding: Encoding.UTF8))
+                            .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
+                            .Enrich.FromLogContext()
+                            .Enrich.WithThreadId())
+                    .WriteTo.Logger(lc =>
+                        lc
+                            .WriteTo.Map(
+                                "ClientName",
+                                (clientName, wt)
+                                    => wt.File(
+                                        Path.Combine(appPaths.LogDirectoryPath, $"log_{clientName}_.log"),
+                                        rollingInterval: RollingInterval.Day,
+                                        outputTemplate: "{Message:l}{NewLine}{Exception}",
+                                        encoding: Encoding.UTF8))
+                            .Filter.ByIncludingOnly(Matching.FromSource<ClientEventLogger>()))
                     .CreateLogger();
 
                 Serilog.Log.Logger.Fatal(ex, "Failed to create/read logger configuration");

+ 38 - 0
MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs

@@ -0,0 +1,38 @@
+using System;
+using MediaBrowser.Model.ClientLog;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Controller.ClientEvent
+{
+    /// <inheritdoc />
+    public class ClientEventLogger : IClientEventLogger
+    {
+        private const string LogString = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level}] [{ClientName}:{ClientVersion}]: UserId: {UserId} DeviceId: {DeviceId}{NewLine}{Message}";
+        private readonly ILogger<ClientEventLogger> _logger;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ClientEventLogger"/> class.
+        /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger{ClientEventLogger}"/> interface.</param>
+        public ClientEventLogger(ILogger<ClientEventLogger> logger)
+        {
+            _logger = logger;
+        }
+
+        /// <inheritdoc />
+        public void Log(ClientLogEvent clientLogEvent)
+        {
+            _logger.Log(
+                LogLevel.Critical,
+                LogString,
+                clientLogEvent.Timestamp,
+                clientLogEvent.Level.ToString(),
+                clientLogEvent.ClientName,
+                clientLogEvent.ClientVersion,
+                clientLogEvent.UserId ?? Guid.Empty,
+                clientLogEvent.DeviceId,
+                Environment.NewLine,
+                clientLogEvent.Message);
+        }
+    }
+}

+ 16 - 0
MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs

@@ -0,0 +1,16 @@
+using MediaBrowser.Model.ClientLog;
+
+namespace MediaBrowser.Controller.ClientEvent
+{
+    /// <summary>
+    /// The client event logger.
+    /// </summary>
+    public interface IClientEventLogger
+    {
+        /// <summary>
+        /// Logs the event from the client.
+        /// </summary>
+        /// <param name="clientLogEvent">The client log event.</param>
+        void Log(ClientLogEvent clientLogEvent);
+    }
+}

+ 1 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -16,7 +16,7 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="System.Threading.Tasks.Dataflow" Version="5.0.0" />
   </ItemGroup>
 

+ 75 - 0
MediaBrowser.Model/ClientLog/ClientLogEvent.cs

@@ -0,0 +1,75 @@
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Model.ClientLog
+{
+    /// <summary>
+    /// The client log event.
+    /// </summary>
+    public class ClientLogEvent
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ClientLogEvent"/> class.
+        /// </summary>
+        /// <param name="timestamp">The log timestamp.</param>
+        /// <param name="level">The log level.</param>
+        /// <param name="userId">The user id.</param>
+        /// <param name="clientName">The client name.</param>
+        /// <param name="clientVersion">The client version.</param>
+        /// <param name="deviceId">The device id.</param>
+        /// <param name="message">The message.</param>
+        public ClientLogEvent(
+            DateTime timestamp,
+            LogLevel level,
+            Guid? userId,
+            string clientName,
+            string clientVersion,
+            string deviceId,
+            string message)
+        {
+            Timestamp = timestamp;
+            UserId = userId;
+            ClientName = clientName;
+            ClientVersion = clientVersion;
+            DeviceId = deviceId;
+            Message = message;
+            Level = level;
+        }
+
+        /// <summary>
+        /// Gets the event timestamp.
+        /// </summary>
+        public DateTime Timestamp { get; }
+
+        /// <summary>
+        /// Gets the log level.
+        /// </summary>
+        public LogLevel Level { get; }
+
+        /// <summary>
+        /// Gets the user id.
+        /// </summary>
+        public Guid? UserId { get; }
+
+        /// <summary>
+        /// Gets the client name.
+        /// </summary>
+        public string ClientName { get; }
+
+        /// <summary>
+        /// Gets the client version.
+        /// </summary>
+        public string ClientVersion { get; }
+
+        ///
+        /// <summary>
+        /// Gets the device id.
+        /// </summary>
+        public string DeviceId { get; }
+
+        /// <summary>
+        /// Gets the log message.
+        /// </summary>
+        public string Message { get; }
+    }
+}