Skip to content

Commit

Permalink
fixes for ClientErrorModel
Browse files Browse the repository at this point in the history
  • Loading branch information
olmobrutall committed Aug 11, 2022
1 parent c9a6f6a commit 0b422b4
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 78 deletions.
2 changes: 1 addition & 1 deletion Signum.Engine/Basics/ExceptionLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static void Start(SchemaBuilder sb)
e.Id,
e.CreationDate,
e.ExceptionType,
e.IsClientSide,
e.Origin,
e.ExceptionMessage,
e.StackTraceHash,
});
Expand Down
74 changes: 25 additions & 49 deletions Signum.Entities/Basics/Exception.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
using System.Threading;
using System.Collections.Specialized;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Extensions;
using System.Net;
using Microsoft.AspNetCore.Http;

namespace Signum.Entities.Basics;

Expand All @@ -16,43 +11,30 @@ public class ExceptionEntity : Entity
#pragma warning disable CS8618 // Non-nullable field is uninitialized.
public ExceptionEntity() { }

public ExceptionEntity(ClientExceptionModel clientException, HttpContext httpContext)
public ExceptionEntity(ClientErrorModel clientError)
{
if (httpContext != null)
{
var req = httpContext.Request;
var connFeature = httpContext.Features.Get<IHttpConnectionFeature>()!;

this.ExceptionType = clientException.ExceptionType;
this.ExceptionMessage = clientException.TypeErrorMessage;
this.StackTrace = new BigStringEmbedded(clientException.TypeErrorStack);
this.ThreadId = -1;
this.UserAgent = Try(300, () => req.Headers["User-Agent"].FirstOrDefault());
this.RequestUrl = Try(int.MaxValue, () => req.GetDisplayUrl());
this.UrlReferer = Try(int.MaxValue, () => req.Headers["Referer"].ToString());
this.UserHostAddress = Try(100, () => connFeature.RemoteIpAddress?.ToString());
this.UserHostName = Try(100, () => connFeature.RemoteIpAddress == null ? null : Dns.GetHostEntry(connFeature.RemoteIpAddress).HostName);

this.MachineName = System.Environment.MachineName;
this.ApplicationName = AppDomain.CurrentDomain.FriendlyName;
this.IsClientSide = true;

this.Form = new BigStringEmbedded();
this.QueryString = new BigStringEmbedded();
this.Session = new BigStringEmbedded();
this.Data = new BigStringEmbedded();
}
this.RebindEvents();
this.ExceptionType = "/".Combine(clientError.ErrorType, clientError.Name);
this.ExceptionMessage = clientError.Message;
this.StackTrace = new BigStringEmbedded(clientError.Stack);
this.ThreadId = -1;

this.MachineName = System.Environment.MachineName;
this.ApplicationName = AppDomain.CurrentDomain.FriendlyName;
this.Origin = ExceptionOrigin.Frontend_React;
}

public ExceptionEntity(Exception ex)
{
this.RebindEvents();
this.ExceptionType = ex.GetType().Name;
this.ExceptionMessage = ex.Message!;
this.StackTrace = new BigStringEmbedded(ex.StackTrace!);
this.ThreadId = Thread.CurrentThread.ManagedThreadId;
ex.Data[ExceptionDataKey] = this;
this.MachineName = System.Environment.MachineName;
this.ApplicationName = AppDomain.CurrentDomain.FriendlyName;
this.Origin = ExceptionOrigin.Backend_DotNet;
}
#pragma warning restore CS8618 // Non-nullable field is uninitialized.

Expand Down Expand Up @@ -142,7 +124,7 @@ public BigStringEmbedded StackTrace

public bool Referenced { get; set; }

public bool IsClientSide { get; set; }
public ExceptionOrigin Origin { get; set; }

public override string ToString()
{
Expand All @@ -153,18 +135,12 @@ public static string Dump(NameValueCollection nameValueCollection)
{
return nameValueCollection.Cast<string>().ToString(key => key + ": " + nameValueCollection[key], "\r\n");
}
}

private static string? Try(int size, Func<string?> getValue)
{
try
{
return getValue()?.TryStart(size);
}
catch (Exception e)
{
return (e.GetType().Name + ":" + e.Message).TryStart(size);
}
}
public enum ExceptionOrigin
{
Backend_DotNet,
Frontend_React
}


