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