Extra Stat collection (#407)

* Cleaned up error interceptor to avoid sending auth errors (when a 500 occurs) to sentry as auth errors aren't issues.

* Added extra stat collection

* Fixed a bad gitignore which ignored anything in a stats directory
This commit is contained in:
Joseph Milazzo 2021-07-20 11:32:37 -05:00 committed by GitHub
parent b9a06d3586
commit b11bb0e3b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 64 additions and 57 deletions

4
.gitignore vendored
View File

@ -491,11 +491,11 @@ appsettings.json
/API/kavita.db-wal /API/kavita.db-wal
/API/Hangfire.db /API/Hangfire.db
/API/Hangfire-log.db /API/Hangfire-log.db
cache/ API/cache/
/API/wwwroot/ /API/wwwroot/
/API/cache/ /API/cache/
/API/temp/ /API/temp/
_temp/ _temp/
_output/ _output/
stats/ API/stats/
UI/Web/dist/ UI/Web/dist/

View File

@ -1,10 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs.Stats;
using API.Extensions; using API.Extensions;
using API.Interfaces.Services; using API.Interfaces.Services;
using API.Services; using API.Services.Tasks;
using Kavita.Common; using Kavita.Common;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View File

@ -1,6 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs.Stats;
using API.Interfaces.Services; using API.Interfaces.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View File

@ -1,6 +1,6 @@
using System; using System;
namespace API.DTOs namespace API.DTOs.Stats
{ {
public class ClientInfoDto public class ClientInfoDto
{ {
@ -16,13 +16,14 @@ namespace API.DTOs
public DetailsVersion Os { get; set; } public DetailsVersion Os { get; set; }
public DateTime? CollectedAt { get; set; } public DateTime? CollectedAt { get; set; }
public bool UsingDarkTheme { get; set; }
public bool IsTheSameDevice(ClientInfoDto clientInfoDto) public bool IsTheSameDevice(ClientInfoDto clientInfoDto)
{ {
return (clientInfoDto.ScreenResolution ?? "").Equals(ScreenResolution) && return (clientInfoDto.ScreenResolution ?? string.Empty).Equals(ScreenResolution) &&
(clientInfoDto.PlatformType ?? "").Equals(PlatformType) && (clientInfoDto.PlatformType ?? string.Empty).Equals(PlatformType) &&
(clientInfoDto.Browser?.Name ?? "").Equals(Browser?.Name) && (clientInfoDto.Browser?.Name ?? string.Empty).Equals(Browser?.Name) &&
(clientInfoDto.Os?.Name ?? "").Equals(Os?.Name) && (clientInfoDto.Os?.Name ?? string.Empty).Equals(Os?.Name) &&
clientInfoDto.CollectedAt.GetValueOrDefault().ToString("yyyy-MM-dd") clientInfoDto.CollectedAt.GetValueOrDefault().ToString("yyyy-MM-dd")
.Equals(CollectedAt.GetValueOrDefault().ToString("yyyy-MM-dd")); .Equals(CollectedAt.GetValueOrDefault().ToString("yyyy-MM-dd"));
} }

View File

@ -1,4 +1,4 @@
namespace API.DTOs namespace API.DTOs.Stats
{ {
public class ServerInfoDto public class ServerInfoDto
{ {
@ -8,5 +8,7 @@
public string KavitaVersion { get; set; } public string KavitaVersion { get; set; }
public string BuildBranch { get; set; } public string BuildBranch { get; set; }
public string Culture { get; set; } public string Culture { get; set; }
public bool IsDocker { get; set; }
public int NumOfCores { get; set; }
} }
} }

View File

@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using API.Entities.Enums; using API.Entities.Enums;
namespace API.DTOs namespace API.DTOs.Stats
{ {
public class UsageInfoDto public class UsageInfoDto
{ {

View File

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace API.DTOs namespace API.DTOs.Stats
{ {
public class UsageStatisticsDto public class UsageStatisticsDto
{ {

View File

@ -1,5 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs.Stats;
namespace API.Interfaces.Services namespace API.Interfaces.Services
{ {

View File

@ -2,7 +2,7 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs.Stats;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Clients namespace API.Services.Clients

View File

@ -6,7 +6,7 @@ using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Data; using API.Data;
using API.DTOs; using API.DTOs.Stats;
using API.Interfaces; using API.Interfaces;
using API.Interfaces.Services; using API.Interfaces.Services;
using API.Services.Clients; using API.Services.Clients;
@ -15,7 +15,7 @@ using Kavita.Common.EnvironmentInfo;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services namespace API.Services.Tasks
{ {
public class StatsService : IStatsService public class StatsService : IStatsService
{ {
@ -142,7 +142,9 @@ namespace API.Services
RunTimeVersion = RuntimeInformation.FrameworkDescription, RunTimeVersion = RuntimeInformation.FrameworkDescription,
KavitaVersion = BuildInfo.Version.ToString(), KavitaVersion = BuildInfo.Version.ToString(),
Culture = Thread.CurrentThread.CurrentCulture.Name, Culture = Thread.CurrentThread.CurrentCulture.Name,
BuildBranch = BuildInfo.Branch BuildBranch = BuildInfo.Branch,
IsDocker = new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker,
NumOfCores = Environment.ProcessorCount
}; };
return serverInfo; return serverInfo;

View File

@ -7,6 +7,7 @@ using API.Services;
using API.Services.HostedServices; using API.Services.HostedServices;
using Hangfire; using Hangfire;
using Hangfire.MemoryStorage; using Hangfire.MemoryStorage;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo; using Kavita.Common.EnvironmentInfo;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;

View File

@ -26,10 +26,6 @@ export class ErrorInterceptor implements HttpInterceptor {
return throwError(error); return throwError(error);
} }
if (!environment.production) {
console.error('error:', error);
}
switch (error.status) { switch (error.status) {
case 400: case 400:
this.handleValidationError(error); this.handleValidationError(error);
@ -99,12 +95,15 @@ export class ErrorInterceptor implements HttpInterceptor {
} }
private handleServerException(error: any) { private handleServerException(error: any) {
console.error('500 error:', error);
const err = error.error; const err = error.error;
if (err.hasOwnProperty('message') && err.message.trim() !== '') { if (err.hasOwnProperty('message') && err.message.trim() !== '') {
if (err.message != 'User is not authenticated') {
console.log('500 error: ', error);
}
this.toastr.error(err.message); this.toastr.error(err.message);
} else { } else {
this.toastr.error('There was an unknown critical error.'); this.toastr.error('There was an unknown critical error.');
console.error('500 error:', error);
} }
} }

View File

@ -7,6 +7,7 @@ export interface ClientInfo {
platformType: string, platformType: string,
kavitaUiVersion: string, kavitaUiVersion: string,
screenResolution: string; screenResolution: string;
usingDarkTheme: boolean;
collectedAt?: Date; collectedAt?: Date;
} }

View File

@ -1,7 +1,13 @@
import { HttpClient } from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import * as Bowser from "bowser";
import { take } from "rxjs/operators";
import { environment } from "src/environments/environment"; import { environment } from "src/environments/environment";
import { ClientInfo } from "../_models/client-info"; import { ClientInfo } from "../_models/stats/client-info";
import { DetailsVersion } from "../_models/stats/details-version";
import { NavService } from "./nav.service";
import { version } from '../../../package.json';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -10,9 +16,27 @@ export class StatsService {
baseUrl = environment.apiUrl; baseUrl = environment.apiUrl;
constructor(private httpClient: HttpClient) { } constructor(private httpClient: HttpClient, private navService: NavService) { }
public sendClientInfo(clientInfo: ClientInfo) { public async sendClientInfo() {
return this.httpClient.post(this.baseUrl + 'stats/client-info', clientInfo); const data = await this.getInfo();
this.httpClient.post(this.baseUrl + 'stats/client-info', data);
}
private async getInfo(): Promise<ClientInfo> {
const screenResolution = `${window.screen.width} x ${window.screen.height}`;
const browser = Bowser.getParser(window.navigator.userAgent);
const usingDarkTheme = await this.navService.darkMode$.pipe(take(1)).toPromise();
return {
os: browser.getOS() as DetailsVersion,
browser: browser.getBrowser() as DetailsVersion,
platformType: browser.getPlatformType(),
kavitaUiVersion: version,
screenResolution,
usingDarkTheme
};
} }
} }

View File

@ -5,7 +5,6 @@ import { take } from 'rxjs/operators';
import { MemberService } from '../_services/member.service'; import { MemberService } from '../_services/member.service';
import { AccountService } from '../_services/account.service'; import { AccountService } from '../_services/account.service';
import { StatsService } from '../_services/stats.service'; import { StatsService } from '../_services/stats.service';
import * as ClientUtils from "../shared/utils/clientUtils";
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -35,9 +34,7 @@ export class HomeComponent implements OnInit {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
this.statsService.sendClientInfo(ClientUtils.getClientInfo()) this.statsService.sendClientInfo();
.pipe(take(1))
.subscribe(resp => {/* No Operation */});
if (user) { if (user) {
this.router.navigateByUrl('/library'); this.router.navigateByUrl('/library');

View File

@ -8,6 +8,7 @@
.poster { .poster {
width: 100%; width: 100%;
max-height: 230px;
} }
.rating-star { .rating-star {

View File

@ -1,21 +0,0 @@
import * as Bowser from "bowser";
import { version } from '../../../../package.json';
import { ClientInfo } from "src/app/_models/client-info";
import { DetailsVersion } from "src/app/_models/details-version";
const getClientInfo = (): ClientInfo => {
const screenResolution = `${window.screen.width} x ${window.screen.height}`;
const browser = Bowser.getParser(window.navigator.userAgent);
return {
os: browser.getOS() as DetailsVersion,
browser: browser.getBrowser() as DetailsVersion,
platformType: browser.getPlatformType(),
kavitaUiVersion: version,
screenResolution
};
}
export { getClientInfo };