Expand Down Expand Up @@ -225,16 +201,16 @@ public class DeleteLogsTypeOverridesEmbedded : EmbeddedEntity
}
}

public class ClientExceptionModel : ModelEntity
[AllowUnathenticated]
public class ClientErrorModel : ModelEntity
{
public string TypeErrorMessage { get; set; }
public string ErrorType { get; set; }

public string Message { get; set; }

public string? TypeErrorStack { get; set; }
public string? Stack { get; set; }

public string? TypeErrorName { get; set; }

public string? ExceptionType { get; set; }
public string? Name { get; set; }
}


#pragma warning restore CS8618 // Non-nullable field is uninitialized.
2 changes: 0 additions & 2 deletions Signum.Entities/Signum.Entities.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Npgsql" Version="6.0.5" />
<PackageReference Include="Signum.Analyzer" Version="3.2.0" />
<PackageReference Include="Signum.MSBuildTask" Version="6.0.0" />
Expand Down
38 changes: 30 additions & 8 deletions Signum.React/ApiControllers/ReflectionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Microsoft.AspNetCore.Http;
using Signum.Engine.Maps;
using Signum.Entities.Authorization;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Extensions;

namespace Signum.React.ApiControllers;

Expand Down Expand Up @@ -32,19 +34,39 @@ public ActionResult<Dictionary<string, TypeInfoTS>> Types()
}


[HttpPost("api/registerClientError"), ValidateModelFilter, ProfilerActionSplitter]
public void ClientError([Required, FromBody] ClientExceptionModel error)
[HttpPost("api/registerClientError"), ValidateModelFilter, SignumAllowAnonymous]
public void ClientError([Required, FromBody] ClientErrorModel error)
{
var httpContext = this.HttpContext;
var clientException = new ExceptionEntity(error, httpContext);

clientException.Version = Schema.Current.Version.ToString();
clientException.ApplicationName = Schema.Current.ApplicationName;
clientException.User = UserEntity.Current;
var req = httpContext.Request;
var connFeature = httpContext.Features.Get<IHttpConnectionFeature>()!;

if (Database.Query<ExceptionEntity>().Any(e => e.ExceptionMessageHash == clientException.ExceptionMessageHash && e.CreationDate.AddSeconds(60) > clientException.CreationDate))
return;
var clientException = new ExceptionEntity(error)
{
UserAgent = Try(300, () => req.Headers["User-Agent"].FirstOrDefault()),
RequestUrl = Try(int.MaxValue, () => req.GetDisplayUrl()),
UrlReferer = Try(int.MaxValue, () => req.Headers["Referer"].ToString()),
UserHostAddress = Try(100, () => connFeature.RemoteIpAddress?.ToString()),
UserHostName = Try(100, () => connFeature.RemoteIpAddress == null ? null : Dns.GetHostEntry(connFeature.RemoteIpAddress).HostName),

Version = Schema.Current.Version.ToString(),
ApplicationName = Schema.Current.ApplicationName,
User = UserEntity.Current,
};

clientException.Save();
}

