diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d95de97..4d1d4d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -105,11 +105,11 @@ jobs: - name: Create zip run: | cd ${{ env.PUBLISH_PATH }} - zip -r ../telemetry-server-${{ steps.vars.outputs.version }}.zip . + zip -r ../b4e-telemetry-${{ steps.vars.outputs.version }}.zip . - name: Generate checksum run: | - sha256sum telemetry-server-${{ steps.vars.outputs.version }}.zip > checksums.txt + sha256sum b4e-telemetry-${{ steps.vars.outputs.version }}.zip > checksums.txt - name: Generate metadata run: | @@ -132,7 +132,7 @@ jobs: prerelease: false generate_release_notes: true files: | - telemetry-server-${{ steps.vars.outputs.version }}.zip + b4e-telemetry-${{ steps.vars.outputs.version }}.zip checksums.txt build-info.json diff --git a/README.md b/README.md index fbb924a..8d7c2c6 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The project follows a Clean Architecture approach: To build the project locally, run the following command from the root directory: ```bash -dotnet build telemetry-server.slnx +dotnet build b4e-telemetry.slnx ``` To run the application: @@ -47,7 +47,7 @@ The project provides several Docker Compose configurations to support different To start the API using the pre-built image, you can pull the latest version: ```bash -docker pull ghcr.io/pyrevitlabs/telemetry-server:latest +docker pull ghcr.io/Bim4Everyone/b4e-telemetry:latest ``` To start the API with a specific database, use the following command structure: diff --git a/telemetry-server.slnx b/b4e-telemetry.slnx similarity index 100% rename from telemetry-server.slnx rename to b4e-telemetry.slnx diff --git a/docker-compose.yml b/docker-compose.yml index c78fd92..0e128c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: telemetry-api: container_name: telemetry-api - image: ghcr.io/pyrevitlabs/telemetry-server:2.0.14-alpha + image: ghcr.io/Bim4Everyone/b4e-telemetry:2.0.0 ports: - "8080:8080" environment: diff --git a/src/Telemetry.Api/Application/DTOs/LogRecordDto.cs b/src/Telemetry.Api/Application/DTOs/LogRecordDto.cs new file mode 100644 index 0000000..480343d --- /dev/null +++ b/src/Telemetry.Api/Application/DTOs/LogRecordDto.cs @@ -0,0 +1,147 @@ +using System.Text.Json.Serialization; +using System.ComponentModel.DataAnnotations; +using Telemetry.Api.Domain.Constants; +using Telemetry.Api.JsonConverters; + +namespace Telemetry.Api.Application.DTOs +{ + /// + /// Log event record class. + /// + public sealed class LogRecordDto + { + /// + /// The time at which the event occurred. + /// + [JsonPropertyName(PropertyNames.Timestamp)] + public DateTimeOffset Timestamp { get; init; } + + /// + /// The level of the event. + /// + [MaxLength(100)] + [JsonPropertyName(PropertyNames.LogLevel)] + public required string Level { get; init; } + + /// + /// The message template describing the event. + /// + [MaxLength(8000)] + [JsonPropertyName(PropertyNames.MessageTemplate)] + public required string MessageTemplate { get; init; } + + /// + /// The render message describing the event. + /// + [MaxLength(8000)] + [JsonPropertyName(PropertyNames.RenderedMessage)] + public required string RenderedMessage { get; init; } + + /// + /// An exception associated with the event, or null. + /// + [MaxLength(8000)] + [JsonPropertyName(PropertyNames.Exception)] + [JsonConverter(typeof(DynamicDataJsonConverter))] + public string? Exception { get; init; } + + /// + /// Revit Session Id (unique revit start instance) + /// + [JsonPropertyName(PropertyNames.LogSessionId)] + public Guid SessionId { get; init; } + + /// + /// Revit plugin name. + /// + [MaxLength(100)] + [JsonPropertyName(PropertyNames.PluginName)] + public required string PluginName { get; init; } + + /// + /// Revit Plugin Session Id (unique revit plugin start instance) + /// + [JsonPropertyName(PropertyNames.PluginSessionId)] + public Guid PluginSessionId { get; init; } + + /// + /// Environment UserName + /// + [MaxLength(100)] + [JsonPropertyName(PropertyNames.EnvironmentUserName)] + public required string EnvironmentUserName { get; init; } + + /// + /// Environment MachineName + /// + [MaxLength(100)] + [JsonPropertyName(PropertyNames.EnvironmentMachineName)] + public required string EnvironmentMachineName { get; init; } + + /// + /// Revit VersionBuild + /// property. + /// + [MaxLength(100)] + [JsonPropertyName(PropertyNames.LogRevitBuild)] + public required string RevitBuild { get; init; } + + /// + /// Revit VersionNumber + /// property. + /// + [JsonPropertyName(PropertyNames.LogRevitVersion)] + public int RevitVersion { get; init; } + + /// + /// Revit Language property. + /// + [MaxLength(100)] + [JsonPropertyName(PropertyNames.RevitLanguage)] + public required string RevitLanguage { get; init; } + + /// + /// Revit Username property. + /// + [MaxLength(100)] + [JsonPropertyName(PropertyNames.RevitUserName)] + public string? RevitUserName { get; init; } + + /// + /// Revit Document.Title + /// property. + /// + [MaxLength(250)] + [JsonPropertyName(PropertyNames.RevitDocumentTitle)] + public string? RevitDocumentTitle { get; init; } + + /// + /// Revit Document.PathName + /// property. + /// + [MaxLength(1024)] + [JsonPropertyName(PropertyNames.RevitDocumentPathName)] + public string? RevitDocumentPathName { get; init; } + + /// + /// Revit ModelPath property. + ///
If + /// Document.IsModelInCloud is true + /// Document.GetCloudModelPath(). + ///
If + /// Document.IsWorkshared is true + /// Document.GetWorksharingCentralModelPath(). + ///
+ [MaxLength(1024)] + [JsonPropertyName(PropertyNames.RevitDocumentModelPath)] + public string? RevitDocumentModelPath { get; init; } + + /// + /// Dynamic properties data. + /// + [MaxLength(8000)] + [JsonPropertyName(PropertyNames.LogEvent)] + [JsonConverter(typeof(DynamicDataJsonConverter))] + public string? LogEvent { get; init; } + } +} \ No newline at end of file diff --git a/src/Telemetry.Api/Application/Interfaces/IApplicationDbContext.cs b/src/Telemetry.Api/Application/Interfaces/IApplicationDbContext.cs index 3a00103..a9f1eea 100644 --- a/src/Telemetry.Api/Application/Interfaces/IApplicationDbContext.cs +++ b/src/Telemetry.Api/Application/Interfaces/IApplicationDbContext.cs @@ -21,6 +21,12 @@ public interface IApplicationDbContext /// A task that represents the asynchronous add operation. Task AddScriptRecord(ScriptRecord scriptRecord, CancellationToken cancellationToken); + /// + /// Asynchronously adds a new log record to the database context. + /// + /// A task that represents the asynchronous add operation. + Task AddLogRecord(LogRecord logRecord, CancellationToken cancellationToken); + /// /// Asynchronously saves all changes made in the current context to the database. /// diff --git a/src/Telemetry.Api/Application/Mappings/TelemetryMappingExtensions.cs b/src/Telemetry.Api/Application/Mappings/TelemetryMappingExtensions.cs index 808d62e..8d4d0d5 100644 --- a/src/Telemetry.Api/Application/Mappings/TelemetryMappingExtensions.cs +++ b/src/Telemetry.Api/Application/Mappings/TelemetryMappingExtensions.cs @@ -208,5 +208,66 @@ public static EventRecordDto ToDto(this EventRecord model) EventArgs = model.EventArgs }; } + + /// + /// Transforms the provided DTO into its corresponding model representation. + /// + /// The data transfer object to be transformed. + /// An instance of the model derived from the given DTO. + public static LogRecord ToModel(this LogRecordDto dto) + { + return new LogRecord + { + Id = Guid.CreateVersion7(), + Timestamp = dto.Timestamp, + Level = dto.Level, + MessageTemplate = dto.MessageTemplate, + RenderedMessage = dto.RenderedMessage, + Exception = dto.Exception, + SessionId = dto.SessionId, + PluginName = dto.PluginName, + PluginSessionId = dto.PluginSessionId, + EnvironmentUserName = dto.EnvironmentUserName, + EnvironmentMachineName = dto.EnvironmentMachineName, + RevitBuild = dto.RevitBuild, + RevitVersion = dto.RevitVersion, + RevitLanguage = dto.RevitLanguage, + RevitUserName = dto.RevitUserName, + RevitDocumentTitle = dto.RevitDocumentTitle, + RevitDocumentPathName = dto.RevitDocumentPathName, + RevitDocumentModelPath = dto.RevitDocumentModelPath, + LogEvent = dto.LogEvent + }; + } + + /// + /// Converts the specified model object to a data transfer object representation. + /// + /// The model object to be converted. + /// A new instance of the data transfer object created from the source. + public static LogRecordDto ToDto(this LogRecord model) + { + return new LogRecordDto + { + Timestamp = model.Timestamp, + Level = model.Level, + MessageTemplate = model.MessageTemplate, + RenderedMessage = model.RenderedMessage, + Exception = model.Exception, + SessionId = model.SessionId, + PluginName = model.PluginName, + PluginSessionId = model.PluginSessionId, + EnvironmentUserName = model.EnvironmentUserName, + EnvironmentMachineName = model.EnvironmentMachineName, + RevitBuild = model.RevitBuild, + RevitVersion = model.RevitVersion, + RevitLanguage = model.RevitLanguage, + RevitUserName = model.RevitUserName, + RevitDocumentTitle = model.RevitDocumentTitle, + RevitDocumentPathName = model.RevitDocumentPathName, + RevitDocumentModelPath = model.RevitDocumentModelPath, + LogEvent = model.LogEvent + }; + } } } \ No newline at end of file diff --git a/src/Telemetry.Api/Domain/Constants/PropertyNames.cs b/src/Telemetry.Api/Domain/Constants/PropertyNames.cs index e9ccb31..54f6847 100644 --- a/src/Telemetry.Api/Domain/Constants/PropertyNames.cs +++ b/src/Telemetry.Api/Domain/Constants/PropertyNames.cs @@ -11,6 +11,9 @@ public static class PropertyNames /// Session identifier. public const string SessionId = "sessionid"; + /// Session identifier. + public const string LogSessionId = "session_id"; + /// Timestamp. public const string Timestamp = "timestamp"; @@ -23,9 +26,15 @@ public static class PropertyNames /// Revit build version. public const string RevitBuild = "revitbuild"; + /// Revit build version. + public const string LogRevitBuild = "revit_build"; + /// Revit version. public const string RevitVersion = "revit"; + /// Revit version. + public const string LogRevitVersion = "revit_version"; + /// pyRevit version. public const string PyRevitVersion = "pyrevit"; @@ -127,5 +136,50 @@ public static class PropertyNames /// Event arguments. public const string EventArgs = "args"; + + /// Specifies the level of logging detail. + public const string LogLevel = "log_level"; + + /// Message template. + public const string MessageTemplate = "message_template"; + + /// Rendered message. + public const string RenderedMessage = "rendered_message"; + + /// Exception. + public const string Exception = "exception"; + + /// Plugin name. + public const string PluginName = "plugin_name"; + + /// Plugin session identifier. + public const string PluginSessionId = "plugin_session_id"; + + /// Environment username. + public const string EnvironmentUserName = "env_username"; + + /// Environment machine name. + public const string EnvironmentMachineName = "env_machinename"; + + /// Revit language. + public const string RevitLanguage = "revit_language"; + + /// Revit username. + public const string RevitUserName = "revit_username"; + + /// Document title. + public const string RevitDocumentTitle = "doc_title"; + + /// Document path name. + public const string RevitDocumentPathName = "doc_pathname"; + + /// Document model path. + public const string RevitDocumentModelPath = "doc_modelpath"; + + /// Log event. + public const string LogEvent = "log_event"; + + /// Meta information. + public const string Meta = "meta"; } } \ No newline at end of file diff --git a/src/Telemetry.Api/Domain/Models/LogRecord.cs b/src/Telemetry.Api/Domain/Models/LogRecord.cs new file mode 100644 index 0000000..af7ba88 --- /dev/null +++ b/src/Telemetry.Api/Domain/Models/LogRecord.cs @@ -0,0 +1,142 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Telemetry.Api.Domain.Constants; + +namespace Telemetry.Api.Domain.Models +{ + /// + /// Log record information. + /// + [Table("logs")] + public class LogRecord + { + /// + /// Log id. + /// + [Key] + [Column(PropertyNames.Id)] + public Guid Id { get; init; } + + /// + /// The time at which the event occurred. + /// + [Column(PropertyNames.Timestamp)] + public DateTimeOffset Timestamp { get; init; } + + /// + /// The level of the event. + /// + [MaxLength(100)] + [Column(PropertyNames.LogLevel)] + public required string Level { get; init; } + + /// + /// The message template describing the event. + /// + [MaxLength(8000)] + [Column(PropertyNames.MessageTemplate)] + public required string MessageTemplate { get; init; } + + /// + /// The render message describing the event. + /// + [MaxLength(8000)] + [Column(PropertyNames.RenderedMessage)] + public required string RenderedMessage { get; init; } + + /// + /// An exception associated with the event, or null. + /// + [MaxLength(8000)] + [Column(PropertyNames.Exception)] + public string? Exception { get; init; } + + /// + /// Revit Session Id (unique revit start instance) + /// + [Column(PropertyNames.LogSessionId)] + public Guid SessionId { get; init; } + + /// + /// Revit plugin name. + /// + [MaxLength(100)] + [Column(PropertyNames.PluginName)] + public required string PluginName { get; init; } + + /// + /// Revit Plugin Session Id (unique revit plugin start instance) + /// + [Column(PropertyNames.PluginSessionId)] + public Guid PluginSessionId { get; init; } + + /// + /// Environment UserName + /// + [MaxLength(100)] + [Column(PropertyNames.EnvironmentUserName)] + public required string EnvironmentUserName { get; init; } + + /// + /// Environment MachineName + /// + [MaxLength(100)] + [Column(PropertyNames.EnvironmentMachineName)] + public required string EnvironmentMachineName { get; init; } + + /// + /// Revit VersionBuild property. + /// + [MaxLength(100)] + [Column(PropertyNames.LogRevitBuild)] + public required string RevitBuild { get; init; } + + /// + /// Revit VersionNumber property. + /// + [Column(PropertyNames.LogRevitVersion)] + public int RevitVersion { get; init; } + + /// + /// Revit Language property. + /// + [MaxLength(100)] + [Column(PropertyNames.RevitLanguage)] + public required string RevitLanguage { get; init; } + + /// + /// Revit Username property. + /// + [MaxLength(100)] + [Column(PropertyNames.RevitUserName)] + public string? RevitUserName { get; init; } + + /// + /// Revit Document.Title property. + /// + [MaxLength(250)] + [Column(PropertyNames.RevitDocumentTitle)] + public string? RevitDocumentTitle { get; init; } + + /// + /// Revit Document.PathName property. + /// + [MaxLength(1024)] + [Column(PropertyNames.RevitDocumentPathName)] + public string? RevitDocumentPathName { get; init; } + + /// + /// Revit ModelPath property. + /// + [MaxLength(1024)] + [Column(PropertyNames.RevitDocumentModelPath)] + public string? RevitDocumentModelPath { get; init; } + + /// + /// Dynamic properties data. + /// + [MaxLength(8000)] + [Column(PropertyNames.LogEvent)] + public string? LogEvent { get; init; } + } +} diff --git a/src/Telemetry.Api/Infrastructure/Persistence/ApplicationDbContext.cs b/src/Telemetry.Api/Infrastructure/Persistence/ApplicationDbContext.cs index 3b0f632..9b00321 100644 --- a/src/Telemetry.Api/Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/Telemetry.Api/Infrastructure/Persistence/ApplicationDbContext.cs @@ -53,6 +53,15 @@ public ApplicationDbContext(DbContextOptions options) : ba /// public DbSet ScriptRecords => Set(); + /// + /// Gets or sets the collection of log records associated with the current context. + /// + /// + /// This property contains a list of log data, which can be used for tracking + /// application events and errors. + /// + public DbSet LogRecords => Set(); + /// /// Asynchronously adds a new event record to the database context. /// @@ -71,6 +80,15 @@ public async Task AddScriptRecord(ScriptRecord scriptRecord, CancellationToken c await ScriptRecords.AddAsync(scriptRecord, cancellationToken); } + /// + /// Asynchronously adds a new log record to the database context. + /// + /// A task that represents the asynchronous add operation. + public async Task AddLogRecord(LogRecord logRecord, CancellationToken cancellationToken) + { + await LogRecords.AddAsync(logRecord, cancellationToken); + } + /// /// Asynchronously checks whether a successful connection to the database can be established. /// This method verifies connectivity without performing any data manipulation or transactions. @@ -187,6 +205,12 @@ private static void ConfigureMongoDbModel(ModelBuilder modelBuilder) entity.Property(e => e.Timestamp) .HasBsonRepresentation(BsonType.DateTime); }); + + modelBuilder.Entity(entity => + { + entity.Property(e => e.Timestamp) + .HasBsonRepresentation(BsonType.DateTime); + }); } private void ConfigureSqlModel(ModelBuilder modelBuilder) diff --git a/src/Telemetry.Api/Infrastructure/Persistence/MongoNativeDbContext.cs b/src/Telemetry.Api/Infrastructure/Persistence/MongoNativeDbContext.cs index e442a8a..502f493 100644 --- a/src/Telemetry.Api/Infrastructure/Persistence/MongoNativeDbContext.cs +++ b/src/Telemetry.Api/Infrastructure/Persistence/MongoNativeDbContext.cs @@ -18,6 +18,7 @@ internal sealed class MongoNativeDbContext : IApplicationDbContext private readonly IMongoCollection _eventsCollection; private readonly IMongoCollection _scriptCollection; + private readonly IMongoCollection _logsCollection; static MongoNativeDbContext() { @@ -31,6 +32,7 @@ public MongoNativeDbContext(IMongoClient mongoClient, string mongodbName) _eventsCollection = _mongoDatabase.GetCollection(GetCollectionName()); _scriptCollection = _mongoDatabase.GetCollection(GetCollectionName()); + _logsCollection = _mongoDatabase.GetCollection(GetCollectionName()); } public async Task AddEventRecord(EventRecord eventRecord, CancellationToken cancellationToken) @@ -43,6 +45,11 @@ public async Task AddScriptRecord(ScriptRecord scriptRecord, CancellationToken c await _scriptCollection.InsertOneAsync(scriptRecord, new InsertOneOptions(), cancellationToken); } + public async Task AddLogRecord(LogRecord logRecord, CancellationToken cancellationToken) + { + await _logsCollection.InsertOneAsync(logRecord, new InsertOneOptions(), cancellationToken); + } + public Task SaveChangesAsync(CancellationToken cancellationToken) { return Task.FromResult(0); @@ -92,6 +99,11 @@ private static void OnModelCreating() RegisterClassScriptRecord(); } + if (!BsonClassMap.IsClassMapRegistered(typeof(LogRecord))) + { + RegisterClassLogRecord(); + } + if (!BsonClassMap.IsClassMapRegistered(typeof(TraceInfo))) { BsonClassMap.RegisterClassMap(classMap => { @@ -206,5 +218,53 @@ private static void RegisterClassScriptRecord() .SetSerializer(new DynamicDataBsonSerializer()); }); } + + private static void RegisterClassLogRecord() + { + BsonClassMap.RegisterClassMap(classMap => + { + classMap.AutoMap(); + + classMap.MapIdProperty(p => p.Id) + .SetElementName(PropertyNames.Id) + .SetSerializer(new GuidSerializer(GuidRepresentation.Standard)); + + classMap.MapProperty(p => p.Timestamp) + .SetElementName(PropertyNames.Timestamp) + .SetSerializer(new DateTimeOffsetSerializer(BsonType.DateTime)); + + classMap.MapProperty(p => p.Level).SetElementName(PropertyNames.LogLevel); + classMap.MapProperty(p => p.MessageTemplate).SetElementName(PropertyNames.MessageTemplate); + classMap.MapProperty(p => p.RenderedMessage).SetElementName(PropertyNames.RenderedMessage); + + classMap.MapProperty(p => p.Exception) + .SetElementName(PropertyNames.Exception) + .SetSerializer(new DynamicDataBsonSerializer()); + + classMap.MapProperty(p => p.SessionId) + .SetElementName(PropertyNames.LogSessionId) + .SetSerializer(new GuidSerializer(GuidRepresentation.Standard)); + + classMap.MapProperty(p => p.PluginName).SetElementName(PropertyNames.PluginName); + + classMap.MapProperty(p => p.PluginSessionId) + .SetElementName(PropertyNames.PluginSessionId) + .SetSerializer(new GuidSerializer(GuidRepresentation.Standard)); + + classMap.MapProperty(p => p.EnvironmentUserName).SetElementName(PropertyNames.EnvironmentUserName); + classMap.MapProperty(p => p.EnvironmentMachineName).SetElementName(PropertyNames.EnvironmentMachineName); + classMap.MapProperty(p => p.RevitBuild).SetElementName(PropertyNames.LogRevitBuild); + classMap.MapProperty(p => p.RevitVersion).SetElementName(PropertyNames.LogRevitVersion); + classMap.MapProperty(p => p.RevitLanguage).SetElementName(PropertyNames.RevitLanguage); + classMap.MapProperty(p => p.RevitUserName).SetElementName(PropertyNames.RevitUserName); + classMap.MapProperty(p => p.RevitDocumentTitle).SetElementName(PropertyNames.RevitDocumentTitle); + classMap.MapProperty(p => p.RevitDocumentPathName).SetElementName(PropertyNames.RevitDocumentPathName); + classMap.MapProperty(p => p.RevitDocumentModelPath).SetElementName(PropertyNames.RevitDocumentModelPath); + + classMap.MapProperty(p => p.LogEvent) + .SetElementName(PropertyNames.LogEvent) + .SetSerializer(new DynamicDataBsonSerializer()); + }); + } } } \ No newline at end of file diff --git a/src/Telemetry.Api/Program.cs b/src/Telemetry.Api/Program.cs index 617d0e0..3b4fff3 100644 --- a/src/Telemetry.Api/Program.cs +++ b/src/Telemetry.Api/Program.cs @@ -127,7 +127,7 @@ { Name = "GNU GPL v3", Url = new Uri( - "https://raw.githubusercontent.com/pyrevitlabs/telemetry-server/refs/heads/main/LICENSE.md") + "https://raw.githubusercontent.com/Bim4Everyone/b4e-telemetry/refs/heads/main/LICENSE.md") } }); }); diff --git a/src/Telemetry.Api/Telemetry.Api.http b/src/Telemetry.Api/Telemetry.Api.http index 08cb095..72b7d47 100644 --- a/src/Telemetry.Api/Telemetry.Api.http +++ b/src/Telemetry.Api/Telemetry.Api.http @@ -89,5 +89,33 @@ Content-Type: application/json } } +### Post Log Record +POST {{Telemetry_Api_HostAddress}}/api/v2/logs +Content-Type: application/json + +{ + "timestamp": "2024-03-27T10:00:00Z", + "log_level": "Information", + "message_template": "Executed command {CommandName}", + "rendered_message": "Executed command TestCommand", + "exception": null, + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "plugin_name": "TestPlugin", + "plugin_session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "env_username": "testuser", + "env_machinename": "TEST-PC", + "revit_build": "2024.1", + "revit_version": 2024, + "revit_language": "ENU", + "revit_username": "testuser", + "doc_title": "test.rvt", + "doc_pathname": "C:\\test.rvt", + "doc_modelpath": "C:\\test.rvt", + "log_event": { + "CommandName": "TestCommand", + "Duration": 123 + } +} + ### Get Prometheus Metrics GET {{Telemetry_Api_HostAddress}}/metrics diff --git a/src/Telemetry.Api/Web/Controllers/TelemetryController.cs b/src/Telemetry.Api/Web/Controllers/TelemetryController.cs index dce449c..c6cf5f4 100644 --- a/src/Telemetry.Api/Web/Controllers/TelemetryController.cs +++ b/src/Telemetry.Api/Web/Controllers/TelemetryController.cs @@ -123,6 +123,41 @@ public async Task PostEvent([FromBody] EventRecordDto dto) } } + /// + /// Adds log record to DB. + /// + /// Adding log records. + /// Returns adding log records task. + [HttpPost("logs")] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + [ProducesResponseType(503)] + public async Task PostLog([FromBody] LogRecordDto dto) + { + try + { + CancellationToken ct = HttpContext.RequestAborted; + + LogRecord record = dto.ToModel(); + await _context.AddLogRecord(record, ct); + + await _context.SaveChangesAsync(ct); + _logger.LogInformation("Log record saved: {Level} in {PluginName}", record.Level, record.PluginName); + return Ok(); + } + catch (DbUpdateException ex) + { + _logger.LogError(ex, "Database error while saving log record"); + return StatusCode(503); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error while saving log record"); + return StatusCode(500); + } + } + /// /// Retrieves the current status. /// diff --git a/src/Telemetry.Migrations.Oracle/Migrations/20260618112248_AddLogRecord.Designer.cs b/src/Telemetry.Migrations.Oracle/Migrations/20260618112248_AddLogRecord.Designer.cs new file mode 100644 index 0000000..cbca2de --- /dev/null +++ b/src/Telemetry.Migrations.Oracle/Migrations/20260618112248_AddLogRecord.Designer.cs @@ -0,0 +1,425 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Oracle.EntityFrameworkCore.Metadata; +using Telemetry.Api.Infrastructure.Persistence; + +#nullable disable + +namespace Telemetry.Migrations.Oracle.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260618112248_AddLogRecord")] + partial class AddLogRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.EventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("RAW(16)") + .HasColumnName("_id"); + + b.Property("Cancellable") + .HasColumnType("BOOLEAN") + .HasColumnName("cancellable"); + + b.Property("Cancelled") + .HasColumnType("BOOLEAN") + .HasColumnName("cancelled"); + + b.Property("DocumentId") + .HasColumnType("NUMBER(10)") + .HasColumnName("docid"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("NVARCHAR2(1024)") + .HasColumnName("docpath"); + + b.Property("DocumentTemplate") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("doctemplate"); + + b.Property("DocumentType") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("doctype"); + + b.Property("EventArgs") + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("args"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("type"); + + b.Property("HandlerId") + .HasColumnType("RAW(16)") + .HasColumnName("handler_id"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("host_user"); + + b.Property("ProjectName") + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("projectname"); + + b.Property("ProjectNum") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("projectnum"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit"); + + b.Property("Status") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("status"); + + b.Property("Timestamp") + .HasColumnType("TIMESTAMP(7) WITH TIME ZONE") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("events"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("RAW(16)") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("RAW(16)") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("NVARCHAR2(1024)") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("NVARCHAR2(1024)") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("NUMBER(10)") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("RAW(16)") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("TIMESTAMP(7) WITH TIME ZONE") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("RAW(16)") + .HasColumnName("_id"); + + b.Property("CloneName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("clone"); + + b.Property("CommandBundle") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("commandbundle"); + + b.Property("CommandExtension") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("commandextension"); + + b.Property("CommandName") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("commandname"); + + b.Property("CommandResults") + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("commandresults"); + + b.Property("CommandUniqueName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("NVARCHAR2(500)") + .HasColumnName("commanduniquename"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("NVARCHAR2(1024)") + .HasColumnName("docpath"); + + b.Property("ExecId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("exec_id"); + + b.Property("ExecTimestamp") + .HasColumnType("TIMESTAMP(7) WITH TIME ZONE") + .HasColumnName("exec_timestamp"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("host_user"); + + b.Property("IsConfig") + .HasColumnType("BOOLEAN") + .HasColumnName("config"); + + b.Property("IsDebug") + .HasColumnType("BOOLEAN") + .HasColumnName("debug"); + + b.Property("IsExecFromGui") + .HasColumnType("BOOLEAN") + .HasColumnName("from_gui"); + + b.Property("PyRevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("pyrevit"); + + b.Property("ResultCode") + .HasColumnType("NUMBER(10)") + .HasColumnName("resultcode"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit"); + + b.Property("ScriptPath") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("NVARCHAR2(1024)") + .HasColumnName("scriptpath"); + + b.Property("SessionId") + .HasColumnType("RAW(16)") + .HasColumnName("sessionid"); + + b.Property("Timestamp") + .HasColumnType("TIMESTAMP(7) WITH TIME ZONE") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("scripts"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.OwnsOne("Telemetry.Api.Domain.Models.TraceInfo", "Trace", b1 => + { + b1.Property("ScriptRecordId") + .HasColumnType("RAW(16)"); + + b1.Property("Message") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("message"); + + b1.HasKey("ScriptRecordId"); + + b1.ToTable("scripts"); + + b1.WithOwner() + .HasForeignKey("ScriptRecordId"); + + b1.OwnsOne("Telemetry.Api.Domain.Models.EngineInfo", "Engine", b2 => + { + b2.Property("TraceInfoScriptRecordId") + .HasColumnType("RAW(16)"); + + b2.Property("Configs") + .HasMaxLength(8000) + .HasColumnType("json") + .HasColumnName("configs"); + + b2.PrimitiveCollection("SysPaths") + .HasColumnType("NVARCHAR2(2000)") + .HasColumnName("syspath"); + + b2.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("type"); + + b2.Property("Version") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("version"); + + b2.HasKey("TraceInfoScriptRecordId"); + + b2.ToTable("scripts"); + + b2.WithOwner() + .HasForeignKey("TraceInfoScriptRecordId"); + }); + + b1.Navigation("Engine") + .IsRequired(); + }); + + b.Navigation("Trace") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Telemetry.Migrations.Oracle/Migrations/20260618112248_AddLogRecord.cs b/src/Telemetry.Migrations.Oracle/Migrations/20260618112248_AddLogRecord.cs new file mode 100644 index 0000000..eea3a53 --- /dev/null +++ b/src/Telemetry.Migrations.Oracle/Migrations/20260618112248_AddLogRecord.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Telemetry.Migrations.Oracle.Migrations +{ + /// + public partial class AddLogRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "logs", + columns: table => new + { + _id = table.Column(type: "RAW(16)", nullable: false), + timestamp = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), + log_level = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false), + message_template = table.Column(type: "NCLOB", maxLength: 8000, nullable: false), + rendered_message = table.Column(type: "NCLOB", maxLength: 8000, nullable: false), + exception = table.Column(type: "NCLOB", maxLength: 8000, nullable: true), + session_id = table.Column(type: "RAW(16)", nullable: false), + plugin_name = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false), + plugin_session_id = table.Column(type: "RAW(16)", nullable: false), + env_username = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false), + env_machinename = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false), + revit_build = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false), + revit_version = table.Column(type: "NUMBER(10)", nullable: false), + revit_language = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: false), + revit_username = table.Column(type: "NVARCHAR2(100)", maxLength: 100, nullable: true), + doc_title = table.Column(type: "NVARCHAR2(250)", maxLength: 250, nullable: true), + doc_pathname = table.Column(type: "NVARCHAR2(1024)", maxLength: 1024, nullable: true), + doc_modelpath = table.Column(type: "NVARCHAR2(1024)", maxLength: 1024, nullable: true), + log_event = table.Column(type: "NCLOB", maxLength: 8000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_logs", x => x._id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "logs"); + } + } +} diff --git a/src/Telemetry.Migrations.Oracle/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Telemetry.Migrations.Oracle/Migrations/ApplicationDbContextModelSnapshot.cs index 8077c90..4b2b37c 100644 --- a/src/Telemetry.Migrations.Oracle/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Telemetry.Migrations.Oracle/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -123,6 +123,112 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("events"); }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("RAW(16)") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("RAW(16)") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("NCLOB") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("NVARCHAR2(1024)") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("NVARCHAR2(1024)") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("NVARCHAR2(250)") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("NVARCHAR2(100)") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("NUMBER(10)") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("RAW(16)") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("TIMESTAMP(7) WITH TIME ZONE") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => { b.Property("Id") diff --git a/src/Telemetry.Migrations.Postgres/Migrations/20260618112253_AddLogRecord.Designer.cs b/src/Telemetry.Migrations.Postgres/Migrations/20260618112253_AddLogRecord.Designer.cs new file mode 100644 index 0000000..ca37ca8 --- /dev/null +++ b/src/Telemetry.Migrations.Postgres/Migrations/20260618112253_AddLogRecord.Designer.cs @@ -0,0 +1,425 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Telemetry.Api.Infrastructure.Persistence; + +#nullable disable + +namespace Telemetry.Migrations.Postgres.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260618112253_AddLogRecord")] + partial class AddLogRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.EventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("_id"); + + b.Property("Cancellable") + .HasColumnType("boolean") + .HasColumnName("cancellable"); + + b.Property("Cancelled") + .HasColumnType("boolean") + .HasColumnName("cancelled"); + + b.Property("DocumentId") + .HasColumnType("integer") + .HasColumnName("docid"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("docpath"); + + b.Property("DocumentTemplate") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("doctemplate"); + + b.Property("DocumentType") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("doctype"); + + b.Property("EventArgs") + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("args"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("type"); + + b.Property("HandlerId") + .HasColumnType("uuid") + .HasColumnName("handler_id"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("host_user"); + + b.Property("ProjectName") + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("projectname"); + + b.Property("ProjectNum") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("projectnum"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit"); + + b.Property("Status") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("status"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("events"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("uuid") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("integer") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("uuid") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("_id"); + + b.Property("CloneName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("clone"); + + b.Property("CommandBundle") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("commandbundle"); + + b.Property("CommandExtension") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("commandextension"); + + b.Property("CommandName") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("commandname"); + + b.Property("CommandResults") + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("commandresults"); + + b.Property("CommandUniqueName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("commanduniquename"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("docpath"); + + b.Property("ExecId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("exec_id"); + + b.Property("ExecTimestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("exec_timestamp"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("host_user"); + + b.Property("IsConfig") + .HasColumnType("boolean") + .HasColumnName("config"); + + b.Property("IsDebug") + .HasColumnType("boolean") + .HasColumnName("debug"); + + b.Property("IsExecFromGui") + .HasColumnType("boolean") + .HasColumnName("from_gui"); + + b.Property("PyRevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("pyrevit"); + + b.Property("ResultCode") + .HasColumnType("integer") + .HasColumnName("resultcode"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit"); + + b.Property("ScriptPath") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("scriptpath"); + + b.Property("SessionId") + .HasColumnType("uuid") + .HasColumnName("sessionid"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("scripts"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.OwnsOne("Telemetry.Api.Domain.Models.TraceInfo", "Trace", b1 => + { + b1.Property("ScriptRecordId") + .HasColumnType("uuid"); + + b1.Property("Message") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("message"); + + b1.HasKey("ScriptRecordId"); + + b1.ToTable("scripts"); + + b1.WithOwner() + .HasForeignKey("ScriptRecordId"); + + b1.OwnsOne("Telemetry.Api.Domain.Models.EngineInfo", "Engine", b2 => + { + b2.Property("TraceInfoScriptRecordId") + .HasColumnType("uuid"); + + b2.Property("Configs") + .HasMaxLength(8000) + .HasColumnType("jsonb") + .HasColumnName("configs"); + + b2.PrimitiveCollection("SysPaths") + .HasColumnType("text[]") + .HasColumnName("syspath"); + + b2.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("type"); + + b2.Property("Version") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("version"); + + b2.HasKey("TraceInfoScriptRecordId"); + + b2.ToTable("scripts"); + + b2.WithOwner() + .HasForeignKey("TraceInfoScriptRecordId"); + }); + + b1.Navigation("Engine") + .IsRequired(); + }); + + b.Navigation("Trace") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Telemetry.Migrations.Postgres/Migrations/20260618112253_AddLogRecord.cs b/src/Telemetry.Migrations.Postgres/Migrations/20260618112253_AddLogRecord.cs new file mode 100644 index 0000000..daa062f --- /dev/null +++ b/src/Telemetry.Migrations.Postgres/Migrations/20260618112253_AddLogRecord.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Telemetry.Migrations.Postgres.Migrations +{ + /// + public partial class AddLogRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "logs", + columns: table => new + { + _id = table.Column(type: "uuid", nullable: false), + timestamp = table.Column(type: "timestamp with time zone", nullable: false), + log_level = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + message_template = table.Column(type: "character varying(8000)", maxLength: 8000, nullable: false), + rendered_message = table.Column(type: "character varying(8000)", maxLength: 8000, nullable: false), + exception = table.Column(type: "character varying(8000)", maxLength: 8000, nullable: true), + session_id = table.Column(type: "uuid", nullable: false), + plugin_name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + plugin_session_id = table.Column(type: "uuid", nullable: false), + env_username = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + env_machinename = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + revit_build = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + revit_version = table.Column(type: "integer", nullable: false), + revit_language = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + revit_username = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + doc_title = table.Column(type: "character varying(250)", maxLength: 250, nullable: true), + doc_pathname = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + doc_modelpath = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + log_event = table.Column(type: "character varying(8000)", maxLength: 8000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_logs", x => x._id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "logs"); + } + } +} diff --git a/src/Telemetry.Migrations.Postgres/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Telemetry.Migrations.Postgres/Migrations/ApplicationDbContextModelSnapshot.cs index c912458..83f23a8 100644 --- a/src/Telemetry.Migrations.Postgres/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Telemetry.Migrations.Postgres/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -123,6 +123,112 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("events"); }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("uuid") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("character varying(250)") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("integer") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("uuid") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => { b.Property("Id") diff --git a/src/Telemetry.Migrations.SqlServer/Migrations/20260618112305_AddLogRecord.Designer.cs b/src/Telemetry.Migrations.SqlServer/Migrations/20260618112305_AddLogRecord.Designer.cs new file mode 100644 index 0000000..f11d942 --- /dev/null +++ b/src/Telemetry.Migrations.SqlServer/Migrations/20260618112305_AddLogRecord.Designer.cs @@ -0,0 +1,425 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Telemetry.Api.Infrastructure.Persistence; + +#nullable disable + +namespace Telemetry.Migrations.SqlServer.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260618112305_AddLogRecord")] + partial class AddLogRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.EventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("_id"); + + b.Property("Cancellable") + .HasColumnType("bit") + .HasColumnName("cancellable"); + + b.Property("Cancelled") + .HasColumnType("bit") + .HasColumnName("cancelled"); + + b.Property("DocumentId") + .HasColumnType("int") + .HasColumnName("docid"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)") + .HasColumnName("docpath"); + + b.Property("DocumentTemplate") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("doctemplate"); + + b.Property("DocumentType") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("doctype"); + + b.Property("EventArgs") + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("args"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("type"); + + b.Property("HandlerId") + .HasColumnType("uniqueidentifier") + .HasColumnName("handler_id"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("host_user"); + + b.Property("ProjectName") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("projectname"); + + b.Property("ProjectNum") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("projectnum"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit"); + + b.Property("Status") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("status"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("events"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("int") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("_id"); + + b.Property("CloneName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("clone"); + + b.Property("CommandBundle") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("commandbundle"); + + b.Property("CommandExtension") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("commandextension"); + + b.Property("CommandName") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("commandname"); + + b.Property("CommandResults") + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("commandresults"); + + b.Property("CommandUniqueName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("commanduniquename"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)") + .HasColumnName("docpath"); + + b.Property("ExecId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("exec_id"); + + b.Property("ExecTimestamp") + .HasColumnType("datetimeoffset") + .HasColumnName("exec_timestamp"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("host_user"); + + b.Property("IsConfig") + .HasColumnType("bit") + .HasColumnName("config"); + + b.Property("IsDebug") + .HasColumnType("bit") + .HasColumnName("debug"); + + b.Property("IsExecFromGui") + .HasColumnType("bit") + .HasColumnName("from_gui"); + + b.Property("PyRevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("pyrevit"); + + b.Property("ResultCode") + .HasColumnType("int") + .HasColumnName("resultcode"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit"); + + b.Property("ScriptPath") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)") + .HasColumnName("scriptpath"); + + b.Property("SessionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("sessionid"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("scripts"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.OwnsOne("Telemetry.Api.Domain.Models.TraceInfo", "Trace", b1 => + { + b1.Property("ScriptRecordId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Message") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("message"); + + b1.HasKey("ScriptRecordId"); + + b1.ToTable("scripts"); + + b1.WithOwner() + .HasForeignKey("ScriptRecordId"); + + b1.OwnsOne("Telemetry.Api.Domain.Models.EngineInfo", "Engine", b2 => + { + b2.Property("TraceInfoScriptRecordId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Configs") + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("configs"); + + b2.PrimitiveCollection("SysPaths") + .HasColumnType("nvarchar(max)") + .HasColumnName("syspath"); + + b2.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("type"); + + b2.Property("Version") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("version"); + + b2.HasKey("TraceInfoScriptRecordId"); + + b2.ToTable("scripts"); + + b2.WithOwner() + .HasForeignKey("TraceInfoScriptRecordId"); + }); + + b1.Navigation("Engine") + .IsRequired(); + }); + + b.Navigation("Trace") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Telemetry.Migrations.SqlServer/Migrations/20260618112305_AddLogRecord.cs b/src/Telemetry.Migrations.SqlServer/Migrations/20260618112305_AddLogRecord.cs new file mode 100644 index 0000000..aeecbce --- /dev/null +++ b/src/Telemetry.Migrations.SqlServer/Migrations/20260618112305_AddLogRecord.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Telemetry.Migrations.SqlServer.Migrations +{ + /// + public partial class AddLogRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "logs", + columns: table => new + { + _id = table.Column(type: "uniqueidentifier", nullable: false), + timestamp = table.Column(type: "datetimeoffset", nullable: false), + log_level = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + message_template = table.Column(type: "nvarchar(max)", maxLength: 8000, nullable: false), + rendered_message = table.Column(type: "nvarchar(max)", maxLength: 8000, nullable: false), + exception = table.Column(type: "nvarchar(max)", maxLength: 8000, nullable: true), + session_id = table.Column(type: "uniqueidentifier", nullable: false), + plugin_name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + plugin_session_id = table.Column(type: "uniqueidentifier", nullable: false), + env_username = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + env_machinename = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + revit_build = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + revit_version = table.Column(type: "int", nullable: false), + revit_language = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + revit_username = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + doc_title = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: true), + doc_pathname = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true), + doc_modelpath = table.Column(type: "nvarchar(1024)", maxLength: 1024, nullable: true), + log_event = table.Column(type: "nvarchar(max)", maxLength: 8000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_logs", x => x._id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "logs"); + } + } +} diff --git a/src/Telemetry.Migrations.SqlServer/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Telemetry.Migrations.SqlServer/Migrations/ApplicationDbContextModelSnapshot.cs index 67fb58f..d6af22b 100644 --- a/src/Telemetry.Migrations.SqlServer/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Telemetry.Migrations.SqlServer/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -123,6 +123,112 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("events"); }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("nvarchar(250)") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("int") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => { b.Property("Id") diff --git a/src/Telemetry.Migrations.Sqlite/Migrations/20260618112300_AddLogRecord.Designer.cs b/src/Telemetry.Migrations.Sqlite/Migrations/20260618112300_AddLogRecord.Designer.cs new file mode 100644 index 0000000..bd73252 --- /dev/null +++ b/src/Telemetry.Migrations.Sqlite/Migrations/20260618112300_AddLogRecord.Designer.cs @@ -0,0 +1,420 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Telemetry.Api.Infrastructure.Persistence; + +#nullable disable + +namespace Telemetry.Migrations.Sqlite.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260618112300_AddLogRecord")] + partial class AddLogRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.9"); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.EventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("_id"); + + b.Property("Cancellable") + .HasColumnType("INTEGER") + .HasColumnName("cancellable"); + + b.Property("Cancelled") + .HasColumnType("INTEGER") + .HasColumnName("cancelled"); + + b.Property("DocumentId") + .HasColumnType("INTEGER") + .HasColumnName("docid"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("TEXT") + .HasColumnName("docpath"); + + b.Property("DocumentTemplate") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("doctemplate"); + + b.Property("DocumentType") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("doctype"); + + b.Property("EventArgs") + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("args"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("HandlerId") + .HasColumnType("TEXT") + .HasColumnName("handler_id"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("host_user"); + + b.Property("ProjectName") + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("projectname"); + + b.Property("ProjectNum") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("projectnum"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit"); + + b.Property("Status") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("status"); + + b.Property("Timestamp") + .HasColumnType("TEXT") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("events"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("TEXT") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("TEXT") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("TEXT") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("INTEGER") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("TEXT") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("TEXT") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("_id"); + + b.Property("CloneName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("clone"); + + b.Property("CommandBundle") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("commandbundle"); + + b.Property("CommandExtension") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("commandextension"); + + b.Property("CommandName") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("commandname"); + + b.Property("CommandResults") + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("commandresults"); + + b.Property("CommandUniqueName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasColumnName("commanduniquename"); + + b.Property("DocumentName") + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("docname"); + + b.Property("DocumentPath") + .HasMaxLength(1024) + .HasColumnType("TEXT") + .HasColumnName("docpath"); + + b.Property("ExecId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("exec_id"); + + b.Property("ExecTimestamp") + .HasColumnType("TEXT") + .HasColumnName("exec_timestamp"); + + b.Property("HostUsername") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("host_user"); + + b.Property("IsConfig") + .HasColumnType("INTEGER") + .HasColumnName("config"); + + b.Property("IsDebug") + .HasColumnType("INTEGER") + .HasColumnName("debug"); + + b.Property("IsExecFromGui") + .HasColumnType("INTEGER") + .HasColumnName("from_gui"); + + b.Property("PyRevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("pyrevit"); + + b.Property("ResultCode") + .HasColumnType("INTEGER") + .HasColumnName("resultcode"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revitbuild"); + + b.Property("RevitVersion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit"); + + b.Property("ScriptPath") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT") + .HasColumnName("scriptpath"); + + b.Property("SessionId") + .HasColumnType("TEXT") + .HasColumnName("sessionid"); + + b.Property("Timestamp") + .HasColumnType("TEXT") + .HasColumnName("timestamp"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.HasKey("Id"); + + b.ToTable("scripts"); + }); + + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => + { + b.OwnsOne("Telemetry.Api.Domain.Models.TraceInfo", "Trace", b1 => + { + b1.Property("ScriptRecordId") + .HasColumnType("TEXT"); + + b1.Property("Message") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b1.HasKey("ScriptRecordId"); + + b1.ToTable("scripts"); + + b1.WithOwner() + .HasForeignKey("ScriptRecordId"); + + b1.OwnsOne("Telemetry.Api.Domain.Models.EngineInfo", "Engine", b2 => + { + b2.Property("TraceInfoScriptRecordId") + .HasColumnType("TEXT"); + + b2.Property("Configs") + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("configs"); + + b2.PrimitiveCollection("SysPaths") + .HasColumnType("TEXT") + .HasColumnName("syspath"); + + b2.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("type"); + + b2.Property("Version") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("version"); + + b2.HasKey("TraceInfoScriptRecordId"); + + b2.ToTable("scripts"); + + b2.WithOwner() + .HasForeignKey("TraceInfoScriptRecordId"); + }); + + b1.Navigation("Engine") + .IsRequired(); + }); + + b.Navigation("Trace") + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Telemetry.Migrations.Sqlite/Migrations/20260618112300_AddLogRecord.cs b/src/Telemetry.Migrations.Sqlite/Migrations/20260618112300_AddLogRecord.cs new file mode 100644 index 0000000..bfe3a84 --- /dev/null +++ b/src/Telemetry.Migrations.Sqlite/Migrations/20260618112300_AddLogRecord.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Telemetry.Migrations.Sqlite.Migrations +{ + /// + public partial class AddLogRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "logs", + columns: table => new + { + _id = table.Column(type: "TEXT", nullable: false), + timestamp = table.Column(type: "TEXT", nullable: false), + log_level = table.Column(type: "TEXT", maxLength: 100, nullable: false), + message_template = table.Column(type: "TEXT", maxLength: 8000, nullable: false), + rendered_message = table.Column(type: "TEXT", maxLength: 8000, nullable: false), + exception = table.Column(type: "TEXT", maxLength: 8000, nullable: true), + session_id = table.Column(type: "TEXT", nullable: false), + plugin_name = table.Column(type: "TEXT", maxLength: 100, nullable: false), + plugin_session_id = table.Column(type: "TEXT", nullable: false), + env_username = table.Column(type: "TEXT", maxLength: 100, nullable: false), + env_machinename = table.Column(type: "TEXT", maxLength: 100, nullable: false), + revit_build = table.Column(type: "TEXT", maxLength: 100, nullable: false), + revit_version = table.Column(type: "INTEGER", nullable: false), + revit_language = table.Column(type: "TEXT", maxLength: 100, nullable: false), + revit_username = table.Column(type: "TEXT", maxLength: 100, nullable: true), + doc_title = table.Column(type: "TEXT", maxLength: 250, nullable: true), + doc_pathname = table.Column(type: "TEXT", maxLength: 1024, nullable: true), + doc_modelpath = table.Column(type: "TEXT", maxLength: 1024, nullable: true), + log_event = table.Column(type: "TEXT", maxLength: 8000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_logs", x => x._id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "logs"); + } + } +} diff --git a/src/Telemetry.Migrations.Sqlite/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Telemetry.Migrations.Sqlite/Migrations/ApplicationDbContextModelSnapshot.cs index d677c7e..dec7840 100644 --- a/src/Telemetry.Migrations.Sqlite/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Telemetry.Migrations.Sqlite/Migrations/ApplicationDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class ApplicationDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.9"); modelBuilder.Entity("Telemetry.Api.Domain.Models.EventRecord", b => { @@ -118,6 +118,112 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("events"); }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.LogRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("_id"); + + b.Property("EnvironmentMachineName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("env_machinename"); + + b.Property("EnvironmentUserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("env_username"); + + b.Property("Exception") + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("exception"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("log_level"); + + b.Property("LogEvent") + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("log_event"); + + b.Property("MessageTemplate") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("message_template"); + + b.Property("PluginName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("plugin_name"); + + b.Property("PluginSessionId") + .HasColumnType("TEXT") + .HasColumnName("plugin_session_id"); + + b.Property("RenderedMessage") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("TEXT") + .HasColumnName("rendered_message"); + + b.Property("RevitBuild") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit_build"); + + b.Property("RevitDocumentModelPath") + .HasMaxLength(1024) + .HasColumnType("TEXT") + .HasColumnName("doc_modelpath"); + + b.Property("RevitDocumentPathName") + .HasMaxLength(1024) + .HasColumnType("TEXT") + .HasColumnName("doc_pathname"); + + b.Property("RevitDocumentTitle") + .HasMaxLength(250) + .HasColumnType("TEXT") + .HasColumnName("doc_title"); + + b.Property("RevitLanguage") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit_language"); + + b.Property("RevitUserName") + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasColumnName("revit_username"); + + b.Property("RevitVersion") + .HasColumnType("INTEGER") + .HasColumnName("revit_version"); + + b.Property("SessionId") + .HasColumnType("TEXT") + .HasColumnName("session_id"); + + b.Property("Timestamp") + .HasColumnType("TEXT") + .HasColumnName("timestamp"); + + b.HasKey("Id"); + + b.ToTable("logs"); + }); + modelBuilder.Entity("Telemetry.Api.Domain.Models.ScriptRecord", b => { b.Property("Id") diff --git a/tests/Telemetry.Api.IntegrationTests/BaseIntegrationTest.cs b/tests/Telemetry.Api.IntegrationTests/BaseIntegrationTest.cs index 51e9577..415ea6f 100644 --- a/tests/Telemetry.Api.IntegrationTests/BaseIntegrationTest.cs +++ b/tests/Telemetry.Api.IntegrationTests/BaseIntegrationTest.cs @@ -107,6 +107,30 @@ protected EventRecordDto CreateSampleEventDto() }; } + protected LogRecordDto CreateSampleLogDto() + { + return new LogRecordDto + { + Timestamp = DateTimeOffset.UtcNow, + Level = "Information", + MessageTemplate = "Test message {Property}", + RenderedMessage = "Test message value", + SessionId = Guid.NewGuid(), + PluginName = "TestPlugin", + PluginSessionId = Guid.NewGuid(), + EnvironmentUserName = "testuser", + EnvironmentMachineName = "test-machine", + RevitBuild = "2024.1", + RevitVersion = 2024, + RevitLanguage = "ENU", + RevitUserName = "revituser", + RevitDocumentTitle = "test.rvt", + RevitDocumentPathName = @"C:\test.rvt", + RevitDocumentModelPath = @"C:\test.rvt", + LogEvent = "{}" + }; + } + [Test] public async Task PostScript_ReturnsOk() { @@ -133,6 +157,19 @@ public async Task PostEvent_ReturnsOk() response.EnsureSuccessStatusCode(); } + [Test] + public async Task PostLog_ReturnsOk() + { + // Arrange + LogRecordDto dto = CreateSampleLogDto(); + + // Act + HttpResponseMessage response = await Client.PostAsJsonAsync("/api/v2/logs", dto); + + // Assert + response.EnsureSuccessStatusCode(); + } + [Test] public async Task PostRawScript_ReturnsOk() { @@ -160,5 +197,19 @@ public async Task PostRawEvent_ReturnsOk() // Assert response.EnsureSuccessStatusCode(); } + + [Test] + public async Task PostRawLog_ReturnsOk() + { + // Arrange + var content = await File.ReadAllTextAsync("assets/log.json"); + var stringContent = new StringContent(content, Encoding.UTF8, "application/json"); + + // Act + HttpResponseMessage response = await Client.PostAsync("/api/v2/logs", stringContent); + + // Assert + response.EnsureSuccessStatusCode(); + } } } \ No newline at end of file diff --git a/tests/Telemetry.Api.IntegrationTests/assets/log.json b/tests/Telemetry.Api.IntegrationTests/assets/log.json new file mode 100644 index 0000000..08be20a --- /dev/null +++ b/tests/Telemetry.Api.IntegrationTests/assets/log.json @@ -0,0 +1,23 @@ +{ + "timestamp": "2024-03-27T10:00:00Z", + "log_level": "Information", + "message_template": "User {UserName} logged in from {IpAddress}", + "rendered_message": "User testuser logged in from 127.0.0.1", + "exception": null, + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "plugin_name": "TestPlugin", + "plugin_session_id": "4fa85f64-5717-4562-b3fc-2c963f66afa7", + "env_username": "testuser", + "env_machinename": "test-machine", + "revit_build": "2024.1", + "revit_version": 2024, + "revit_language": "ENU", + "revit_username": "revit-testuser", + "doc_title": "test.rvt", + "doc_pathname": "C:\\test.rvt", + "doc_modelpath": "C:\\test.rvt", + "log_event": { + "UserName": "testuser", + "IpAddress": "127.0.0.1" + } +} diff --git a/tests/Telemetry.Api.UnitTests/Web/Controllers/TelemetryControllerTests.cs b/tests/Telemetry.Api.UnitTests/Web/Controllers/TelemetryControllerTests.cs index 3e9a243..b3fe9bd 100644 --- a/tests/Telemetry.Api.UnitTests/Web/Controllers/TelemetryControllerTests.cs +++ b/tests/Telemetry.Api.UnitTests/Web/Controllers/TelemetryControllerTests.cs @@ -64,6 +64,23 @@ public async Task PostEvent_ShouldReturnOk_WhenSuccessful() _mockContext.Verify(c => c.SaveChangesAsync(It.IsAny()), Times.Once); } + [Test] + public async Task PostLog_ShouldReturnOk_WhenSuccessful() + { + // Arrange + LogRecordDto dto = CreateValidLogRecordDto(); + _mockContext.Setup(c => c.AddLogRecord(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + // Act + IActionResult result = await _controller.PostLog(dto); + + // Assert + await Assert.That(result).IsTypeOf(); + _mockContext.Verify(c => c.AddLogRecord(It.IsAny(), It.IsAny()), Times.Once); + _mockContext.Verify(c => c.SaveChangesAsync(It.IsAny()), Times.Once); + } + [Test] public async Task GetStatus_ShouldReturnOk_WhenDatabaseConnected() { @@ -146,5 +163,24 @@ private EventRecordDto CreateValidEventRecordDto() EventArgs = "args" }; } + + private LogRecordDto CreateValidLogRecordDto() + { + return new LogRecordDto() + { + Timestamp = DateTimeOffset.Now, + Level = "Information", + MessageTemplate = "Test message", + RenderedMessage = "Test message", + SessionId = Guid.NewGuid(), + PluginName = "TestPlugin", + PluginSessionId = Guid.NewGuid(), + EnvironmentUserName = "user", + EnvironmentMachineName = "machine", + RevitBuild = "2024.1", + RevitVersion = 2024, + RevitLanguage = "ENU" + }; + } } } \ No newline at end of file