private static string? Try(int size, Func<string?> getValue)
{
try
{
return getValue()?.TryStart(size);
}
catch (Exception e)
{
return (e.GetType().Name + ":" + e.Message).TryStart(size);
}
}
}
2 changes: 1 addition & 1 deletion Signum.React/Scripts/Exceptions/Exception.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function Exception(p: { ctx: TypeContext<ExceptionEntity> }) {
<ValueLine ctx={sc.subCtx(f => f.userHostAddress)} />
<ValueLine ctx={sc.subCtx(f => f.userHostName)} />
<ValueLine ctx={sc.subCtx(f => f.userAgent)} valueLineType="TextArea" />
<ValueLine ctx={sc.subCtx(f => f.isClientSide)} />
<ValueLine ctx={sc.subCtx(f => f.origin)} />
</div>
</div>
<ValueLine ctx={ctx.subCtx(f => f.requestUrl)} />
Expand Down
38 changes: 29 additions & 9 deletions Signum.React/Scripts/Modals/ErrorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Modals from '../Modals';
import { Dic } from '../Globals';
import { ajaxPost, ExternalServiceError, ServiceError, ValidationError } from '../Services';
import { JavascriptMessage, FrameMessage, ConnectionMessage } from '../Signum.Entities'
import { ClientExceptionModel, ExceptionEntity } from '../Signum.Entities.Basics'
import { ClientErrorModel, ExceptionEntity } from '../Signum.Entities.Basics'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import "./Modals.css"
import { newLite } from '../Reflection';
Expand Down Expand Up @@ -103,28 +103,45 @@ export default function ErrorModal(p: ErrorModalProps) {



var lastError: { model: ClientErrorModel, date: Date } | undefined;

function logError(error: Error) {

if (error instanceof ServiceError || error instanceof ValidationError)
return;

var errorModel: ClientExceptionModel = ClientExceptionModel.New({

exceptionType : (error as Object).constructor.name,
typeErrorMessage : error.message ?? error.toString(),
typeErrorStack : error.stack ?? null,
typeErrorName : error.name,
var errorModel = ClientErrorModel.New({
errorType: (error as Object).constructor.name,
message: error.message ?? error.toString(),
stack: error.stack ?? null,
name: error.name,
});

var date = new Date();

if (lastError != null) {
if (
lastError.model.errorType == errorModel.errorType &&
lastError.model.message == errorModel.message &&
lastError.model.stack == errorModel.stack &&
lastError.model.errorType == errorModel.errorType &&
((date.valueOf() - lastError.date.valueOf()) / 1000) < 10
) {
return;
}
}

lastError = { model: errorModel, date: date };

ajaxPost({ url: "~/api/registerClientError" }, errorModel);
}

ErrorModal.register = () => {

window.onunhandledrejection = p => {
var error = p.reason;
logError(error);
if (Modals.isStarted()) {
logError(error);
ErrorModal.showErrorModal(error);
}
else
Expand All @@ -134,7 +151,10 @@ ErrorModal.register = () => {
var oldOnError = window.onerror;
window.onerror = (message: Event | string, filename?: string, lineno?: number, colno?: number, error?: Error) => {

if (Modals.isStarted())
if (error != null)
logError(error);

if (Modals.isStarted())
ErrorModal.showErrorModal(error);
else if (oldOnError != null) {
if (error instanceof ServiceError)
Expand Down
21 changes: 13 additions & 8 deletions Signum.React/Scripts/Signum.Entities.Basics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export interface BigStringEmbedded extends Entities.EmbeddedEntity {
text: string | null;
}

export const ClientExceptionModel = new Type<ClientExceptionModel>("ClientExceptionModel");
export interface ClientExceptionModel extends Entities.ModelEntity {
Type: "ClientExceptionModel";
typeErrorMessage: string;
typeErrorStack: string | null;
typeErrorName: string | null;
exceptionType: string | null;
export const ClientErrorModel = new Type<ClientErrorModel>("ClientErrorModel");
export interface ClientErrorModel extends Entities.ModelEntity {
Type: "ClientErrorModel";
errorType: string;
message: string;
stack: string | null;
name: string | null;
}

export const DeleteLogParametersEmbedded = new Type<DeleteLogParametersEmbedded>("DeleteLogParametersEmbedded");
Expand Down Expand Up @@ -66,9 +66,14 @@ export interface ExceptionEntity extends Entities.Entity {
data: BigStringEmbedded;
hResult: number;
referenced: boolean;
isClientSide: boolean;
origin: ExceptionOrigin;
}

export const ExceptionOrigin = new EnumType<ExceptionOrigin>("ExceptionOrigin");
export type ExceptionOrigin =
"Backend_DotNet" |
"Frontend_React";

export interface IUserEntity extends Entities.Entity {
}

Expand Down

2 comments on commit 0b422b4

@olmobrutall
Copy link
Collaborator Author

@olmobrutall olmobrutall commented on 0b422b4 Aug 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging Client-Side Errors.

This commit adds the ability to save a ExceptionEntity for each client-side Error that is displayed to the user.

The ExceptionEntity has a new ExceptionOrigin enum to differentiate the two types:

  • Backend_DotNet
  • Frontend_React

Maybe more values will be included in the future.

The client-side prevents saving two identical erroes that get produced in less than 10 seconds of difference.

Appart from that the new client-side exceptions should look quite similar to sever-side ones.

Enjoy!

@MehdyKarimpour
Copy link
Contributor

@MehdyKarimpour MehdyKarimpour commented on 0b422b4 Aug 14, 2022 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.